Chapter 8. Writing R functions

If we find ourselves running the same code repeatedly, it is probably a good idea to turn it into a function. In programming it is best to reduce redundancy whenever possible. There are several reasons for doing so, including maintainability and ease of reuse. R has a convenient way to make functions but it is very different from other languages, so some expectation adjustment might be necessary.

8.1. Hello, World!

This would not be a serious book about a programming language if we did not include a “Hello, World!” example, so we will start with that. Let’s build a function that simply prints “Hello, World!” to the console.

> say.hello <- function()
+ {
+     print("Hello, World!")
+ }

First, note that in R the period (.) is just another character and has no special meaning,1 unlike in other languages. This allows us to call this function say.hello.

1. One exception is that objects with names starting with a period are accessible but invisible, so they will not be found by ls.

Next, we see that functions are assigned to objects just like any other variable, using the <- operator. This is the strangest part of writing functions for people coming from other languages.

Following function are a set of parentheses that can either be empty—not have any arguments—or contain any number of arguments. We will cover those in Section 8.2.

The body of the function is enclosed in curly braces ({ and }). This is not necessary if the function contains only one line, but that is rare. Notice the indenting for the commands inside the function. While not required, it is good practice to properly indent code to ensure readability. It is here in the body that we put the lines of code we want the function to perform. A semicolon (;) can be used to indicate the end of the line but is not necessary, and its use is actually frowned upon.

Calling say.hello() prints as desired.

8.2. Function Arguments

More often than not we want to pass arguments to our function. These are easily added inside the parentheses of the function declaration. We will use an argument to print “Hello Jared.”

Before we do that, however, we need to briefly learn about the sprintf function. Its first argument is a string with special input characters and subsequent arguments that will be substituted into the special input characters.

> # one substitution
> sprintf("Hello %s", "Jared")

[1] "Hello Jared"

> # two substitutions
> sprintf("Hello %s, today is %s", "Jared", "Sunday")

[1] "Hello Jared, today is Sunday"

We now use sprintf to build a string to print based on a function’s arguments.

> hello.person <- function(name)
+ {
+     print(sprintf("Hello %s", name))
+ }
> hello.person("Jared")

[1] "Hello Jared"

> hello.person("Bob")

[1] "Hello Bob"

> hello.person("Sarah")

[1] "Hello Sarah"

The argument name can be used as a variable inside the function (it does not exist outside the function), and can be used like any other variable and as an argument to further function calls.

We can add a second argument to be printed as well. When calling functions with more than one argument, there are two ways to specify which argument goes with which value, either positionally or by name.

> hello.person <- function(first, last)
+ {
+     print(sprintf("Hello %s %s", first, last))
+ }
> # by position
> hello.person("Jared", "Lander")

[1] "Hello Jared Lander"

> # by name
> hello.person(first = "Jared", last = "Lander")

[1] "Hello Jared Lander"

> # the other order
> hello.person(last = "Lander", first = "Jared")

[1] "Hello Jared Lander"

> # just specify one name
> hello.person("Jared", last = "Lander")

[1] "Hello Jared Lander"

> # specify the other
> hello.person(first = "Jared", "Lander")

[1] "Hello Jared Lander"

> # specify the second argument first then provide the first argument
> # with no name
> hello.person(last = "Lander", "Jared")

[1] "Hello Jared Lander"

Being able to specify the arguments by name adds a lot of flexibility to calling functions. Even partial argument names can be supplied but this should be done with care.

> hello.person(fir = "Jared", l = "Lander")

[1] "Hello Jared Lander"

8.2.1. Default Arguments

When using multiple arguments it is sometimes desirable to not have to enter a value for each. In other languages functions can be overloaded by defining the function multiple times, each with a differing number of arguments. R instead provides the ability to specify default arguments. These can be NULL, characters, numbers or any valid R object.

Let’s rewrite hello.person to provide “Doe” as the default last name.

> hello.person <- function(first, last = "Doe")
+ {
+     print(sprintf("Hello %s %s", first, last))
+ }
>
> # call without specifying last
> hello.person("Jared")

[1] "Hello Jared Doe"

> # call with a different last
> hello.person("Jared", "Lander")

[1] "Hello Jared Lander"

8.2.2. Extra Arguments

R offers a special operator that allows functions to take an arbitrary number of arguments that do not need to be specified in the function definition. This is the dot-dot-dot argument (...). This should be used very carefully, although it can allow great flexibility. For now we will just see how it can absorb extra arguments; later we will find a use for it when passing arguments between functions.

> # call hello.person with an extra argument
> hello.person("Jared", extra = "Goodbye")

Error: unused argument (extra = "Goodbye")

> # call it with two valid arguments and a third
> hello.person("Jared", "Lander", "Goodbye")

Error: unused argument ("Goodbye")

>
> # now build hello.person with ... so that it absorbs extra arguments
> hello.person <- function(first, last = "Doe", ...)
+ {
+     print(sprintf("Hello %s %s", first, last))
+ }
> # call hello.person with an extra argument
> hello.person("Jared", extra = "Goodbye")

[1] "Hello Jared Doe"

> # call it with two valid arguments and a third
> hello.person("Jared", "Lander", "Goodbye")

[1] "Hello Jared Lander"

8.3. Return Values

Functions are generally used for computing some value, so they need a mechanism to supply that value back to the caller. This is called returning and is done quite easily. There are two ways to accomplish this with R. The value of the last line of code in a function is automatically returned, although this can be bad practice. The return command more explicitly specifies that a value should be returned and the function should be exited.

To illustrate, we will build a function that doubles its only argument and returns that value.

> # first build it without an explicit return
> double.num <- function(x)
+ {
+     x * 2
+ }
>
> double.num(5)

[1] 10

>
> # now build it with an explicit return
> double.num <- function(x)
+ {
+     return(x * 2)
+ }
>
> double.num(5)

[1] 10

>
> # build it again, this time with another argument after the explicit
> # return
> double.num <- function(x)
+ {
+     return(x * 2)
+
+     # below here is not executed because the function already exited
+     print("Hello!")
+     return(17)
+ }
>
> double.num(5)

[1] 10

8.4. do.call

A particularly underused trick is the do.call function. This allows us to specify the name of a function either as a character or as an object, and provide arguments as a list.

> do.call("hello.person", args = list(first = "Jared", last = "Lander"))

[1] "Hello Jared Lander"

> do.call(hello.person, args = list(first = "Jared", last = "Lander"))

[1] "Hello Jared Lander"

This is particularly useful when building a function that allows the user to specify an action. In the following example the user supplies a vector and a function to be run.

> run.this <- function(x, func = mean)
+ {
+     do.call(func, args = list(x))
+ }
>
> # finds the mean by default
> run.this(1:10)

[1] 5.5

> # specify to calculate the mean
> run.this(1:10, mean)

[1] 5.5

> # calculate the sum
> run.this(1:10, sum)

[1] 55

> # calculate the standard deviation
> run.this(1:10, sd)

[1] 3.028

8.5. Conclusion

Functions allow us to create reusable code that avoids repetition and allows easy modification. Important points to remember are function arguments, default values and returned values. Later in this book we will see functions that get far more complicated than the ones we have seen so far.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset