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. Useseq_along(x)orseq_len(n)rather than1:length(x), which gives1 0for an empty vector and runs twice.while (cond) { ... }andrepeat { ... }withbreak.nextskips 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
ifneeds one TRUE/FALSE;ifelse()is the vectorised version- Loop with
seq_along(x)orseq_len(n), never1:length(x) &&/||inif,&/|on vectors
Watch out for
- Using
1:length(x)on a possibly empty vector, which iterates over 1 and 0.