Conditions and For Loop

Reading Assignments

Conditionals and Iterations

There are two primary tools of control flow: conditionals and iterations.

  • Conditionals (= choices):
    • like if statements and switch() calls
    • Run different code depending on the input
  • Iterations (= loops):
    • like for and while
    • Repeatedly run code, typically with changing options.

Conditionals

Conditionals: If-Then-Else Statement

  • The syntax of if-then-else statement is
if (condition) {
  # do something
} else {
  # do something else
}

Example:

x = 42

if (x > 100) {
  print("x is greater than 100")
} else {
  print("x is less than or equal to 100")
}
#> [1] "x is less than or equal to 100"
x <- c(42, 179, 89) # x is a vector

# The codes below can't run
if (x > 100) {
  print("x is greater than 100")
} else {
  print("x is less than or equal to 100")
}
#> Error in if (x > 100) {: the condition has length > 1

Use Vectorized if statement ifelse

  • ifelse() is a vectorized function with test, yes, and no vectors (that will be recycled to the same length).
ifelse(x > 100, "x is greater than 100", "x is less than or equal to 100")
#> [1] "x is less than or equal to 100" "x is greater than 100"         
#> [3] "x is less than or equal to 100"

Conditionals: Chain Multiple If-Else Statements

  • The syntax of multiple if’s statement is
if (condition) {
  # run this code if the condition evaluates to TRUE
} else if (condition){
  # run this code if the condition evaluates to FALSE
} else {
  
}

Example:

# setup data
m = 22
n = 0

# series of if-else if statements
if (m < 20) {
  n = 20 # will run if m < 20
} else if (m < 40) {
  n = 40 # will run if 20 <= m < 40
} else if (m < 60) {
  n = 60 # will run if 40 <= m < 60
} else {
  n = Inf # will run if 60 <= m
}

# check "result"
n
#> [1] 40

Example: Place the if-else if statement within a function

# setup data
m = 22

# Create find.n() function

find.n <- function(m){
  if (m < 20) {
    return(20) # will run if m < 20
  } else if (m < 40) {
    return(40) # will run if 20 <= m < 40
  } else if (m < 60) {
    return(60) # will run if 40 <= m < 60
  } else {
    return(Inf) # will run if 60 <= m
  }
}

# Apply find.n() on m = 22
find.n(m)
#> [1] 40

Use switch() statement

  • switch() is closely related to if.
  • switch() is a compact, special purpose equivalent.
  • Limitations: It is recommended to use switch() only with character inputs.
    • It is also possible to use switch() with a numeric x, but is harder to read, and has undesirable failure modes if x is not a whole number.

Example: Convert the day of the week into a number.

  • Use if and else if
day <- "Tuesday" # Change this value!

if (day == 'Sunday') {
  num_day <- 1
} else if (day == "Monday") {
  num_day <- 2
} else if (day == "Tuesday") {
  num_day <- 3
} else if (day == "Wednesday") {
  num_day <- 4
} else if (day == "Thursday") {
  num_day <- 5
} else if (day == "Friday") {
  num_day <- 6
} else if (day == "Saturday") {
  num_day <- 7
}

num_day
#> [1] 3
day <- "Tuesday" # Change this value!

switch(day, # The expression to be evaluated.
  Sunday = 1,
  Monday = 2,
  Tuesday = 3,
  Wednesday = 4,
  Thursday = 5,
  Friday = 6,
  Saturday = 7,
  NA) # an (optional) default value if there are no matches
#> [1] 3

Place switch() within a function

# Create function legs()

legs <- function(x) {
  switch(x,
    cow = ,
    horse = ,
    dog = 4,
    human = ,
    chicken = 2,
    plant = 0,
    stop("Unknown input")
  )
}
  • If multiple inputs have the same output, we can leave the right hand side of = empty and the input will “fall through” to the next value.
legs("cow")
#> [1] 4

legs("dog")
#> [1] 4

legs("bear")
#> Error in legs("bear"): Unknown input

Iterations

For Loop

  • For loops are used when you know how many times a series of calculations need to be repeated.

The generic syntax of for loop is:

for (iterator in times) { 
  do_something
}

Example 1:

