## 4.2 Making Decisions: Conditionals

Another type of flow control involves determining *which parts* of the program to execute, depending upon certain conditions.

### 4.2.1 If Statements

Let’s design a simple guessing-game for the user:

- The computer will pick randomly a whole number between 1 and 4.
- The user will then be asked to guess the number.
- If the user is correct, then the computer will congratulate the user.

```
number <- sample(1:4, size = 1)
guess <- as.numeric(readline("Guess the number (1-4): "))
if ( guess == number ) {
cat("Congratulations! You are correct.")
}
```

The `sample()`

function randomly picks a value from the vector that is is given. The `size`

parameter specifies how many numbers to pick. (This time we only want one number.)

Flow control enters the picture with the reserved word `if`

. Immediately after `if`

is a Boolean expression enclosed in parentheses. This expression is often called the *condition*. If the condition evaluates to `TRUE`

, then the body of the `if`

statement—the code enclosed in the brackets—will be executed. On the other hand if the condition evaluates to `FALSE`

, then R skips past the bracketed code.^{13}

The general form of an `if`

expression is as follows:

The code above congratulates the a lucky guesser, but it has nothing at all to say to someone who did not guess correctly. The way to provide an alternative is through the addition of the `else`

reserved-word:

```
number <- sample(1:4, size = 1)
guess <- as.numeric(readline("Guess the number (1-4): "))
if ( guess == number ) {
cat("Congratulations! You are correct.")
} else {
cat("Sorry, the correct number was ", number, ".\n", sep = "")
cat("Better luck next time!")
}
```

The general form of an `if-else`

expression is as follows:

```
if ( condition ) {
# code to run if the condition evaluates to TRUE
} else {
# code to run if condition evaluates to FALSE
}
```

An `if-else`

can be followed by any number of `if-else`

’s, setting up a chain of alternative responses:

```
number <- sample(1:4, size = 1)
guess <- as.numeric(readline("Guess the number (1-4): "))
if ( guess == number ) {
cat("Congratulations! You are correct.")
} else if ( abs(guess - number) == 1 ){
cat("You were close!\n")
cat("The correct number was ", number, ".\n", sep = "")
} else {
cat("You were way off.\n")
cat("The correct number was ", number, ".\n", sep = "")
}
```

In general, a chain looks like this:

```
if ( condition1) {
# code to run if condition1 evaluates to TRUE
} else if ( condition2 ) {
# code to run if condition2 evaluates to TRUE
} else if ( condition3 ) {
# code to run if condition2 evaluates to TRUE
} else if ......
# and so on until
} else if ( conditionN ) {
# code to run if conditionN evaluates to TRUE
}
```

### 4.2.2 Application: Validating Arguments

Recall the function `manyCat()`

:

```
manyCat <- function(word, n) {
wordWithNewline <- paste(word, "\n", sep = "")
lines <- rep(wordWithNewline, times = n)
cat(lines, sep = "")
}
```

What would happen if a user were to call it with an unusable argument for the parameter `n`

, a negative number, for instance?

`## Error in rep(wordWithNewline, times = n): invalid 'times' argument`

For us, it’s clear enough what is wrong. After all, we wrote the function in the previous chapter, and we know that the `rep()`

function in its body requires that the `times`

parameter be set to some positive integer. On the other hand, to someone who is unfamiliar with the body of `manyCat()`

and who has no access to help on how `manyCat()`

is to be used it may not be so obvious what has gone wrong and why.

In the case of complex functions, we cannot expect ordinary users to search through the function’s definition to learn how to fix an error that arises from improper input. Accordingly, it can be good practice to *validate* user-input. Conditionals allow us to do this.

Here, a possible approach is to attempt to coerce the user’s input for `n`

into an integer, using the `as.integer()`

function:

`## [1] 3`

`## [1] 4`

`## [1] 4`

`## Warning: NAs introduced by coercion`

`## [1] NA`

In the last example, the result is `NA`

, and a cryptic warning was issued. In order to keep the warning from the user, we should wrap any call to `as.integer()`

in the `suppressWarnings()`

function.

Let’s try out a piece of code that checks for validity:

```
n <- "two" # this will not convert to a number at all
converted <- suppressWarnings(as.integer(n))
!is.na(converted) && converted >= 1
```

`## [1] FALSE`

Think about what happened:

- First we attempted to converted the
`"two"`

to an integer. - Since the result of
`as.integer("two")`

is`NA`

, the expression`is.na(converted)`

evaluates to`TRUE`

- Hence
`!is.na(converted)`

evaluates to`FALSE`

. - Hence
`!is.na(converted) && converted > 1`

evaluates to`FALSE`

.

