10. Flow control

Most programs have two general control-flow features: iteration and alternation. Iteration is done via “loops”, alteration via “if-then-else” branches.

Looping

Loops are typically used to repeat the same code a number of times for a set of cases. For example, compute the average grade for each student.

In R we avoid loops wherever we can, as they tend to be slower than ‘vectorized’ computation. We also generally prefer functions like apply (see the previous chapter), as this is more concise. At first code using for-loops may seem easier to read, but after using R for a while, the reverse is true is most cases. Nevertheless, there are cases where loops are much easier to write and clearer to read than using vectorized approaches.

There are two types of loops: ‘for-loops’ and ‘while-loops’. A ‘for-loop’ repeats the same code a predefined number of times. A ‘while-loop’ continues until a certain condition has been met (and is therefore prone to the dreaded “infinite loop” that never finishes!)

for-loops

Here is a basic for-loop that does not do anything useful. The trick is that in the parenthesis after for, you define a sequence of values. This sets the number of repetitions (the length of the sequence) and potentially provides a value that changes what is done in each loop.

Note that the braces { } are used to open and close a ‘block’ of code.

for (i in 1:3) {
    print('hi')
}
## [1] "hi"
## [1] "hi"
## [1] "hi"

Now let’s do something with i. You normally do not use print inside a loop. I am only doing that to illustrate what is going on.

j <- 0
for (i in 1:3) {
    print(i)
    j <- j + i
}
## [1] 1
## [1] 2
## [1] 3
j
## [1] 6

The loop above was used to sum the values 1, 2, and 3. Of course, it would have been easier to use sum(1:3).

Another example.

for (i in 1:3) {
    txt <- paste('the square of', i, 'is', i * i)
    print(txt)
}
## [1] "the square of 1 is 1"
## [1] "the square of 2 is 4"
## [1] "the square of 3 is 9"

The example below is a bit more complex. It shows how iterator i is typically used in a loop (to get a single case from a collection of cases and compute a new value for that case).

s <- 0
a <- 1:6
b <- 6:1

# initialization of output variables
res <- vector(length=length(a))

# i goes from 1 to 6 (the length of b)
for (i in 1:length(b)) {
  s <- s + a[i]
  res[i] <- a[i] * b[i]
}
s
## [1] 21
res
## [1]  6 10 12 12 10  6

Again, for this simple problem, it would have been simpler to do s <- sum(a) and res <- a * b.

break and next

Sometimes you want to include a condition to either “break out” of a for loop, or to skip the remainder of the for block and go to the next iteration. How to do that is illustrated below:

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

while-loops

“while-loops” are not nearly as common as “for-loops”. Here is an example.

i <- 0
while (i < 4) {
   print(paste(i, 'and counting ...'))
   i <- i + 1
}
## [1] "0 and counting ..."
## [1] "1 and counting ..."
## [1] "2 and counting ..."
## [1] "3 and counting ..."

And one that is less predictable, as it depends on the value of a random number.

set.seed(1)
i <- 0
while(i < 0.5) {
    i <- runif(1)
    print(i)
}
## [1] 0.2655087
## [1] 0.3721239
## [1] 0.5728534

You can also combine while with break.

set.seed(1)
while(TRUE) {
    i <- runif(1)
    print(i)
    if (i > 0.5) {
        break
    }
}
## [1] 0.2655087
## [1] 0.3721239
## [1] 0.5728534

Branching

Branching is an important mechanism in computer programs. A branch allows you to execute some code if certain conditions are met, and do something else in other cases. This is illustrated below. Note that the braces { } are used to open and close a ‘block’ of code.

We have two variables, x and y

x <- 5
y <- 10

We want to change y, depending on the value of x.

We need to branch our R code using one or more conditional statements (if, then, else) and some boolean logic (a statement that can evaluate to TRUE or FALSE.

if (x == 5) {
    y <- 15
}
y
## [1] 15

We tested for one condition, x==5. If this condition evaluated to TRUE, the code within the block, { y <- 15 } is executed. If it evaluates to FALSE, the code within the block is ignored. Note that the expression within the parenthesis if(), or else() should always evaluate to a single value of either TRUE or FALSE (not to NA or to multiple values).

Here is a more complex example, where we evaluate three cases. x > 20 (x is larger than 20), x >= 5 & x < 10 (x is in between 5 and 10, including 5, but not 10), and all other cases.

if (x > 20) {
    y <- y + 2
} else if (x > 5 & x < 10) {
    y <- y - 1
} else {
    y <- x
}

y
## [1] 5

If we have a boolean variable

b <- TRUE

You can do

if (b == TRUE) {
    print('hello')
}
## [1] "hello"

But it is more elegant to do

if (b) {
    print('hello')
}
## [1] "hello"

Now combining the previous chapter with this one, a for loop with an if/else branch:

a <- 1:5
f <- vector(length=length(a))
for (i in 1:length(a)) {
    if (a[i] > 2) {
        f[i] <- a[i] / 2
    } else {
        f[i] <- a[i] * 2
    }
}
f
## [1] 2.0 4.0 1.5 2.0 2.5