Data In The Wild
  1. Module 4
  2. 4.5: Writing Functions
  • Home
  • Contact Us
  • For Instructors

  • Module 1
    • Overview
    • 1.1: Introduction to R and RStudio
    • 1.2: Intro to Coding in R
    • 1.3: Introduction to the tidyverse
    • Assignment 2
    • Assignment 3
  • Module 2
    • Overview
    • 2.1: Good Food Gone Bad
    • 2.2: Plotting with ggplot2
    • 2.3: Data Visualization
    • 2.4: Exploring geom Functions
    • 2.5: Module 2 Wrap-Up
    • Assignment 1
    • Assignment 2
    • Assignment 3
  • Module 3
    • Overview
    • 3.1: Leopard Seals
    • 3.2: T-Tests
    • 3.3: Comparing (Multiple) Means
    • Assignment 1
    • Assignment 2
  • Module 4
    • Overview
    • 4.1: Combining Datasets (Joins & Binds)
    • 4.2: K-Nearest Neighbor
    • 4.3: Roads and Regressions
    • 4.4: Multiple Regression
    • 4.5: Writing Functions
    • Assignment 1
    • Assignment 2
    • Assignment 3
    • Assignment 4
  • Module 5
    • Overview
    • 5.1: Population Growth
    • 5.2: Sustainable Fishing
    • 5.3: Comparing Populations
    • Assignment 1
    • Assignment 2
    • Assignment 3
  • Final Project

  • Resources

On this page

  • Functions
    • Learning Outcomes
    • Introduction
      • What is a function?
      • Writing Our First Function
      • Why write our own functions?
    • Functions That Work on Data
      • Exercise 1: Simple Function
      • Exercise 2: A Summary Function
      • Exercise 3: A Plotting Function
      • Great Work!
  1. Module 4
  2. 4.5: Writing Functions

4.5: Writing Functions

Author

Lily McMullen

Functions

Learning Outcomes

  • Students will be able to explain what a function is and identify its arguments, body, and return value.
  • Students will be able to write a custom function that performs a simple task.
  • Students will be able to explain why functions are useful and how they connect to repeating a task.

Introduction

In this lesson, we’ll learn how to write functions in R. Functions let you automate tasks and reuse code across your entire dataset.

Let’s get started!

What is a function?

We’ve actually been using functions quite a lot in the course already, but let’s remind ourselves exactly what a function actually is.

A function is a block of code designed to perform a specific task. A function is executed when it is called. This means that the block of code is run every time you use the function!

A function takes specific arguments as input, processes them, and returns the output.

We’ve used a lot of built-in functions already, for example, the mean() function.

Now, we are going to write our own functions!

Writing Our First Function

Following the syntax from the image above, let’s write a function called add_numbers that takes two numbers as arguments and returns their sum.

add_numbers <- function(x, y) {
  z <- x + y
  return(z)
}

Notice that we didn’t put any actual numbers in the function! Instead, we used generic arguments (e.g., x and y) to represent the values that will fill those slots when we call the function.

Inside the function body, we write a statement that adds x and y together and saves that value as an object called z.

We use the return() function to indicate that z is the value we want the add_numbers function to give back to us when it is executed.

Let’s test our function with numbers, now!

add_numbers(5, 5)
[1] 10
my_sum <- add_numbers(10, 10)

my_sum
[1] 20

Let’s Practice

Now try to write a function called multiply_numbers that takes three numbers as arguments and returns the product.

Make sure to test your function, and remember a function should have a name, a list of arguments, and return something. Use the same syntax as our add_numbers() example above.

# Write your code here

Answer:

multiply_numbers <- function(x, y, z) {
  product <- x * y * z
  return(product)
}

multiply_numbers(1, 3, 3)
[1] 9

Why write our own functions?

We just wrote add_numbers, but R can already add with +. So why bother writing functions at all?

Once a task is wrapped in a function, you can run it on new inputs again and again without rewriting the code, and you can apply it across an entire column of data in one line.

Think back to how mutate() applied a calculation to every row of a data frame: a function lets you package up any task you want and hand it to tools like mutate() to repeat across all your data.

Functions That Work on Data

For the rest of the lesson, we’ll write functions that operate on a data frame. Let’s load the tidyverse and our hairgrass data first.

# Write your code here

Answer:

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
hairgrass <- read_csv("data/hairgrass_data.csv")
Rows: 240 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
dbl (8): hairgrass_density, soil_nitrogen, soil_phosphorus, penguin_density,...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Exercise 1: Simple Function

Objective: Write a function called inspect that takes a data frame as an argument and prints the head and tail of the data frame.

