FES 720 Introduction to R

Functions and Lists

Functions are tools

mean()

sum()

sd()

Why write your own functions?

E.g., set defaults for nice plots

plot(1:10)

par(lwd = 2, tcl = 0.3, 
    font.lab = 2, las = 1, 
    cex = 1.5, cex.lab = 1.5, cex.axis = 1.5, 
    mar = c(5,5,2,2))
plot(1:10)

Functions are an abstraction

# Calculate the mean of your data

sum(my.data) / length(my.data)

We can separate what we want to do from how we do it.

# Function to calculate mean
my.mean <- function(x){
  sum(x) / length(x)
}

# Calculate mean of my data
my.mean(my.data)

Functions make code more readable

This code is shorter but harder to understand

data$response.logit <- log(data$response / (1 - data$response))

This code is more lines, but:

logit <- function(p){
  log(p / (1-p))
}

data$response.logit <- logit(data$response)

Functions help avoid coding errors

Functions help you become more productive

How functions work

  1. Take an object

  2. Perform an action/s

  3. Return another object or output

The structure of a function

  1. Name: Can be any valid name. Do not write over existing functions. Follow style guide to function names.

  2. Input arguments: What are the inputs or data to the function? As many inputs as you want.

  3. Actions: What do you want the function to do with the inputs? Create a plot? Calculate a statistic? Run a regression analysis?

  4. Action arguments: Are there any options and/or defaults you want to set?

  5. Output: What output or final product do you want? A scalar? A vector? A dataframe? A plot? A table?

Structure of a function: Code

# The basic structure of a function

NAME <- function(ARGUMENTS) {


  ACTIONS


  return(OUTPUT) # Optional

}

An example function

# Create the function my.mean()

my.mean <- function(x) {   # Single input called x

  output <- sum(x) / length(x) # Calculate output

return(output)  # Return output to the user after running the function

}

Or ..

# Create the function my.mean()

my.mean <- function(x) {   # Single input called x

 sum(x) / length(x) # Calculate output

}

Abstracting Functions

e.g., Calculate mean DBH of trees

i. Directly using the data

sum(dat_tree$DBH) / length(dat_tree$DBH)

ii. Turn this into a function

my_mean <- function(dat_tree$DBH){

  sum(dat_tree$DBH) / length(dat_tree$DBH)

}

iii. Abstract it

my_mean <- function(x){

  sum(x) / length(x)

}

Functions within functions

You can call functions from within functions.

We already did this in the function above, where we used sum() and length().

my_mean <- function(x){

  sum(x) / length(x)

}

e.g., return SD and mean tree DBH

meanSD <- function(x){

  out <- c( my_mean(x), sd(x) )

  return(out)

}

Adding to the basic structure

Save and source() your functions

source("customFunctions.R")

Build and test functions

Function design

1. A function should do one thing well

2. A function should be easily understandable in isolation


Style Guide for Functions

1. Use verbs for function names …

# Good

checkNames()

calcBA()


# Bad
nameChecker()

baCalculator()

… a different style from variables

# Variables

names_original

names_checked

tree_dbh

tree_ba

2. Indent multiple lines to where definition starts

# Good

checkNames <- function(x, 
                   names_correct = 'file-of-correct-names.txt'
                   ) {
  # Code of function body goes here, indented
    
}


# Bad

checkNames <- function(x, 
  names_correct = 'file-of-correct-names.txt') {
  # Here it is hard to tell the arguments from the code body
}


3. No line breaks within assignments

# Good

calcCarbon <- function(dbh, height, 
                   genus = 'Quercus')


# Bad

calcCarbon <- function(dbh, height, genus =
                  'Quercus')


4. Use comments to explain why, not what or how

Each line of comment should begin with a hash and a single space.

# A comment explains why
 

5. Function documentation

There are many ways to document your functions.

It should include:

# One example

funcName <- function(x, a, b, 
                   arg1 = 'bananas',
                   arg2 = 6.45) {
# A short description of what the function does  
#
# Arguments:
# Followed by a list of the arguments and description
#   x: data from ..., numeric
#   a: something, numeric 
#   b: something else, numeric
#   arg1: type of fruit, character
#   arg2: a constant modifier, numeric
#
# Returns:
#   The total biomass of fruit in a forest

  code body here
  
}

Lists

Not that kind of list

Vectors handle 1-D objects

Lists are like trees or wardrobes: complex hierarchical objects.

Make a list with list()

# A simple list of three integers
x <- list(1, 2, 3)

str(x)
#> List of 3
#>  $ : num 1 
#>  $ : num 2
#>  $ : num 3

# We can name each part of the list
x_named <- list(a = 1, b = 2, c = 3)

str(x_named)
#> List of 3
#>  $ a: num 1
#>  $ b: num 2
#>  $ c: num 3

Lists can contain different types of objects

y <- list("a", 1, 1.5, TRUE)

str(y)
#> List of 4
#>  $ : chr "a"
#>  $ : int 1
#>  $ : num 1.5
#>  $ : logi TRUE

Lists can contain other lists

z <- list(list(1, 2), list(3, 4))

str(z)
#> List of 2
#>  $ :List of 2
#>   ..$ : num 1
#>   ..$ : num 2
#>  $ :List of 2
#>   ..$ : num 3
#>   ..$ : num 4

Three ways to subset a list

# An example list
a <- list(a = 1:3, b = "some text", c = pi, d = list(4, 5))

# The structure of 'a'
str(a)
#> List of 4
#>  $ a: int [1:3] 1 2 3
#>  $ b: chr "some text"
#>  $ c: num 3.14
#>  $ d:List of 2
#>   ..$ : num 4
#>   ..$ : num 5

You can use:

  1. [ ]
  2. [[ ]]
  3. $

1. [ ] extracts a sub-list.

str(a[1:2])
#> List of 2
#>  $ a: int [1:3] 1 2 3
#>  $ b: chr "some text"
str( a[c("a", "b")] )
#> List of 2
#>  $ a: int [1:3] 1 2 3
#>  $ b: chr "some text"

2. [[ ]] extracts a single component from a list.

str(a[[1]])
#> int [1:3] 1 2 3

3. $ works for named elements in a list.

str(a[["a"]])
#> int [1:3] 1 2 3


str(a$a)
#> int [1:3] 1 2 3

Updated: 2017-10-03