4  Control Structures

Control structures in R allow you to control the flow of execution of a series of R expressions. Basically, control structures allow you to put some ‘logic’ into your R code, rather than just always executing the same R code every time.

4.1 Loops

Sometimes it is necessary to repeat a calculation multiple times, e.g. calculate the sum of each row of a matrix. You can use for loops to do this.

Lets assume a simple matrix with 5 rows and 3 columns. How would we generate the sum of each row?
We could do so, by calculating the sum step-by-step:

#build a dataframe
m <- matrix(1:15, 5)
m
     [,1] [,2] [,3]
[1,]    1    6   11
[2,]    2    7   12
[3,]    3    8   13
[4,]    4    9   14
[5,]    5   10   15
#sum rows without a loop --> sum row 1
sum(m[1, ])
[1] 18
#sum rows without a loop --> sum row 2, ...
sum(m[2, ])
[1] 21

However, you will quickly see that this becomes quite tedious. So lets do this via a loop, in which we:

  • define that we want to loop through rows 1 to 5 (1:5)
  • for (i in 1:5) –> we say that we want to run a for loop, in which we name our variable i
  • {} –> defines the operation we want to loop through
  • in our case we want to sum a specific row (rows 1 through 5)
  • so in i we store the numbers 1,2,3,4 and 5 and then run through the sum functions 5x
#run a loop to get the sum for all five rows
for (i in 1:5) {
print(sum(m[i, ]))
}
[1] 18
[1] 21
[1] 24
[1] 27
[1] 30

How would we store our results in a new dataframe and do not just print them to the screen? To do this, we:

  • define a variable (named results) in which we store an empty vector with c(). We need this empty vector to have something to store our results in while running the loop.
  • start the for loop and define i as before
  • run the sum as before but now store the results for each iteration in results
#store the results, Note that here, the variable results had to be created before as an empty vector.
results <- c()
for (i in 1:5) {
results[i] <- sum(m[i, ])
}

results
[1] 18 21 24 27 30

Very often, there are some R packages or functions that are faster than loops.
The apply() function is such an example. apply() applies a function, i.e. sum, across a matrix.

However, since loops are also a very useful feature in bash or python it is useful to understand their general concept.

#way to do the same using the apply function (faster than loops)
results2 <- apply(m, 1, sum)
results2
[1] 18 21 24 27 30

Apply takes the following arguments:

  1. Our input, the matrix,
  2. The dimension of the matrix on which the function should be applied ((1 means rows and 2 means columns))
    • apply(data, 1, mean) = apply mean on each row
    • apply(data, 2, mean) = apply mean on each column
  3. The function we want to use. Here, the function sum is applied to each row of m.

Actually, there is a family of apply functions, depending on the object you pass as an input, and/or the object you want as output. You can read a brief tutorial under this link.

Two other examples are sapply and lapply, which work on lists.

4.2 if-else

One can also create a decision making structure in R. A decision making structure has at least one condition to be evaluated together with a statement or statements to be evaluated if the condition is TRUE, and optionally, other statements to be executed if the condition is FALSE, as we can see in the figure.

The test_expression has to be logical, i.e., it has to be a expression that, when evaluated, is either TRUE or FALSE. The logical operators listed above can be used to construct them. For example, we can use an if-else statement to check if a number is positive or negative,

#store a random number
x <- -5

#write a loop and ask if our number is positive or negative
if (x > 0) {
print("Positive number")
} else if (x == 0) {
print("Number is zero")
} else {
print("Negative number")
}
[1] "Negative number"

Note that in the example there was an else if. In that way we can check more than 2 conditions.

This of course is a very simplistic example. But if-else statements can be useful if we want to deal with larger dataframes, i.e. our growth data.

Specifically, we want to:

  • Create a new column, with the name category
  • If a value in our Root length column is equal to or smaller than 10, we want to say this category is small.
  • If that is not the case, we want to say it is large
#apply ifelse
growth_data$category <- ifelse(growth_data$Rootlength<=10.0, "small", "large")

