R - For Loop with Examples
R for loops iterate over elements in a sequence, executing a code block for each element. The basic syntax follows the pattern `for (variable in sequence) { expression }`.
Key Insights
- R for loops iterate over sequences using
for (variable in sequence)syntax, supporting vectors, lists, and custom sequences with minimal overhead - Break and next statements provide precise control flow, while nested loops enable multi-dimensional data processing common in matrix operations and data frame manipulation
- Vectorized operations and apply family functions often outperform for loops in R, but loops remain essential for complex conditional logic and sequential dependencies
Basic For Loop Syntax
R for loops iterate over elements in a sequence, executing a code block for each element. The basic syntax follows the pattern for (variable in sequence) { expression }.
# Simple iteration over a vector
for (i in 1:5) {
print(i)
}
# Output: 1 2 3 4 5
# Iterate over character vector
fruits <- c("apple", "banana", "orange")
for (fruit in fruits) {
print(paste("I like", fruit))
}
# Output:
# [1] "I like apple"
# [1] "I like banana"
# [1] "I like orange"
# Iterate with index
numbers <- c(10, 20, 30, 40)
for (i in seq_along(numbers)) {
print(paste("Index:", i, "Value:", numbers[i]))
}
The seq_along() function generates indices matching the vector length, preventing out-of-bounds errors common with hardcoded ranges.
Looping Through Data Structures
For loops handle various R data structures including lists, data frames, and matrices.
# Loop through list elements
my_list <- list(
name = "John",
age = 30,
scores = c(85, 90, 95)
)
for (element in my_list) {
print(element)
}
# Loop through data frame rows
df <- data.frame(
id = 1:3,
name = c("Alice", "Bob", "Charlie"),
score = c(85, 92, 78)
)
for (i in 1:nrow(df)) {
print(paste(df$name[i], "scored", df$score[i]))
}
# Loop through matrix
matrix_data <- matrix(1:9, nrow = 3, ncol = 3)
for (i in 1:nrow(matrix_data)) {
for (j in 1:ncol(matrix_data)) {
print(paste("Element [", i, ",", j, "] =", matrix_data[i, j]))
}
}
When working with data frames, accessing columns directly proves more efficient than row-wise iteration for vectorizable operations.
Break and Next Statements
Control flow statements modify loop execution. break exits the loop entirely, while next skips to the next iteration.
# Break statement - stop when condition met
for (i in 1:10) {
if (i > 5) {
break
}
print(i)
}
# Output: 1 2 3 4 5
# Next statement - skip specific iterations
for (i in 1:10) {
if (i %% 2 == 0) {
next # Skip even numbers
}
print(i)
}
# Output: 1 3 5 7 9
# Practical example: finding first match
search_value <- 42
numbers <- c(10, 25, 42, 67, 42, 89)
position <- NULL
for (i in seq_along(numbers)) {
if (numbers[i] == search_value) {
position <- i
break
}
}
print(paste("First occurrence at index:", position))
These statements enable early termination and conditional skipping without wrapping the entire loop body in conditional blocks.
Nested Loops
Nested loops process multi-dimensional data structures or generate combinations.
# Multiplication table
for (i in 1:5) {
for (j in 1:5) {
cat(i * j, "\t")
}
cat("\n")
}
# Process matrix by regions
matrix_data <- matrix(rnorm(20), nrow = 4, ncol = 5)
row_sums <- numeric(nrow(matrix_data))
for (i in 1:nrow(matrix_data)) {
sum_val <- 0
for (j in 1:ncol(matrix_data)) {
sum_val <- sum_val + matrix_data[i, j]
}
row_sums[i] <- sum_val
}
# Generate all pairs
items <- c("A", "B", "C")
for (i in 1:(length(items) - 1)) {
for (j in (i + 1):length(items)) {
print(paste(items[i], "-", items[j]))
}
}
# Output: A - B, A - C, B - C
Nested loops increase computational complexity multiplicatively. A loop with n iterations containing a loop with m iterations executes n × m times.
Building Results with Loops
Accumulating results requires pre-allocation for performance. Growing objects dynamically causes repeated memory reallocation.
# Bad practice - growing vector
result <- c()
for (i in 1:1000) {
result <- c(result, i^2) # Inefficient
}
# Good practice - pre-allocation
n <- 1000
result <- numeric(n)
for (i in 1:n) {
result[i] <- i^2
}
# Building a list of data frames
result_list <- vector("list", length = 3)
for (i in 1:3) {
result_list[[i]] <- data.frame(
id = i,
value = rnorm(5),
category = paste0("Cat", i)
)
}
# Combine results
final_df <- do.call(rbind, result_list)
Pre-allocating with numeric(), character(), or vector() eliminates performance penalties from dynamic resizing.
Conditional Logic Within Loops
Complex conditional logic within loops handles business rules and data validation.
# Data cleaning example
sales_data <- data.frame(
product = c("A", "B", "C", "D"),
quantity = c(10, -5, 0, 20),
price = c(100, 50, 75, 0)
)
cleaned_data <- sales_data
for (i in 1:nrow(sales_data)) {
# Fix negative quantities
if (sales_data$quantity[i] < 0) {
cleaned_data$quantity[i] <- 0
warning(paste("Negative quantity fixed for row", i))
}
# Flag missing prices
if (sales_data$price[i] == 0) {
cleaned_data$price[i] <- NA
message(paste("Zero price converted to NA for row", i))
}
}
# Multi-condition categorization
scores <- c(45, 67, 89, 92, 54, 78)
grades <- character(length(scores))
for (i in seq_along(scores)) {
if (scores[i] >= 90) {
grades[i] <- "A"
} else if (scores[i] >= 80) {
grades[i] <- "B"
} else if (scores[i] >= 70) {
grades[i] <- "C"
} else if (scores[i] >= 60) {
grades[i] <- "D"
} else {
grades[i] <- "F"
}
}
Performance Considerations
R for loops carry performance overhead compared to vectorized operations. Understanding when to use each approach matters.
# Timing comparison
n <- 100000
x <- rnorm(n)
# For loop approach
system.time({
result_loop <- numeric(n)
for (i in 1:n) {
result_loop[i] <- x[i]^2
}
})
# Vectorized approach
system.time({
result_vec <- x^2
})
# When loops are necessary - sequential dependency
fibonacci <- numeric(20)
fibonacci[1] <- fibonacci[2] <- 1
for (i in 3:20) {
fibonacci[i] <- fibonacci[i-1] + fibonacci[i-2]
}
# Complex conditional logic requiring loops
data <- data.frame(
value = rnorm(100),
group = sample(c("A", "B", "C"), 100, replace = TRUE)
)
data$adjusted <- numeric(nrow(data))
running_sum <- list(A = 0, B = 0, C = 0)
for (i in 1:nrow(data)) {
group <- as.character(data$group[i])
running_sum[[group]] <- running_sum[[group]] + data$value[i]
data$adjusted[i] <- data$value[i] / (running_sum[[group]] + 1)
}
Vectorized operations leverage R’s optimized C implementation. Reserve loops for sequential dependencies, complex state management, or operations without vectorized equivalents.
Alternative Approaches
The apply family functions and purrr package provide functional alternatives to explicit loops.
# lapply for list operations
numbers_list <- list(a = 1:5, b = 6:10, c = 11:15)
means <- lapply(numbers_list, mean)
# sapply for simplified output
sums <- sapply(numbers_list, sum)
# Using purrr (if installed)
# library(purrr)
# result <- map(numbers_list, ~ .x * 2)
# When to use loops vs alternatives:
# Loops: Sequential dependencies, complex state, early termination
# Apply/purrr: Independent operations on list elements
# Vectorization: Element-wise operations on vectors
Choose for loops when code clarity improves or when operations require sequential state that apply functions cannot maintain efficiently.