Lab 08: Conditionals

Case by Case

Let’s ask a simple question. What can we currently do if we want our code to handle different cases?

For example, suppose we would like to get the season (as a string) given a month.

which_season <- function(month) {
  # What should we write here?
}

Case by Case

Let’s ask a simple question. What can we currently do if we want our code to handle different cases?

For example, suppose we would like to get the season (as a string) given a month.

which_season <- function(month) {
  c(rep("Winter", 2), 
    rep("Spring", 3), 
    rep("Summer", 3), 
    rep("Fall", 3), 
    "Winter")[month]
}

We could use a vector.

This is a neat work around, but in more complex cases this won’t be possible (or practical).

Fizzing, Buzzing, and FizzBuzzing

Let’s consider another case: the famous FizzBuzz problem.

Given a number n, return:

  • "Fizz" if it is divisible by 3.
  • "Buzz" if it is divisible by 5.
  • "FizzBuzz" if it is divisible by 3 and 5.
  • The number n as a string otherwise.
fizzbuzz <- function(n) {
  # What should we write here? 
}

Fizzing, Buzzing, and FizzBuzzing

Here’s an idea:

fizzbuzz <- function(n) {
  c(as.character(n),
    "Fizz",
    "Buzz",
    "FizzBuzz")[((n %% 3 == 0) + (2 * (n %% 5 == 0))) + 1]
}

Oh my, can you make sense of this?

Fizzing, Buzzing, and FizzBuzzing

Maybe this?

fizzbuzz <- function(n) {
  c("FizzBuzz",
    "Fizz",
    "Buzz",
    as.character(n))[
      c(n %% 3 == 0 && n %% 5 == 0, n %% 3 == 0, n %% 5 == 0, TRUE)
    ][1]
}

Umm… Are your eyes hurting yet?

Fizzing, Buzzing, and FizzBuzzing

What about this?

fizzbuzz <- function(n) {
  c("FizzBuzz",
    "Fizz",
    "Buzz",
    as.character(n))[
      c(n %% 3 == 0 && n %% 5 == 0, n %% 3 == 0, n %% 5 == 0, TRUE) |> 
        which() |> 
        min()
      ]
}

… Isn’t that terrible? Can you explain how it works?

Clearly we need some better tools for handling cases.

Oh, if Only!

Recall that we prefer for our code to be expressive.

Our description of fizzbuzz included “IF”. Wouldn’t it be nice to write this directly in our code?

We can!

x <- 10
if (x >= 10)
  x <- 5
x
[1] 5

The if Statement

The if statement follows the intuitive structure:

if (<logical>) <body>

where <body> is executed if <logical> is true.

Note that the parenthesis are required1.

The if statement – multiline body?

What if we want to execute multiple lines of code if a condition is true?

do_some_math <- function(ok, a, b) {
  if (ok)
    print("a * 10 is: ", a * 10)
  if (ok)
    print("b * 10 is: ", b * 10)
  if (ok)
    print("a * b is: ", a * b)
}

Well this seems silly…

To include multiple lines into <body>, we must place them inside curly brackets: { ... }.

The if statement – multiline body?

We rewrite do_some_math:

do_some_math <- function(ok, a, b) {
  if (ok) {
    print("a * 10 is: ", a * 10)
    print("b * 10 is: ", b * 10)    
    print("a * b is: ", a * b)    
  }
}

On Curly Brackets

Note that when using curly brackets, the last expression evaluated will be the value of the curly bracket block.

food <- "boba"
lover <- {  # Variables are typically not set like this in R. 
  if (food == "banana")
    "Monkey"
  if (food == "boba")
    "Cal Student"
}

What is lover?

lover
[1] "Cal Student"

Not This, That!

Maybe we’d like to execute some code if a condition is true, and execute some other code otherwise. We could write,

if (conditional) {
  # body A
}
if (!conditional) {
  # body B
}

But this:

  • wastefully evaluates conditional twice. If the first if failed, we know conditional is FALSE.
  • is not very expressive.
  • won’t work as intended if body A modifies conditional.

The if else Statement

Instead, we use the if else statement:

if (<logical>) <body A> else <body B>

where,

  • <body A> is executed if <logical> is true.
  • <body B> is executed if <logical> is false.
can_i_drive <- function(age) {
  if (age < 16) 
    "No, you're too young."
  else
    "If you have a license."
}
can_i_drive(21)
[1] "If you have a license."

The else if Statement

When we need to account for more cases than just two (true or false), we can use an else if statement:

if (<logical 1>) <body 1>

else if (<logical 2>) <body 2>

