13 Intro to Functions

R comes with many functions and packages that let us perform a wide variety of tasks, and so far we’ve been using a number of them. In fact, most of the things we do in R is by calling some function. Sometimes, however, there is no function to do what we want to achieve. When this is the case, we may very well want to write our own functions.

In this chapter we’ll describe how to start writing small and simple functions. We are going to start covering the “tip of the iceberg”, and in the following chapters we will continue discussing more aspects about writing functions, and describing how R works when you invoke (call) a function.

13.1 Motivation

We’ve used the formula of future value, given below, which is useful to answer questions like: If you deposit $1000 into a savings account that pays an annual interest of 2%, how much will you have at the end of year 10?

\[ \text{FV} = \text{PV} \times (1 + r)^n \]

  • \(\text{FV}\) = future value (how much you’ll have)
  • \(\text{PV}\) = present value (the initial deposit)
  • \(r\) = rate of return (e.g. annual rate of return)
  • \(n\) = number of periods (e.g. number of years)

R has a large number of functions—e.g. sqrt(), log(), mean(), sd(), exp(), etc—but it does not have a built-in function to compute future value.

Wouldn’t it be nice to have a future_value() function—or an fv() function—that you could call in R? Perhaps something like:

future_value(present = 1000, rate = 0.02, year = 10)

Let’s create such a function!

13.2 Writing a Simple Function

This won’t always be the case, but in our current example we have a specific mathematical formula to work with (which makes things a lot easier):

\[ \text{FV} = \text{PV} \times (1 + r)^n \]

Like other programming languages that can be used for scientific computations, we can take advantage of the syntax in R to write an expression that is almost identical to the algebraic formulation:

fv = pv * (1 + r)^n

We will use this simple line of code as our starting point for creating a future value function. Here is how to do it “logically” step by step.

Step 1: Start with a concrete example

You should always start with a small and concrete example, focusing on writing code that does the job. For example, we could write the following lines:

# inputs
pv = 1000
r = 0.02
n = 10

# process
fv = pv * (1 + r)^n

# output
fv
> [1] 1218.994

When I say “small example” I mean working with objects containing just a few values. Here, the objects pv, r, and n are super simple vectors of size 1. Sometimes, though, you may want to start with less simple—yet small—objects containing just a couple of values. That’s fine too.

Sometimes you may even need to start not just with one, but with a couple of concrete examples that will help you get a better feeling of what kind of objects, and operations you need to use.

As you get more experience creating and writing functions, you may want to start with a “medium-size” concrete example. Personally, I don’t tend to start like this. Instead, I like to take baby-steps, and I also like to take my time, without rushing the coding. You know the old-saying: “measure twice, cut once.”

An important part of starting with a concrete example is so that you can identify what the inputs are, what computations or process the inputs will go through, and what the output should be.

Inputs:

  • pv
  • r
  • n

Process:

  • fv = pv * (1 + r)^n

Output:

  • fv

Step 2: Make your code more generalizable

After having one (or a few) concrete example(s), the next step is to make your code more generalizable, or if you prefer, to make it more abstract (or at least less concrete).

Instead of working with specific values pv, r, and n, you can give them a more algebraic spirit. For instance, the code below considers “open-ended” inputs without assigning them any values

# general inputs (could take "any" values)
pv
r
n

# process
fv = pv * (1 + r)^n

# output
fv

Obviously this piece of code is very abstract and not intended to be executed in R; this is just for the sake of conceptual illustration.

Step 3: Encapsulate the code into a function

The next step is to encapsulate your code as a formal function in R. I will show you how to do this in two logical substeps, although keep in mind that in practice you will merge these two substeps into a single one.

The encapsulation process involves placing the “inputs” inside the function function(), separating each input with a comma. Formally speaking, the inputs of your functions are known as the arguments of the function.

Likewise, the lines of code that correspond to the “process” and “output” are what will become the body of the function. Typically, you encapsulate the code of the body by surrounding it with curly braces { }

# encapsulating code into a function
function(pv, r, n) {
  fv = pv * (1 + r)^n
  fv
}

The other substep typically consists of assigning a name to the code of your function. For example, you can give it the name FV:

# future value function
FV = function(pv, r, n) {
  fv = pv * (1 + r)^n
  fv
}

In summary:

  • the inputs go inside function(), separating each input with a comma

  • the processing step and the output are surrounded within curly braces { }

  • you typically assign a name to the code of your function

Step 4: Test that the function works

Once the function is created, you test it to make sure that everything works. Very likely you will test your function with the small and concrete example:

# test it
FV(1000, 0.02, 10)
> [1] 1218.994

And then you’ll keep testing your function with other less simple examples. In this case, because the code we are working with is based on vectors, and uses common functions for vectors, we can further inspect the behavior of the function by providing vectors of various sizes for all the arguments:

# vectorized years
FV(1000, 0.02, 1:5)
> [1] 1020.000 1040.400 1061.208 1082.432 1104.081
# vectorized rates
FV(1000, seq(0.01, 0.02, by = 0.005), 1)
> [1] 1010 1015 1020
# vectorized present values
FV(c(1000, 2000, 3000), 0.02, 1)
> [1] 1020 2040 3060

Notice that the function is vectorized, this is because we are using arithmetic operators (e.g. multiplication, subtraction, division) which are in turn vectorized.

