Chapter 10 R Functions
(Anatomy of a function: arguments, parameters and values; the concept of functional programming.)
10.1 Overview
10.1.1 Abstract:
In this unit we discuss the “anatomy”” of R functions: arguments, parameters and values, and how R’s treatment of functions supports “functional programming”.
10.1.2 Objectives:
This unit will:
- introduce the basic pattern of R functions;
- discuss arguments and parameters;
- show how to retrieve the source code from within a function;
- practice writing your own functions.
10.1.3 Outcomes:
After working through this unit you:
- know how to pass parameters into functions and assign the returned values;
- can read, analyze, and write your own functions.
10.1.4 Deliverables:
Time management: Before you begin, estimate how long it will take you to complete this unit. Then, record in your course journal: the number of hours you estimated, the number of hours you worked on the unit, and the amount of time that passed between start and completion of this unit.
Journal: Document your progress in your Course Journal. Some tasks may ask you to include specific items in your journal. Don’t overlook these.
Insights: If you find something particularly noteworthy about this unit, make a note in your insights! page.
10.2 Functions
R is considered an (impure) functional programming language and thus the focus of R programs is on functions. The key advantage is that this encourages programming without side-effects and this makes it easier to write error free code and maintain it. Function parameters17 are instantiated for use inside a function as the function’s arguments, and a single result is returned[However a function may have side-effects, such as writing something to console, plotting graphics, saving data to a file, or changing the value of variables outside the function scope. But changing values outside the scope is poor practice, always to be avoided.]. The return values can either be assigned to a variable, or used directly as the argument of another function. This means functions can be nested, and intermediate assignment is not required.
Functions are either built-in (i.e. available in the basic R installation), loaded via specific packages, or they can be easily defined by you (see below). In general a function is invoked through its name, followed by one or more arguments in parentheses, separated by commas. Whenever I refer to a function, I write the parentheses to identify it as such and not a constant or other keyword eg. log(). Here are some examples for you to try and play with:
cos(pi) #"pi" is a predefined constant.
## [1] -1
Note the rounding error. This number is not really different from zero.
sin(pi)
## [1] 1.224647e-16
Trigonometric functions use radians as their argument - this conversion calculates sin(30 degrees).
sin(30 * pi/180)
## [1] 0.5
“e” is not predefined, but easy to calculate.
exp(1)
## [1] 2.718282
functions can be arguments to functions - nested functions are evaluated from the inside out.
log(exp(1)) #
## [1] 1
log() calculates natural logarithms; convert to any base by dividing by the log of the base. Here: log to base 10.
log(10000) / log(10)
## [1] 4
Euler’s identity
exp(complex(r=0, i=pi))
## [1] -1+0i
There are several ways to populate the argument list for a function and R makes a reasonable guess what you want to do. Arguments can either be used in their predefined order, or assigned via an argument name. Let’s look at the complex() function to illustrate this. Consider the specification of a complex number in Euler’s identity above. The function complex() can work with a number of arguments that are explained in the documentation (see: ?complex). Its signature includes length.out, real, imaginary, and some more.
complex(length.out = 0, real = numeric(), imaginary = numeric(), modulus = 1, argument = 0)
## [1] 1+0i
The length.out argument creates a vector with one or more complex numbers. If nothing else is specified, this will be a vector of complex zero(s). If there are two, or three arguments, they will be placed in the respective slots. However, since the arguments are named, we can also define which slot of the argument list they should populate.
Consider the following to illustrate this:
complex(1)
## [1] 0+0i
parameter is in the first slot -> length.out
complex(4)
## [1] 0+0i 0+0i 0+0i 0+0i
imaginary part missing
complex(1, 2)
## [1] 2+0i
one complex number with real and imaginary parts defined
complex(1, 2, 3)
## [1] 2+3i
four complex numbers
complex(4, 2, 3)
## [1] 2+3i 2+3i 2+3i 2+3i
defining values via named parameters
complex(real = 0, imaginary = pi)
## [1] 0+3.141593i
same thing - if names are used, order is not important
complex(imaginary = pi, real = 0)
## [1] 0+3.141593i
names can be abbreviated …
complex(re = 0, im = pi)
## [1] 0+3.141593i
… to the shortest string that is unique among the named parameters, but this is poor practice, strongly advises against.
complex(r = 0, i = pi)
## [1] 0+3.141593i
Think: what have I done here? Why does this work?
complex(i = pi, 1, 0)
## [1] 0+3.141593i
The terms parameter and argument have similar but distinct meanings. A parameter is an item that appears in the function definition, an argument is the actual value that is passed into the function.↩︎