rm(list = ls()) # clean-up workspace
Report due by the end of this week
Lab 2 solutions
No class next Monday (Labor day holiday)
Terminal(the Shell) in RStudio
Functions are a fundamental building block of R
Functions are objects in their own right (so that they can have attributes()
)
All R functions have three parts:
the formals()
, the list of arguments which controls how you can call the function
the body()
, the code inside the function
the environment()
, the “map” of the location of the function’s variables
f <- function(x) x^2
f
## function(x) x^2
formals(f)
## $x
body(f)
## x^2
environment(f)
## <environment: R_GlobalEnv>
There is no special syntax for defining and naming a function
simply create a function object (with function
) and bind it to a name with <-
DoNothing <- function() {
return(invisible(NULL))
}
DoNothing()
mean(1:10, na.rm = TRUE)
## [1] 5.5
args <- list(1:10, na.rm = TRUE)
do.call(mean, args)
## [1] 5.5
do.call()
.Now let’s discuss scoping
R uses lexical scoping that follows four primary rules:
Name masking
Functions versus variables
A fresh start
Dynamic lookup
x <- 10
y <- 20
g02 <- function(){
x <- 1 # a local variable to the function
y <- 2
c(x, y)
}
g02()
## [1] 1 2
x <- 2
g03 <- function() {
y <- 1
c(x, y)
}
g03()
## [1] 2 1
y
## [1] 20
R searches inside the current function, then looks where the function is defined and so on, all the way up to the global environment.
Finally, R looks in other loaded packages.
y <- 10
f <- function(x) {
y <- 2
y^2 + g(x)
}
g <- function(x) {
x * y
}
What is the value of f(3)
?
In R, functions are ordinary objects. This means the scoping rules described above also apply to functions.
However, when rules get complicated when functions and non-functions share the same name.
Better avoid assigning same names to objects
g11 <- function() {
if (!exists("a")) {
a <- 1
} else {
a <- a + 1
}
a
}
g11()
## [1] 1
g11()
## [1] 1
What happens if we do
a <- 1:5
g11()
g11()
Lexical scoping determines where to look for values.
R looks for values when the function is run, not when the function is created.
g12 <- function() x + 1
x <- 15
g12()
## [1] 16
x <- 20
g12()
## [1] 21
Depending on variables defined in the global environment can be bad!
codetools::findGlobals()
can be helpful
You can define default values for arguments
Default values can be in terms of other arguments, or even in terms of variables defined later in the function
This is because R uses Lazy Evaluation that function arguments are only evaluated if accessed.
h04 <- function(x = 1, y = x * 2, z = a + b) {
a <- 10
b <- 100
c(x, y, z)
}
h04()
## [1] 1 2 110
...
(dot-dot-dot)Functions can have a special argument ...
With ...
, a function can take any number of additional arguments
You can use ...
to pass those additional arguments on to another function
Pro
x <- list(c(1, 3, NA), c(4, NA, 6))
str(lapply(x, mean, na.rm = TRUE))
## List of 2
## $ : num 2
## $ : num 5
Con
sum(1, 2, NA, na_rm = TRUE)
## [1] NA
These are the basic control-flow constructs of the R language. They function in much the same way as control statements in any Algol-like (Algol short for “Algorithmic Language”) language. They are all reserved words.
keyword | usage |
---|---|
if | if(cond) expr |
if-else | if(cond) cons.expr else alt.expr |
for | for(var in seq) expr |
while | while(cond) expr |
break | breaks out of a for loop |
next | halts the processing of the current iteration and advances the looping index |
Most functions exit in one of two ways:
return a value, indicating success
throw an error, indicating failure
There are two ways that a function can return a value:
j01 <- function(x) {
if (x < 10) {
0
} else {
10
}
}
j01(5)
## [1] 0
j01(15)
## [1] 10
return()
j02 <- function(x) {
if (x < 10) {
return(0)
} else {
return(10)
}
}
invisible()
to the last value:j04 <- function() invisible(1)
j04()
If a function cannot complete its assigned task, it should throw an error with stop()
, which immediately terminates the execution of the function.
j05 <- function() {
stop("I'm an error")
return(10)
}
j05()
## Error in j05(): I'm an error
Use on.exit()
to set up an exit handler that is run regardless of whether the function exits normally or with an error
Always set add = TRUE
when using on.exit()
. Otherwise, each call will overwrite the previous exit handler.
j06 <- function(x) {
cat("Hello\n")
on.exit(cat("Goodbye!\n"), add = TRUE)
if (x) {
return(10)
} else {
stop("Error")
}
}
j06(TRUE)
## Hello
## Goodbye!
## [1] 10
j06(FALSE)
## Hello
## Error in j06(FALSE): Error
## Goodbye!
with_dir <- function(dir, code) {
old <- setwd(dir)
on.exit(setwd(old), add = TRUE)
code
}
getwd()
## [1] "/Users/xji3/Dropbox/My_Files/Tulane/Teaching/tulane-math7360.github.io/slides/06-Functions"
with_dir("~", getwd())
## [1] "/Users/xji3"
getwd()
## [1] "/Users/xji3/Dropbox/My_Files/Tulane/Teaching/tulane-math7360.github.io/slides/06-Functions"