├── .gitignore ├── 01-map.R ├── 02-typed-functions.R ├── 03-challenges.R ├── 04-map2.R ├── 05-purrr-list-columns.R ├── 06-other-features.R ├── LICENSE.md ├── README.md ├── challenges ├── 01-mtcars.R ├── 02-word_count.R ├── 03-starwars.R ├── 04-weather.R ├── 05-swapi.R └── word_count_files │ ├── geospatial-README.md │ ├── nass-README.md │ └── purrr-workshop-README.md ├── code ├── neiss.R ├── star_wars_tbl.R └── swapi.R ├── data ├── neiss_by_day.rda ├── planet_lookup.rda ├── swapi.rda └── weather │ ├── dec8.csv │ └── plots │ └── dec8.png ├── purrr_workshop.Rproj ├── slides.pdf ├── slides_with_transitions.pdf └── useR-abstract.md /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | _hide 6 | *.key -------------------------------------------------------------------------------- /01-map.R: -------------------------------------------------------------------------------- 1 | library(purrr) 2 | 3 | library(repurrrsive) 4 | # includes objects: sw_films, sw_people, sw_vehicles, 5 | # sw_starships, sw_planets & sw_species 6 | 7 | # How many elements are in sw_people? 8 | length(sw_people) 9 | 10 | # Who is the first person in sw_people? 11 | sw_people[[1]] # Luke! 12 | 13 | # A list inside a list 14 | sw_people[1] 15 | 16 | # A list, dropped one level of heirachy 17 | sw_people[[1]] 18 | 19 | # How many starships has each character been on? 20 | map(sw_people, ~ length(.x$starships)) 21 | 22 | # For later 23 | planet_lookup <- map_chr(sw_planets, "name") %>% 24 | set_names(map_chr(sw_planets, "url")) 25 | planet_lookup 26 | save(planet_lookup, file = "data/planet_lookup.rda", compress = FALSE) 27 | -------------------------------------------------------------------------------- /02-typed-functions.R: -------------------------------------------------------------------------------- 1 | library(purrr) 2 | library(repurrrsive) 3 | 4 | # Typed functions 5 | sw_people <- sw_people %>% set_names(map_chr(sw_people, "name")) 6 | 7 | # How many starships has each character been in? 8 | map(sw_people, ~ length(.x[["starships"]])) 9 | 10 | # What color is each characters hair? 11 | map(sw_people, ~ .x[["hair_color"]]) 12 | 13 | # Is the character male? 14 | map(sw_people, ~ .x[["gender"]] == "male") 15 | 16 | # How heavy is each character? 17 | map(sw_people, ~ .x[["mass"]]) 18 | 19 | 20 | 21 | 22 | # Solutions --------------------------------------------------------------- 23 | 24 | # How many starships has each character been in? 25 | map_int(sw_people, ~ length(.x[["starships"]])) 26 | 27 | # What color is each characters hair? 28 | map_chr(sw_people, ~ .x[["hair_color"]]) 29 | 30 | # Is the character male? 31 | map_lgl(sw_people, ~ .x[["gender"]] == "male") 32 | 33 | # How heavy is each character? 34 | map_dbl(sw_people, ~ .x[["mass"]]) 35 | # Doesn't work...because we get a string back 36 | map(sw_people, ~ .x[["mass"]]) 37 | 38 | # A little risky 39 | map_dbl(sw_people, ~ as.numeric(.x[["mass"]])) 40 | 41 | # Probably want something like: 42 | map_chr(sw_people, ~ .x[["mass"]]) %>% 43 | readr::parse_number(na = "unknown") 44 | 45 | -------------------------------------------------------------------------------- /03-challenges.R: -------------------------------------------------------------------------------- 1 | # Star wars challenges 2 | library(purrr) 3 | library(repurrrsive) 4 | 5 | # Which film (see films) has the most characters? 6 | map(sw_films, "characters") %>% 7 | map_int(length) %>% 8 | set_names(map_chr(sw_films, "title")) %>% 9 | sort() 10 | 11 | # Which species has the most possible eye colors? 12 | sw_species[[1]]$eye_colors 13 | 14 | map_chr(sw_species, "eye_colors") %>% 15 | strsplit(", ") %>% 16 | map_int(length) %>% 17 | set_names(map_chr(sw_species, "name")) 18 | # this is lazy, what about n/a and unknown? 19 | 20 | # Which planets do we know the least about? 21 | # For one, entry 61 22 | map_lgl(sw_planets[[61]], ~ "unknown" %in% .x) %>% 23 | sum() 24 | 25 | # For all 26 | map_int(sw_planets, 27 | ~ map_lgl(.x, ~ "unknown" %in% .x) %>% sum()) %>% 28 | set_names(map_chr(sw_planets, "name")) %>% 29 | sort(decreasing = TRUE) 30 | -------------------------------------------------------------------------------- /04-map2.R: -------------------------------------------------------------------------------- 1 | library(repurrrsive) 2 | gap_split_small <- gap_split[1:10] 3 | countries <- names(gap_split_small) 4 | 5 | # For each country create a ggplot of Life Expectancy through time 6 | # with a title 7 | 8 | # For one country 9 | ggplot(gap_split_small[[1]], aes(year, lifeExp)) + 10 | geom_line() + 11 | labs(title = countries[[1]]) 12 | 13 | # For all countries 14 | plots <- map2(gap_split_small, countries, 15 | ~ ggplot(.x, aes(year, lifeExp)) + 16 | geom_line() + 17 | labs(title = .y)) 18 | 19 | plots[[1]] 20 | # Display all plots 21 | walk(plots, print) # this might take awhile 22 | 23 | # Save all plots 24 | walk2(.x = plots, .y = countries, 25 | ~ ggsave(filename = paste0(.y, ".pdf"), plot = .x)) 26 | 27 | # Argh! I didn't want all those pictures in this directory, 28 | # remove them all 29 | file.remove(paste0(countries, ".pdf")) 30 | 31 | 32 | -------------------------------------------------------------------------------- /05-purrr-list-columns.R: -------------------------------------------------------------------------------- 1 | # star wars people list to data frame 2 | library(tidyverse) 3 | library(repurrrsive) 4 | 5 | # A useful lookup table ----------------------------------------------- 6 | film_number_lookup <- map_chr(sw_films, "url") %>% 7 | map(~ stringr::str_split_fixed(.x, "/", 7)[, 6]) %>% 8 | as.numeric() %>% 9 | set_names(map_chr(sw_films, "url")) 10 | 11 | # Putting a few vars in a tibble -------------------------------------- 12 | people_tbl <- tibble( 13 | name = sw_people %>% map_chr("name"), 14 | films = sw_people %>% map("films"), 15 | height = sw_people %>% map_chr("height") %>% 16 | readr::parse_number(na = "unknown"), 17 | species = sw_people %>% map_chr("species", .null = NA_character_) 18 | ) 19 | 20 | # Turning parts of our list to a tibble ----------------------------------- 21 | people_tbl$films 22 | 23 | # Use map with mutate to manipulate list columns 24 | people_tbl <- people_tbl %>% 25 | mutate( 26 | film_numbers = map(films, 27 | ~ film_number_lookup[.x]), 28 | n_films = map_int(films, length) 29 | ) 30 | 31 | people_tbl %>% select(name, film_numbers, n_films) 32 | 33 | 34 | # Your Turn --------------------------------------------------------------- 35 | 36 | # Create a new character column that collapses the film numbers in a single string, 37 | # e.g. for Luke " 6, 3, 2, 1, 7" 38 | 39 | people_tbl <- people_tbl %>% 40 | mutate(films_squashed = map_chr(film_numbers, paste, 41 | collapse = ", ")) 42 | 43 | people_tbl %>% select(name, n_films, films_squashed) 44 | 45 | -------------------------------------------------------------------------------- /06-other-features.R: -------------------------------------------------------------------------------- 1 | library(purrr) 2 | library(readr) 3 | 4 | # safely() and transpose() ------------------------------------------------- 5 | 6 | urls <- list( 7 | example = "http://example.org", 8 | asdf = "http://asdfasdasdkfjlda" 9 | ) 10 | 11 | map(urls, read_lines) 12 | 13 | safe_readlines <- safely(readLines) 14 | safe_readlines 15 | 16 | # Use the safe_readLines() function with map(): html 17 | html <- map(urls, safe_readlines) 18 | 19 | # Call str() on html 20 | str(html) 21 | 22 | # Extract the result from the element that was successful 23 | html[["example"]][["result"]] 24 | 25 | # Extract the error from the element that was unsuccessful 26 | html[["asdf"]][["error"]] 27 | 28 | str(transpose(html)) 29 | 30 | # Extract the results: res 31 | res <- transpose(html)[["result"]] 32 | 33 | # Extract the errors: errs 34 | errs <- transpose(html)[["error"]] 35 | 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Creative Commons License
purrr tutorial by Charlotte Wickham is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A purrr tutorial 2 | 3 | This repo hosts the materials for a [purrr](http://purrr.tidyverse.org/) tutorial. The materials currently reflect the version planned for the useR! Brussels, Belgium, July 2017. 4 | 5 | ## Upcoming in-person tutorials 6 | 7 | * [useR!](https://user2017.brussels/) Brussels, Belgium, July 4 2017 8 | * [London R-ladies](https://www.meetup.com/rladies-london/) London, U.K., July 12 2017 9 | 10 | Older versions of the materials, from prior in-person tutorials, are also available: 11 | 12 | * [Cascadia R Conf June 3 2017 (1.75 hours)](https://github.com/cwickham/purrr-tutorial/tree/v0.2) 13 | 14 | * [rstudio::conf Jan 2017 (2.25 hours)](https://github.com/cwickham/purrr-tutorial/tree/v0.1) 15 | 16 | 17 | ## Outline 18 | 19 | Code with a lot of duplication is harder to understand, troubleshoot and maintain. The goal of this tutorial is help you remove duplication in your code by using functions that write `for` loops for you. 20 | 21 | You'll learn to use the functions in the `purrr` package to perform iterative tasks: tasks that look like "for each _____ do _____". 22 | 23 | By the end of the tutorial you'll be writing code that is more readable and easier to update and you'll be ready to solve new iteration problems faster and with fewer mistakes. 24 | 25 | ## Learning Objectives 26 | 27 | By the end of the tutorial, you'll be able to: 28 | 29 | * Move from solving a problem on a single element, to iterating that solution over many elements with `map()`. 30 | * Identify when to use the typed variants of `map()`: `map_lgl()`, `map_int()`, `walk()` etc. 31 | * Iterate over two arguments with `map2()`. 32 | * Leverage `purrr` to get list data into tibbles. 33 | * Use `purrr` to work with list columns in tibbles. 34 | 35 | ## Pre-requisites 36 | 37 | Don't worry if you have never written a `for` loop, used `lapply()`, written your own function or heard of a `tibble`, this tutorial is designed to be accessible to beginners. 38 | 39 | That said, you should be familiar with exploring and subsetting the basic data structures in R including lists and data frames. 40 | 41 | This is a hands-on tutorial, you'll need your laptop with R installed, as well as a few packages: 42 | 43 | ```{r} 44 | install.packages("tidyverse") 45 | 46 | # install.packages("devtools") 47 | devtools::install_github("jennybc/repurrrsive") 48 | ``` 49 | 50 | ## License 51 | 52 | Creative Commons License
This purrr tutorial by Charlotte Wickham is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License. 53 | -------------------------------------------------------------------------------- /challenges/01-mtcars.R: -------------------------------------------------------------------------------- 1 | # Many models challenge 2 | library(purrr) 3 | 4 | ?mtcars 5 | 6 | # Your Task --- 7 | # Fit a regression model, of mpg against disp, for cars 8 | # of the same number of cylinders. Then summarise those 9 | # models using r^2 and the estimated slope. 10 | 11 | # A list of data frames 12 | mtcars_by_cyl <- mtcars %>% split(mtcars$cyl) 13 | mtcars_by_cyl 14 | 15 | # Fit a regression model to each element 16 | 17 | 18 | # Extract R^2 from each regression model 19 | 20 | 21 | # Extract the slope from each regression model -------------------------------------------------------------------------------- /challenges/02-word_count.R: -------------------------------------------------------------------------------- 1 | # Word count challenge 2 | library(purrr) 3 | 4 | # Your task ---- 5 | # Produce a named numeric vector that contains the word counts for all 6 | # files with a *.md extension: 7 | ## geospatial-README.md nass-README.md purrr-workshop-README.md 8 | ## 232 312 216 9 | 10 | # To get you started 11 | files <- dir("challenges/word_count_files", pattern = "*.md", 12 | full.names = TRUE) 13 | files 14 | 15 | ?readr::read_lines 16 | ?stringi::stri_stats_latex 17 | -------------------------------------------------------------------------------- /challenges/03-starwars.R: -------------------------------------------------------------------------------- 1 | # Star Wars challenge 2 | library(purrr) 3 | 4 | # Your task ----- 5 | # There are only 11 instances of a character using a vehicle. 6 | # Your task is to produce text for each of these instances like 7 | # the following: 8 | # "Luke Skywalker used a Snowspeeder" 9 | 10 | # To get you started 11 | load("data/swapi.rds") 12 | 13 | luke <- people[[1]] 14 | 15 | luke[["vehicles"]] 16 | 17 | vehicles[[5]][["url"]] 18 | vehicles[[5]][["name"]] 19 | 20 | 21 | # Extra challenge ----- 22 | # Add the film to your text, e.g. 23 | # "Luke Skywalker used a Snowspeeder in The Empire Strikes Back" -------------------------------------------------------------------------------- /challenges/04-weather.R: -------------------------------------------------------------------------------- 1 | # Weather data challenge 2 | library(purrr) 3 | 4 | # Your task --- 5 | # Below you will find code that downloads, manipulates, plots and saves 6 | # one day, Dec 8, of weather data for Corvallis. Your job is to rewrite 7 | # this to replicate for Dec 6 - Dec 10 8 | 9 | library(tidyverse) 10 | 11 | # Downloading data -------------------------------------------------------- 12 | # note date is part of url 13 | download.file("https://www.wunderground.com/history/airport/KCVO/2016/12/8/DailyHistory.html?format=1", "data/weather/dec8.csv") 14 | 15 | # Read in file ----------------------------------------------------------- 16 | col_types <- c("cnnnnnccccccnc") 17 | 18 | dec8 <- read_csv("data/weather/dec8.csv", skip = 1, na = c("-", "N/A"), 19 | col_types = col_types) 20 | 21 | # Add date columns -------------------------------------------------------- 22 | 23 | dec8 <- mutate(dec8, year = 2016, month = 12, day = 8) 24 | # add add a datetime variable 25 | dec8 <- mutate(dec8, 26 | datetime = as.POSIXct(strptime( 27 | paste(year, month, day, TimePST, sep = " "), "%Y %m %d %I:%M %p"))) 28 | 29 | # Make and save plot ------------------------------------------------------- 30 | 31 | qplot(datetime, TemperatureF, data = dec8, 32 | geom = "line") + 33 | ggtitle("Dec 8") 34 | ggsave("data/weather/plots/dec8.png") 35 | 36 | -------------------------------------------------------------------------------- /challenges/05-swapi.R: -------------------------------------------------------------------------------- 1 | # Star Wars API Challenge 2 | # install_github("ironholds/rwars", ref = "0.5.0") 3 | library(rwars) 4 | library(purrr) 5 | 6 | # Your task ---- 7 | # You should use the rwars package to download all the data, i.e. replicate 8 | # what I did to create the data you've been using. That is, at the end 9 | # you should have a people, species, films, vehicles, starships and planets 10 | # lists. 11 | 12 | # Here's a function I wrote that requests the "next" page of 13 | # results, until there is no next page 14 | get_all <- function(x = NULL, old_data = NULL, fun, ...){ 15 | data <- fun(x, ...) 16 | next_url <- getElement(data, "next") 17 | if(is_null(next_url)) { 18 | return(append(old_data, list(data))) 19 | } else { 20 | get_all(next_url, append(old_data, list(data)), fun, ...) 21 | } 22 | } 23 | 24 | ?get_all_people 25 | 26 | # For example getting all the people results 27 | people <- get_all(fun = get_all_people, parse_result = TRUE) 28 | 29 | listviewer::jsonedit(people, mode = "view") 30 | 31 | entities <- c("species", "people", "films", "vehicles", "starships", 32 | "planets") 33 | -------------------------------------------------------------------------------- /challenges/word_count_files/geospatial-README.md: -------------------------------------------------------------------------------- 1 | # geospatial 2 | 3 | [![Travis-CI Build Status](https://travis-ci.org/cwickham/geospatial.svg?branch=master)](https://travis-ci.org/cwickham/geospatial) 4 | 5 | An R package with the data sets used in the [DataCamp](www.datacamp.com) course [Working with Geospatial Data in R](https://www.datacamp.com/courses/working-with-geospatial-data-in-r). 6 | 7 | ## Installation 8 | 9 | `geospatial` isn't on CRAN but you can get it with: 10 | 11 | ```R 12 | # install.packages("devtools") 13 | devtools::install_github("cwickham/geospatial") 14 | ``` 15 | 16 | ## Usage 17 | 18 | Datasets in this package have the same names as those in the course. For example, 19 | to replicate the very first plot in the first video: 20 | 21 | ```R 22 | library(geospatial) 23 | library(ggplot2) 24 | ?sales # Chapter 1 sales data 25 | head(sales) 26 | 27 | ggplot(sales, aes(lon, lat)) + 28 | geom_point() 29 | ``` 30 | 31 | ## Contents 32 | 33 | This package contains the data sets corresponding to each chapter in the course 34 | as listed below. 35 | 36 | Chapter 1: 37 | 38 | * `sales` - point data on sales in Corvallis 39 | * `ward_sales` - sales summarized at the ward level 40 | * `preds` - a grid of predictions over Corvallis 41 | 42 | Chapter 2: 43 | 44 | * `countries_sp` 45 | * `countries_spdf` 46 | * `tiny_countries_spdf` 47 | 48 | Chapter 3: 49 | 50 | * `pop` 51 | * `pop_by_age` 52 | * `prop_by_age` 53 | * `migration` 54 | * `land_cover` 55 | 56 | Chapter 4: 57 | 58 | * `neighborhoods` 59 | * `nyc_income` 60 | * `water` 61 | * `income_grid` 62 | 63 | ## Citations 64 | 65 | If you use the data in this package please cite both this package and 66 | the original data source as listed in the relevant help file. 67 | 68 | ## Data generation 69 | 70 | If you are interested in how the data sets were constructed you'll find the 71 | scripts in the `data-raw` directory. 72 | -------------------------------------------------------------------------------- /challenges/word_count_files/nass-README.md: -------------------------------------------------------------------------------- 1 | # nass 2 | 3 | Data from: http://www.nhtsa.gov/Data/National-Automotive-Sampling-System-(NASS) 4 | 5 | In development: **Currently only CDS from 2015** 6 | 7 | ## Crashworthiness Data System (CDS) 8 | http://www.nhtsa.gov/Data/National-Automotive-Sampling-System-(NASS)/NASS-Crashworthiness-Data-System 9 | 10 | > NASS CDS has detailed data on a representative, random sample of thousands of minor, serious, and fatal crashes. Field research teams located at Primary Sampling Units (PSU's) across the country study about 5,000 crashes a year involving passenger cars, light trucks, vans, and utility vehicles. Trained crash investigators obtain data from crash sites, studying evidence such as skid marks, fluid spills, broken glass, and bent guard rails. They locate the vehicles involved, photograph them, measure the crash damage, and identify interior locations that were struck by the occupants. These researchers follow up on their on-site investigations by interviewing crash victims and reviewing medical records to determine the nature and severity of injuries. 11 | 12 | ## General Estimates System (GES) 13 | http://www.nhtsa.gov/Data/National-Automotive-Sampling-System-(NASS)/NASS-General-Estimates-System 14 | 15 | > In order for a crash to be eligible for the GES sample a police accident report (PAR) must be completed, it must involve at least one motor vehicle traveling on a traffic way, and the result must be property damage, injury, or death. 16 | > 17 | > These accident reports are chosen from 60 areas that reflect the geography, roadway mileage, population, and traffic density of the U.S. GES data collectors make weekly visits to approximately 400 police jurisdictions in the 60 areas across the United States, where they randomly sample about 50,000 PARs each year. The collectors obtain copies of the PARs and send them to a central contractor for coding. No other data are collected beyond the selected PARs. 18 | 19 | ## To Do List 20 | 21 | * Tidy `cds_acc_desc` to have one record per case (e.g. concatenate multi-row description) 22 | * Improve documentation 23 | * Add more years of CDS data 24 | * Add GES data 25 | 26 | -------------------------------------------------------------------------------- /challenges/word_count_files/purrr-workshop-README.md: -------------------------------------------------------------------------------- 1 | # Abstract 2 | 3 | Happy R users purrr: using functional programming to solve iteration problems 4 | 5 | Code with a lot of duplication is harder to understand, troubleshoot and maintain. The goal of this tutorial is help you remove duplication in your code with two strategies: writing your own functions and using functions that write `for` loops for you. 6 | 7 | We'll assume you already know the mechanics of defining a function but give you some tips on when and how to write a function emphasizing that good functions aren't just correct, they are also understandable. Then we'll talk about using functions as inputs to other functions, a key idea in functional programming. In the rest of the tutorial you'll learn to use the functions in the `purrr` package that remove code that is duplicated across for loops. By the end of the tutorial you'll be writing code that is more readable and easier to update and you'll be ready to solve new iteration problems faster and with fewer mistakes. 8 | 9 | # Prerequisites 10 | 11 | * You know how to define your own function, and have written a few. 12 | * You know how to write a for loop. 13 | * You have seen `sapply()` and `lapply()`, but it's OK if you don't use them (or don't know why you should use them). 14 | -------------------------------------------------------------------------------- /code/neiss.R: -------------------------------------------------------------------------------- 1 | library(neiss) 2 | library(tidyverse) 3 | library(lubridate) 4 | 5 | 6 | # Add time vars ----------------------------------------- 7 | 8 | injuries <- injuries %>% mutate( 9 | wday = factor(wday(trmt_date, label = TRUE)), 10 | month = factor(month(trmt_date, label = TRUE)) 11 | ) 12 | 13 | # most indicated products ------------------------------------------------- 14 | 15 | # can use products df to look up 16 | product_lookup <- products$title %>% set_names(products$code) 17 | 18 | product_counts <- table(c(injuries$prod1, injuries$prod2)) 19 | # at least 10000 occurances 20 | common_codes <- names(product_counts[product_counts > 50000]) 21 | common_names <- product_lookup[common_codes] 22 | 23 | 24 | # split by common products ------------------------------------------------- 25 | # SLOW! probably find a better way, summarise first? 26 | by_prod <- map(common_codes, ~ filter(injuries, prod1 == .x | prod2 == .x)) 27 | 28 | # summarise to day counts 29 | per_day <- map(by_prod, 30 | ~ .x %>% group_by(trmt_date, month, wday) %>% summarise(count = n())) 31 | 32 | per_day <- set_names(per_day, common_codes) 33 | save(per_day, common_codes, common_names, file = "data/neiss_by_day.rda", 34 | compress = TRUE) 35 | 36 | -------------------------------------------------------------------------------- /code/star_wars_tbl.R: -------------------------------------------------------------------------------- 1 | # star wars people list to data frame 2 | library(purrr) 3 | library(tibble) 4 | library(dplyr) 5 | 6 | load("data/swapi.rda") 7 | 8 | # Luke has all fields 9 | names(people[[1]]) 10 | 11 | # Putting it all in a tibble 12 | people_tbl <- tibble( 13 | name = people %>% map_chr("name"), 14 | height = people %>% map_chr("height") %>% readr::parse_number(na = "unknown"), 15 | mass = people %>% map_chr("mass") %>% readr::parse_number(na = "unknown"), 16 | hair_color = people %>% map_chr("hair_color"), 17 | skin_color = people %>% map_chr("skin_color"), 18 | eye_color = people %>% map_chr("eye_color"), 19 | birth_year = people %>% map_chr("birth_year"), 20 | gender = people %>% map_chr("gender"), 21 | homeworld = people %>% map_chr("homeworld"), 22 | films = people %>% map("films"), 23 | species = people %>% map_chr("species", .null = NA_character_), 24 | vehicles = people %>% map("vehicles"), 25 | starships = people %>% map("starships"), 26 | created = people %>% map_chr("created") %>% readr::parse_datetime(), 27 | edited = people %>% map_chr("edited") %>% readr::parse_datetime(), 28 | url = people %>% map_chr("url") 29 | ) 30 | 31 | 32 | # Some useful lookup tables ----------------------------------------------- 33 | 34 | film_lookup <- map_chr(films, "title") %>% set_names(map_chr(films, "url")) 35 | film_number_lookup <- stringr::str_split_fixed(names(film_lookup), "/", 7)[, 6] %>% 36 | as.numeric() %>% 37 | set_names(map_chr(films, "url")) 38 | 39 | 40 | # use map() on a list column ---------------------------------------------- 41 | 42 | # finding film info for each person 43 | people_tbl <- people_tbl %>% 44 | mutate( 45 | n_films = map_int(films, length), 46 | film_names = map(films, ~ film_lookup[.x]), 47 | film_numbers = map(films, ~ film_number_lookup[.x]) 48 | ) 49 | 50 | people_tbl %>% select(n_films, film_names) 51 | people_tbl$film_names 52 | 53 | # Your Turn --------------------------------------------------------------- 54 | 55 | # Create a new character column that collapses the film numbers in a single string, 56 | # e.g. for Luke " 6, 3, 2, 1, 7" 57 | 58 | # Do it for luke 59 | paste(people_tbl$film_numbers[[1]], 60 | collapse = ", ") 61 | 62 | # Do it for all 63 | people_tbl <- people_tbl %>% 64 | mutate(films_squashed = map_chr(film_numbers, 65 | ~ paste(.x, collapse = ", "))) 66 | 67 | people_tbl %>% select(name, films_squashed) 68 | 69 | # In order would be better 70 | people_tbl <- people_tbl %>% 71 | mutate(films_squashed = map_chr(film_numbers, 72 | ~ paste(sort(.x), collapse = ", "))) 73 | 74 | people_tbl %>% select(name, films_squashed) 75 | -------------------------------------------------------------------------------- /code/swapi.R: -------------------------------------------------------------------------------- 1 | ## http://swapi.co/ 2 | # install.packages("rwars") 3 | library(rwars) 4 | library(purrr) 5 | 6 | # recursive function to travel down next pages 7 | get_all <- function(x = NULL, old_data = NULL, fun, ...){ 8 | data <- fun(x, ...) 9 | next_url <- getElement(data, "next") 10 | if(is_null(next_url)) { 11 | return(append(old_data, list(data))) 12 | } else { 13 | get_all(next_url, append(old_data, list(data)), fun, ...) 14 | } 15 | } 16 | 17 | entities <- c("species", "people", "films", "vehicles", "starships", 18 | "planets") 19 | entity_funs <- map(paste0("get_all_", entities), match.fun) 20 | 21 | all_ents <- map(entity_funs, ~ get_all(fun = .x, parse_result = TRUE)) 22 | 23 | all_ents_res <- map(all_ents, 24 | ~ transpose(.x)[["results"]] %>% flatten()) 25 | 26 | map2(entities, all_ents, ~ assign(.x, .y, envir = globalenv())) 27 | 28 | save(list = entities, file = "data/swapi.rda", compress = FALSE) 29 | -------------------------------------------------------------------------------- /data/neiss_by_day.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwickham/purrr-tutorial/98e9a5913535dbbabcf1889ebf2cc716f66a7b41/data/neiss_by_day.rda -------------------------------------------------------------------------------- /data/planet_lookup.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwickham/purrr-tutorial/98e9a5913535dbbabcf1889ebf2cc716f66a7b41/data/planet_lookup.rda -------------------------------------------------------------------------------- /data/swapi.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwickham/purrr-tutorial/98e9a5913535dbbabcf1889ebf2cc716f66a7b41/data/swapi.rda -------------------------------------------------------------------------------- /data/weather/dec8.csv: -------------------------------------------------------------------------------- 1 | 2 | TimePST,TemperatureF,Dew PointF,Humidity,Sea Level PressureIn,VisibilityMPH,Wind Direction,Wind SpeedMPH,Gust SpeedMPH,PrecipitationIn,Events,Conditions,WindDirDegrees,DateUTC
3 | 12:15 AM,28.4,24.8,86,30.05,10.0,NNW,6.9,-,N/A,,Unknown,330,2016-12-08 08:15:00
4 | 12:35 AM,28.4,24.8,86,30.04,10.0,NW,4.6,-,N/A,,Unknown,310,2016-12-08 08:35:00
5 | 12:55 AM,28.4,24.8,86,30.04,10.0,WNW,6.9,-,N/A,,Unknown,300,2016-12-08 08:55:00
6 | 1:15 AM,28.4,24.8,86,30.04,10.0,NW,5.8,-,N/A,,Unknown,310,2016-12-08 09:15:00
7 | 1:35 AM,28.4,24.8,86,30.03,10.0,NW,5.8,-,N/A,,Unknown,310,2016-12-08 09:35:00
8 | 1:55 AM,28.4,24.8,86,30.02,10.0,WNW,5.8,-,N/A,,Unknown,300,2016-12-08 09:55:00
9 | 2:15 AM,30.2,24.8,80,30.01,10.0,WNW,4.6,-,N/A,,Unknown,300,2016-12-08 10:15:00
10 | 2:35 AM,28.4,24.8,86,30.00,10.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-08 10:35:00
11 | 2:55 AM,28.4,24.8,86,30.00,10.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-08 10:55:00
12 | 3:15 AM,28.4,24.8,86,29.99,10.0,West,3.5,-,N/A,,Unknown,280,2016-12-08 11:15:00
13 | 3:35 AM,30.2,24.8,80,29.98,10.0,NW,4.6,-,N/A,,Unknown,320,2016-12-08 11:35:00
14 | 3:55 AM,30.2,26.6,86,29.97,10.0,WNW,3.5,-,N/A,,Unknown,290,2016-12-08 11:55:00
15 | 4:15 AM,30.2,26.6,86,29.97,10.0,NW,4.6,-,N/A,,Unknown,310,2016-12-08 12:15:00
16 | 4:35 AM,30.2,24.8,80,29.96,10.0,NW,5.8,-,N/A,,Unknown,320,2016-12-08 12:35:00
17 | 4:55 AM,30.2,26.6,86,29.96,10.0,WNW,4.6,-,N/A,,Unknown,300,2016-12-08 12:55:00
18 | 5:15 AM,30.2,26.6,86,29.95,10.0,WNW,5.8,-,N/A,,Unknown,300,2016-12-08 13:15:00
19 | 5:35 AM,30.2,26.6,86,29.94,10.0,NW,3.5,-,N/A,,Unknown,320,2016-12-08 13:35:00
20 | 5:55 AM,30.2,26.6,86,29.94,10.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-08 13:55:00
21 | 6:15 AM,30.2,24.8,80,29.94,10.0,NW,3.5,-,N/A,,Unknown,320,2016-12-08 14:15:00
22 | 6:35 AM,30.2,26.6,86,29.94,10.0,WNW,3.5,-,N/A,,Unknown,290,2016-12-08 14:35:00
23 | 6:55 AM,30.2,26.6,86,29.94,10.0,NW,4.6,-,N/A,,Unknown,320,2016-12-08 14:55:00
24 | 7:15 AM,30.2,26.6,86,29.94,7.0,NW,4.6,-,N/A,,Unknown,320,2016-12-08 15:15:00
25 | 7:35 AM,30.2,26.6,86,29.93,9.0,NW,3.5,-,N/A,,Unknown,320,2016-12-08 15:35:00
26 | 7:55 AM,30.2,26.6,86,29.93,5.0,North,5.8,-,N/A,,Unknown,360,2016-12-08 15:55:00
27 | 8:15 AM,32.0,28.4,87,29.92,3.0,North,5.8,-,N/A,,Unknown,360,2016-12-08 16:15:00
28 | 8:35 AM,32.0,28.4,87,29.92,2.5,North,5.8,-,0.01,,Unknown,10,2016-12-08 16:35:00
29 | 8:55 AM,32.0,28.4,87,29.91,1.5,North,3.5,-,0.02,,Unknown,360,2016-12-08 16:55:00
30 | 9:15 AM,32.0,28.4,87,29.91,2.0,North,3.5,-,0.06,,Unknown,350,2016-12-08 17:15:00
31 | 9:35 AM,33.8,30.2,87,29.90,2.5,North,3.5,-,0.09,,Unknown,360,2016-12-08 17:35:00
32 | 9:55 AM,33.8,30.2,87,29.90,2.0,North,4.6,-,0.13,,Unknown,360,2016-12-08 17:55:00
33 | 10:15 AM,33.8,30.2,87,29.90,1.2,Calm,Calm,-,0.04,,Unknown,0,2016-12-08 18:15:00
34 | 10:35 AM,33.8,30.2,87,29.88,1.5,NNE,5.8,-,0.08,,Unknown,30,2016-12-08 18:35:00
35 | 10:55 AM,33.8,30.2,87,29.88,3.0,NNE,6.9,-,0.10,,Unknown,20,2016-12-08 18:55:00
36 | 11:15 AM,33.8,30.2,87,29.88,2.0,NNE,5.8,-,0.01,,Unknown,20,2016-12-08 19:15:00
37 | 11:35 AM,33.8,30.2,87,29.87,1.8,North,8.1,-,0.06,,Unknown,10,2016-12-08 19:35:00
38 | 11:55 AM,33.8,30.2,87,29.87,2.5,NNE,4.6,-,0.10,,Unknown,20,2016-12-08 19:55:00
39 | 12:15 PM,33.8,30.2,87,29.86,2.5,NNE,4.6,-,0.02,,Unknown,20,2016-12-08 20:15:00
40 | 12:35 PM,33.8,30.2,87,29.85,1.8,North,4.6,-,0.03,,Unknown,10,2016-12-08 20:35:00
41 | 12:55 PM,33.8,32.0,93,29.84,2.0,Calm,Calm,-,0.03,,Unknown,0,2016-12-08 20:55:00
42 | 1:15 PM,35.6,32.0,87,29.84,1.8,Calm,Calm,-,0.01,,Unknown,0,2016-12-08 21:15:00
43 | 1:35 PM,35.6,32.0,87,29.83,1.8,Calm,Calm,-,0.02,,Unknown,0,2016-12-08 21:35:00
44 | 1:55 PM,35.6,32.0,87,29.83,1.8,NE,3.5,-,0.04,,Unknown,40,2016-12-08 21:55:00
45 | 2:15 PM,35.6,33.8,93,29.83,1.8,Calm,Calm,-,0.01,,Unknown,0,2016-12-08 22:15:00
46 | 2:35 PM,35.6,32.0,87,29.83,2.0,ENE,3.5,-,0.03,,Unknown,60,2016-12-08 22:35:00
47 | 2:55 PM,35.6,32.0,87,29.83,2.5,East,4.6,-,0.05,,Unknown,90,2016-12-08 22:55:00
48 | 3:15 PM,35.6,32.0,87,29.83,2.0,ENE,6.9,-,0.01,,Unknown,60,2016-12-08 23:15:00
49 | 3:35 PM,35.6,30.2,81,29.83,1.5,NE,6.9,-,0.02,,Unknown,50,2016-12-08 23:35:00
50 | 3:55 PM,35.6,30.2,81,29.84,4.0,NE,6.9,-,0.05,,Unknown,50,2016-12-08 23:55:00
51 | 4:15 PM,35.6,30.2,81,29.84,2.5,ENE,6.9,-,0.02,,Unknown,60,2016-12-09 00:15:00
52 | 4:35 PM,35.6,30.2,81,29.85,5.0,ENE,4.6,-,0.03,,Unknown,70,2016-12-09 00:35:00
53 | 4:55 PM,35.6,30.2,81,29.86,4.0,East,5.8,-,0.03,,Unknown,90,2016-12-09 00:55:00
54 | 5:15 PM,35.6,30.2,81,29.86,2.5,East,4.6,-,N/A,,Unknown,80,2016-12-09 01:15:00
55 | 5:35 PM,35.6,30.2,81,29.87,2.5,East,5.8,-,0.01,,Unknown,90,2016-12-09 01:35:00
56 | 5:55 PM,35.6,30.2,81,29.88,4.0,East,5.8,-,0.01,,Unknown,90,2016-12-09 01:55:00
57 | 6:15 PM,35.6,30.2,81,29.88,3.0,Calm,Calm,-,0.01,,Unknown,0,2016-12-09 02:15:00
58 | 6:35 PM,35.6,30.2,81,29.89,3.0,Calm,Calm,-,0.01,,Unknown,0,2016-12-09 02:35:00
59 | 6:55 PM,35.6,30.2,81,29.89,3.0,Calm,Calm,-,0.01,,Unknown,0,2016-12-09 02:55:00
60 | 7:15 PM,35.6,30.2,81,29.91,4.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-09 03:15:00
61 | 7:35 PM,35.6,30.2,81,29.91,4.0,ENE,3.5,-,0.01,,Unknown,70,2016-12-09 03:35:00
62 | 7:55 PM,35.6,30.2,81,29.93,7.0,ENE,3.5,-,0.01,,Unknown,60,2016-12-09 03:55:00
63 | 8:15 PM,35.6,32.0,87,29.94,4.0,East,3.5,-,N/A,,Unknown,80,2016-12-09 04:15:00
64 | 8:35 PM,35.6,30.2,81,29.95,10.0,East,4.6,-,N/A,,Unknown,80,2016-12-09 04:35:00
65 | 8:55 PM,35.6,30.2,81,29.96,10.0,East,3.5,-,N/A,,Unknown,90,2016-12-09 04:55:00
66 | 9:15 PM,35.6,30.2,81,29.98,10.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-09 05:15:00
67 | 9:35 PM,35.6,30.2,81,29.97,10.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-09 05:35:00
68 | 9:55 PM,35.6,30.2,81,29.98,10.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-09 05:55:00
69 | 10:15 PM,35.6,30.2,81,29.99,10.0,ENE,3.5,-,N/A,,Unknown,60,2016-12-09 06:15:00
70 | 10:35 PM,35.6,32.0,87,29.99,10.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-09 06:35:00
71 | 10:55 PM,35.6,30.2,81,30.01,10.0,Calm,Calm,-,N/A,,Unknown,0,2016-12-09 06:55:00
72 | 11:15 PM,35.6,32.0,87,30.02,5.0,Calm,Calm,-,0.01,,Unknown,0,2016-12-09 07:15:00
73 | 11:35 PM,35.6,32.0,87,30.01,10.0,Calm,Calm,-,0.01,,Unknown,0,2016-12-09 07:35:00
74 | 11:55 PM,35.6,32.0,87,30.02,10.0,Calm,Calm,-,0.01,,Unknown,0,2016-12-09 07:55:00
75 | -------------------------------------------------------------------------------- /data/weather/plots/dec8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwickham/purrr-tutorial/98e9a5913535dbbabcf1889ebf2cc716f66a7b41/data/weather/plots/dec8.png -------------------------------------------------------------------------------- /purrr_workshop.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwickham/purrr-tutorial/98e9a5913535dbbabcf1889ebf2cc716f66a7b41/slides.pdf -------------------------------------------------------------------------------- /slides_with_transitions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cwickham/purrr-tutorial/98e9a5913535dbbabcf1889ebf2cc716f66a7b41/slides_with_transitions.pdf -------------------------------------------------------------------------------- /useR-abstract.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Solving iteration problems with purrr" 3 | author: | 4 | | Charlotte Wickham^1^ 5 | | 6 | | 1. Oregon State University 7 | output: html_document 8 | --- 9 | 10 | **Keywords**: purrr, tidyverse, tibble 11 | 12 | ## Outline 13 | 14 | Code with a lot of duplication is harder to understand, troubleshoot and maintain. The goal of this tutorial is help you remove duplication in your code by using functions that write `for` loops for you. 15 | 16 | You'll learn to use the functions in the `purrr` package to perform iterative tasks: tasks that look like "for each _____ do _____". 17 | 18 | By the end of the tutorial you'll be writing code that is more readable and easier to update and you'll be ready to solve new iteration problems faster and with fewer mistakes. 19 | 20 | ## Learning Objectives 21 | 22 | By the end of the tutorial, you'll be able to: 23 | 24 | * Move from solving a problem on a single element, to iterating that solution over many elements with `map()`. 25 | * Identify when to use the typed variants of `map()`: `map_lgl()`, `map_int()`, `walk()` etc. 26 | * Iterate over two arguments with `map2()`. 27 | * Leverage `purrr` to get list data into tibbles. 28 | * Use `purrr` to work with list columns in tibbles. 29 | 30 | ## Pre-requisites 31 | 32 | Don't worry if you have never written a `for` loop, used `lapply()`, written your own function or heard of a `tibble`, this tutorial is designed to be accessible to beginners. 33 | 34 | That said, you should be familiar with exploring and subsetting the basic data structures in R including lists and data frames. 35 | 36 | This is a hands-on tutorial, you'll need your laptop with R and `purrr` installed. Check http://bit.ly/purrr-user17 closer to the date for more details. 37 | 38 | ## Instructor Info 39 | 40 | Charlotte Wickham 41 | 42 | * [cwickham@gmail.com](cwickham@gmail.com) 43 | * [cwick.co.nz](http://www.cwick.co.nz) 44 | * @[cvwickham](http://www.twitter.com/cvwickham) 45 | 46 | --------------------------------------------------------------------------------