library(tidyverse)
library(nycflights13)
AE 10: Writing functions
Suggested answers
Packages
We will use the following packages in this application exercise.
- tidyverse: For data import, wrangling, and visualization.
- nycflights13: For data sets.
Vector function: fizzbuzz
Fizz buzz is a children’s game that teaches about division. Players take turns counting incrementally, replacing any number divisible by three with the word “fizz” and any number divisible by five with the word “buzz”.
We will write a vector function that helps the user play fizzbuzz by calculating the correct response for any possible combination of divisors.
Function requirements
The function you write should adhere to the following requirements:
- Three arguments/inputs
nums
: A vector of “integers”1div1
: An integer value (default value is 3)div2
: An integer value (default value is 5)
- Output: A vector of characters
- If the number is divisible by
div1
, return"Fizz"
. - If the number is divisible by
div2
, return"Buzz"
. - If the number is divisible by
div1
anddiv2
, return"FizzBuzz"
. - Otherwise, return the number as a character.
- If the number is divisible by
We have not yet covered explicit iterative operations. There is no need to use a for
loop, apply()
or map()
functions, or any other explicit iteration. Instead, use your existing knowledge of vectorized operations and functions to write the function.
%%
is modular division. It returns the remainder left over after the division, rather than a floating point number.
5 / 3
[1] 1.666667
5 %% 3
[1] 2
<- function(nums, div1 = 3L, div2 = 5L) {
fizzbuzz # case_when() makes a lot of sense here
case_when(
# perform most stringent test first, otherwise will get incorrect result
%% div1 == 0 & nums %% div2 == 0 ~ "FizzBuzz",
nums %% div1 == 0 ~ "Fizz",
nums %% div2 == 0 ~ "Buzz",
nums .default = as.character(nums)
) }
Test the function
Test your function to ensure it produces the correct results.
- Create a sequence of integers between 1 and 30
- Use the function to calculate the fizzbuzz response for each number and the default rules (divisors 3 and 5). Store the output as a vector object.
- Use the function to calculate the fizzbuzz response for each number and the divisors 3 and 4. Store the output as a vector object.
- Repeat your tests, but this time store the results as columns in a data frame along with the original value. Write the operation using
mutate()
.
# test on a vector of numbers
<- 1:30
test_nums
# output is a character vector
fizzbuzz(nums = test_nums)
[1] "1" "2" "Fizz" "4" "Buzz" "Fizz"
[7] "7" "8" "Fizz" "Buzz" "11" "Fizz"
[13] "13" "14" "FizzBuzz" "16" "17" "Fizz"
[19] "19" "Buzz" "Fizz" "22" "23" "Fizz"
[25] "Buzz" "26" "Fizz" "28" "29" "FizzBuzz"
fizzbuzz(nums = test_nums, div2 = 4L)
[1] "1" "2" "Fizz" "Buzz" "5" "Fizz"
[7] "7" "Buzz" "Fizz" "10" "11" "FizzBuzz"
[13] "13" "14" "Fizz" "Buzz" "17" "Fizz"
[19] "19" "Buzz" "Fizz" "22" "23" "FizzBuzz"
[25] "25" "26" "Fizz" "Buzz" "29" "Fizz"
# implement function within a data frame using mutate()
tibble(nums = test_nums) |>
mutate(
fizzbuzz_3_5 = fizzbuzz(nums = nums),
fizzbuzz_3_4 = fizzbuzz(nums = nums, div2 = 4L)
)
# A tibble: 30 × 3
nums fizzbuzz_3_5 fizzbuzz_3_4
<int> <chr> <chr>
1 1 1 1
2 2 2 2
3 3 Fizz Fizz
4 4 4 Buzz
5 5 Buzz 5
6 6 Fizz Fizz
7 7 7 7
8 8 8 Buzz
9 9 Fizz Fizz
10 10 Buzz 10
# ℹ 20 more rows
Data frame functions
nycflights13 is an R package that contains several data tables containing information about all flights that departed from NYC (e.g. EWR, JFK and LGA) to destinations in the United States, Puerto Rico, and the American Virgin Islands) in 2013. In total it includes 336,776 flights.
Use the datasets from nycflights13 to write the following functions.
Find all flights that were cancelled (i.e. is.na(arr_time)
) or delayed by more than an hour
<- function(df = NULL) {
filter_severe |>
df filter(is.na(arr_time) | arr_delay > 60)
}
|> filter_severe() flights
# A tibble: 36,502 × 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 1 811 630 101 1047 830
2 2013 1 1 848 1835 853 1001 1950
3 2013 1 1 957 733 144 1056 853
4 2013 1 1 1114 900 134 1447 1222
5 2013 1 1 1120 944 96 1331 1213
6 2013 1 1 1255 1200 55 1451 1330
7 2013 1 1 1301 1150 71 1518 1345
8 2013 1 1 1337 1220 77 1649 1531
9 2013 1 1 1342 1320 22 1617 1504
10 2013 1 1 1400 1250 70 1645 1502
# ℹ 36,492 more rows
# ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
# tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
# hour <dbl>, minute <dbl>, time_hour <dttm>
Count the number of cancelled flights and the number of flights delayed by more than an hour
<- function(df = NULL) {
summarize_severe |>
df summarize(
n_cancelled = sum(is.na(arr_time)),
n_delayed = sum(arr_delay > 60, na.rm = TRUE)
)
}
|> group_by(dest) |> summarize_severe() flights
# A tibble: 105 × 3
dest n_cancelled n_delayed
<chr> <int> <int>
1 ABQ 0 25
2 ACK 0 12
3 ALB 21 59
4 ANC 0 0
5 ATL 342 1433
6 AUS 22 219
7 AVL 12 21
8 BDL 31 41
9 BGR 17 44
10 BHM 28 46
# ℹ 95 more rows
Find all flights that were cancelled or delayed by more than a user supplied number of hours
<- function(df = NULL, hours = 1) {
filter_severe |>
df filter(is.na(arr_time) | arr_delay > hours * 60)
}
|> filter_severe(hours = 2) flights
# A tibble: 18,747 × 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <int> <dbl> <int> <int>
1 2013 1 1 811 630 101 1047 830
2 2013 1 1 848 1835 853 1001 1950
3 2013 1 1 957 733 144 1056 853
4 2013 1 1 1114 900 134 1447 1222
5 2013 1 1 1505 1310 115 1638 1431
6 2013 1 1 1525 1340 105 1831 1626
7 2013 1 1 1549 1445 64 1912 1656
8 2013 1 1 1558 1359 119 1718 1515
9 2013 1 1 1732 1630 62 2028 1825
10 2013 1 1 1803 1620 103 2008 1750
# ℹ 18,737 more rows
# ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
# tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
# hour <dbl>, minute <dbl>, time_hour <dttm>
Summarize the weather to compute the minimum, mean, and maximum, of a user supplied variable
<- function(df = NULL, var = NULL) {
summarize_weather |>
df summarize(
min = min({{ var }}, na.rm = TRUE),
mean = mean({{ var }}, na.rm = TRUE),
max = max({{ var }}, na.rm = TRUE)
)
}
|> summarize_weather(temp) weather
# A tibble: 1 × 3
min mean max
<dbl> <dbl> <dbl>
1 10.9 55.3 100.
Convert the user supplied variable that uses clock time (e.g., dep_time
, arr_time
, etc.) into a decimal time (i.e. hours + (minutes / 60))
%/%
is integer division. It returns the quotient of the division, rather than a floating point number.
5 / 3
[1] 1.666667
5 %/% 3
[1] 1
<- function(df = NULL, var = NULL) {
standardize_time |>
df mutate(
:= {{ var }} %/% 100 + {{ var }} %% 100 / 60
{{ var }}
)
}
|> standardize_time(sched_dep_time) flights
# A tibble: 336,776 × 19
year month day dep_time sched_dep_time dep_delay arr_time sched_arr_time
<int> <int> <int> <int> <dbl> <dbl> <int> <int>
1 2013 1 1 517 5.25 2 830 819
2 2013 1 1 533 5.48 4 850 830
3 2013 1 1 542 5.67 2 923 850
4 2013 1 1 544 5.75 -1 1004 1022
5 2013 1 1 554 6 -6 812 837
6 2013 1 1 554 5.97 -4 740 728
7 2013 1 1 555 6 -5 913 854
8 2013 1 1 557 6 -3 709 723
9 2013 1 1 557 6 -3 838 846
10 2013 1 1 558 6 -2 753 745
# ℹ 336,766 more rows
# ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
# tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
# hour <dbl>, minute <dbl>, time_hour <dttm>
Acknowledgments
- Data frame function exercises are drawn from R for Data Science
::session_info() sessioninfo
─ Session info ───────────────────────────────────────────────────────────────
setting value
version R version 4.3.2 (2023-10-31)
os macOS Ventura 13.5.2
system aarch64, darwin20
ui X11
language (EN)
collate en_US.UTF-8
ctype en_US.UTF-8
tz America/New_York
date 2024-03-07
pandoc 3.1.1 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/ (via rmarkdown)
─ Packages ───────────────────────────────────────────────────────────────────
package * version date (UTC) lib source
cli 3.6.2 2023-12-11 [1] CRAN (R 4.3.1)
colorspace 2.1-0 2023-01-23 [1] CRAN (R 4.3.0)
digest 0.6.34 2024-01-11 [1] CRAN (R 4.3.1)
dplyr * 1.1.4 2023-11-17 [1] CRAN (R 4.3.1)
evaluate 0.23 2023-11-01 [1] CRAN (R 4.3.1)
fansi 1.0.6 2023-12-08 [1] CRAN (R 4.3.1)
fastmap 1.1.1 2023-02-24 [1] CRAN (R 4.3.0)
forcats * 1.0.0 2023-01-29 [1] CRAN (R 4.3.0)
generics 0.1.3 2022-07-05 [1] CRAN (R 4.3.0)
ggplot2 * 3.4.4 2023-10-12 [1] CRAN (R 4.3.1)
glue 1.7.0 2024-01-09 [1] CRAN (R 4.3.1)
gtable 0.3.4 2023-08-21 [1] CRAN (R 4.3.0)
here 1.0.1 2020-12-13 [1] CRAN (R 4.3.0)
hms 1.1.3 2023-03-21 [1] CRAN (R 4.3.0)
htmltools 0.5.7 2023-11-03 [1] CRAN (R 4.3.1)
htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.3.1)
jsonlite 1.8.8 2023-12-04 [1] CRAN (R 4.3.1)
knitr 1.45 2023-10-30 [1] CRAN (R 4.3.1)
lifecycle 1.0.4 2023-11-07 [1] CRAN (R 4.3.1)
lubridate * 1.9.3 2023-09-27 [1] CRAN (R 4.3.1)
magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.3.0)
munsell 0.5.0 2018-06-12 [1] CRAN (R 4.3.0)
nycflights13 * 1.0.2 2021-04-12 [1] CRAN (R 4.3.0)
pillar 1.9.0 2023-03-22 [1] CRAN (R 4.3.0)
pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.3.0)
purrr * 1.0.2 2023-08-10 [1] CRAN (R 4.3.0)
R6 2.5.1 2021-08-19 [1] CRAN (R 4.3.0)
readr * 2.1.5 2024-01-10 [1] CRAN (R 4.3.1)
rlang 1.1.3 2024-01-10 [1] CRAN (R 4.3.1)
rmarkdown 2.25 2023-09-18 [1] CRAN (R 4.3.1)
rprojroot 2.0.4 2023-11-05 [1] CRAN (R 4.3.1)
rstudioapi 0.15.0 2023-07-07 [1] CRAN (R 4.3.0)
scales 1.2.1 2024-01-18 [1] Github (r-lib/scales@c8eb772)
sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.3.0)
stringi 1.8.3 2023-12-11 [1] CRAN (R 4.3.1)
stringr * 1.5.1 2023-11-14 [1] CRAN (R 4.3.1)
tibble * 3.2.1 2023-03-20 [1] CRAN (R 4.3.0)
tidyr * 1.3.0 2023-01-24 [1] CRAN (R 4.3.0)
tidyselect 1.2.0 2022-10-10 [1] CRAN (R 4.3.0)
tidyverse * 2.0.0 2023-02-22 [1] CRAN (R 4.3.0)
timechange 0.2.0 2023-01-11 [1] CRAN (R 4.3.0)
tzdb 0.4.0 2023-05-12 [1] CRAN (R 4.3.0)
utf8 1.2.4 2023-10-22 [1] CRAN (R 4.3.1)
vctrs 0.6.5 2023-12-01 [1] CRAN (R 4.3.1)
withr 2.5.2 2023-10-30 [1] CRAN (R 4.3.1)
xfun 0.41 2023-11-01 [1] CRAN (R 4.3.1)
yaml 2.3.8 2023-12-11 [1] CRAN (R 4.3.1)
[1] /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library
──────────────────────────────────────────────────────────────────────────────
Footnotes
You can interpret this literally as an integer type in R, but here I use “integer” in a mathematical sense. The function should work for any numeric type, including integers, doubles, and complex numbers, as long as the value represents a whole number.↩︎