# Chapter 10 Functions and control flow statements

## 10.1 Writing your own functions

So far we’ve been using a variety of built in functions in R. However the real power of a programming language is the ability to write your own functions. Functions are a mechanism for organizing and abstracting a set of related computations. We usually write functions to represent sets of computations that we apply frequently, or to represent some conceptually coherent set of manipulations to data.

The general form of an R function is as follows:

```
funcname <- function(arg1, arg2) {
# one or more expressions that operate on the fxn arguments
# last expression is the object returned
# or you can explicitly return an object
}
```

To make this concrete, here’s an example where we define a function to calculate the area of a circle:

Since R returns the value of the last expression in the function, the `return`

call is optional and we could have simply written:

Very short and concise functions are often written as a single line. In practice I’d probably write the above function as:

The `area.of.circle`

function takes one argument, `r`

, and calculates the area of a circle with radius r. Having defined the function we can immediately put it to use:

If you type a function name without parentheses R shows you the
function’s definition. This works for built-in functions as well
(thought sometimes these functions are defined in C code in which case R
will tell you that the function is a `.Primitive`

).

### 10.1.1 Function arguments

Function arguments can specify the data that a function operates on or parameters that the function uses. Function arguments can be either required or optional. In the case of optional arguments, a default value is assigned if the argument is not given.

Take for example the `log`

function. If you examine the help file for the `log`

function (type `?log`

now) you’ll see that it takes two arguments, refered to as `x`

and `base`

. The
argument `x`

represents the numeric vector you pass to the function and is a required argument (see what happens when you type `log()`

without giving an argument). The argument `base`

is optional. By default the value of `base`

is \(e = 2.71828\ldots\). Therefore by default the `log`

function returns natural logarithms. If you want logarithms to a different base you can change the `base`

argument as in the following examples:

```
log(2) # log of 2, base e
#> [1] 0.6931472
log(2,2) # log of 2, base 2
#> [1] 1
log(2, 4) # log of 2, base 4
#> [1] 0.5
```

Because base 2 and base 10 logarithms are fairly commonly used, there are convenient aliases for calling `log`

with these bases.

### 10.1.2 Writing functions with optional arguments

To write a function that has an optional argument, you can simply specify the optional argument and its default value in the function definition as so:

```
# a function to substitute missing values in a vector
sub.missing <- function(x, sub.value = -99){
x[is.na(x)] <- sub.value
return(x)
}
```

You can then use this function as so:

```
m <- c(1, 2, NA, 4)
sub.missing(m, -999) # explicitly define sub.value
#> [1] 1 2 -999 4
sub.missing(m, sub.value = -333) # more explicit syntax
#> [1] 1 2 -333 4
sub.missing(m) # use default sub.value
#> [1] 1 2 -99 4
m # notice that m wasn't modified within the function
#> [1] 1 2 NA 4
```

Notice that when we called `sub.missing`

with our vector `m`

, the vector did *not* get modified in the function body. Rather a new vector, `x`

was created within the function and returned. However, if you did the missing value subsitute outside of a function call, then the vector would be modified:

### 10.1.3 Putting R functions in Scripts

When you define a function at the interactive prompt and then close the interpreter your function definition will be lost. The simple way around this is to define your R functions in a script that you can than access at any time.

In RStudio choose `File > New File > R Script`

. This will bring up a blank editor window. Type your function(s) into the editor. Everything in this file will be interpretted as R code, so you should not use the code block notation that is used in Markdown notebooks. Save the source file in your R working directory with a name like
`myfxns.R`

.

```
# functions defined in myfxns.R
area.of.circle <- function(r) {pi * r^2}
area.of.rectangle <- function(l, w) {l * w}
area.of.triangle <- function(b, h) {0.5 * b * h }
```

Once your functions are in a script file you can make them accesible by using the `source`

function, which reads the named file as input and evaluates any definitions or statements in the input file (See also the `Source`

button in the R Studio GUI):

