class: center, middle, inverse, title-slide .title[ # MACS 30500 LECTURE 11 ] .author[ ### Topics: Functions. ] --- class: inverse, middle ## Agenda **Key concepts** * Using existing functions * Writing your own function * Practice 1 **More concepts** * The `return` statement * `stop()` * Anonymous functions * Practice 2 Next lecture, we conclude this topic by introducing a few more concepts! <!-- MOVE TO NEXT TIME * Writing more complex functions: example * Functions with more than one argument * Use functions to organize your code * Documenting your function * Functions and `{{ }}` * Practice 3 AFTER CLASS NOTES SUMMER 2024 Online this summer it took 90 min to do part 1 and part 2a (including the second set of practice exercises); moved everything after that to the next lecture practice 1: budget 8-10 min to do it and 3-4 minutes to review (about 14 min) practice 2: budget 10-12 min to do it and 3-4 minutes to review (about 18 min) total at least 30 min of practice and about 45 min of explanation --> --- class: inverse, middle ## Functions key concepts From using to writing functions: * Using existing functions * Writing your own function * Practice Part 1 --- ### Using existing functions Functions are all over R! We have been **using functions** from day 1 of this class. For example: ```r a <- c(1:5) a ``` ``` ## [1] 1 2 3 4 5 ``` ```r sum(a) ``` ``` ## [1] 15 ``` ```r mean(a) ``` ``` ## [1] 3 ``` --- ### Using existing functions A function: * is a block of code that accomplishes a task * has a name that typically describes the task, like `sum()` or `mean()` * takes one or more inputs (e.g., arguments), processes it, and returns one or more outputs --- ### Using existing functions To **use** a function, we need to **know its arguments and pass them correctly.** In the console, type `help()` with the function's name in parenthesis to learn more about a function behavior and its arguments: ```r help(sum) help(mean) ``` <!-- mean(c(100, 1, 2, 3, 4, 5, 100, trim = 0.1)) --> -- Some functions come from base R, like those above. Other functions belong to packages, like `summarize()` or `mutate()` which come from `dplyr` <!-- illustrate help(mutate) but load tidyverse fist --> --- ### Writing custom functions Besides **using** functions created by others, we can also **write our own functions from scratch!** When we write our own function we need to provide the following: * **name**: something that describes the task that the function performs * **arguments (input)**: at least one data structure (e.g., value, vector, etc.) * **body**: self-contained lines of code that manipulate the input, defining what the function does * **return (output):** statement that returns the result of the function's calculations Syntax: ``` name <- function(arg1, arg2, ...) { result <- body with code that does things using arg1, arg2, etc. return(result) } ``` <!-- Notice: * we can assign the function to a named object like any other object * typically the body is multiple lines with multiple intermediate variables --> --- ### Writing custom functions For example, rather than using the built-in `sum()` function, we could **write our own sum function** to calculate the sum of the elements of a numeric vector. Syntax: ``` name <- function(arg1, arg2, ...) { result <- body with code that does things using arg1, arg2, etc. return(result) } ``` Our sum function: ```r my_sum <- function(vector) { total <- 0 for (element in vector) { total <- total + element } return(total) } ``` <!-- LET'S STOP A SECOND AN UNPACK THIS CODE! DO THIS ON WORKBENCH --> --- ### Writing custom functions To use a function, we need to **call it** with specific values. Our custom function `my_sum()` takes one argument, `vector`. When we call the function, this will be a concrete vector, such as `x`, `y`, etc. ```r x <- c(2,1,5,2) my_sum(vector = x) ``` ``` ## [1] 10 ``` ```r y <- c(4,3,7) my_sum(y) ``` ``` ## [1] 14 ``` <!-- SKIP ```` #j <- c("1", 6, 6) #my_sum(j) my_sum <- function(vector) { # convert vector to numeric, if possible # R coerces numeric strings like "1" to numeric values 1 (try if this was "one" ) vector <- as.numeric(vector) #print(vector) #print(class(vector)) # same code as above total <- 0 for (v in vector) { total <- total + v } return(total) } ``` --> --- ### Writing custom functions Notice a even better way to write our `my_sum` custom function is by looping over the vector indexes vs its elements: ``` # looping directly over elements my_sum <- function(vector) { total <- 0 for (element in vector) { total <- total + element } return(total) } # looping over indexes (better) my_sum <- function(vector) { total <- 0 for (i in seq_along(vector)) { total <- total + vector[i] } return(total) } ``` --- ### Advantages of writing custom functions Use custom-written functions to... * reduce repetitive code and chances for mistakes * reuse code * organize code (e.g., one function imports the data, another cleans the data, etc.) <!-- Can do with a loop ``` v <- list(a, b) output <- vector(mode = "list", length = length(v)) for (i in seq_along(v)) { output[[i]] <- (sum(v[[i]]/length(v[[i]]))) #output[[i]] <- (mean(v[[i]])) } output ``` --> --- ### Things to remember when writing functions: name Use **unique names** * if your function name matches the name of an existing R function, your function will replace the existing function for your current session (e.g., write `my_sum`, do not re-write `sum`) * unless there is a specific reason (e.g. learning purposes, etc.), do not create a new function like `my_sum()` when R already has a built-in `sum()` function you can use Use **informative names**: * the name should suggest what the function does * it should be short * avoid reserved words like `if`, `else`, `for`, `function`, etc. To see the full list of reserved words type `help(reserved)` in the Console --- ### Things to remember when writing functions: variables scope Variables defined inside a function are **not available outside it.** Their scope lies within and is limited to the function itself: ```r my_sum <- function(input_vector) { total <- 0 for (element in input_vector) { total <- total + element } return(total) } my_sum(c(1,2,3)) ``` ``` ## [1] 6 ``` ```r total ``` ``` ## Error in eval(expr, envir, enclos): object 'total' not found ``` --- class: inverse, middle ## Practice writing functions. Part 1 ### Download today's class materials from the website. Complete questions 1 and 2. --- class: inverse, middle ## More concepts * The `return` statement * `stop()` * Anonymous functions * Practice 2 --- ### The role of the return statement(s) If you do not write a `return` statement, your output will be the last statement in your code: ```r my_sum <- function(input_vector) { total <- 0 for (element in input_vector) { total <- total + element } total } my_sum(c(1,2,3)) ``` ``` ## [1] 6 ``` -- This works, but you might want to explicitly use `return` statements to enhance code clarity and readability. Remember that any code after `return` will be ignored. Let's see an example... --- ### Write multiple return statements Use multiple returns using `if-else`: ```r check_number <- function(x) { if (x > 0) { return("positive") } else if (x < 0) { return("negative") } else { return("zero") } } check_number(1) ``` ``` ## [1] "positive" ``` If x > 0, the function returns "positive" without evaluating rest of the body. --- ### Write one return statement but with multiple outputs Sometimes you want to return multiple objects and collect them into a list or a vector: ```r my_sum <- function(input_vector) { total <- 0 for (element in input_vector) { total <- total + element } return(list(total, element)) } my_sum(c(1,2,3)) ``` ``` ## [[1]] ## [1] 6 ## ## [[2]] ## [1] 3 ``` Why is this a list? Can it be a vector in this case? <!-- Explain why this is a list, will work as as vector here, but not always depends on type Try accessing list elements s <- my_sum(c(1,2,3)) s[1] s[2] s[[1]] s[[2]] s[1][[1]] s[2][[1]] ``` my_mean <- function(vector) { total_values <- length(vector) result <- (sum(vector/total_values)) return(list(total_values, result)) } my_mean(1:10) ``` --> --- ### Using `stop()` when writing a function Define a function `celsius_to_fahr()` that converts temperatures from Celsius to Fahrenheit using the formula `fahrenheit = (celsius * 9/5) + 32` ```r celsius_to_fahr <- function(celsius) { fahr <- (celsius * 9/5) + 32 return(fahr) } ``` ```r celsius_to_fahr(0) ``` ``` ## [1] 32 ``` ```r celsius_to_fahr(-20) ``` ``` ## [1] -4 ``` --- ### Using `stop()` when writing a function To ensure this function works properly, the argument must be numeric. Otherwise, the conversion between the two temperature scales will not work. Solution: use `if-else` to verify if the provided argument is numeric; if not, use `stop()` to raise an error and stop the function execution ```r celsius_to_fahr <- function(celsius) { if (!is.numeric(celsius)) { stop("input must be a number") } else { fahr <- (celsius * 9/5) + 32 return(fahr) } } ``` ```r celsius_to_fahr("zero") ``` ``` ## Error in celsius_to_fahr("zero"): input must be a number ``` <!-- Why use `stop()` and not `print()`? `stop():` interrupts the execution of the function and throw an error message when a condition is not met `print()`: doesnt stop the function execution just print the message so the first enforces a condition that must be met for the function to execute, and prints a message (which is optional); the other is only a message --> --- ### Anonymous functions <!-- You won't define anonymous or unnamed functions a lot, but you need to know they exist and recognize their syntax.--> Anonymous functions are **functions without a name**. **When to use anonymous functions?** * when the function is short and you need it only once * when you use it in conjunction with other functions, such as those from the `apply()` family In all other cases: do not use them, write the function explicitly instead! --- ### Anonymous functions Imagine we have the following function: ```r add <- function(x) { x + 3 } add(2) ``` ``` ## [1] 5 ``` -- Re-write it as an anonymous function. Note: one line, no name, and call it with `()`: ```r (function(x) {x + 3}) (2) ``` ``` ## [1] 5 ``` -- Often anonymous functions are written without the `{}`, like this: ```r (function(x) x + 3)(2) ``` ``` ## [1] 5 ``` <!-- Here's an anonymous function for calculating the mean of a vector `x`. In the following example, the input `x` to the function is each element of the list `l`. ```r l <- list(1:5, 5:7) lapply(l, FUN = function(x){sum(x)/length(x)}) ``` ``` ## [[1]] ## [1] 3 ## ## [[2]] ## [1] 6 ``` see http://adv-r.had.co.nz/Functional-programming.html#anonymous-functions --> --- ### Anonymous functions: with `sapply()` and `lapply()` Same function as before but we pass a vector rather than a single number: ```r add <- function(x) { x + 1 } add(c(1,2,3)) ``` ``` ## [1] 2 3 4 ``` To rewrite it as anonymous function, this code won't work: ```r (function(x) x + 1) (1,2,3) ``` ``` ## Error in (function(x) x + 1)(1, 2, 3): unused arguments (2, 3) ``` It only works on single values, not separated by commas: ```r (function(x) x + 1) (1:3) ``` ``` ## [1] 2 3 4 ``` --- ### Anonymous functions: with `sapply()` and `lapply()` Instead, we can rewrite it using `sapply()`: ```r sapply(1:3, function(x) x + 1) ``` ``` ## [1] 2 3 4 ``` ```r sapply(c(1,2,3), function(x) x + 1) ``` ``` ## [1] 2 3 4 ``` * `sapply()` means "simplify apply", turn output to a vector * `lapply()` means "list apply", turn output to a list Use them to apply a function to each element of a list or vector. The first turns the output into a vector, the latter leaves it as list. --- ### Anonymous functions: the `purrr` way The `purrr` package (remember the `map()`, `map_dbl()`, etc. functions from last week? they come from this package!) provides another way to write anonymous functions with the **`~.`** syntax. Same function as before that adds 1 to the input ```r add <- function(x) { x + 1 } add(c(1,2,3)) ``` ``` ## [1] 2 3 4 ``` Rewrite it as anonymous function in `purrr` (https://purrr.tidyverse.org/reference/map.html) ```r map_dbl(c(1,2,3), ~.x + 1) ``` ``` ## [1] 2 3 4 ``` --- class: inverse, middle ## Practice writing functions. Part 2 ### Download today's class materials from the website. Complete questions 3, 4, and 5.