#check the structure of our data
kable(growth_data) %>%
  kable_styling() %>%
  scroll_box(width = "700px", height = "400px")
SampleID Nutrient Condition FW_shoot_mg Rootlength category
noP noP MgCl 10.26 5.931015 small
noP noP MgCl 6.52 5.743447 small
noP noP MgCl 12.17 6.834720 small
noP noP MgCl 11.37 6.742735 small
noP noP MgCl 9.80 6.736886 small
noP noP MgCl 3.75 4.236349 small
noP noP MgCl 5.38 4.753485 small
noP noP MgCl 4.53 5.532333 small
noP noP MgCl 7.75 5.484364 small
noP noP MgCl 8.64 5.963254 small
noP noP MgCl 10.36 5.359044 small
noP noP MgCl 7.69 5.725348 small
noP noP MgCl 8.57 6.424601 small
noP_101 noP Strain101 10.49 7.143667 small
noP_101 noP Strain101 8.91 7.669841 small
noP_101 noP Strain101 9.32 7.807710 small
noP_101 noP Strain101 6.76 7.508370 small
noP_101 noP Strain101 5.99 6.607630 small
noP_101 noP Strain101 8.26 7.269267 small
noP_101 noP Strain101 7.61 7.973207 small
noP_101 noP Strain101 7.56 7.399504 small
noP_101 noP Strain101 7.90 6.717792 small
noP_101 noP Strain101 6.69 6.721007 small
noP_101 noP Strain101 8.14 7.070333 small
noP_101 noP Strain101 6.07 5.947965 small
noP_101 noP Strain101 8.19 6.393722 small
noP_230 noP Strain230 4.96 7.166174 small
noP_230 noP Strain230 6.20 7.515659 small
noP_230 noP Strain230 5.97 7.250036 small
noP_230 noP Strain230 5.32 7.134681 small
noP_230 noP Strain230 5.45 6.917319 small
noP_230 noP Strain230 6.03 6.120089 small
noP_230 noP Strain230 5.70 7.665526 small
noP_230 noP Strain230 6.04 7.809111 small
noP_230 noP Strain230 5.22 6.601296 small
noP_230 noP Strain230 3.99 6.524710 small
noP_230 noP Strain230 4.85 8.197116 small
noP_28 noP Strain28 8.63 6.160052 small
noP_28 noP Strain28 7.27 4.720711 small
noP_28 noP Strain28 9.51 6.917185 small
noP_28 noP Strain28 7.46 4.384756 small
noP_28 noP Strain28 7.91 6.069185 small
noP_28 noP Strain28 7.40 7.113879 small
noP_28 noP Strain28 9.36 5.428766 small
noP_28 noP Strain28 9.74 6.694142 small
noP_28 noP Strain28 4.15 5.270298 small
noP_28 noP Strain28 8.60 6.811773 small
noP_28 noP Strain28 5.41 5.106644 small
noP_28 noP Strain28 8.30 7.413519 small
noP_28 noP Strain28 9.93 7.112385 small
noP_28 noP Strain28 9.44 7.428674 small
noP_28 noP Strain28 7.85 6.372659 small
P P MgCl 32.42 11.286793 large
P P MgCl 21.03 10.456630 large
P P MgCl 18.35 11.106341 large
P P MgCl 21.04 10.816896 large
P P MgCl 16.61 10.608252 large
P P MgCl 25.79 7.121587 small
P P MgCl 31.00 9.165891 small
P P MgCl 36.13 11.053978 large
P P MgCl 17.50 8.271196 small
P P MgCl 21.42 8.649225 small
P P MgCl 19.60 10.313985 large
P P MgCl 11.62 8.098859 small
P P MgCl 18.76 10.061778 large
P P MgCl 21.55 11.048822 large
P P MgCl 19.05 9.741622 small
P_101 P Strain101 20.91 12.119304 large
P_101 P Strain101 23.48 12.057252 large
P_101 P Strain101 17.47 12.064659 large
P_101 P Strain101 24.49 13.576118 large
P_101 P Strain101 25.70 10.983965 large
P_101 P Strain101 28.24 12.935397 large
P_101 P Strain101 17.70 11.921333 large
P_101 P Strain101 32.90 13.030630 large
P_101 P Strain101 22.80 12.644756 large
P_101 P Strain101 32.47 14.715830 large
P_101 P Strain101 22.05 13.186593 large
P_101 P Strain101 23.47 13.513763 large
P_230 P Strain230 23.46 10.981978 large
P_230 P Strain230 22.93 12.563616 large
P_230 P Strain230 17.08 12.174456 large
P_230 P Strain230 15.95 13.151797 large
P_230 P Strain230 15.03 12.072456 large
P_230 P Strain230 13.00 10.692603 large
P_230 P Strain230 18.30 12.236482 large
P_230 P Strain230 17.80 11.792879 large
P_230 P Strain230 17.43 11.914695 large
P_230 P Strain230 14.93 11.281731 large
P_230 P Strain230 22.75 11.502507 large
P_230 P Strain230 37.90 13.170210 large
P_230 P Strain230 13.27 10.055399 large
P_28 P Strain28 37.14 11.908378 large
P_28 P Strain28 39.39 12.612785 large
P_28 P Strain28 28.27 12.434356 large
P_28 P Strain28 29.50 12.559904 large
P_28 P Strain28 27.29 11.585637 large
P_28 P Strain28 22.82 11.925326 large
P_28 P Strain28 25.05 12.831222 large
P_28 P Strain28 17.48 11.644037 large
P_28 P Strain28 15.85 11.900193 large
P_28 P Strain28 9.54 10.698752 large
P_28 P Strain28 27.91 11.668794 large
P_28 P Strain28 26.63 12.555404 large
P_28 P Strain28 29.96 11.771277 large