In Summary

  • To define a new function in R you use the function function().

  • Usually, you specify a name for the function, and then assign function() to the chosen name.

  • You also need to define optional arguments (i.e. inputs of the function).

  • And of course, you must write the code (i.e. the body) so the function does something when you use it.

13.2.1 Arguments with default values

Sometimes it’s a good idea to add a default value to one (or more) of the arguments. For example, we could give default values to the arguments in such a way that when the user executes the function without any input, FV() returns the value of 100 monetary units invested at a rate of return of 1% for 1 year:

# future value function with default arguments
FV = function(pv = 100, r = 0.01, n = 1) {
  fv = pv * (1 + r)^n
  fv
}

# default execution
FV()
> [1] 101

An interesting side effect of giving default values to the arguments of a function is that you can also call it by specifying arguments in an order different from the order in which the function was created:

FV(r = 0.02, n = 3, pv = 1000)
> [1] 1061.208

13.3 Writing Functions for Humans

When writing functions (or coding in general), you should write code not just for the computer, but also for humans. While it is true that R doesn’t care too much about what names and symbols you use, your code will be used by a human being: either you or someone else. Which means that a human will have to take a look at the code.

Here are some options to make our code more human friendly. We can give the function a more descriptive name such as future_value(). Likewise, we can use more descriptive names for the arguments: e.g. present, rate, and years.

# future value function
future_value = function(present, rate, years) {
  future = present * (1 + rate)^years
  future
}

# test it
future_value(present = 1000, rate = 0.02, years = 10)
> [1] 1218.994

Even better: whenever possible, as we just said, it’s a good idea to give default values to the arguments (i.e. inputs) of the function:

# future value function
future_value = function(present = 100, rate = 0.01, years = 1) {
  future = present * (1 + rate)^years
  future
}

future_value()
> [1] 101

13.3.1 Naming Functions

Since we just change the name of the function from fv() to future_value(), you should also learn about the rules for naming R functions. A function cannot have any name. For a name to be valid, two things must happen:

  • the first character must be a letter (either upper or lower case) or the dot .

  • besides the dot, the only other symbol allowed in a name is the underscore _ (as long as it’s not used as the first character)

Following the above two principles, below are some valid names that could be used for the future value function:

  • fv()

  • fv1()

  • future_value()

  • future.value()

  • futureValue()

  • .fv(): a function that starts with a dot is a valid name, but the function will be a hidden function.

In contrast, here are examples of invalid names:

  • 1fv(): cannot begin with a number

  • _fv(): cannot begin with an underscore

  • future-value(): cannot use hyphenated names

  • fv!(): cannot contain symbols other than the dot and the underscore (not in the 1st character)

13.3.2 Function’s Documentation

Part of writing a human-friendly function involves writing its documentation, usually providing the following information:

  • title: short title

  • description: one or two sentences of what the function does

  • arguments: short description for each of the arguments

  • output: description of what the function returns

Once you are happy with the status of your function, include comments for its documentation, for example:

# title: future value function
# description: computes future value using compounding interest
# inputs:
# - present: amount for present value
# - rate: annual rate of return (in decimal)
# - years: number of years
# output:
# - computed future value
future_value = function(present = 100, rate = 0.01, years = 1) {
  future = present * (1 + rate)^years
  future
}

Writing documentation for a function seems like a waste of time and energy. Shouldn’t a function (with its arguments, body, and output) be self-descriptive? In an ideal world that would be the case, but this rarely happens in practice.

Yes, it does take time to write these comments. And yes, you will be constantly asking yourself the same question: “Do I really need to document this function that I’m just planning to use today, and no one else will ever use?”

Yes!

I’ll be the first one to admit that I’ve created so many functions without writing their documentation. And almost always—sooner or later—I’ve ended up regretting my laziness for not including the documentation. So do yourself and others (especially your future self) a big favor by including some comments to document your functions.

Enough about this chapter. Although, obviously, we are not done yet with functions. After all, this is a book about programming in R, and there is still a long way to cover about the basics and not so basics of functions.


13.4 Exercises

1) In the second part of the book we have talked about the Future Value (FV), and we have extensively used its simplest version of the FV formula. Let’s now consider the “opposite” value: the Present Value which is the current value of a future sum of money or stream of cash flows given a specified rate of return.

Consider the simplest version of the formula to calculate the Present Value given by:

\[ \text{PV} = \frac{\text{FV}}{(1 + r)^n} \]

  • \(\text{PV}\) = present value (the initial deposit)

  • \(\text{FV}\) = future value (how much you’ll have)

  • \(r\) = rate of return (e.g. annual rate of return)

  • \(n\) = number of periods (e.g. number of years)

Write a function present_value() to compute the Present Value based on the above formula.


2) Write another function to compute the Future Value, but this time the output should be a list with two elements:

  • vector year from 0 to provided year

  • vector amount from amount at year 0, till amount at the provided year

For example, something like this:

fv_list(present = 1000, rate = 0.02, year = 3)
$year
[1] 0 1 2 3

$amount
[1] 1000.000 1020.000 1040.400 1061.208


3) Write another function to compute the Future Value, but this time the output should be a “table” with two columns: year and amount. For example, something like this:

fv_table(present = 1000, rate = 0.02, year = 3)
  year   amount
1    0 1000.000
2    1 1020.000
3    2 1040.400
4    3 1061.208

Note: by “table” you can use either a matrix or a data.frame. Even better, try to create two separate functions: 1) fv_matrix() that returns a matrix, and 2) fv_df() that returns a data frame.