library(tidyverse)
data("mpg", package = "ggplot2")
mpg |>
mutate(
efficiency = if_else(
condition = cty >= 20,
true = "High",
false = "Low"
)
) |>
select(manufacturer, model, cty, efficiency)
## # A tibble: 234 × 4
## manufacturer model cty efficiency
## <chr> <chr> <int> <chr>
## 1 audi a4 18 Low
## 2 audi a4 21 High
## 3 audi a4 20 High
## 4 audi a4 21 High
## 5 audi a4 16 Low
## 6 audi a4 18 Low
## 7 audi a4 18 Low
## 8 audi a4 quattro 18 Low
## 9 audi a4 quattro 16 Low
## 10 audi a4 quattro 20 High
## # ℹ 224 more rowsWeek 14: Program
[14a] Conditionals
Topics
- Evaluate comparisons and combine multiple conditions using Boolean logic
- Summarize logical vectors to count occurrences and calculate proportions
- Control data transformations conditionally using
if_else()andcase_when()
Readings
- R4DS (2E) Chapter 12: Logical vectors
Slides
*Note that you can click the three-line (hamburger) icon on the bottom-left of the slides to access a navigation menu. You can click inside the slides region and press the left and right arrow keys on your keyboard to advance and reverse the slides and animations.
Practice
1) Categorizing City Mileage
Using the mpg dataset (from {ggplot2}), let’s use if_else() to create a new categorical column based on city fuel efficiency.
- Use
mutate()andif_else()to create a new column calledefficiency. - If
ctyis greater than or equal to 20, label it “High”. Otherwise, label it “Low”. - Select the
manufacturer,model,cty, andefficiencycolumns to view your results.
Answer key
2) Sleep Summaries
Let’s use the msleep dataset (from {ggplot2}) to practice summarizing logical conditions across groups.
- Use
summarize()to calculate two new metrics. - First, calculate the proportion of animals that sleep more than 12 hours (
sleep_total > 12) and name itprop_heavy_sleepers. - Second, check if any animal in the group has a body weight greater than 500 (
bodywt > 500) and name itany_giants. - Group the summary by the
vorecolumn using the.byargument.
Answer key
library(tidyverse) data("msleep", package = "ggplot2") msleep |> summarize( prop_heavy_sleepers = mean(sleep_total > 12), any_giants = any(bodywt > 500), .by = vore ) ## # A tibble: 5 × 3 ## vore prop_heavy_sleepers any_giants ## <chr> <dbl> <lgl> ## 1 carni 0.421 TRUE ## 2 omni 0.2 FALSE ## 3 herbi 0.438 TRUE ## 4 <NA> 0.286 FALSE ## 5 insecti 0.6 FALSE
3) Sizing Up Starwars Characters
The starwars dataset (from {dplyr}) contains physical attributes of characters. Let’s practice handling multiple conditions using case_when().
- Use
mutate()andcase_when()to create a new column calledsize_categorybased on themasscolumn. - If
massis less than 50, label it “Light”. - If
massis less than 100, label it “Medium”. - If
massis 100 or greater, label it “Heavy”. - Use the
.defaultargument to label any remaining cases (like missing values) as “Unknown”. - Select the
name,mass, andsize_categorycolumns to verify your logic worked correctly. Note: Remember thatcase_when()evaluates from top to bottom!
Answer key
library(tidyverse) data("starwars", package = "dplyr") starwars |> mutate( size_category = case_when( mass < 50 ~ "Light", mass < 100 ~ "Medium", mass >= 100 ~ "Heavy", .default = "Unknown" ) ) |> select(name, mass, size_category) ## # A tibble: 87 × 3 ## name mass size_category ## <chr> <dbl> <chr> ## 1 Luke Skywalker 77 Medium ## 2 C-3PO 75 Medium ## 3 R2-D2 32 Light ## 4 Darth Vader 136 Heavy ## 5 Leia Organa 49 Light ## 6 Owen Lars 120 Heavy ## 7 Beru Whitesun Lars 75 Medium ## 8 R5-D4 32 Light ## 9 Biggs Darklighter 84 Medium ## 10 Obi-Wan Kenobi 77 Medium ## # ℹ 77 more rows
[14b] Functions
Topics
- Package repetitive code into custom functions using the DRY principle
- Control function behavior using arguments, defaults, and branching logic
- Apply custom vector functions inside tidyverse data pipelines
Readings
- R4DS (2E) Chapter 25: Functions
Slides
*Note that you can click the three-line (hamburger) icon on the bottom-left of the slides to access a navigation menu. You can click inside the slides region and press the left and right arrow keys on your keyboard to advance and reverse the slides and animations.
Practice
1) Building an Outlier Flag
Let’s build a function to identify unusually high values in a dataset. We will start with the basic math.
- Create a custom function named
flag_outlier()with a single argumentx. - Inside the function, calculate a cutoff value: the
mean()ofxplus 2 times thesd()ofx. (Be sure to usena.rm = TRUEfor both!). - Return a logical value indicating whether
xis greater than your cutoff. - Test your function on the
Ozonecolumn of theairqualitydataset (from the {datasets} package).
Answer key
library(tidyverse) data("airquality", package = "datasets") flag_outlier <- function(x) { cutoff <- mean(x, na.rm = TRUE) + (2 * sd(x, na.rm = TRUE)) x > cutoff } flag_outlier(airquality$Ozone) ## [1] FALSE FALSE FALSE FALSE NA FALSE FALSE FALSE FALSE NA FALSE FALSE ## [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE ## [25] NA NA NA FALSE FALSE TRUE FALSE NA NA NA NA NA ## [37] NA FALSE NA FALSE FALSE NA NA FALSE NA NA FALSE FALSE ## [49] FALSE FALSE FALSE NA NA NA NA NA NA NA NA NA ## [61] NA TRUE FALSE FALSE NA FALSE FALSE FALSE FALSE FALSE FALSE NA ## [73] FALSE FALSE NA FALSE FALSE FALSE FALSE FALSE FALSE FALSE NA NA ## [85] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE ## [97] FALSE FALSE TRUE FALSE TRUE NA NA FALSE FALSE FALSE NA FALSE ## [109] FALSE FALSE FALSE FALSE FALSE FALSE NA FALSE TRUE FALSE NA FALSE ## [121] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE ## [133] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE ## [145] FALSE FALSE FALSE FALSE FALSE NA FALSE FALSE FALSE
2) Adding Customization
Let’s make the function more flexible by allowing the user to decide exactly how extreme an outlier needs to be before it gets flagged.
- Redefine your function and add a second argument called
multiplier. Set its default value to2. - Update your cutoff calculation. Instead of hardcoding a number, multiply the standard deviation by your new
multiplierargument. - Test your upgraded function by passing
airquality$Ozoneto it and setting themultiplierto3. - Pipe the result of that test directly into the
any()function to quickly check if there are any extreme outliers in the vector.
Answer key
flag_outlier <- function(x, multiplier = 2) { cutoff <- mean(x, na.rm = TRUE) + (multiplier * sd(x, na.rm = TRUE)) x > cutoff } flag_outlier(airquality$Ozone, multiplier = 3) |> any() ## [1] TRUE
3) Functions in Pipelines
Because your function takes a vector and returns a logical vector of the exact same length, it drops perfectly into your data wrangling workflows.
- Open a tidyverse pipeline using the
airqualitydataset. - Immediately pipe it to the
as_tibble()function to make it a tibble. - Use
mutate()to create a new column calledis_high_ozoneusing yourflag_outlier()function. - Use
select()to view only theMonth,Day,Ozone, andis_high_ozonecolumns.
Answer key
airquality |> as_tibble() |> mutate(is_high_ozone = flag_outlier(Ozone)) |> select(Month, Day, Ozone, is_high_ozone) ## # A tibble: 153 × 4 ## Month Day Ozone is_high_ozone ## <int> <int> <int> <lgl> ## 1 5 1 41 FALSE ## 2 5 2 36 FALSE ## 3 5 3 12 FALSE ## 4 5 4 18 FALSE ## 5 5 5 NA NA ## 6 5 6 28 FALSE ## 7 5 7 23 FALSE ## 8 5 8 19 FALSE ## 9 5 9 8 FALSE ## 10 5 10 NA NA ## # ℹ 143 more rows
[14c] Iteration
Topics
- Iterate functions across multiple columns using
across() - Use list objects to store complex, mixed-type data
- Efficiently apply functions to vectors using
map()
Readings
- R4DS (2E) Chapter 26: Iteration
Slides
*Note that you can click the three-line (hamburger) icon on the bottom-left of the slides to access a navigation menu. You can click inside the slides region and press the left and right arrow keys on your keyboard to advance and reverse the slides and animations.
Practice
1) Summarizing Multiple Columns
We can use iteration to apply the same summary function to many columns at once without repetitive typing. Let’s use the iris dataset (from {datasets}) to find the median of all numeric columns for each species.
- Use the appropriate summarizing and iteration functions to calculate the median for all numeric columns automatically.
- Group your summary by the
Speciescolumn.
Answer key
library(tidyverse) data("iris", package = "datasets") iris |> summarize( .by = Species, across(.cols = where(is.numeric), .fns = median) ) ## Species Sepal.Length Sepal.Width Petal.Length Petal.Width ## 1 setosa 5.0 3.4 1.50 0.2 ## 2 versicolor 5.9 2.8 4.35 1.3 ## 3 virginica 6.5 3.0 5.55 2.0
2) Inline Operations with Anonymous Functions
Anonymous functions let us apply custom mathematical transformations without having to formally save and name a new function. Let’s use the penguins dataset (from {datasets})to convert several measurements from millimeters to centimeters.
- Use the appropriate mutation and iteration functions to target the
bill_len,bill_dep, andflipper_lencolumns. - Apply an anonymous function to these columns that divides their values by 10.
- Select your modified columns along with the
speciescolumn to view the results.
Answer key
library(tidyverse) data("penguins", package = "datasets") penguins |> as_tibble() |> mutate( across( .cols = c(bill_len, bill_dep, flipper_len), .fns = \(x) x / 10 ) ) |> select(species, bill_len, bill_dep, flipper_len) ## # A tibble: 344 × 4 ## species bill_len bill_dep flipper_len ## <fct> <dbl> <dbl> <dbl> ## 1 Adelie 3.91 1.87 18.1 ## 2 Adelie 3.95 1.74 18.6 ## 3 Adelie 4.03 1.8 19.5 ## 4 Adelie NA NA NA ## 5 Adelie 3.67 1.93 19.3 ## 6 Adelie 3.93 2.06 19 ## 7 Adelie 3.89 1.78 18.1 ## 8 Adelie 3.92 1.96 19.5 ## 9 Adelie 3.41 1.81 19.3 ## 10 Adelie 4.2 2.02 19 ## # ℹ 334 more rows
3) Iterating over Lists of Complex Objects
While across() is the best tool for iterating over columns inside a single dataset, map() shines when working with lists of complex objects like multiple data frames. Let’s practice by doing a quick sanity check on the size of several built-in datasets at once.
- Create a list named
my_datasetscontaining themtcars,iris, andToothGrowthdatasets (all from {datasets}). - Use the
set_names()function to name the elements of your list “mtcars”, “iris”, and “ToothGrowth” so your final output is easily readable. - Use
map_int()to iterate over your list and return an integer vector. - Set the
.fargument to thenrowfunction to extract the number of rows for each dataset.
Answer key
library(tidyverse) data("mtcars", "iris", "ToothGrowth", package = "datasets") my_datasets <- list(mtcars, iris, ToothGrowth) |> set_names(c("mtcars", "iris", "ToothGrowth")) map_int(.x = my_datasets, .f = nrow) ## mtcars iris ToothGrowth ## 32 150 60