So the function works like this:

ifelse(our_test, value_to_return_if_test_is_true, value_to_return_if_test_is_false)

We can also combine different statements with the & (AND) or | (OR) symbol. I.e. we only send things in the big category if the roots are longer than 10cm AND the shoot weight is larger than 15mg

#apply ifelse
growth_data$category <- ifelse(growth_data$Rootlength>10 & growth_data$FW_shoot_mg>15, "large", "small")

#check the structure of our data
kable(growth_data) %>%
  kable_styling() %>%
  scroll_box(width = "700px", height = "400px")
SampleID Nutrient Condition FW_shoot_mg Rootlength category
noP noP MgCl 10.26 5.931015 small
noP noP MgCl 6.52 5.743447 small
noP noP MgCl 12.17 6.834720 small
noP noP MgCl 11.37 6.742735 small
noP noP MgCl 9.80 6.736886 small
noP noP MgCl 3.75 4.236349 small
noP noP MgCl 5.38 4.753485 small
noP noP MgCl 4.53 5.532333 small
noP noP MgCl 7.75 5.484364 small
noP noP MgCl 8.64 5.963254 small
noP noP MgCl 10.36 5.359044 small
noP noP MgCl 7.69 5.725348 small
noP noP MgCl 8.57 6.424601 small
noP_101 noP Strain101 10.49 7.143667 small
noP_101 noP Strain101 8.91 7.669841 small
noP_101 noP Strain101 9.32 7.807710 small
noP_101 noP Strain101 6.76 7.508370 small
noP_101 noP Strain101 5.99 6.607630 small
noP_101 noP Strain101 8.26 7.269267 small
noP_101 noP Strain101 7.61 7.973207 small
noP_101 noP Strain101 7.56 7.399504 small
noP_101 noP Strain101 7.90 6.717792 small
noP_101 noP Strain101 6.69 6.721007 small
noP_101 noP Strain101 8.14 7.070333 small
noP_101 noP Strain101 6.07 5.947965 small
noP_101 noP Strain101 8.19 6.393722 small
noP_230 noP Strain230 4.96 7.166174 small
noP_230 noP Strain230 6.20 7.515659 small
noP_230 noP Strain230 5.97 7.250036 small
noP_230 noP Strain230 5.32 7.134681 small
noP_230 noP Strain230 5.45 6.917319 small
noP_230 noP Strain230 6.03 6.120089 small
noP_230 noP Strain230 5.70 7.665526 small
noP_230 noP Strain230 6.04 7.809111 small
noP_230 noP Strain230 5.22 6.601296 small
noP_230 noP Strain230 3.99 6.524710 small
noP_230 noP Strain230 4.85 8.197116 small
noP_28 noP Strain28 8.63 6.160052 small
noP_28 noP Strain28 7.27 4.720711 small
noP_28 noP Strain28 9.51 6.917185 small
noP_28 noP Strain28 7.46 4.384756 small
noP_28 noP Strain28 7.91 6.069185 small
noP_28 noP Strain28 7.40 7.113879 small
noP_28 noP Strain28 9.36 5.428766 small
noP_28 noP Strain28 9.74 6.694142 small
noP_28 noP Strain28 4.15 5.270298 small
noP_28 noP Strain28 8.60 6.811773 small
noP_28 noP Strain28 5.41 5.106644 small
noP_28 noP Strain28 8.30 7.413519 small
noP_28 noP Strain28 9.93 7.112385 small
noP_28 noP Strain28 9.44 7.428674 small
noP_28 noP Strain28 7.85 6.372659 small
P P MgCl 32.42 11.286793 large
P P MgCl 21.03 10.456630 large
P P MgCl 18.35 11.106341 large
P P MgCl 21.04 10.816896 large
P P MgCl 16.61 10.608252 large
P P MgCl 25.79 7.121587 small
P P MgCl 31.00 9.165891 small
P P MgCl 36.13 11.053978 large
P P MgCl 17.50 8.271196 small
P P MgCl 21.42 8.649225 small
P P MgCl 19.60 10.313985 large
P P MgCl 11.62 8.098859 small
P P MgCl 18.76 10.061778 large
P P MgCl 21.55 11.048822 large
P P MgCl 19.05 9.741622 small
P_101 P Strain101 20.91 12.119304 large
P_101 P Strain101 23.48 12.057252 large
P_101 P Strain101 17.47 12.064659 large
P_101 P Strain101 24.49 13.576118 large
P_101 P Strain101 25.70 10.983965 large
P_101 P Strain101 28.24 12.935397 large
P_101 P Strain101 17.70 11.921333 large
P_101 P Strain101 32.90 13.030630 large
P_101 P Strain101 22.80 12.644756 large
P_101 P Strain101 32.47 14.715830 large
P_101 P Strain101 22.05 13.186593 large
P_101 P Strain101 23.47 13.513763 large
P_230 P Strain230 23.46 10.981978 large
P_230 P Strain230 22.93 12.563616 large
P_230 P Strain230 17.08 12.174456 large
P_230 P Strain230 15.95 13.151797 large
P_230 P Strain230 15.03 12.072456 large
P_230 P Strain230 13.00 10.692603 small
P_230 P Strain230 18.30 12.236482 large
P_230 P Strain230 17.80 11.792879 large
P_230 P Strain230 17.43 11.914695 large
P_230 P Strain230 14.93 11.281731 small
P_230 P Strain230 22.75 11.502507 large
P_230 P Strain230 37.90 13.170210 large
P_230 P Strain230 13.27 10.055399 small
P_28 P Strain28 37.14 11.908378 large
P_28 P Strain28 39.39 12.612785 large
P_28 P Strain28 28.27 12.434356 large
P_28 P Strain28 29.50 12.559904 large
P_28 P Strain28 27.29 11.585637 large
P_28 P Strain28 22.82 11.925326 large
P_28 P Strain28 25.05 12.831222 large
P_28 P Strain28 17.48 11.644037 large
P_28 P Strain28 15.85 11.900193 large
P_28 P Strain28 9.54 10.698752 small
P_28 P Strain28 27.91 11.668794 large
P_28 P Strain28 26.63 12.555404 large
P_28 P Strain28 29.96 11.771277 large