Functions

Writing Your Own Tools

Last Time

  1. Use comparison operators to compare two values.
  2. if-then runs code only when a condition is TRUE.
  3. if-else chooses between two blocks of code based on condition.
  4. A for loop repeats code for each element of a vector.
  5. ifelse() is a vectorized if-else.

Key Ideas for Today

  1. A function bundles code into a reusable tool.
  2. Functions created with function(), saved as objects.
  3. Arguments make it flexible; defaults make it quick.
  4. The last expression is returned (or use return()).
  5. Scoping determines where a function looks for objects.

A question to ponder . . .

What will the following code return?

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

Defining a Function

Ex. colSums()

mat <- matrix(1:4, nrow = 2)
mat
     [,1] [,2]
[1,]    1    3
[2,]    2    4
colSums(mat)
[1] 3 7

How could I compute this without colSums()?

c(sum(mat[, 1]), sum(mat[, 2]))
[1] 3 7

I could generalize with a for-loop.

colsum_vec <- rep(0, ncol(mat))

for (coln in 1:ncol(mat)) {
  colsum_vec[coln] <- sum(mat[, coln])
}

colsum_vec
[1] 3 7

Why use colSums() instead?

  1. Make your code more readable
  2. Don’t Repeat Yourself (DRY)
  3. Focus on the variables, not the constants
  4. Easier to test, update

Many individual functions = wrench set








Custom function = socket wrench set.

Building a function

  (9/5) * temp_c + 32

Building a function

c_to_f <- function(temp_c) {
  (9/5) * temp_c + 32
}

Building a function

c_to_f <- function(temp_c) {
  (9/5) * temp_c + 32
}
  • Create a new function with function()
  • Define the function name, arguments, and expression
  • Creates a new object, c_to_f() in your environment
  • Last line will be returned (or use return())

greet <- function(name) {
  paste0("Hello ", name, "! Welcome to class.")
}

name_vec <- c("larry", "moe", "curly")
name_vec <- sample(name_vec)

for (name in name_vec) {
  print(greet(name))
  Sys.sleep(8)
}

Guess the Metaphor

A function is a like a mill: arguments go in, return value comes out (flour). The hidden machinery does the work.

Scoping

Question: What will the following code return?

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

x <- 2
g03 <- function() {
  y <- 1
  c(x, y)
}
g03()
[1] 2 1
  • The environment within the function includes the environment “one level up”
  • Objects defined in the function environment mask those one level up
  • Best to encapsulate functions (not make them dependent on objects defined outside their environment / arguments)

Building a Die Roller

Roll a Die

Task: return a numeric vector with the result of rolling a fair six-sided die.

  sample(x = 1:6, size = 1)

Roll a Die

Task: return a numeric vector with the result of rolling a fair six-sided die.

roll_die <- function() {
  sample(x = 1:6, size = 1)
}

Question: Modify this function so it can

  1. work for arbitrary n-sided dice
  2. return more than 1 roll of the die
02:00

Roll a Die

Task: return a numeric vector with the result of rolling a fair six-sided die.

roll_die <- function(n_sides = 6) {
  sample(x = 1:n_sides, size = 1)
}

Question: Modify this function so it can

  1. work for arbitrary n-sided dice
  2. return more than 1 roll of the die

Roll a Die

Task: return a numeric vector with the result of rolling a fair six-sided die.

roll_die <- function(n_sides = 6, n_rolls = 1) {
  sample(x = 1:n_sides, size = n_rolls)
}

Question: Modify this function so it can

  1. work for arbitrary n-sided dice
  2. return more than 1 roll of the die

Building a Grade Calculator

Your Turn

Write a function called course_grade() that takes assignment scores as inputs as outputs your current grade in the course.

02:30

course_grade <- function(ps, proj_studio, quiz, final) {
  grade <- 0.01 * mean(ps) + 0.20 * mean(proj_studio) + 
    0.49 * mean(quiz) + 0.3 * final
  return(grade)
}
ps <- c(1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1)
proj_studio <- c(1, 1, .66, 1, .33, 1)
quiz <- c(.8, .91, .82, .98, .79, .85, .92)
final <- .85
course_grade(ps, proj_studio, quiz, final)
[1] 0.855519

What if the weighting scheme were different?

Add flexibility through arguments.

course_grade <- function(ps, proj_studio, quiz, final) {
  grade <- 0.01 * mean(ps) + 0.20 * mean(proj_studio) + 
    0.49 * mean(quiz) + 0.3 * final
  return(grade)
}

course_grade <- function(ps, proj_studio, quiz, final,
                         ps_w, proj_studio_w, quiz_w, final_w) {
  grade <- ps_w * mean(ps) + proj_studio_w * mean(proj_studio) + 
    quiz_w * mean(quiz) + final_w * final
  return(grade)
}
course_grade(ps, proj_studio, quiz, final,
             0, .20, .5, .3)
[1] 0.8549048

What if we have no final_exam grade yet?

Save typing and head off errors by setting defaults.

course_grade <- function(ps = 0, proj_studio = 0, quiz = 0, final = 0,
                         ps_w = .01, proj_studio_w = .2, quiz_w = .49, final_w = .3) {
  grade <- ps_w * mean(ps) + proj_studio_w * mean(proj_studio) + 
    quiz_w * mean(quiz) + final_w * final
  return(grade)
}


How else can we generalize this function for other policies?

Why write functions?

  1. Make your code more readable
  2. Don’t Repeat Yourself (DRY)
  3. Focus on the variables, not the constants
  4. Easier to test, update

Key Ideas for Today

  1. A function bundles code into a reusable tool.
  2. Functions created with function(), saved as objects.
  3. Arguments make it flexible; defaults make it quick.
  4. The last expression is returned (or use return()).
  5. Scoping determines where a function looks for objects.

For Next Time

  1. Sign up for Quiz 2 on PrairieTest.
  2. Work through the functions questions on the problem set.
  3. Come to Project Studio 1 this week.