Conditions and loops

R · Essentials · 10 min

You need control flow for scripts and functions, and must know when a vectorised alternative is better.

if (cond) { ... } else { ... } needs cond to be a single TRUE or FALSE. Since R 4.2 a condition of length greater than one is an error, and an NA condition is always an error, so guard with isTRUE() or !is.na() when values may be missing.

if is an expression and returns a value: label <- if (x > 0) "pos" else "non-pos". Note that else must be on the same line as the closing brace when you type at the top level, otherwise R thinks the if is finished.

For element-wise choices use the vectorised ifelse(test, yes, no), or dplyr::case_when for several branches.

Loops:

  • for (i in seq_along(x)) { ... } iterates over a vector. Use seq_along(x) or seq_len(n) rather than 1:length(x), which gives 1 0 for an empty vector and runs twice.
  • while (cond) { ... } and repeat { ... } with break. next skips to the next iteration.

Use && and || (single values, short-circuit) in if conditions, and & and | (vectorised) for filtering vectors.

Loops in R aren't as slow as their reputation, but most of the time a vectorised expression or an apply-style function is shorter and clearer. When you do loop and build a result, preallocate it (out <- numeric(n)) instead of growing it.

Example

x <- c(-2, 0, 5)
for (i in seq_along(x)) {
  label <- if (x[i] > 0) "pos" else if (x[i] < 0) "neg" else "zero"
  cat(x[i], label, "\n")
}
#> -2 neg
#> 0 zero
#> 5 pos

print(ifelse(x > 0, "pos", "non-pos"))  #> [1] "non-pos" "non-pos" "pos"

empty <- c()
for (i in seq_along(empty)) print("never runs")
print(1:length(empty))                  #> [1] 1 0  (the bug)

n <- 0
while (TRUE) { n <- n + 1; if (n^2 > 50) break }
print(n)                                #> [1] 8
res <- tryCatch(if (NA) 1, error = function(e) "NA condition is an error")
print(res)                              #> [1] "NA condition is an error"

Remember

  • if needs one TRUE/FALSE; ifelse() is the vectorised version
  • Loop with seq_along(x) or seq_len(n), never 1:length(x)
  • &&/|| in if, &/| on vectors

Watch out for

  • Using 1:length(x) on a possibly empty vector, which iterates over 1 and 0.

Check yourself

  1. What does 1:length(c()) produce?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cran.r-project.org/doc/manuals/r-release/R-intro.html#Loops-and-conditional-execution