Before we attempt to write this function, you’ll need to know about the print() function.

In R, when a function has multiple output statements, it only displays the last one by default. To see all outputs, wrap each one in the print() function.

# demonstration of the print function
print(mean(hairgrass$soil_ph))
[1] 6.479609

Now we can start to write our function! Fill in the blanks below so the function prints the head and tail of the data frame. If you’re up for an extra challenge, have it print the first 10 and last 10 rows (instead of the default 6).

# Fill in the blanks (____)

inspect <- function(dataframe) {
  print(head(____))
  print(tail(____))
}

# test it with the hairgrass data
inspect(____)

Answer:

inspect <- function(dataframe) {
  print(head(dataframe, 10))
  print(tail(dataframe, 10))
}

inspect(hairgrass)
# A tibble: 10 × 8
   hairgrass_density soil_nitrogen soil_phosphorus penguin_density
               <dbl>         <dbl>           <dbl>           <dbl>
 1               137          17.0            6.44              15
 2               103          20.1            5.96              21
 3               107          12.4            7.69              10
 4               121          13.2            6.76              22
 5               150          25.9            7.16              14
 6               120          15.3            3.04              14
 7               136          26.6            4.96              10
 8               127          23.1            5.74              16
 9               112          19.8            2.47              15
10                98          15.0            3.76              13
# ℹ 4 more variables: avg_summer_tempC <dbl>, avg_windspeed <dbl>,
#   soil_ph <dbl>, percent_rock <dbl>
# A tibble: 10 × 8
   hairgrass_density soil_nitrogen soil_phosphorus penguin_density
               <dbl>         <dbl>           <dbl>           <dbl>
 1               119         24.8             4.43              13
 2                94         20.2             2.34              15
 3               123          9.80            5.69              13
 4               133         21.4             5.32               7
 5               117         10.6             5.29              23
 6               129         19.2             4.03              21
 7               115         19.3             6.25              10
 8               152         26.9             7.57              14
 9               135         14.9             5.54              19
10               126         24.0             5.24              18
# ℹ 4 more variables: avg_summer_tempC <dbl>, avg_windspeed <dbl>,
#   soil_ph <dbl>, percent_rock <dbl>

Exercise 2: A Summary Function

Objective: Write a function called summarize_column that takes one numeric column and prints its mean and standard deviation.

You already know how to calculate a mean and a standard deviation, this exercise is just about wrapping that code inside a function. Your function takes one argument (a column) and prints both values.

Hint: pass a column name in the same way you did with mean() above, using dataframe$column when you test it.

# Fill in the blanks (____)

summarize_column <- function(column) {
  print(mean(____))
  print(sd(____))
}

# test it with the soil_ph column first
summarize_column(hairgrass$____)

Answer:

summarize_column <- function(column) {
  print(mean(column))
  print(sd(column))
}

summarize_column(hairgrass$soil_ph)
[1] 6.479609
[1] 0.4048466

The whole point: the body is mean() and sd(). The only new idea is that column is a stand-in filled when the function is called.

Encourage them to test it on other columns (hairgrass$soil_phosphorus, etc.)

Exercise 3: A Plotting Function

Objective: Write a function called plot_relationship that makes a scatter plot of two columns with a line of best fit.

Just like the last exercise, the body is ggplot code you’ve already written many times in this module, you’re just wrapping it in a function. Your function takes two columns as arguments (passed as vectors, like you did in Exercise 2).

# Fill in the blanks (____)

plot_relationship <- function(x_column, y_column) {
  ggplot(mapping = aes(x = x_column, y = y_column)) +
    geom_point() +
    geom_smooth(method = "____") +
    theme_classic()
}

# test it: soil_ph on the x-axis, hairgrass_density on the y-axis. Again, put the column name after the $
plot_relationship(hairgrass$____, hairgrass$____)

Answer:

plot_relationship <- function(x_column, y_column) {
  ggplot(mapping = aes(x = x_column, y = y_column)) +
    geom_point() +
    geom_smooth(method = "lm") +
    theme_classic()
}

plot_relationship(hairgrass$soil_ph, hairgrass$hairgrass_density)
`geom_smooth()` using formula = 'y ~ x'

Why no dataframe argument? In Exercise 2, we passed hairgrass$soil_ph, which is already a vector of numbers.

When we pass a vector directly to a function, we can use it in aes() without the dataframe. This keeps the function simple.

Encourage them to test it on other column pairs (e.g., soil_phosphorus vs nitrogen_ppm).

Great Work!

Functions let you take something you already know how to do and package it so you can reuse it whenever you want. :)

2026, University of Arizona & Lewis & Clark College

 
  • Made with Quarto