Control Flows

There are two primary tools for control flow: choices and loops. Choices, like if statements and switch() calls, allow you to run different code depending on the input. Loops, like for and while, allow you to repeatedly run code, typically with changing options

Conditionals

The basic form of an if statement in R is as follows:

if (condition) true_action
if (condition) true_action else false_action

If condition is TRUE, true_action is evaluated; if condition is FALSE, the optional false_action is evaluated.

a <- -3
if(a < 0) print('Negative number')
[1] "Negative number"
a <- 3
if(a < 0) print('Negative number')
a <- 3
if(a < 0) print('Negative number') else print('positive number')
[1] "positive number"

Note that you cannot start a new line with the else condition, unless in an enclosed environment, something of which we have not tackled. To be safe, always write else in the same line as if. If you have multiple statements to be evaluated, use curly braces as shown below:

x <- -3
if(x<0){ # The opening curly bracket must be in the same line as if
  print('X is negative')
} else { # The else statement must be immediately after }
  print('x is positive')
}
[1] "X is negative"

DO not do this:

x <- -3
if(x<0)
  print('x is negative')
else
  print('x is positive')
Error: <text>:4:1: unexpected 'else'
3:   print('x is negative')
4: else
   ^

Note that when R parses the statements above, else is taken to be a stand alone and not part of the if statement, hence cannot be parsed.

{
  x <- -3
if(x<0)
  print('x is negative')
else
  print('x is positive')
}
[1] "x is negative"

Note that I have enclosed all the work in curly braces, hence they form a scope thus parsed as one entity.

