Source : Hands-On Programming with R
function
is used to declare a function in R.A simple example:
identity <- function(x = NULL) {
x
}
identity
## function(x = NULL) {
## x
## }
formals(identity)
## $x
## NULL
body(identity)
## {
## x
## }
If you want to learn about a function and learn programming inspect experienced programmers code!
In RStudio, write gl()
in the editor, place cursor on a function and press F2.
Can you figure out what it does?…
“To understand computations in R, two slogans are helpful:
- Everything that exists is an object.
- Everything that happens is a function call.” — John Chambers
Every operation in R is a function call, even the most unsuspected ones. For example:
x <- 1; y <- 3
x + y
## [1] 4
`+`(x, y)
## [1] 4
Also:
`<-`("a", 1) # actually assign() is more powerfull
a
## [1] 1
BTW, functions can return invisible values, which are not printed out by default when you call the function. The assign operator is one of them. You can force invisible values to be printed to the screen by enclosing them in between parenthesis:
a <- 2 # nothing is printed
(a <- 2)
## [1] 2
However the fact that assignment returns the assigned value allows to use it in constructs such as:
rm(x) # removing previous instance of object x
exists("x") # make sure x does not exist anymore
## [1] FALSE
y <- mean(x <- round(runif(4, max = 10)))
x
## [1] 9 1 3 8
Even when you type a variable name in the interpreter and press enter, you actually run a function:
eval(a)
## [1] 2
a
## [1] 2
h <- function(x) {
a <- 2
x + a
}
h(x = 1)
## [1] 3
Note that for example in there:
h <- function(x) {
a <- 2
x + a
}
a <- 0
h(x = 1)
## [1] 3
a
## [1] 0
a
in not modified by the call to h()
! It is the cocoon effect! Variables oustide the execution environment are protected (to some extent).
Because of this fuzziness, it is important to be clear about which argument corresponds to which formal parameter of the function.
This argument matching process follows rules:
# These calls are all equivalent
sample(x = 1:10, size = 10, replace = TRUE)
sample(1:10, 10, TRUE)
sample(x = 1:10, replace = TRUE, s = 10)
A few more rules and advices when you call a function:
args(write) # Let's display the formal arguments of a function:
## function (x, file = "data", ncolumns = if (is.character(x)) 1 else 5,
## append = FALSE, sep = " ")
## NULL
How can you specify default values of optional arguments in your functions:
missing(param)
. Drawback: it does not make it obvious to the user that a default value is required and that something will happen if the argument is missing..The ...
or ellipsis argument:
àpply
familly (discussed later).SquareRnorm <- function(n, ...) {
rnorm(n = n, ...) ^ 2
}
SquareRnorm(1)
## [1] 5.94359
SquareRnorm(1, mean = 200)
## [1] 39167.4
SquareRnorm(1, sd = 1000)
## [1] 3136240
This is one of the first few lines of the body of colMeans():
if (!is.array(x) || length(dn <- dim(x)) < 2L) stop("'x' must be an array of at least two dimensions")
It is indeed typical at the beginning of a function to test that arguments (here x) meet certain conditions that are absolutely required for proper execution of the function.
If these conditions are not met, execution is stopped . Notice the ||
operator that is often used in this context.
Inside a function body you can use variables that are defined outside of the functions execution environment, R will look for them (scoping rules apply). But it is not recommended because this is not very transparent for a user. It is better to define a parameter that will be passed to the function body.
The result of the evaluation of the last expression in a function becomes the return value, the result of invoking the function.
A function can exit and return a value prematurely using the return(valueObject)
function:
Bet <- function() { # Notice that it has no formal argument
outcome <- sample(c(TRUE, FALSE), 1)
if(!outcome) return("Looser!") # Early termination
cat("You are an oracle!! Here is your reward.\n")
"100000000 Euros"
}
Bet()
## [1] "Looser!"
Functions can return only a single object. But this is not a limitation because you can return a list containing any number of objects.
sixnum <- function(x) {
if (missing(x)) x <- runif(10)
fiveNum <- fivenum(x)
count <- length(x)
list(TukeyFiveNumber = fiveNum, CountOfObs = count)
}
str(sixnum())
## List of 2
## $ TukeyFiveNumber: num [1:5] 0.214 0.384 0.649 0.895 0.945
## $ CountOfObs : int 10
The function setwd()
has a side effect (change the working directory) and an (invisible) return value: a character string of the working directory in effect before the change.
oldWd <- setwd(.libPaths()[1])
getwd()
## [1] "/home/cunnac/R/x86_64-pc-linux-gnu-library/3.2"
oldWd
## [1] "/media/cunnac/DONNEES/CUNNAC/Lab-Related/Communications/Teaching/R_trainning_module/slides/RmdFilesWithoutAnswersForHandouts"
setwd(oldWd)
What is going on here?
Many graphical function have no return values (TRUE?) and are used only for their side effect that is to print a plot into the current graphic device as we will see later. Other than that, most R functions have a return value
It is advisable when you write your own functions to keep them as “pure” as possible by avoiding undesirable side effects.
For example, what happens here?
x <- 1
Increment <- function(by = 1) {x <<- x + by ; cat("Incremented 'x'!")}
Increment(by = 2)
## Incremented 'x'!
x
## [1] 3
Note the use of the “super assignment” operator that will reassign x
in a parent environment of the function’s execution environment. Use with caution…
It may be better to write Increment()
differently to make it explicit what it is doing and not to mess with outside variables.
x <- 1
BetterIncrement <- function(numb, by = 1) {numb <- numb + by}
x <- BetterIncrement(numb = x, by = 2)
x
## [1] 3
Clarify the following list of odd function calls (use the help pages):
x <- sample(replace = TRUE, 20, x = c(1:10, NA))
y <- runif(min = 0, max = 1, 20)
cor(m = "k", y = y, u = "p", x = x)
What does this function return if called with no value? With f(ls())
? Why? Which principle does it illustrate?
f <- function(x = ls()) {
a <- 1
x
}
What does this function return? Why? Which principle does it illustrate?
z <- 1
f2 <- function(x = z) {
z <- 100
x
}
Create virtual cubic dice embedded in a function. Each time you toss the dice (invoke the function) it will return a value between 1 and 6.
RunDice <- function() { # How did you call YOUR function BTW?
ceiling(6*runif(1)) # could have been done with sample()
}
RunDice()
## [1] 5
What does ceilling()
do? What for?
Write a function that computes the perimeter and surface of a rectangle using l1
and l2
, the measures of its sides.
It should return these values in a data frame together with the width and length.