for (i in 1:5){ # i, j, k are commonly used for the iterator
  print(i)
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5
# Equivalent, but not recommended

for (some_index in 1:5){
  print(some_index)
}
#> [1] 1
#> [1] 2
#> [1] 3
#> [1] 4
#> [1] 5

Example 2:

  • It’s recommended to use seq_along and iterate over indexes rather than elements of a vector.

The following four chunk of code are equivalent:

# Recommended

value <- 2
times <- c('one', 'two', 'three', 'four') # Character Vector

for (i in seq_along(times)) { # seq_along(times) gives c(1, 2, 3, 4)
  value <- value * 2
  print(value)
}
#> [1] 4
#> [1] 8
#> [1] 16
#> [1] 32
value <- 2
times <- c('one', 'two', 'three', 'four')

for (i in 1:length(times)) { # 1:length(times) gives c(1, 2, 3, 4)
  value <- value * 2 
  print(value)
}
#> [1] 4
#> [1] 8
#> [1] 16
#> [1] 32
value <- 2
times <- c('one', 'two', 'three', 'four')

for (i in times) { # Works, but not recommended
  value <- value * 2 
  print(value)
}
#> [1] 4
#> [1] 8
#> [1] 16
#> [1] 32
value <- 2
times <- c(100, 200, 500, 800)

for (i in times) { # Works, but not recommended
  value <- value * 2 
  print(value)
}
#> [1] 4
#> [1] 8
#> [1] 16
#> [1] 32

Example 3:

The iterator i changes along with the “iterations vector”

# Recommended

i = 0
y = letters[4:1]
times <- c('one', 'two', 'three', 'four') # Character Vector

for (i in seq_along(times)) { # seq_along(times) gives c(1, 2, 3, 4)
  print(i)
  print(y[i])
}
#> [1] 1
#> [1] "d"
#> [1] 2
#> [1] "c"
#> [1] 3
#> [1] "b"
#> [1] 4
#> [1] "a"

i
#> [1] 4
i = 0
y = letters[4:1]
times <- c('one', 'two', 'three', 'four')

for (i in 1:length(times)) { # 1:length(times) gives c(1, 2, 3, 4)
  print(i)
  print(y[i])
}
#> [1] 1
#> [1] "d"
#> [1] 2
#> [1] "c"
#> [1] 3
#> [1] "b"
#> [1] 4
#> [1] "a"

i
#> [1] 4
i = 0
y = letters[4:1]
times <- c('one', 'two', 'three', 'four')

for (i in times) {
  print(i)
  print(y[i]) # doesn't work
}
#> [1] "one"
#> [1] NA
#> [1] "two"
#> [1] NA
#> [1] "three"
#> [1] NA
#> [1] "four"
#> [1] NA

i
#> [1] "four"
i = 0
y = letters[4:1]
times <- c(100, 200, 500, 800)

for (i in times) {
  print(i)
  print(y[i]) # doesn't work
}
#> [1] 100
#> [1] NA
#> [1] 200
#> [1] NA
#> [1] 500
#> [1] NA
#> [1] 800
#> [1] NA

i
#> [1] 800

Example 4 (using cat() instead of print()):

The iterator i changes along with the “iterations vector”

# Recommended

i = 0
y = letters[4:1]
times <- c('one', 'two', 'three', 'four') # Character Vector

for (i in seq_along(times)) { # seq_along(times) gives c(1, 2, 3, 4)
  cat(i, "\n")
  cat(y[i], "\n")
}
#> 1 
#> d 
#> 2 
#> c 
#> 3 
#> b 
#> 4 
#> a

i
#> [1] 4
i = 0
y = letters[4:1]
times <- c('one', 'two', 'three', 'four')

for (i in 1:length(times)) { # 1:length(times) gives c(1, 2, 3, 4)
  cat(i, "\n")
  cat(y[i], "\n")
}
#> 1 
#> d 
#> 2 
#> c 
#> 3 
#> b 
#> 4 
#> a

i
#> [1] 4
i = 0
y = letters[4:1]
times <- c('one', 'two', 'three', 'four')

for (i in times) {
  cat(i, "\n")
  cat(y[i], "\n") # doesn't work
}
#> one 
#> NA 
#> two 
#> NA 
#> three 
#> NA 
#> four 
#> NA

i
#> [1] "four"
i = 0
y = letters[4:1]
times <- c(100, 200, 500, 800)

for (i in times) {
  cat(i, "\n")
  cat(y[i], "\n") # doesn't work
}
#> 100 
#> NA 
#> 200 
#> NA 
#> 500 
#> NA 
#> 800 
#> NA

i
#> [1] 800

Next and Break Statements

  • next – skipping certain iterations
  • break – stop a loop from iterating

Example:

for (i in 1:10){
  
  if (i == 2) {
    next
  }
  
  if(i == 7){
    break
  }
  
  print(i)
  
}
#> [1] 1
#> [1] 3
#> [1] 4
#> [1] 5
#> [1] 6

Vectorized Alternative Solutions to For Loops

  • In R, avoid using for loops unless you truly need them.

Example 1:

x = 1:10

for (i in seq_along(x)) { # Equivalently, for (i in 1:length(x))
  x[i] = i^2
}

x
#>  [1]   1   4   9  16  25  36  49  64  81 100

However, using vectorization is way better in this case.

x = 1:10
x^2
#>  [1]   1   4   9  16  25  36  49  64  81 100

Example 2:

# Recore find.n() function

find.n <- function(m){
  if (m < 20) {
    return(20) # will run if m < 20
  } else if (m < 40) {
    return(40) # will run if 20 <= m < 40
  } else if (m < 60) {
    return(60) # will run if 40 <= m < 60
  } else {
    return(Inf) # will run if 60 <= m
  }
}

# Now m is a vector
m <- c(42, 37, 8, 72)

# check "result"
find.n(m)
#> Error in if (m < 20) {: the condition has length > 1

You may use for loop for this example (but not recommended).

n <- numeric()

### For loop in this case is NOT recommended
for (i in seq_along(m)){
  n[i] <- find.n(m[i])
}
n
#> [1]  60  40  20 Inf

However, it’s better to use lapply() or sapply() in this case.

lapply(m, find.n)
#> [[1]]
#> [1] 60
#> 
#> [[2]]
#> [1] 40
#> 
#> [[3]]
#> [1] 20
#> 
#> [[4]]
#> [1] Inf
sapply(m, find.n)
#> [1]  60  40  20 Inf

Apply Functions (lapply(), sapply())

The lapply general syntax is

lapply(X = some_list, FUN = f)
  • some_list is a vector (atomic vector or list)
  • The function f will be “applied” to each element of some_list.
  • lapply is returning a list.

Example:

# Create a list
set.seed(42)
ex_list = list(a = runif(5),
               b = runif(5),
               c = runif(5))
ex_list
#> $a
#> [1] 0.9148060 0.9370754 0.2861395 0.8304476 0.6417455
#> 
#> $b
#> [1] 0.5190959 0.7365883 0.1346666 0.6569923 0.7050648
#> 
#> $c
#> [1] 0.4577418 0.7191123 0.9346722 0.2554288 0.4622928
# Use lapply() on the list above

## Find range
lapply(ex_list, range)
#> $a
#> [1] 0.2861395 0.9370754
#> 
#> $b
#> [1] 0.1346666 0.7365883
#> 
#> $c
#> [1] 0.2554288 0.9346722

## Find maximum
lapply(ex_list, max)
#> $a
#> [1] 0.9370754
#> 
#> $b
#> [1] 0.7365883
#> 
#> $c
#> [1] 0.9346722
  • Notice that, each element of the last returning list is an atomic vector of length one, of the same type. In this case, we could use sapply(), where s refers to the simplifying action taken by the function.
sapply(ex_list, max)
#>         a         b         c 
#> 0.9370754 0.7365883 0.9346722

sapply() is returning a vector.

For Loop vs Apply Functions

  • How to decide you should use a loop or apply function?
    • Use an apply function when the results of each iteration are independent.
    • Use a loop when the result of the next iteration depends on the result of the previous iteration.

Example 13.4

set.seed(345)    # for replication purposes
amount0 = 1000
rates = rnorm(n = 10, mean = 0.10, sd = 0.18)

# output vector (to be populated)
amounts = c(amount0, double(length = 10)) ### preallocate the output container

year = 1:10

# for loop
for (s in seq_along(year)) { ### Use seq_along(year) instead of 1:length(year) is recommended
  amounts[s+1] = amounts[s] * (1 + rates[s])
}

amounts
#>  [1] 1000.0000  958.7165 1006.3527 1077.7409 1129.1412 1228.3298 1211.0918
#>  [8] 1129.9604 1590.9151 2223.8740 3170.9926

Recommendations when using for loop:

  • If you’re generating data, make sure to preallocate the output container. Otherwise the loop will be very slow. Do not grow vectors.
  • It’s recommended to use seq_along(x), instead of 1:length(x) or vector x, as the iterations vector.