18 Control structures
Per se, imperative program code is executed strictly sequentially, command by command. Sometimes, however, one wants to deviate from this purely sequential order of commands and control the execution order of commands more flexibly. Principal tools for this purpose are conditional control structures and iterative control structures. Conditional control structures control program execution by specifying that certain commands are executed only when defined conditions are met. For this purpose, R provides if() and switch() statements. Often, one also wants to repeat (iterate) certain sections of code frequently in loops. For this purpose, R provides for(), while(), and repeat() loops. In this section, we introduce the basic conditional and iterative control structures in R, following the presentation in Cotton (2013), Chapter 8 and Chapter 9, as well as Wickham (2019), Chapter 5.
18.1 Conditional control structures
Conditional control structures allow the condition-dependent execution of commands.
if-statements
The basic conditional control structure is the if-statement. The general syntax for an if-statement in R has the following form:
Here, if the variable condition has the value TRUE, that is, if it is true, the commands denoted by true_actions are executed. If the value of the variable condition is FALSE, by contrast, the commands true_actions are not executed, and program execution continues on the next line after the if-statement. For example, in the following code the command print("x is greater than 0") is executed, since x has the value 1 after the preceding assignment.
The if-statement can be extended by an else condition to specify which commands are to be executed in the case that the variable condition has the value FALSE.
Here, the commands true_actions are executed if the variable condition has the value TRUE, and the commands false_actions are executed if the variable condition has the value FALSE. For example, in the following code the command print("y is not greater than 0") is executed because the condition y > 0 is not met.
[1] "y is not greater than 0"
R includes the relation operators listed in Table 18.1 for testing binary logical conditions.
| Relation operator | Meaning |
|---|---|
== |
Equal |
!= |
Unequal |
<, > |
Smaller, greater |
<=, >= |
Smaller or equal, greater or equal |
& |
AND |
| |
OR |
The relation operators <, <=, >, >= are mostly applied only to numerical values. The equality operators ==,!= can be applied to arbitrary data structures to check their equality. The logical links & and | are mostly applied to logical values. Finally, the function xor() implements the exclusive OR. For example, the following tests of equality and relation can be performed:
[1] "x is unequal to y"
[1] "x is smaller than y"
The if-statement tests can be extended by logical conditions, as the following example is intended to demonstrate:
[1] "x is smaller than or equal to y"
[1] "x is smaller than or equal to y"
For if-statements, it is crucial that the condition corresponds to a single logical value. The following conditions, for example, are erroneous:
switch-statements
Another conditional control structure is the switch-statement. Switch-statements are motivated by the fact that combined if-else-statements can easily become unclear, as the following example shows:
[1] "action 2"
Thus, if one has a case in which one of many actions is to be executed depending on the value of a variable, a switch-statement can be useful for improving code readability. R implements switch-statements in the switch() function, which works slightly differently depending on the data type of its variable. If the switch variable is of type integer, the \(i\)th action is executed, as the following example demonstrates:
[1] "action 2"
If the switch variable is of type character, by contrast, the action with the corresponding name is executed:
[1] "action a"
In general, it can be said of if- and switch-statements that every if-statement can be formulated equivalently as a switch-statement and vice versa. In practice, if-statements are certainly encountered more frequently than switch-statements. In general, for human code comprehension it makes sense not to formulate if-else-statements too complexly. If this is necessary, however, complex if-else-statements can be simplified by switch-statements.
18.2 Iterative control structures
Iterative control structures serve to execute certain commands repeatedly. The basic iterative control structure in R is the for loop. It has the general form
The command perform_action is executed once for each item in vector, and the value of item is updated each time. The following example is intended to illustrate this:
Here the vector consists of 1:3 = [1,2,3], the command executed is print(i), and the items are the entries of the vector. However, the command within the for loop does not necessarily have to refer to the items, as the following example shows:
Here, the constant value a is output three times. Typically, something is generated and stored within a for loop. In doing so, the corresponding storage structure should generally be preallocated, because the corresponding space in memory is then already provided and does not have to be provided during the execution of the for loop. The efficiency gain of this strategy may be negligible in most cases, but this procedure also makes it easier in particular to detect errors, for example if there are discrepancies in dimensional expectations or missing entries that are then already allocated as NaN. The following example demonstrates the iterative filling of a vector with means.
[1] 0.2388975 NaN NaN
[1] 0.23889754 -0.06565672 NaN
[1] 0.23889754 -0.06565672 0.24848672
To iterate linearly along a vector with variable entries, the function seq_along() is useful. It generates the linear indices 1,2, 3, … up to the length of the vector in question, as the following example demonstrates:
mu = c(0,5,50) # three expected-value parameters
X_bar = rep(NaN, ns) # storage structure
# simulation iterations
for(i in seq_along(mu)){ # iteration indices are typically i,j,k
X = rnorm(12, mu[i], 1) # realization of 12 N(mu,1) variables
X_bar[i] = mean(X) # mean of the ith realization
print(X_bar) # display for demonstration
}[1] 0.009096054 NaN NaN
[1] 0.009096054 4.921538604 NaN
[1] 0.009096054 4.921538604 50.189367556
for loops can also be nested, that is, further for loops can be executed within for loops. This may take some getting used to. The crucial point here is to realize that for each iteration of the outer loop, all iterations of the inner loop are run through. It is often helpful first to clarify the general logic of a nested loop structure with the help of print() statements, as the following example demonstrates:
[1] 1 1
[1] 1 2
[1] 1 3
[1] 2 1
[1] 2 2
[1] 2 3
The following example, for instance, generates a 4 x 5 matrix whose elements correspond to the respective column indices. The inner for loop with iterator variable j serves the column indices, and the outer for loop with iterator variable i serves the row indices:
[,1] [,2] [,3] [,4] [,5]
[1,] 1 2 3 4 5
[2,] 1 2 3 4 5
[3,] 1 2 3 4 5
[4,] 1 2 3 4 5
Analogously, the following example generates a 4 x 5 matrix whose elements correspond to the respective row indices. The inner for loop with iterator variable j again serves the column indices, and the outer for loop with iterator variable i serves the row indices:
[,1] [,2] [,3] [,4] [,5]
[1,] 1 1 1 1 1
[2,] 2 2 2 2 2
[3,] 3 3 3 3 3
[4,] 4 4 4 4 4
Finally, in the following example, two nested for loops and an if-statement are used to generate a 4 x 5 matrix whose elements are equal to the column indices if the respective column index is greater than or equal to the row index, and whose elements are otherwise zero:
[,1] [,2] [,3] [,4] [,5]
[1,] 1 2 3 4 5
[2,] 0 2 3 4 5
[3,] 0 0 3 4 5
[4,] 0 0 0 4 5