Processing math: 100%
+ - 0:00:00
Notes for current slide
Notes for next slide

Functions

Advanced R

Meenakshi Kushwaha

2020/09/02 (updated: 2020-09-11)

1 / 37

The Basics

2 / 37

Three components

  • Argument | formals

    • list of arguments
  • Body

    • code inside the function
  • Environment

    • data structure that determines how the function finds the values associated with the names
    • specified implicitly based on where you define the function

The Basics

3 / 37

Exception - Primitive functions

  • Exist primarily in C so their formals(), body(), and environment() are NULL
  • Only found in base R
  • Examples - sum(), [
typeof(sum)
#> [1] "builtin"
typeof(`[`)
#> [1] "special"

The Basics

4 / 37

First-class functions

  • R functions are objects in their own right
  • No special syntax for defining and naming a function
  • Typically, create a function object using function and bind it to a name with <-
    • Binding step is not necessary
lapply(mtcars, function(x) length(unique(x)))
Filter(function(x) !is.numeric(x), mtcars)
integrate(function(x) sin(x) ^ 2, 0, pi)

The Basics

5 / 37

Invoking a function

Use do.call if arguments are already in a data structure

args <- list(1:10, na.rm = TRUE)
do.call(mean, args)
> [1] 5.5

The Basics

6 / 37

Function composition

7 / 37

Nesting

x <- runif(100)
sqrt(mean(square(deviation(x))))
#> [1] 0.274

Concise, well suited for short sequences
x Can be hard to read

Function composition

8 / 37

Note- code highlight differs if r is not within parentheses Function composition

Intermediate Objects

out <- deviation(x)
out <- square(out)
out <- mean(out)
out <- sqrt(out)
out
#> [1] 0.274

x intermediate names required
This is useful only when intermediate objects are important

Function composition

9 / 37

Piping

x %>%
deviation() %>%
square() %>%
mean() %>%
sqrt()
#> [1] 0.274

More readable code
x Requires third party package
Function composition

10 / 37

Lexical scoping

How does R find the value associated with a name?

"It looks up the values of names based on how a function is defined, not how it is called"

11 / 37

Four rules of lexical scoping

  • Name masking

  • Functions versus variables

  • A fresh start

  • Dynamic lookup

    Lexical scoping
12 / 37

Name masking

Lexical scoping

Names defined within the function mask names outside the function

x <- 10
y <- 20
g02 <- function() {
x <- 1
y <- 2
c(x, y)
}
g02()
#> [1] 1 2
13 / 37

Name masking

Lexical scoping

Names defined within the function mask names outside the function

x <- 10
y <- 20
g02 <- function() {
x <- 1
y <- 2
c(x, y)
}
g02()
#> [1] 1 2

If a name isn’t defined inside a function, R looks one level up.

x <- 2
g03 <- function() {
y <- 1
c(x, y)
}
g03()
#> [1] 2 1
y
#> [1] 20
14 / 37

Name masking

Lexical scoping

Names defined within the function mask names outside the function

x <- 10
y <- 20
g02 <- function() {
x <- 1
y <- 2
c(x, y)
}
g02()
#> [1] 1 2

If a name isn’t defined inside a function, R looks one level up.

x <- 2
g03 <- function() {
y <- 1
c(x, y)
}
g03()
#> [1] 2 1
y
#> [1] 20

The same rules apply if a function is defined inside another function.

15 / 37

Functions vs variables

  • Since functions are objects, scoping rules also apply to functions
  • What happens if you use the same name for a function and non-function object?
    • R ignores non-function objects when looking for that value
g09 <- function(x) x + 100
g10 <- function() {
g09 <- 10
g09(g09)
}
g10()
#> [1] 110

!!! Using same name for different things is a bad idea!!!
Lexical scoping

16 / 37

g09 takes two different values best to not use same names (caution from Hadley) but knowing this can help us troubleshoot

A fresh start

  • Every time a function is called a new environment is created to host its execution.
  • Each invocation is completely independent
g11 <- function() {
if (!exists("a")) {
a <- 1
} else {
a <- a + 1
}
a
}
g11()
g11()

Lexical scoping

17 / 37

Dynamic Look up

Lexical scoping

  • 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
18 / 37

Dynamic Look up

Lexical scoping

  • 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
  • If there is a a spelling mistake in the code, you won’t get an error message when you create the function
  • Use codetools to detect the external dependencies
19 / 37

Dynamic Look up

Lexical scoping

  • 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
  • If there is a a spelling mistake in the code, you won’t get an error message when you create the function
  • Use codetools to detect the external dependencies
    codetools::findGlobals(g12)
    #> [1] "+" "x"
20 / 37

Lazy evaluation

In R, function arguments are lazily evaluated: they’re only evaluated if accessed.

h01 <- function(x) {
10
}
h01(stop("This is an error!"))
#> [1] 10
21 / 37

Promises

Lazy evaluation is powered by "promise": a data structure

A promise has three components

  • An expression, like x + y, which gives rise to the delayed computation
  • An environment where the expression should be evaluated
  • A value, which is computed and cached the first time a promise is accessed when the expression is evaluated in the specified environment
double <- function(x) {
message("Calculating...")
x * 2
}
h03 <- function(x) {
c(x, x)
}
h03(double(20))
> Calculating...
> [1] 40 40

Lazy evaluation

22 / 37

Default arguments

Default arguments can be defined in terms of

  • other arguments
  • variables defined later in the function
    • !!! Not recommended because
      • makes the code harder to understand
      • need to know the exact order of evaluation

Lazy evaluation

23 / 37

Missing arguments

you can use missing() to see if the argument's value comes from the user or default

h06 <- function(x = 10) {
list(missing(x), x)
}
str(h06())
> List of 2
> $ : logi TRUE
> $ : num 10
str(h06(10))
#> List of 2
#> $ : logi FALSE
#> $ : num 10

Lazy evaluation

24 / 37

First example uses default and returns TRUE second example uses user supplied so logi is FALSE

... (dot dot dot) | ellipsis

  • function can take any number of named and unnamed arguments
  • Pass arbitrary no. of functions to your function
red.plot <- function(x, y, ...) {
plot(x, y, col="red", ...)
}
red.plot(1:10, 1:10, xlab="My x axis", ylab="My y axis")

Lazy evaluation. Example from https://nicercode.github.io/guides/functions/

25 / 37

Exiting a function

26 / 37

functions exit in two ways, they either return a value, indicating success, or they throw an error

Implicit vs explicit return

j01 <- function(x) {
if (x < 10) {
0
} else {
10
}
}
j01(5)
#> [1] 0
j01(15)
#> [1] 10
j02 <- function(x) {
if (x < 10) {
return(0)
} else {
return(10)
}
}

Exiting a function

27 / 37

Invisible values

  • The most common function that returns invisibly is <-
  • Can make invisible by applying invisible() to the last value
j04 <- function() invisible(1)
j04()
  • Verify if the value exists by using print() or wrapping in ()
print(j04())
#> [1] 1
(j04())
#> [1] 1

Exiting a function

28 / 37

Errors

If a function cannot complete its assigned task, it should throw an error with stop() indicating the termination of function execution

j05 <- function() {
stop("I'm an error")
return(10)
}
j05()
#> Error in j05(): I'm an error

Exiting a function

29 / 37

Exit handlers

Undo any changes made to the global environment

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!

Exiting a function

30 / 37

can use it to switch back directories on exit

Function forms

31 / 37

Prefix form

  • Any function can be written in prefix form

  • Function name comes before the arguments eg. foofy(a, b, c)

Helps better understand the structure of the language

x + y
`+`(x, y)
names(df) <- c("x", "y", "z")
`names<-`(df, c("x", "y", "z"))
for(i in 1:10) print(i)
`for`(i, 1:10, print(i))

Function forms

32 / 37

Using prefix form

  • By position, like help(mean).
  • Partial matching, like help(top = mean).
  • By name, like help(topic = mean)
    !!! Use positional matching only for the first one or two arguments. Avoid using positional matching for less commonly used arguments, and never use partial matching.!!!

Function forms

33 / 37

Use positional matching only for the first one or two arguments; they will be the most commonly used, and most readers will know what they are.

Infix form

  • the function name comes in between its arguments, like x + y
  • User defined functions begin with %
`%+%` <- function(a, b) paste0(a, b)
"new " %+% "string"
#> [1] "new string"
  • names can contain any sequence of characters except %

Function forms

34 / 37

Replacement

  • Functions that replace values by assignment, like names(df) <- c("a", "b", "c")
  • They have the special name xxx<-, must have arguments named x and value, and must return the modified object
`second<-` <- function(x, value) {
x[2] <- value
x
}

Function forms

35 / 37

Special forms

  • They don't have a consistent form like [[, if, and for
  • Knowing the name of the function that underlies a special form is useful for getting documentation
  • All special forms are implemented as primitive functions; this means printing these functions is not informative
`for`
> .Primitive("for")

Function forms

36 / 37

Thank you

kmeena@uw.edu, twitter: @envhealthspeak

37 / 37

The Basics

2 / 37
Paused

Help

Keyboard shortcuts

, , Pg Up, k Go to previous slide
, , Pg Dn, Space, j Go to next slide
Home Go to first slide
End Go to last slide
Number + Return Go to specific slide
b / m / f Toggle blackout / mirrored / fullscreen mode
c Clone slideshow
p Toggle presenter mode
t Restart the presentation timer
?, h Toggle this help
Esc Back to slideshow