Having sourced the file you can now use your functions like so:

```
radius <- 3
len <- 4
width <- 5
base <- 6
height <- 7
area.of.circle(radius)
#> [1] 28.27433
area.of.rectangle(len, width)
#> [1] 20
area.of.triangle(base, height)
#> [1] 21
```

Note that if you change the source file, such as correcting a mistake or adding a new function, you need to call the `source`

function again to make those changes available.

## 10.2 Control flow statements

Control flow statements control the order of execution of different pieces of code. They can be used to do things like make sure code is only run when certain conditions are met, to iterate through data structures, to repeat something until a specified event happens, etc. Control flow statements are frequently used when writing functions or carrying out complex data transformation.

### 10.2.1 `if`

and `if-else`

statements

`if`

and `if-else`

blocks allow you to structure the flow of execution so that certain expressions are executed only if particular conditions are met.

The general form of an `if`

expression is:

```
if (Boolean expression) {
Code to execute if
Boolean expression is true
}
```

Here’s a simple `if`

expression in which we check whether a number is less than 0.5, and if so assign a values to a variable.

```
x <- runif(1) # runif generates a random number between 0 and 1
face <- NULL # set face to a NULL value
if (x < 0.5) {
face <- "heads"
}
face
#> [1] "heads"
```

The `else`

clause specifies what to do in the event that the `if`

statement is *not* true. The combined general for of an `if-else`

expression is:

```
if (Boolean expression) {
Code to execute if
Boolean expression is true
} else {
Code to execute if
Boolean expression is false
}
```

Our previous example makes more sense if we include an `else`

clause.

With the addition of the `else`

statement, this simple code block can be thought of as simulating the toss of a coin.

#### 10.2.1.1 `if-else`

in a function

Let’s take our “if-else” example above and turn it into a function we’ll call `coin.flip`

. A literal re-interpretation of our previous code in the context of a function is something like this:

```
# coin.flip.literal takes no arguments
coin.flip.literal <- function() {
x <- runif(1)
if (x < 0.5) {
face <- "heads"
} else {
face <- "tails"
}
face
}
```

`coin.flip.literal`

is pretty long for what it does — we created a temporary variable `x`

that is only used once, and we created the variable `face`

to hold the results of our `if-else`

statement, but then immediately returned the result. This is inefficient and decreases readability of our function. A much more compact implementation of this function is as follows:

Note that in our new version of `coin.flip`

we don’t bother to create temporary the variables `x`

and `face`

and we immediately return the results within the `if-else`

statement.

#### 10.2.1.2 Multiple `if-else`

statements

When there are more than two possible outcomes of interest, multiple `if-else`

statements can be chained together. Here is an example with three outcomes:

### 10.2.2 for loops

A `for`

statement iterates over the elements of a sequence (such as vectors or lists). A common use of for statements is to carry out a calculation on each element of a sequence (but see the discussion of `map`

below) or to make a calculation that involves all the elements of a sequence.

The general form of a for loop is:

```
for (elem in sequence) {
Do some calculations or
Evaluate one or more expressions
}
```

As an example, say we wanted to call our `coin.flip`

function multiple times. We could use a for loop to do so as follows:

```
flips <- c() # empty vector to hold outcomes of coin flips
for (i in 1:20) {
flips <- c(flips, coin.flip()) # flip coin and add to our vector
}
flips
#> [1] "tails" "tails" "tails" "tails" "tails" "heads" "heads" "tails"
#> [9] "tails" "heads" "tails" "tails" "heads" "heads" "heads" "heads"
#> [17] "tails" "tails" "heads" "heads"
```

Let’s use a `for`

loop to create a `multi.coin.flip`

function thats accepts an optional argument `n`

that specifies the number of coin flips to carry out:

