RStudio project
Open the RStudio project that we created in the previous session. We recommend to use this RStudio project for the entire course and within the RStudio project create separate R scripts for each session.
# Session 6: Control flows - ifelse, loops and functions
and save the file in your folder “scripts” within your project folder,
e.g. as “6_ControlFlows.R”Control-flow constructs are among the most important building blocks in programming because they help to structure the workflow. We use loops for repeating workflows, and conditional expressions and switch statements for making choices between alternative control flows based on some conditions.
We use conditional expressions if specific computations and actions should only be performed under certain conditions. R knows three conditional expressions:
if (condition) {command block}
if (condition) {command block} else {alternative command block}
ifelse (condition, command block, alternative command block)
These functions always require a Boolean condition, meaning that it
needs to evaluate to TRUE
or FALSE
. Single
commands do not need to be surrounded by curly brackets. Still, for
beginners, they facilitate overview.
a <- 12
if (a >= 10)
{
print("a is greater than or equal to 10")
}
## [1] "a is greater than or equal to 10"
Test it yourself
a
and run the if-clause
again. What happens if a is smaller than 10?a <- 12
if (a >= 10)
{
print("a is greater than or equal to 10")
} else {
print("a is smaller than 10")
}
## [1] "a is greater than or equal to 10"
Conditional expressions may be nested, meaning that another if-clause
follows on an existing one (else if
).
if (a > 10) {
print("a is greater than 10")
} else
if (a == 10) {
print("a is equal to 10")
} else {
print("a is smaller than 10")
}
## [1] "a is greater than 10"
Another example:
# Should I renew my streaming subscription, and if so by one month or for a whole year?
no_weeks <- 6 # number of remaining lockdown weeks
if (no_weeks < 4) {
renew <- "No"
} else
if (no_weeks >= 4 & no_weeks < 8) {
renew <- "By a month and then see"
} else {
renew <- "By a whole year"
}
renew
## [1] "By a month and then see"
The function ifelse()
allows elementwise evaluation of
the condition.
age <- 31
ifelse (age <= 30, "I am 30 or younger", "I'm older than 30")
ifelse()
is most useful for evaluating elements of a
vector according to a specific condition. We illustrate this by just
slightly changing the above example to see how many participants in (a
hypothetical) class are younger or older than 30 years.
(ages <- sample(22:35, 30, replace = TRUE))
(age_groups <- ifelse (ages <= 30, "Younger than or just 30", "Older than 30"))
# table() allows you to count how many entries in an object exist for unique cases
table(age_groups)
You will often hear that loops should be avoided in R code wherever
possible because in certain cases they can make your code slower.
Nevertheless, they are an important element in programming. We use them,
for example, for iterating (parts of) code blocks with different
parameters or initial values. R offers three different loop structures:
repeat
, while
and for
, where the
latter is the most commonly used.
The for
loop iterates through all elements of a provided
vector: for (i in M) {command block}
. See the help page
?"for"
.
Below, we show two different ways of defining the vector that the
for
loop cycles through. In the first example, the vector
is defined first and the loop cycles through each element of the vector.
In the second example, we define the vector in the call of the
for
loop by 1:length(x)
. This expression means
that we cycle through all indices of the vector x
from
index 1 to the last. (Note that the loop could also start at any other
index value, e.g. 3:length(x)
)
x <- c(101:120)
# Option 1: cycle through each element of x
for (i in x) {
print(i)
}
# Option 2: cycle through the indices of x
for (i in 1:length(x)){
print(i)
}
Now, we use for
loops to roll dice.
x <- c(1:100)
# roll a dice 100 times and print result into console
for (i in x) {
print(sample(1:6, 1))
}
# roll a dice 100 times and build the cumulative sum of the faces. For this define a place holder variable that is then used to store the cumulative sum within the for loop.
y <- 0
for (i in 1:length(x)){
y <- y + sample(1:6,1)
# stop after 100 times
if (i >= 100){
break
} # end of if
} # end of for
y
# Stop rolling the dice as soon as the cumulative sum is higher than 150
y <- 0
for (i in 1:length(x)){
y <- y + sample(1:6,1)
# stop after 100 times OR after the cumulative sum is higher than 150
if (i >= 100 | y >= 150){
break
} # end of if
} # end of for
y
# This does almost the same thing but uses a while loop. Look up the help page of ?tail
y <- 0
while(tail(y, 1) < 150){
y <- c(y, tail(y, 1) + sample(1:6, 1))
}
y
R and the contributed packages contain a myriad of built-in functions. Still, it might be useful to write your own functions at some point. For example, you can combine several consecutive commands under one name/function. This facilitates recurring computations and improves the clarity of your scripts.
Function definitions always start with function()
followed by a command block in curly brackets {}
.
Obligatory and optional arguments are defined in the round brackets ().
You can define arguments with and without default settings, and the
argument list may also be empty.
# function without arguments:
greetings <- function() {
print('Hello world')
}
greetings()
# function with obligatory argument without default:
greetings <- function(name) {
print(paste('Hello', name))
}
greetings('students')
#function with one obligatory argument and another argument with default
greetings <- function(name, greet = 'Hello') {
print(paste(greet, name))
}
greetings('students')
greetings('students', 'Wake up')
Ideally, you write your own functions to simplify calculations or workflows that you often need. Let’s try an example for converting km to miles and vice versa.
# define the functions
km_to_miles <- function(km){
km * 0.62137
}
miles_to_km <- function(mi){
mi/0.62137
}
# use the functions
km_to_miles(80)
## [1] 49.7096
miles_to_km(50)
## [1] 80.46735
R automatically returns the last output of function. You can also specify what is to be returned. Compare the two examples below to understand the difference.
km_miles_conversion <- function(km, mi){
km * 0.62137
mi/0.62137
}
km_miles_conversion2 <- function(km, mi){
miles <- km * 0.62137
km_new <- mi/0.62137
return(c(miles, km_new))
}
km_miles_conversion(80,50)
## [1] 80.46735
km_miles_conversion2(80,50)
## [1] 49.70960 80.46735
Exercises:
Task 1: Write a function that calculates degrees Fahrenheit from degrees Celsius and another function for the other way around.
Task 2: Build two nested for-loops cycling through each element of
vec1
and vec2
and sum these up. You can store
the results in a new variable or simply print the results in the console
using print()
:
vec1 <- 1:10
vec2 <- seq(10, 100, 10)