if control structures

if-else control structures are very helpful for solving complex conditional operations:

if (condition) expression

example:

if (age >= 18) adult <- TRUE

The expression can take multiple lines which are enclosed with braces:

if (age >= 18 && gender == "male") { 
  adult <- TRUE
  n_males <- n_males + 1
}

if-else control structures

Sometimes, the if structure is extended with else:

if (condition) expression1 else expression2

example:

if (age >= 18) out <- "full-aged" else out <- "underage"

More complex example

if (age >= 18 && gender == "male") {  
  adult <- TRUE  
  n_males <- n_males + 1  
} else if (age >= 18 && gender == "female") { 
  adult <- TRUE  
  n_females <- n_females + 1  
} else if (age < 18) {    
  adult <- FALSE  
}

The if structure is not vectorized.
That is, the condition must return a single logical TRUE or FALSE value. This code will throw an error or warning message:

age <- c(18, 15)
if (age >= 18) cat("Adult!") else cat("Underage!")

ifelse

ifelse is a vectorized version of if() else which returns a vector:

ifelse(condition, expression_true, expression_false)

Example:

age <- c(15, 18, 14, 12)
ifelse(age >= 18, "adult", "underage")
[1] "underage" "adult"    "underage" "underage"

Task: Read the code

What is the object results after executing the code?

age <- c(15, 19, 21, 18, 17, 23)
cut_full_aged <- 18
language <- "german"
if (language == "english") {
  label_adult <- "adult"
  label_underage <- "underage"
} else if (language == "german") {
  label_adult <- "Volljährig"
  label_underage <- "Minderjährig"
} else {
  stop("Unknown language!")
}

results <- ifelse(age < cut_full_aged, label_underage, label_adult)

age <- c(15, 19, 21, 18, 17, 23)
cut_full_aged <- 18
language <- "german"
if (language == "english") {
  label_adult <- "adult"
  label_underage <- "underage"
} else if (language == "german") {
  label_adult <- "Volljährig"
  label_underage <- "Minderjährig"
} else {
  stop("Unknown language!")
}

results <- ifelse(age < cut_full_aged, label_underage, label_adult)

results
[1] "Minderjährig" "Volljährig"   "Volljährig"   "Volljährig"   "Minderjährig"
[6] "Volljährig"  

Task: Read the code

What is contained in res after executing the code?

by <- 1900 + 1:5 * 20
res <- ifelse(by > 1996, "Generation Z", 
                  ifelse(by > 1980, "Millenial", 
                         ifelse(by > 1964, "Generation X", 
                                ifelse(by >1945, "Babyboomer", 
                                       ifelse(by > 1927, "Silent generation", "Unknown")))))

by <- 1900 + 1:5 * 20
res <- ifelse(by > 1996, "Generation Z", 
                  ifelse(by > 1980, "Millenial", 
                         ifelse(by > 1964, "Generation X", 
                                ifelse(by >1945, "Babyboomer", 
                                       ifelse(by > 1927, "Silent generation", "Unknown")))))
                       
res
[1] "Unknown"           "Silent generation" "Babyboomer"       
[4] "Generation X"      "Generation Z"     

For loops

With a for loop you can iterate the values of a vector:

for (variable in vector) expression

example:

for (i in 1:10) print(i)
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10

An expression can have multiple lines enclosed in braces:

adult <- 0
minor <- 0
for(i in c(20, 12, 15, 16, 18)) {
  if (i >= 18) adult = adult + 1
  if (i < 18) minor = minor + 1
}
cat("Adults:", adult)
Adults: 2
cat("Minors:", minor)
Minors: 3

Oftentimes for loops are used to iterate multiple vectors.

Can you read this code and predict the object comparison ?

age1 <- c(20, 12, 15, 16, 18)
age2 <- c(17, 13, 17, 16, 20)
comparison <- c()
for(i in 1:length(age1)) {
  if (age1[i] < age2[i]) tmp <- "younger"
  if (age1[i] > age2[i]) tmp <- "older"
  if (age1[i] == age2[i]) tmp <- "equal"
  comparison <- c(comparison, tmp)
}

comparison

You can use for loops to iterate through a dataframe:

mtcars$impact <- "average"