Let’s try it on another “wrong” value:

```
n <- -2 # number, but it's negative
converted <- suppressWarnings(as.integer(n))
!is.na(converted) && converted >= 1
```

`## [1] FALSE`

This time `converted`

gets an assigned value, namely -2, but since it’s not at least 1 the expression `!is.na(converted) && converted >= 1`

evaluates to `FALSE`

.

Now let’s try it on a “good” value:

`## [1] TRUE`

Our code appears to be working well.

Now that we’ve figured out a way to determine whether any given input is a usable number, let’s employ a conditional to implement validation in `manyCat()`

:

```
manyCat <- function(word, n) {
n <- suppressWarnings(as.integer(n))
isValid <- !is.na(n) && n >= 1
if (!isValid) {
message <- "Sorry, n must be a whole number at least 1.\n"
return(cat(message))
}
wordWithNewline <- paste(word, "\n", sep = "")
lines <- rep(wordWithNewline, times = n)
cat(lines, sep = "")
}
```

The idea is to force an early return—along with a helpful message to the Console—if the user’s argument for `n`

is no good.

Let’s watch it in action:

`## Sorry, n must be a whole number at least 1.`

```
## Hello
## Hello
## Hello
```

### 4.2.3 Application: Invisible Returns

Let’s think again about the \(\pi\)-computing function from Section 3.4.1:

We could use `if`

to write in a “talky” option:

```
madhavaPI <- function(n = 1000000, verbose = FALSE) {
k <- 1:n
terms <- (-1)^(k+1)*4/(2*k-1)
approx <- sum(terms)
if ( verbose) {
cat("Madhava's approximation is: ", approx, ".\n", sep = "")
cat("This is based on ", n, " terms.\n", sep = "")
}
approx
}
```

Try it out:

```
## Madhava's approximation is: 3.140593.
## This is based on 1000 terms.
```

`## [1] 3.140593`

It’s a bit awkward that the approximation gets printed out at the end: after the message on the console, the user doesn’t need to see it. But if we were to remove the final `approx`

expression, then the function would not return an approximation that could be used for further computations.

The solution to this dilemma is R’s `invisible()`

function.

```
madhavaPI <- function(n = 1000000, verbose = FALSE) {
k <- 1:n
terms <- (-1)^(k+1)*4/(2*k-1)
approx <- sum(terms)
if ( verbose) {
cat("Madhava's approximation is: ", approx, ".\n", sep = "")
cat("This is based on ", n, " terms.\n", sep = "")
}
invisible(approx)
}
```

If you wrap an expression in `invisible()`

, then it won’t be printed out to the console:

```
## Madhava's approximation is: 3.140593.
## This is based on 1000 terms.
```

Nevertheless it is still returned, as we can see from the following code, in which the approximation is computed without any output to the console and stored in the variable `p`

for use later on in a `cat()`

statement.

`## Pi plus 10 is about 13.14159.`

### 4.2.4 Ifelse

The `ifelse()`

function is a special form of the if-else construct that is used to make assignments, and is especially handy in the context of vectorization.

Suppose that you have a lot of heights:

You would like to classify each person as either “tall” or “short”, depending on whether they are respectively more or less than 71 inches in height. `ifelse()`

makes quick work of it:

`## [1] "short" "short" "short" "tall" "short" "short" "tall" "short"`

Note that `ifelse()`

takes three parameters:

`test`

: the condition you want to evaluate;`yes`

: the value that gets assigned when`test`

is true;`no`

: the value assigned when`test`

is false;

Most programmers don’t name the parameters. This is fine—just remember to keep the test-yes-no order:

`## [1] "short" "short" "short" "tall" "short" "short" "tall" "short"`

Here’s another example of the power of `ifelese()`

. If a triangle has three sides of length \(x\), \(y\) and \(z\), then the sum of any two sides must be greater than the remaining side:

\[\begin{aligned}
x + y &> z, \\
x + z &> y, \\
y + z &> x.
\end{aligned}\]
This fact is known as the *Triangle Inequality*. It works the other way around, too: if three positive numbers are such that the sum of any two exceeds the third, then three line segments having those numbers as lengths could be arranged into a triangle.

We can write a function that, when given three lengths, determines whether or not they can make a triangle:

`isTriangle()`

simply evaluates a Boolean expression involving `x`

, `y`

and `z`

. It will return `TRUE`

when the three quantities satisfy the Triangle Inequality; otherwise, it returns `FALSE`

. Let’s try it out:

`## [1] TRUE`

Recall that Boolean expressions can involve vectors of any length. So suppose that we are would like to know which of the following six triples of numbers could be the side-lengths of a triangle:

