9 Control Flow
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:
- Use
if
andelse
statements to conditionally evaluate R expressions. - Use
ifelse
as a vectorized alternative toif
andelse
statements.
9.1 if
First, for documentation on control flow in R, use:
::Control ?base
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.
= "bar"
foo if (is.character(foo)) {
print(foo)
}
#> [1] "bar"
= 42
baz 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:
= 42
x 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.
= 42
x 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
= 22
m = 0
n
# series of if-else statements
if (m < 20) {
= 20 # will run if m < 20
n else if (m < 40) {
} = 40 # will run if 20 <= m < 40
n else if (m < 60) {
} = 60 # will run if 40 <= m < 60
n else {
} = Inf # will run if 60 <= m
n
}
# 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:
::ifelse ?base
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.
= c(TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, TRUE)
test_ex = c(0, 1)
yes_ex = c(2, 4, 6)
no_ex 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
- TODO: see also: Hands-On Programming with R: else Statements
- TODO: we used print here a lot, that doesn’t mean that you should.
- TODO:
switch
Control flow is a defining characteristic of imperative programming.↩︎
This will also be benefical when we also add
else
statements.↩︎