else if (<logical 3>) <body 3> ...

else <body N>

  • Only one body will be evaluated.
  • The initial if statement is required, but the final else is not.
  • You may include as many else ifs as you’d like.

A Note on Style

The curly brackets are optional if the body is one line.

  • Some prefer you always use curly brackets.
  • Regardless, it is recommended you use curly brackets for all bodies if at least one uses curly brackets.

Don’t do this.

year <- 2
is_sophomore <- NA
if (year == 2) {
  print("You're a sophomore!")
  is_sophomore <- TRUE
} else
  is_sophomore <- FALSE

Do this instead.

year <- 2
is_sophomore <- NA
if (year == 2) {
  print("You're a sophomore!")
  is_sophomore <- TRUE
} else {
  is_sophomore <- FALSE
}

A Note on Style

Note that R requires else/else if be placed after the closing curly bracket (}).

This is not allowed.

year <- 2
is_sophomore <- NA
if (year == 2) {
  print("You're a sophomore!")
  is_sophomore <- TRUE
} 
else {
  is_sophomore <- FALSE
}

This format is required.

year <- 2
is_sophomore <- NA
if (year == 2) {
  print("You're a sophomore!")
  is_sophomore <- TRUE
} else {
  is_sophomore <- FALSE
}

Control Flow, Branching

You may see these statement referred to as,

  • Control Flow Statements. They control which lines of code are evaluated, which are ignored, and the order in which they’re evaluated.
  • Branching Statements. They may cause the program to “branch” to another line of code (instead of the one immediately after it).

Back to which_season

Let’s rewrite our which_season function:

which_season <- function(month) {
  c(rep("Winter", 2), 
    rep("Spring", 3), 
    rep("Summer", 3), 
    rep("Fall", 3), 
    "Winter")[month]
}

Back to which_season

Now with control flow statements.

which_season <- function(month) {
  if (month < 1 || month > 12)
    NA
  else if (month == 12 || month %in% 1:2)
    "Winter"
  else if (month %in% 3:5)
    "Spring"
  else if (month %in% 6:8)
    "Summer"
  else
    "Fall"
}

Back to fizzbuzz

Let’s rewrite our fizzbuzz function:

fizzbuzz <- function(n) {
  c(as.character(n),
    "Fizz",
    "Buzz",
    "FizzBuzz")[((n %% 3 == 0) + (2 * (n %% 5 == 0))) + 1]
}

Back to fizzbuzz

Now with control flow statements. Pick your poison!

fizzbuzz <- function(n) {
  # This one is what I prefer. 
  s <- character()
  if (n %% 3 == 0) {
    s <- "Fizz"
  }
  if (n %% 5 == 0) {
    s <- paste0(s, "Buzz")
  }
  if (!length(s)) {
    s <- as.character(n)
  }
  s
}
fizzbuzz <- function(n) {
  if (n %% 3 == 0 && n %% 5 == 0) {
    "FizzBuzz"
  }
  else if (n %% 3 == 0) {
    "Fizz"
  }
  else if (n %% 5 == 0) {
    "Buzz"
  }
  else {
    as.character(n)
  }
}
fizzbuzz <- function(n) {
  if (n %% 3 == 0) {
    # To avoid redundant computation. 
    if (n %% 5 == 0)
      "FizzBuzz"
    else
      "Fizz"
  } else if (n %% 5 == 0) {
    "Buzz"
  } else {
    as.character(n)
  }
}
fizzbuzz <- function(n) {
  # To avoid redundant computation. 
  div_3 <- n %% 3 == 0
  div_5 <- n %% 5 == 0
  if (div_3 && div_5) {
    "FizzBuzz"
  } else if (div_3) {
    "Fizz"
  } else if (div_5) {
    "Buzz"
  } else {
    as.character(n)
  }
}

The Vectorized if else, ifelse()

We can use the function ifelse to apply the if else operation to a vector. Its function signature is

ifelse(test, yes, no)

where,

  • test is a vector of logical values.
  • yes is the value to be placed in the result vector if the corresponding logical value is true.
  • no is the value to be placed in the result vector if the corresponding logical values is false.

Simple ifelse() Example

x <- 1:10
ifelse(x %% 2 == 0, "Divisible by 2", "Not divisible by 2")
 [1] "Not divisible by 2" "Divisible by 2"     "Not divisible by 2"
 [4] "Divisible by 2"     "Not divisible by 2" "Divisible by 2"    
 [7] "Not divisible by 2" "Divisible by 2"     "Not divisible by 2"
[10] "Divisible by 2"