9  Control Flow

Warning

This chapter was developed from scratch for the Fall 2022 semester. As such, you might notice a few extra typos, or some topics that are not well explained. If you encounter these issues, please let us know on the discussion forum. Except some additional changes to what is currently published while this warning persists.

So far, all of the code we’ve written contained a sequence of R expressions that were to be evaluated in order. While this type of code can still be useful, by introducing control flow1, we can use code to more interestingly define when and how many times various expressions should be evaluated.

After reading this chapter you should be able to:

9.1 if

First, for documentation on control flow in R, use:

?base::Control

We’ll start with the simplest and most important control flow constructs, the if statement, which allows for conditional evaluation of R expressions. The general syntax is given by:

if (condition) {
  code_to_evaluate
}

First, note that if is not a function, and we place a space between if and the ( that follows to make this clear. Inside those parentheses, the condition determines whether or not to evaluate code_to_evaluate. In particular, condition must be an R expression that evaluates to a length one logical vector containing either TRUE or FALSE. Much like functions, the “body” of the if statement is contained within curly braces, {}. This is not strictly necessary, and can be avoided for one-line statements, but we recommend that beginners always use the braces to clarify exactly which expressions will be conditionally evaluated.2

if (TRUE) {
  print("Hello, World!") # evaluates because condition is TRUE
}
#> [1] "Hello, World!"
if (FALSE) {
  print("Hello, World!") # does not evaluate because condition is TRUE
}

Obviously, using TRUE or FALSE for the input to the condition is not particularly useful. Instead, using code the evaluates to TRUE or FALSE will be much more common.

foo = "bar"
if (is.character(foo)) {
  print(foo)
}
#> [1] "bar"
baz = 42
if (is.character(baz)) {
  print(baz)
}

Note that if the condition is not a logical value, R will attempt to coerce it to be logical.

if (42) {
  print("Hello, World!")
}
#> [1] "Hello, World!"

As we’ve seen previously, sometimes this is not possible.

if ("foo") {
  print("Hello, World!")
}
#> Error in if ("foo") {: argument is not interpretable as logical

Be aware that the condition must be length one. Conditions greater than length one will result in an error.

if (c(TRUE, FALSE)) {
  42
}
#> Error in if (c(TRUE, FALSE)) {: the condition has length > 1

9.2 else

Consider the following code:

x = 42
if (x > 100) {
  print("x is greater than 100")
}
if (x <= 100) {
  print("x is less than or equal to 100")
}
#> [1] "x is less than or equal to 100"

This is valid code, however, it might not be the best way to write it. We can improve this code by adding an else statement.

x = 42
if (x > 100) {
  print("x is greater than 100")
} else {
  print("x is less than or equal to 100")
}
#> [1] "x is less than or equal to 100"

You’ll probably first notice that this code looks similar to the previous code. The benefit to using else here is that we do no need to determine what the complement of x > 100 is, to ensure that the body of only one of the if statements is run. In this case it is obvious, but in more complex situations, determine the complement expression could be non-trivial.

While if can be used without else, an else usage must follow an if usage. The general syntax is:

if (condition) {
  # run this code if the condition evaluates to TRUE
} else {
  # run this code if the condition evaluates to FALSE
}

It is also possible to chain together multiple if and else statements.

# setup data
m = 22
n = 0

# series of if-else statements
if (m < 20) {
  n = 20 # will run if m < 20
} else if (m < 40) {
  n = 40 # will run if 20 <= m < 40
} else if (m < 60) {
  n = 60 # will run if 40 <= m < 60
} else {
  n = Inf # will run if 60 <= m
}

# check "result"
n
#> [1] 40

One “trick” to be aware of, R will evaluate the code in the body of the first condition met. For example 22 < 40 and 22 < 60 will both evaluate to TRUE, but 22 < 40 occurs first, thus n = 40 is evaluated, and the remainder to the chain is ignored.

9.3 ifelse

Knowing that R is extremely vector-focused, if statements might feel like a bit of an outlier given that the condition must evaluate to a single TRUE or FALSE value, that is, a logical vector of length one.

Thankfully, R includes the ifelse function, which can be thought of as a “vectorized” version of the usual if and else construction. For details, use:

?base::ifelse

First let’s translate the following if and else code to an ifelse usage.

if (42 > 2) {
  "!"
} else {
  "?"
}
#> [1] "!"
ifelse(test = 42 > 2, yes = "!", no = "?")
#> [1] "!"

Simple enough, but that was a terribly boring example, because 42 > 2 evaluates to TRUE, and thus is a logical vector of length one. The following example will better demonstrate the power of the ifelse function.

ifelse(1:10 %% 2 == 0, "even", "odd")
#>  [1] "odd"  "even" "odd"  "even" "odd"  "even" "odd"  "even" "odd"  "even"

First, note that we are not using the argument names in this example, as often, that will be how you see ifelse examples presented.

Next, let’s look at the result of evaluating the value passed to the test argument.

1:10 %% 2 == 0
#>  [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE

So, it appears that the ifelse function takes as input a logical vector, potentially of length greater than one, and returns a vector with the yes argument’s value ("even") in place of any TRUE values, and the no argument’s value ("odd") in place of any FALSE value.

Hopefully, you’re already yelling: “But there is actually vector recycling taking place!”

R will recycle the yes and no inputs to have the same length as the test. If either of yes or no is longer than test, any element at an index beyond the length of test is ignored.

Use what you’ve already learned about recycling to reason through the following example.

test_ex = c(TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE)
yes_ex  = c(0, 1)
no_ex   = c(2, 4, 6)
ifelse(test_ex, yes_ex, no_ex)
#> [1] 0 4 0 1 4 6 0 1

9.4 Loops

Loops (for, while, and repeat) are another form of control flow. We will put off discussing these until next chapter so that we can first introduce the apply functions as a more common R approach to performing the types of operations usually done in other languages with loops.

9.5 Summary

  • TODO: You’ve learned to…

9.6 What’s Next?

  • TODO: iteration

9.7 TODO


  1. Control flow is a defining characteristic of imperative programming.↩︎

  2. This will also be benefical when we also add else statements.↩︎