These exercises are adapted from Allison Horst’s EDS 221: Scientific Programming Essentials Course for the Bren School’s Master of Environmental Data Science program.
1 Getting Ready
Setup
Make sure you’re in the right project (training_{USERNAME}) and use the Git workflow by Pulling to check for any changes in the remote repository (aka repository on GitHub).
Create a new Quarto Document.
Title it “R Practice: Functions”.
Save the file and name it “r-practice-functions”.
Organize your Quarto Document and folders in a meaningful way. Organization is personal - so this is up to you! Consider the different ways we’ve organized previous files using: headers, bold text, naming code chunks, comments in code chunks. Consider what directories (folders) we’ve used or talked about to organize these files. What is most important is organizing and documenting the file so that your future self (or if you share this file with others!) understands it as well as your current self does right now.
Use the Git workflow. After you’ve set up your project and uploaded your data go through the workflow: Stage (add) -> Commit -> Pull -> Push
2 R Functions Warm Up
We’re going to start by creating some simple functions. Recall that the anatomy of a function is the same for all functions and each one contains:
a function name,
arguments that allow a user to specify inputs,
and body of commands and outputs enclosed within a set of curly braces {}
Exercise 1
Create a function called double_it() that doubles any value input value. Then try out the function, are the values returned what you expect?
Answer
# create function #double_it <-function(x) {return(2* x)}# try it out ## explicit notationdouble_it(x =24)# non explicit notationdouble_it(24)
Exercise 2
Write a function called exclaim_age() that returns the statement “I am ___ years old!”, where the blank is entered by the user as argument age.
Then try out the function, are the values returned what you expect?
Answer
# write function #exclaim_age <-function(age) {return(paste("I am", age, "years old!"))}# try it out ## explicit notationexclaim_age(age =12)# non explicit notationexclaim_age(12)
3 Functions with Conditionals
Exercise 3
Consider the function called find_max():
Talk to your neighbor about what this function does and what you expect the output would be.
Run the function with some values. Is it running how you expect?
Run the function again, but this time use the function in an arithmetic expression. Is the output what you expect?
# example using `find_max()` in an arithmetic expression ## we expect the answer to be 205*find_max(4, 2)
4 Adding Error or Warning Messages
Let’s continue to test the find_max() function and make sure it runs appropriately in the following function calls in the following exercises
Exercise 4
Run find_max(4, 2, 5) in the Console.
What happens? What kind of message appears? Is it sufficient? If not, consider adding a warning or error message using warning() or stop(). Remember, use ?function to access the Help page. Add additional logic to the function, as needed.
Answer
When you run find_max(4, 2, 5), the following error message appears:
Error in find_max(4, 2, 5) : unused argument (5)
This is an error message that is automatically created by R since our function only requires two parameters. This is a sufficient error message.
Exercise 5
Run find_max(4, 4) in the Console.
What happens? What kind of message appears? Is it sufficient? If not, consider adding a warning or error message using warning() or stop(). Remember, use ?function to access the Help page. Add additional logic to the function, as needed.
Hint
When you run find_max(4, 4), no message appears and the function is sent to the Console, but no value is either returned or printed.
To account for this scenario, add an if() statement to the beginning of the function, and then use either warning() or stop().
Answer
# `find_max()` function with error messagefind_max <-function(value_1, value_2) {if (value_1 == value_2) {stop("Values must be different from each other.") }if (value_1 > value_2) {return(value_1) }elseif (value_2 > value_1) {return(value_2) }}# try it out ## does the message appear as you expected?find_max(4, 4)
Exercise 6
Run find_max(4, "cow") in the Console.
What happens? What kind of message appears? Is it sufficient? If not, consider adding a warning or error message using warning() or stop(). Remember, use ?function to access the Help page. Add additional logic to the function, as needed.
Hint
When you run find_max(4, "cow"), the function runs as is and returns the value “cow”. This is not expected because these two values aren’t necessarily comparable since they’re different data types.
However, find_max() doesn’t know any better since this scenario hasn’t been defined in the body of the function yet.
To account for this scenario, add additional logic that checks the class of each argument before the function continues to execute.
The logical operator for OR is |. The not-equal-to operator is !=.
Answer
# `find_max()` function with error messages and checksfind_max <-function(value_1, value_2) {# `|` is the logical OR operator# `!=` is the not-equal-to operatorif (is.numeric(value_1) !=TRUE|is.numeric(value_2) !=TRUE) {# alt expression: is.numeric(value_1) == FALSE | is.numeric(value_2) == FALSEstop("Value must be a numeric type.") }if (value_1 == value_2) {stop("Values must be different from each other.") }if (value_1 > value_2) {return(value_1) }elseif (value_2 > value_1) {return(value_2) }}# try it out ## does the message appear as you expected?find_max(4, "cow")find_max("cow", 4)
Exercise 7
Run find_max(4, 4) in the Console. Previously we coded our function to report an error. But perhaps the user would prefer to have the function return the shared value, as an option. Add an argument with a reasonable default value to allow the user to control this behavior. Add additional logic to the function, as needed.
Hint
Before, if value_1 == value_2, we used stop() to create an error. But with an additional argument, we can adjust how the function responds by testing the value of that argument.
Often for arguments that turn on or turn off a behavior, a TRUE/FALSE value makes sense so you could easily include the argument in a logical test.
Answer
# `find_max()` function with error messages and checksfind_max <-function(value_1, value_2, equal_ok =FALSE) {# `|` is the logical OR operator# `!=` is the not-equal-to operatorif (is.numeric(value_1) !=TRUE|is.numeric(value_2) !=TRUE) {# alt expression: is.numeric(value_1) == FALSE | is.numeric(value_2) == FALSEstop("Value must be a numeric type.") }if (value_1 == value_2) {### the values are equal; is value of the equal_ok argument TRUE?if(equal_ok) return(value_1)### if equal_ok is not TRUE, then report an errorstop("Values must be different from each other.") }if (value_1 > value_2) {return(value_1) }elseif (value_2 > value_1) {return(value_2) }}# try it out ## does the message appear as you expected?find_max(4, 4)find_max(4, 4, equal_ok =TRUE)