3. Control Structures

Control structures are used to control the flow of your code or program. Basically, control structures are used to decide what to do; as basic as they are, control structures are the basis of intelligence in computer programs.

3.1. if-else

In the if-else statement, you must evaluate an expression that will return a boolean (TRUE or FALSE) value. Typically, expressions are evaluated using mathematical comparison operators.

  • < less than

  • > greater than

  • <= less than or equal

  • >= greater than or equal

  • == equals to

  • != not equals

Here are some example using simple if-else statements to evaluate a variable x satisfies some comparisons.

[1]:
x <- 10

if (x < 10) {
    print('x < 10')
} else {
    print('x >= 10')
}
[1] "x >= 10"
[2]:
x <- 10

if (x <= 10) {
    print('x <= 10')
} else {
    print('x > 10')
}
[1] "x <= 10"
[3]:
x <- 10

if (x != 10) {
    print('x != 10')
} else {
    print('x == 10')
}
[1] "x == 10"

3.1.1. if-elseif-else

The complete if-else statement can have multiple else if blocks.

[4]:
x <- 10

if (x < 10) {
    print('x < 10')
} else if (x == 10) {
    print('x == 10')
} else {
    print('x > 10')
}
[1] "x == 10"
[5]:
x <- 12

if (x < 10) {
    print('x < 10')
} else if (x == 10) {
    print('x == 10')
} else if (x < 12) {
    print('x < 12')
} else {
    print('x >= 12')
}
[1] "x >= 12"

3.1.2. ifelse

The ifelse function provides (what is known in other programming languages as) a ternary operator.

[6]:
x <- 2
y <- ifelse(x %% 2 == 0, 'even', 'odd')
y
'even'

3.1.3. Logical operators

You may chain comparison operators through logical operators.

  • ! logical not

  • && logical and

  • || logical or

  • xor logical xor

Below are some examples using these logical operators.

[7]:
isMale = FALSE

if (!isMale) {
    print('not male')
} else {
    print('male')
}
[1] "not male"
[8]:
isMale = FALSE
isAdult = TRUE

if (isMale && isAdult) {
    print('male adult')
} else if (isMale && !isAdult) {
    print('male minor')
} else if (!isMale && isAdult) {
    print('female adult')
} else {
    print('female minor')
}
[1] "female adult"
[9]:
isHungry = TRUE
isThirsty = FALSE

if (isHungry || isThirsty) {
    print('time for some grub')
} else {
    print('keep working')
}
[1] "time for some grub"
[10]:
isHungry = TRUE
isThirsty = TRUE

if (xor(isHungry, isThirsty)) {
    print('time for some grub')
} else {
    print('you waited too long to eat')
}
[1] "you waited too long to eat"

3.2. For loops

3.2.1. Looping over a vector

[11]:
names <- c('John', 'Jane', 'Jack', 'Joyce')
for (name in names) {
    print(name)
}
[1] "John"
[1] "Jane"
[1] "Jack"
[1] "Joyce"

3.2.2. Looping over a range of numbers

[12]:
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

3.2.3. sapply and lapply

You may also loop over elements in a vector using the sapply and lapply functions. The sapply function returns a vector and the lapply returns a list.

Typically, you should favor sapply and lapply over a for loop as these functions are vectorized. When you perform vectorized operations, they are faster since they are done in parallel, but the context will determine which is appropriate for your use.

[13]:
names <- c('John', 'Jane', 'Jack', 'Joyce')
a <- lapply(names, print)
print(a)
[1] "John"
[1] "Jane"
[1] "Jack"
[1] "Joyce"
[[1]]
[1] "John"

[[2]]
[1] "Jane"

[[3]]
[1] "Jack"

[[4]]
[1] "Joyce"

[14]:
names <- c('John', 'Jane', 'Jack', 'Joyce')
a <- sapply(names, print)
print(a)
[1] "John"
[1] "Jane"
[1] "Jack"
[1] "Joyce"
   John    Jane    Jack   Joyce
 "John"  "Jane"  "Jack" "Joyce"

Here, we use lapply to apply the sin function to a list of numbers [1:5].

[15]:
a <- lapply(1:5, sin)
print(a)
[[1]]
[1] 0.841471

[[2]]
[1] 0.9092974

[[3]]
[1] 0.14112

[[4]]
[1] -0.7568025

[[5]]
[1] -0.9589243

3.3. While loops

The while loop is another loop structure like the for loop, however, it has a termination condition that must be satisified for the loop to end. In the example below, we loop until x is greater than 5. Note that we increment x at the end of every iteration x <- x + 1.

[16]:
x <- 1

while (x <= 5) {
    print(x)
    x <- x + 1
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5

3.4. Conditions

Conditions may also interrupt the flow of your program. There are three types of conditions.

  • error

  • warning

  • message

3.4.1. Error

Use stop to signal an error.

[17]:
a <- 10
b <- 0

if (b == 0) {
    stop('divide by zero detected')
} else {
    print(a / b)
}
Error in eval(expr, envir, enclos): divide by zero detected
Traceback:

1. stop("divide by zero detected")   # at line 5 of file <text>

3.4.2. Warning

[18]:
a <- 10
b <- 0

if (b == 0) {
    warning('divide by zero detected, will divide by a small number instead')
    print(a / 0.00001)
} else {
    print(a / b)
}
Warning message in eval(expr, envir, enclos):
“divide by zero detected, will divide by a small number instead”
[1] 1e+06

You may suppress warnings with supressWarnings.

[19]:
a <- 10
b <- 0

suppressWarnings({
    if (b == 0) {
        warning('divide by zero detected, will divide by a small number instead')
        print(a / 0.00001)
    } else {
        print(a / b)
    }
})
[1] 1e+06

3.4.3. Message

[20]:
a <- 10
b <- 3

if (b == 0) {
    stop('divide by zero detected')
} else {
    message('parameters are good')
    print(paste('final results:', a / b))
}
parameters are good
[1] "final results: 3.33333333333333"
[21]:
a <- 10
b <- 3

suppressMessages({
    if (b == 0) {
        stop('divide by zero detected')
    } else {
        message('parameters are good')
        print(paste('final results:', a / b))
    }
})
[1] "final results: 3.33333333333333"

3.4.4. Handling condtions with try-catch

[22]:
a <- 10
b <- 0

result <- tryCatch({
    if (b == 0) {
        stop('divide by zero')
    } else {
        print(a / b)
    }
}, warning = function(w) {
    print(a / 0.00001)
}, error = function(e) {
    print(a / 0.00001)
}, finally = {
    print('i am done computing a / b')
})
[1] 1e+06
[1] "i am done computing a / b"