Lesson 7 / 25
Control Flow and Vectorisation
Write conditions and loops, and prefer vectorised alternatives.
When to loop and when not to
R has if/else, for, while, repeat with break, and next to skip an iteration. if expects a single TRUE or FALSE; passing a longer logical vector is an error in recent R versions, which is why vectorised work uses ifelse() or dplyr's if_else() and case_when() instead. Loops are fine for things that are genuinely sequential, such as simulations where each step depends on the previous one, reading files one by one, or calling an API per item. But for element-wise calculations, vectorised functions (sum, cumsum, arithmetic, comparisons) are much faster and clearer, because they run in compiled code. When you do write a loop that builds a result, pre-allocate the output (results <- numeric(n)) rather than growing a vector with c() each time, which copies it repeatedly. seq_along(x) is safer than 1:length(x), which misbehaves when x is empty (it gives c(1, 0)).
Loop versus vectorised
A loop processes elements one at a time; a vectorised function processes the whole vector in one call.
Loops done right, and vectorised alternatives
seq_along, pre-allocation and case_when.
marks <- c(72, 45, 90, 38, 66)
# vectorised: preferred
grades <- dplyr::case_when(
marks >= 85 ~ "A",
marks >= 70 ~ "B",
marks >= 40 ~ "C",
.default = "Fail"
)
# loop with pre-allocation, when each step depends on the previous one
balance <- numeric(12)
balance[1] <- 10000
for (m in seq_along(balance)[-1]) {
balance[m] <- balance[m - 1] * 1.01 + 500 # interest plus monthly deposit
}
round(tail(balance, 1))
# safe iteration over a possibly empty vector
empty <- c()
for (i in seq_along(empty)) print(i) # runs zero times
# for (i in 1:length(empty)) ... # runs for i = 1 and i = 0: a bug
if (mean(marks) > 60) {
message("Class average is above 60")
} else {
message("Class needs support")
}if needs one TRUE or FALSE
if (marks > 40) with a vector of marks is an error. Decide whether you mean "for each element" (use ifelse/case_when) or "any" or "all" (use any() or all()).
Quick check: Why is seq_along(x) preferred over 1:length(x) in loops?
- It correctly produces an empty sequence when x is empty
- It is faster for long vectors
- It sorts x
- It works only with lists
Answer
It correctly produces an empty sequence when x is empty — 1:length(x) gives c(1, 0) for an empty x, causing a loop to run when it should not.