for(i in 1:nrow(mtcars)) {
  if (mtcars$mpg[i] < 18 && mtcars$cyl[i] == 8 && mtcars$wt[i] > 4) {
    mtcars$impact[i] <- "Big waster!"
  }
  if (mtcars$mpg[i] > 22 && mtcars$cyl[i] == 4 && mtcars$wt[i] < 2.59) {
    mtcars$impact[i] <- "Light eco!"
  }
}

mtcars[, c("mpg", "cyl", "wt", "impact")]

mtcars$impact <- "average"

for(i in 1:nrow(mtcars)) {
  if (mtcars$mpg[i] < 18 && mtcars$cyl[i] == 8 && mtcars$wt[i] > 4) 
    mtcars$impact[i] <- "Big waster!"
  if (mtcars$mpg[i] > 22 && mtcars$cyl[i] == 4 && mtcars$wt[i] < 2.59) 
    mtcars$impact[i] <- "Light eco!"
}

mtcars[1:10 , c("mpg", "cyl", "wt", "impact")]

Functions

Repeated operations can be put into functions.

functionname <- function(argument, argument, argument, …) {expression}

power2 <- function(x) {
  out <- x * x
  out
}

power2(4)
[1] 16
power2(c(2, 3, 4,5))
[1]  4  9 16 25

Function example

w20_die <- function(dice) {
  if (dice < 1 || dice > 20) return("This is not a decent W20 die!")
  if (dice == 20) return("Critical hit!!")
  if (dice == 1) return("Epic fail")
  dice
}

return() stops the function and returns an object. If no return is defined, the function will return the last object that is printed.

w20_die <- function(dice) {
  if (dice < 1 || dice > 20) return("This is not a decent W20 die!")
  if (dice == 20) return("Critical hit!!")
  if (dice == 1) return("Epic fail")
  dice
}
w20_die(20)
[1] "Critical hit!!"
w20_die(3)
[1] 3
w20_die(1)
[1] "Epic fail"
w20_die(0)
[1] "This is not a decent W20 die!"

Task Read the code

What happens here?

percentage <- function(x, na.rm = TRUE) {
  if (!is.logical(x)) stop("Please provide a logical vector.")
  if (na.rm) x <- x[!is.na(x)]
  sum(x) / length(x) * 100
}

var <- c(12, 16, 19, 20, 11)

percentage(var >= 18)
[1] 40

Task

w20_die <- function(dice) {
  if (dice < 1 || dice > 20) return("This is not a decent W20 die!")
  if (dice == 20) return("Critical hit!!")
  if (dice == 1) return("Epic fail")
  dice
}
  • Vectorize the w20_die function so it will return a vector with results when you provide a vector with numbers as an argument:
results <- c(21, 2, 16, 20, 1, 0)
w20_die(results)
[1] "This is not a decent W20 die!" "2"                            
[3] "16"                            "Critical hit!!"               
[5] "Epic fail"                     "This is not a decent W20 die!"

Solution 1: With a for loop:

w20_die <- function(dice) {
  out <- dice
  for(i in 1:length(dice)) {
    if (dice[i] < 1 || dice[i] > 20) out[i] <- "This is not a decent W20 die!"
    if (dice[i] == 20) out[i] <- "Critical hit!!"
    if (dice[i] == 1) out[i] <- "Epic fail"
  }
  out
}
results <- c(21, 2, 16, 20, 1, 0)
w20_die(results)
[1] "This is not a decent W20 die!" "2"                            
[3] "16"                            "Critical hit!!"               
[5] "Epic fail"                     "This is not a decent W20 die!"

Solution 2: With subsetting:

w20_die <- function(dice) {
  out <- dice
  out[dice < 1 | dice > 20] <- "This is not a decent W20 die!"
  out[dice == 20] <- "Critical hit!!"
  out[dice == 1] <- "Epic fail"
  out
}
results <- c(21, 2, 16, 20, 1, 0)
w20_die(results)
[1] "This is not a decent W20 die!" "2"                            
[3] "16"                            "Critical hit!!"               
[5] "Epic fail"                     "This is not a decent W20 die!"

Solution 3: With ifelse:

w20_die <- function(dice) {
  ifelse(dice < 1 | dice > 20, "This is not a decent W20 die!",
         ifelse(dice == 20, "Critical hit!!",
                ifelse(dice == 1, "Epic fail", dice)))
}
results <- c(21, 2, 16, 20, 1, 0)
w20_die(results)
[1] "This is not a decent W20 die!" "2"                            
[3] "16"                            "Critical hit!!"               
[5] "Epic fail"                     "This is not a decent W20 die!"