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.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.