#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
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.
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:
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#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:
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.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:
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.
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:
category#apply ifelse
growth_data$category <- ifelse(growth_data$Rootlength <= 75, "small", "large")
#check the first rows of our data
head(growth_data)So the function works like this:
ifelse(our_test, value_to_return_if_test_is_true, value_to_return_if_test_is_false)
If the root length is missing (NA), the test can’t be answered, and ifelse() returns NA as well.
We can also combine different statements with the & (AND) or | (OR) symbol. I.e. we only send things in the large category if the roots are longer than 75 mm AND the shoot weight is larger than 15 mg
#apply ifelse
growth_data$category <- ifelse(growth_data$Rootlength > 75 & growth_data$FW_shoot_mg > 15, "large", "small")
#check the first rows of our data
head(growth_data)What happens with the plants where the shoot weight is missing? Let’s look at them:
#show the rows where the shoot weight is missing
growth_data[is.na(growth_data$FW_shoot_mg), ]Some of them are small and some are NA. Why? For the plants with roots shorter than 75 mm, the first test is already FALSE. With AND, both tests must be TRUE, so the result is FALSE, whatever the shoot weight is. R only returns NA if the answer really depends on the missing value: when the roots are longer than 75 mm, the result depends on the shoot weight, and that is unknown.