In case you have multiple statements you must use { for each part. ie:

x <- -3
if(x<0){
  print('x is negative')
  print('This is what we want')
} else{
  print('x is positive')
  print('This is not what we want')
}
[1] "x is negative"
[1] "This is what we want"

Another example:

x <- -3
y <- if(x > 0) x else 0 # What is the value of y?
y
[1] 0

Because that was a small statement, we were able to assign all of it to the variable y.

Sometimes you need to evaluate a lot of statements before you find the value of y

x <- -3
if(x>0){
  z <- 3
  y <- z * x
} else {
  z <- -0.03
  p <- z*10
  y <- x * p
}
y
[1] 0.9

Sometimes we use else if: ie nested

x <- 57
if(x < 25) "small" else if(x<75) "medium" else "large"
[1] "medium"

The above could also be written as:

x <- 100
if(x < 25) {
  "small"
} else if(x<75) {
  "medium"
}else {
  "large"
}
[1] "large"
x <- 5
if(x < 75) {
  if(x < 25)
  "small"
  else{
  "medium"
  }
} else {
  "large"
}
[1] "small"

The above is called a nested if-statement. ie we have an if inside another if.

Sometimes It becomes tedious to write all the if else statements:

x <- "c"
if (x == "a") {
    "option 1"
} else if (x == "b") {
    "option 2" 
} else if (x == "c") {
    "option 3"
} else if (x == "d") {
    "option 4"
}else {
    "option 5"
}
[1] "option 3"

This can succinctly be written using the switch function:

x <- "c"
switch(x,
    a = "option 1",
    b = "option 2",
    c = "option 3",
    d = "option 4",
    "option 5"
  )
[1] "option 3"

If using | conditional statements:

x <- "cow"
if(x == "cow" | x == "horse" | x == "dog") {
  4
} else if(x == "human" | x == "bird") {
  2
} else if (x == "plant") {
  0
} else {
  "Unknown input"
}
[1] 4

For the switchfunction, if multiple inputs have the same output, you can leave the right hand side of = empty and the input will “fall through” to the next value.

x <- "cow"
 switch(x,
    cow = ,
    horse = ,
    dog = 4,
    human = ,
    chicken = 2,
    plant = 0,
    "Unknown input"
  )
[1] 4

Notice what happens when x is a vector:

x <- -3:3
if(x > 0) x else 0
Error in if (x > 0) x else 0: the condition has length > 1

This shows you that if can only work on a single element rather than a vector.

Note that there is a vectorized function ifelse

usage: ifelse(condition, true_action, false_action)

x <- -1:3
ifelse(x > 0, x, 0)
[1] 0 0 1 2 3
ifelse(x<0, print('x is Negative'), print('x is positive'))
[1] "x is Negative"
[1] "x is positive"
[1] "x is Negative" "x is positive" "x is positive" "x is positive"
[5] "x is positive"

What happened? Notice that while if and else statements were control flows, the ifelse is a function which returns a value. It is only advisable to use it where values need to be returned rather than printed.

It is also possible to use switch() with a numeric x, but is harder to read, and has undesirable failure modes if x is a not a whole number. I recommend using switch() only with character inputs.

Loops

for - loop

This iterates over items in a vector.

hey have the following basic form:

for (item in vector) perform_action

For each item in vector, perform_action is called once; updating the value of item each time.

x <- 1:3
for (i in x) {
  print(i)
}
[1] 1
[1] 2
[1] 3

We can also use indexing. ie

x <- c(10, 20, 30, -10)

for (i in seq_along(x)) {
  cat("iteration: ", i, "X = ", x[i], "\n")
}
iteration:  1 X =  10 
iteration:  2 X =  20 
iteration:  3 X =  30 
iteration:  4 X =  -10 

Now we can compute the means, sds of the columns of a matrix using for-loop:

A <- matrix(1:24, 4,6)
A
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    5    9   13   17   21
[2,]    2    6   10   14   18   22
[3,]    3    7   11   15   19   23
[4,]    4    8   12   16   20   24
n <- ncol(A)
B <- numeric(n) # We know we have 6 columns, so we will end up with 6 values
C <- numeric(n)

for (i in seq_len(n)){
  B[i] <- mean(A[,i])
  C[i] <- sd(A[,i])
}
B #The means
[1]  2.5  6.5 10.5 14.5 18.5 22.5
C # The standard deviations
[1] 1.290994 1.290994 1.290994 1.290994 1.290994 1.290994

while loop

Unlike the for-loop, this takes a condition and as long as the condition is satisfied, the iteration is carried out.

while(condition) perform_action

x <- 0
while (x < 5){
  print(x)
  x <- x + 1 # increase x by 1
}
[1] 0
[1] 1
[1] 2
[1] 3
[1] 4

Notice that The increment statement is at the very end. Depending on the nature of the problem, you can change the position of the increment statement and the condition for the loop to terminate.

Loop Termination

There are two ways to terminate a loop early:

  • next exits the current iteration.

  • break exits the entire for loop.

for (i in seq_len(10)) {
  if (i < 3) 
    next

  print(i)
  
  if (i >= 5)
    break
}
[1] 3
[1] 4
[1] 5
n <- 0
while(TRUE) {
  print(n)
  if(n>=4) break
  n <- n + 1
}
[1] 0
[1] 1
[1] 2
[1] 3
[1] 4

repeat loop

Almost similar to a while loop with the exception that it uses break statement to terminate.

error <- 0
repeat {
  # do something
  error <- error + 1
  if(error > 5) break
}

Examples:

my_cumsum <- function(x){
  result <- rep(0, length(x))
  result[1] <- x[1]
  for(i in seq(2,length(x))) result[i] <- result[i-1] + x[i]
  result
}
my_cumsum(1:10)
 [1]  1  3  6 10 15 21 28 36 45 55
my_sum <- function(x){
  result <- 0
  for(i in x) result <- result + i
  result
}
my_sum(1:10)
[1] 55
my_sum1 <- function(x){
  result <- 0
  for(i in seq_along(x)) result <- result + x[i]
  result
}
my_sum1(1:10)
[1] 55
my_max <- function(x){
  result  <- x[1]
  for(i in x){
    if(i>result) result <-i
  }
  result
}
my_max(c(3,8,10,2,-1))
[1] 10
rowMax <- function(x){
  results <- c()
  for(i in seq(nrow(x))){
    mid_result <- x[i, 1]
    for(j in seq(ncol(x))){
      if(x[i,j]>mid_result) mid_result <- x[i,j]
    }
      results[i] <- mid_result
  }
  results
}
rowMax(matrix(1:24, 4,6)) 
[1] 21 22 23 24

Quick Lesson on Control Characters [2]for printing on screen.

These characters enable one to neatly print elements on the screen.

  • \b backspace- Deletes one element to the left- Moves cursor one element to the left

    cat("This iss\b good\n") # One s has been deleted
    This iss good
  • \n new line/line feed - prints the next text in a new line. Moves cursor to a new line.

    cat("First line\nSecond Line\n\nThiiird line")
    First line
    Second Line
    
    Thiiird line
  • \r carriage return - Moves cursor to the beggining of the current line

    cat("This replaced by \rthat \n")
    This replaced by 
    that 
  • \t tab - Moves cursor 4-spaces ahead

    cat("Hello\tWorld\n")
    Hello   World

Note that instead of cat we could use print though print does not easily append strings together as cat.

{
  cat("\t\t|----------------------------------------\n")
  cat("\t\t|A\t|B\t|C\tD\t|E\t|\n")
  cat("\t\t|----------------------------------------\n")
  for(i in seq_len(5)){
    cat("Iteration", i)
    for(j in seq_len(5)){
      cat("\t|", j, sep='')
    }
    cat("\t|\n")
  }
}
        |----------------------------------------
        |A  |B  |C  D   |E  |
        |----------------------------------------
Iteration 1 |1  |2  |3  |4  |5  |
Iteration 2 |1  |2  |3  |4  |5  |
Iteration 3 |1  |2  |3  |4  |5  |
Iteration 4 |1  |2  |3  |4  |5  |
Iteration 5 |1  |2  |3  |4  |5  |

Of course we can control the number of digits/ the width of the text to be printed using sprintf function.

Notice how I used extra curly braces {} to wrap the whole code as one. By removing the outermost braces, the behavior changes. The code needs to be run as a single unit and that’s why we put the extra braces

Exercise 3

  1. Factorial: \(n! = 1\times2\times3\times\cdots\times n=\prod_{i=1}^n\). write a function called fact(n) that has one parameter n, and computes n! Fun Fact: \(0!=1\) Hence your function should output 1 whenever \(n=0\). Accomplish this using a for-loop

  2. Write a function that prints a multiplication table. Use double for-loops:

  3. Write a function grade(x) that would take in the marks of a student and output the grade as per the table below eg grade(73) should output C

    A B C D E F
    90-100 80-89 70-79 60-69 50-59 <50
  4. Write a function grades(x) that would take in a vector of scores and output the grades as per the table in question one. eg grades(c(73, 92, 80, 49)) should output C A B F

  5. \(\mathbf{\pi}\) Estimation: Using the formula: \(\pi = 4 - \frac{4}{3} + \frac{4}{5} - \frac{4}{7} - \frac{4}{9}+\cdots\) use the first 100,000 terms to estimate \(\pi\). Does it converge?

    Hint: \(\pi = 4\sum_{i=1}^n \frac{(-1)^{i-1}}{2i-1}\)

  6. The sequence above converges slowly. For faster convergence we use Ramanujan’s equation for \(\pi\) ie: \({\frac {1}{\pi }}={\frac {2{\sqrt {2}}}{9801}}\sum _{k=0}^{\infty }{\frac {(4k)!(1103+26390k)}{(k!)^{4}396^{4k}}}\) . Use the first 5 terms to estimate \(\pi\) . You can print more decimals on the screen by using print(pi, digits = 22) or using sprintf("%.30f", pi). Compare the \(\pi\) in R vs the \(\pi\) you estimated above. (You should watch the movie - “The man who knew Infinity”)

  7. Square root -Average Method: To compute the square root of a number, one can often use an iterative approach of line search.

    Assume we want the \(\sqrt{2}\). Let our initial points be 0 and 2, then we could do:

    \[ \begin{aligned} \operatorname{begin} &= 0, \quad\operatorname{end}=2\\ x_1 &= \frac{0 + 2}{2} = 1 ~~ie~~ \text{midpoint between }0 \text{ and }2 \\ \operatorname{error} &= 2-1 = 1\\ \text{Since error}&>0, \operatorname{begin=}1\quad x_2 = \frac{1+2}{2} = 1.5 ~~ie~~ \text{midpoint between }1 \text{ and }2 \\ \operatorname{error} &= 2-1.5^2 = -0.25\\ \text{Since error}&<0, \operatorname{end=}1.5\quad x_3 = \frac{1+1.5}{2} = 1.25\\ \operatorname{error} &= 2-1.25^2 = 0.4375\\ \text{Since error}&>0, \operatorname{begin=}1.25\\ x_4 &= \frac{1.25+1.5}{2} = 1.375\\ \vdots \end{aligned} \]

    This algorithm converges slowly. We continue averaging until the \(|\epsilon|\leq\tau\) where \(\tau\) is taken to be a very small number for example \(\tau =10^{-8}\) =1e-8. Write a function sqrt_avg(x) implementing the algorithm above.

  8. Square root - Heron’s Method-: To calculate \(\sqrt{S}\) we use the equation
    \[ x_{n+1} = \frac{1}{2}\left(x_n + \frac{S}{x_n}\right) \]

    where \(x_0\) is any arbitrary positive integer. The closer it is to \(\sqrt{S}\) the better.

    Given \(S=125348\) ,we can estimate to six significant figures the \(\sqrt{S}\) . using the rough estimation method above to get

    \[ \begin{array}{ll}x_0=6 \cdot 10^2 & =600.000 \\x_1=\frac{1}{2}\left(x_0+\frac{S}{x_0}\right)=\frac{1}{2}\left(600.000+\frac{125348}{600.000}\right)=404.457 \\x_2=\frac{1}{2}\left(x_1+\frac{S}{x_1}\right)=\frac{1}{2}\left(404.457+\frac{125348}{404.457}\right)=357.187 \\x_3=\frac{1}{2}\left(x_2+\frac{S}{x_2}\right)=\frac{1}{2}\left(357.187+\frac{125348}{357.187}\right)=354.059 \\x_4=\frac{1}{2}\left(x_3+\frac{S}{x_3}\right)=\frac{1}{2}\left(354.059+\frac{125348}{354.059}\right)=354.045 \\x_5=\frac{1}{2}\left(x_4+\frac{S}{x_4}\right)=\frac{1}{2}\left(354.045+\frac{125348}{354.045}\right)=354.045\end{array}\\ \therefore \sqrt{125348} \approx 354.045 \]

    Note that \(x_0\) does not necessarily have to be the number given above. It can be any positive number eg 1. Using the algorithm described above, write a function named sqrt_heron(x) that takes in one parameter and computes the square root of any non-negative real number. Use a tolerance level of 1e-8

Back to top