```
multi.coin.flip <- function(n = 1) {
# create an empty character vector of length n
flips <- vector(mode="character", length=n)
for (i in 1:n) {
flips[i] <- coin.flip()
}
flips
}
```

With this new definition, a single call of `coin.flip`

returns a single outcome:

And calling `multi.coin.flip`

with a numeric argument returns multiple coin flips:

```
multi.coin.flip(n=10)
#> [1] "heads" "tails" "heads" "tails" "tails" "heads" "heads" "heads"
#> [9] "heads" "tails"
```

#### 10.2.2.1 Efficiency tip

An alternate way to write the `multi.coin.flip`

function above would be:

```
## This is inefficient, see description eblow
multi.coin.flip.alt <- function(n = 1) {
flips <- c()
for (i in 1:n) {
flips <- c(flips, coin.flip())
}
flips
}
```

If you know the final length of your vector, it is much faster to create an empty vector of the needed length:

e.g., `vector(mode="character", length=n) # runs fast`

than it is to create an empty vector of zero elength, and then extend it sequentially:

e.g., `flips <- c(flips, coin.flip()) # runs slow`

### 10.2.3 `break`

statement

A `break`

statement allows you to exit a loop even if it hasn’t completed. This is useful for ending a control statement when some criteria has been satisfied. `break`

statements are usually nested in `if`

statements.

In the following example we use a `break`

statement inside a `for`

loop. In this example, we pick random real numbers between 0 and 1, accumulating them in a vector (`random.numbers`

). The `for`

loop insures that we never pick more than 20 random numbers before the loop ends. However, the `break`

statement allows the loop to end prematurely if the number picked is greater than 0.95.

```
random.numbers <- c()
for (i in 1:20) {
x <- runif(1)
random.numbers <- c(random.numbers, x)
if (x > 0.95) {
break
}
}
random.numbers
#> [1] 0.346144271 0.886941766 0.319471383 0.817354602 0.627596058
#> [6] 0.840769497 0.448983685 0.006001374 0.830100021 0.863958652
#> [11] 0.489005927 0.847016982 0.727580304 0.029593085 0.440570396
#> [16] 0.105275129 0.473030424 0.745306079 0.141316468 0.339277409
```

### 10.2.4 `repeat`

loops

A `repeat`

loop will loop indefinitely until we explicitly break out of the loop with a `break`

statement. For example, here’s an example of how we can use `repeat`

and `break`

to simulate flipping coins until we get a head:

### 10.2.5 `next`

statement

A `next`

satement allows you to halt the processing of the current iteration of a loop and immediately move to the next item of the loop. This is useful when you want to skip calculations for certain elements of a sequence:

### 10.2.6 while statements

A `while`

statement iterates as long as the condition statement it contains is true. In the following example, the `while`

loop calls `coin.flip`

until “heads” is the result, and keeps track of the number of flips. Note that this represents the same logic as the `repeat-break`

example we saw earlier, but in a a more compact form.

### 10.2.7 `ifelse`

The `ifelse`

function is equivalent to a `for`

-loop with a nested `if-else`

statement. `ifelse`

applies the specified test to each element of a vector, and returns different values depending on if the test is true or false.

Here’s an example of using `ifelse`

to replace `NA`

elements in a vector with zeros.

```
x <- c(3, 1, 4, 5, 9, NA, 2, 6, 5, 4)
newx <- ifelse(is.na(x), 0, x)
newx
#> [1] 3 1 4 5 9 0 2 6 5 4
```

The equivalent for-loop could be written as:

```
x <- c(3, 1, 4, 5, 9, NA, 2, 6, 5, 4)
newx <- c() # create an empty vector
for (elem in x) {
if (is.na(elem)) {
newx <- c(newx, 0) # append zero to newx
} else {
newx <- c(newx, elem) # append elem to newx
}
}
newx
#> [1] 3 1 4 5 9 0 2 6 5 4
```

The `ifelse`

function is clearly a more compact and readable way to accomplish this.