\[(2,4,5),(4.7,1,3.8),(5.2,8,12),\\
(6, 6, 13), (6, 6, 11), (9, 3.5, 6.2)\]
We could enter the triples one at a time into `isTriangle()`

. On the other hand we could arrange the sides into three vectors of length six each:

Then we can decide about all six triples at once:

`## [1] TRUE TRUE FALSE FALSE TRUE TRUE`

We could also use `ifelse()`

to create a new character-vector that expresses our results verbally:

`## [1] "triangle" "triangle" "not" "not" "triangle" "triangle"`

### 4.2.5 Switch

If you have to make a decision involving two or more alternatives you can use a chain of `if ... else`

constructions. When the alternatives involve no more than the assignment of a value to a variable, you might also consider using the `switch()`

function.

For example, suppose that you have days of the week expressed as numbers. Maybe it’s like this:

- 1 stands for Sunday
- 2 for Monday
- 3 for Wednesday
- and so on.

If you would like to convert a day-number to the right day name, then you could write a function like this:

```
dayWord <- function(dayNumber) {
switch(dayNumber,
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday")
}
dayWord(3)
```

`## [1] "Tuesday"`

In `switch()`

above, the first argument after `dayNumber`

is what goes with 1, the second argument is what goes with 2, and so on.

When the item you want to convert is a string rather than a number, then the `switch()`

function works a little bit differently. Suppose, for instance, that you want to *abbreviate* the names of the weekdays. You might write an abbreviation-function as follows:

```
abbrDay <- function(day) {
switch(day,
Monday = "Mon",
Tuesday = "Tue",
Wednesday = "Wed",
Thursday = "Th",
Friday = "Fri",
Saturday = "Sat")
}
abbrDay("Wednesday")
```

`## [1] "Wed"`

In the above call to `switch()`

, the weekday names you want to abbreviate appear as the names of named character-vectors, each of length one. The value of each vector is what the its name will be converted to.

When you are converting strings you have the option to provide a default conversion for values that don’t fit into the pattern you have in mind. All you have to do is to provide the default value as an additional argument. (It should NOT have a name.) Thus:

```
abbrDay <- function(day) {
switch(day,
Monday = "Mon",
Tuesday = "Tue",
Wednesday = "Wed",
Thursday = "Th",
Friday = "Fri",
Saturday = "Sat",
"not a weekday!")
}
abbrDay("Wednesday")
```

`## [1] "Wed"`

`## [1] "not a weekday!"`

### 4.2.6 Practice Exercises

Consider the following function:

Write a “talky” version of the above function that returns the square invisibly.. It should be called

`computeSquare2()`

and should take two parameters:`x`

: the number to be squared`verbose`

: whether or not to`cat()`

out a report to the Console.

Typical examples of use would be:

`## The square of 4 is 16.`

`## [1] 36`

Write a function called

`findKarl()`

that takes a character vector and returns a character vector that reports whether or not each element of the given vector was equal to the string`"Karl"`

. It should work like this:`## [1] "Sorry, not our guy." "Sorry, not our guy." "Yep, that's Karl!" ## [4] "Sorry, not our guy."`

Here’s a function that is supposed to return

`"small!"`

when given a number less than 100, and return`"big!"`

when the number if at least 100:But it doesn’t work:

`## [1] "big!"`

`## [1] "big!"`

Fix the code.

Add some validation to the

`isTriangle()`

function so that it stops the user if one or more of the parameters`x`

,`y`

and`z`

cannot be interpreted as a positive real number.

### 4.2.7 Solutions to Practice Exercises

Here’s the desired function:

Here’s one way to write it:

A function always returns the value of the last expression that it evaluates. As it stands, the function will always end at the line

`"big!"`

, so`"big"`

will always be returned. One way to get the desired behavior is to force the function to stop executing once it prints out`"small!"`

. You can do this with the`return()`

function:Another way is to use the

`if ... else`

construction:Here is one approach:

`isTriangle <- function(x, y, z) { x <- suppressWarnings(as.numeric(x)) x <- suppressWarnings(as.numeric(x)) x <- suppressWarnings(as.numeric(x)) xValid <- all(!is.na(x) & x > 0) yValid <- all(!is.na(y) & y > 0) zValid <- all(!is.na(z) & z > 0) if (!(xValid & yValid & zValid)) { return(cat("Sorry, all inputs must be positive real numbers.\n")) } (x + y > z) & (x +z > y) & (y + z > x) }`

Try it out:

`## Sorry, all inputs must be positive real numbers.`

`## [1] FALSE FALSE TRUE`

Actually you don’t need the brackets if you plan to put only one expression in them. Many people keep the brackets, though, for the sake of clarity.↩