├── .gitignore ├── .nojekyll ├── LICENSE ├── README.html ├── README.md ├── _site.yml ├── about.Rmd ├── about.html ├── bk00_vectors-and-lists.Rmd ├── bk00_vectors-and-lists.html ├── bk01_base-functions.Rmd ├── bk01_base-functions.html ├── by-nc.png ├── ex20_bulk-gmail.Rmd ├── ex20_bulk-gmail.html ├── ex22_github-issues-pull-requests.Rmd ├── ex22_github-issues-pull-requests.html ├── ex24_xml-wrangling.Rmd ├── ex24_xml-wrangling.html ├── ex26_ny-food-market-json.Rmd ├── ex26_ny-food-market-json.html ├── foodMarkets ├── nys.dbf ├── nys.prj ├── nys.sbn ├── nys.sbx ├── nys.shp ├── nys.shp.xml ├── nys.shx └── retail_food_markets.json ├── footer.html ├── index.Rmd ├── index.html ├── ls00_inspect-explore.Rmd ├── ls00_inspect-explore.html ├── ls01_map-name-position-shortcuts.Rmd ├── ls01_map-name-position-shortcuts.html ├── ls02_map-extraction-advanced.Rmd ├── ls02_map-extraction-advanced.html ├── ls03_map-function-syntax.Rmd ├── ls03_map-function-syntax.html ├── ls08_trump-tweets.Rmd ├── ls08_trump-tweets.html ├── ls08_trump-tweets_cache └── html │ ├── __packages │ ├── unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.RData │ ├── unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.rdb │ └── unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.rdx ├── ls12_different-sized-samples.Rmd ├── ls12_different-sized-samples.html ├── ls13_list-columns.Rmd ├── ls13_list-columns.html ├── ls13_list-columns_files └── figure-html │ ├── unnamed-chunk-22-1.png │ └── unnamed-chunk-23-1.png ├── more-resources.Rmd ├── more-resources.html ├── pt00_gotchas.Rmd ├── pt00_gotchas.html ├── purrr-slides-trump-tweets.png ├── purrr-tutorial.Rproj ├── site_libs ├── bootstrap-3.3.5 │ ├── css │ │ ├── bootstrap-theme.css │ │ ├── bootstrap-theme.css.map │ │ ├── bootstrap-theme.min.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── cerulean.min.css │ │ ├── cosmo.min.css │ │ ├── darkly.min.css │ │ ├── flatly.min.css │ │ ├── fonts │ │ │ ├── Lato.ttf │ │ │ ├── LatoBold.ttf │ │ │ ├── LatoItalic.ttf │ │ │ ├── NewsCycle.ttf │ │ │ ├── NewsCycleBold.ttf │ │ │ ├── OpenSans.ttf │ │ │ ├── OpenSansBold.ttf │ │ │ ├── OpenSansBoldItalic.ttf │ │ │ ├── OpenSansItalic.ttf │ │ │ ├── OpenSansLight.ttf │ │ │ ├── OpenSansLightItalic.ttf │ │ │ ├── Raleway.ttf │ │ │ ├── RalewayBold.ttf │ │ │ ├── Roboto.ttf │ │ │ ├── RobotoBold.ttf │ │ │ ├── RobotoLight.ttf │ │ │ ├── RobotoMedium.ttf │ │ │ ├── SourceSansPro.ttf │ │ │ ├── SourceSansProBold.ttf │ │ │ ├── SourceSansProItalic.ttf │ │ │ ├── SourceSansProLight.ttf │ │ │ └── Ubuntu.ttf │ │ ├── journal.min.css │ │ ├── lumen.min.css │ │ ├── paper.min.css │ │ ├── readable.min.css │ │ ├── sandstone.min.css │ │ ├── simplex.min.css │ │ ├── spacelab.min.css │ │ ├── united.min.css │ │ └── yeti.min.css │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── js │ │ ├── bootstrap.js │ │ ├── bootstrap.min.js │ │ └── npm.js │ └── shim │ │ ├── html5shiv.min.js │ │ └── respond.min.js ├── font-awesome-5.1.0 │ ├── css │ │ ├── all.css │ │ └── v4-shims.css │ └── webfonts │ │ ├── fa-brands-400.eot │ │ ├── fa-brands-400.svg │ │ ├── fa-brands-400.ttf │ │ ├── fa-brands-400.woff │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.eot │ │ ├── fa-regular-400.svg │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff │ │ ├── fa-regular-400.woff2 │ │ ├── fa-solid-900.eot │ │ ├── fa-solid-900.svg │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff │ │ └── fa-solid-900.woff2 ├── highlightjs-9.12.0 │ ├── default.css │ ├── highlight.js │ └── textmate.css ├── htmlwidgets-1.2 │ └── htmlwidgets.js ├── htmlwidgets-1.3 │ └── htmlwidgets.js ├── jquery-1.11.3 │ └── jquery.min.js ├── jqueryui-1.11.4 │ ├── README │ ├── images │ │ ├── ui-icons_444444_256x240.png │ │ ├── ui-icons_555555_256x240.png │ │ ├── ui-icons_777620_256x240.png │ │ ├── ui-icons_777777_256x240.png │ │ ├── ui-icons_cc0000_256x240.png │ │ └── ui-icons_ffffff_256x240.png │ ├── index.html │ ├── jquery-ui.css │ ├── jquery-ui.js │ ├── jquery-ui.min.css │ ├── jquery-ui.min.js │ ├── jquery-ui.structure.css │ ├── jquery-ui.structure.min.css │ ├── jquery-ui.theme.css │ └── jquery-ui.theme.min.css ├── jsonedit-binding-2.0.0 │ ├── core-js │ │ ├── LICENSE │ │ ├── dist │ │ │ └── shim.min.js │ │ └── package.json │ ├── jsonedit.js │ ├── jsonedit.yaml │ ├── jsoneditor │ │ ├── LICENSE │ │ ├── NOTICE │ │ ├── README.md │ │ └── dist │ │ │ ├── img │ │ │ ├── jsoneditor-icons.png │ │ │ └── jsoneditor-icons.svg │ │ │ ├── jsoneditor.min.css │ │ │ └── jsoneditor.min.js │ ├── reactjson.js │ ├── reactjson.yaml │ └── reactjson │ │ ├── LICENSE │ │ ├── README.md │ │ ├── dist │ │ └── main.js │ │ └── package.json ├── jsonedit-binding-2.1.0 │ └── jsonedit.js ├── jsoneditor-5.25.6 │ ├── img │ │ ├── jsoneditor-icons.png │ │ └── jsoneditor-icons.svg │ ├── jsoneditor.min.css │ └── jsoneditor.min.js ├── jsoneditor-5.5.5 │ ├── img │ │ ├── jsoneditor-icons.png │ │ └── jsoneditor-icons.svg │ ├── jsoneditor.min.css │ └── jsoneditor.min.js ├── navigation-1.1 │ ├── codefolding.js │ ├── sourceembed.js │ └── tabsets.js └── tocify-1.9.1 │ ├── jquery.tocify.css │ └── jquery.tocify.js ├── talks.Rmd ├── talks.html ├── talks ├── 2018-09_latinr │ ├── .gitignore │ ├── 00_purrr-motivation.R │ ├── 01_list-exploration.R │ ├── 02_basic-map-workflow.R │ ├── 03_gapminder-walk-map-dfrow.R │ ├── 99_pipe.R │ └── README.md ├── earl-examples.R ├── got-pov.R ├── ice.rds ├── trump-tweets-setup.R ├── trump-tweets.R └── trump-tweets.csv └── trump_tweets_df.rda /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | purrr-slides.key 5 | purrr-slides.pdf 6 | ideas.R 7 | json-to-tibble.Rmd 8 | memory_lane.R 9 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/.nojekyll -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | https://creativecommons.org/licenses/by-nc/4.0/ 2 | -------------------------------------------------------------------------------- /README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | README.utf8.md 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 41 | 42 | 43 | 44 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 100 | 101 | 102 | 171 | 172 | 189 | 190 |
191 | 192 | 193 | 194 | 247 | 248 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 296 | 297 | 303 | 304 | 305 |
306 |

purrr-tutorial

307 |

Source for an R Markdown website for learning how to use purrr:

308 |

https://jennybc.github.io/purrr-tutorial

309 |
310 | 311 |

Creative Commons License

312 | 313 | 314 | 315 |
316 | 317 | 329 | 330 | 331 | 339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purrr-tutorial 2 | 3 | Source for an R Markdown website for learning how to use [purrr](http://purrr.tidyverse.org): 4 | 5 | 6 | -------------------------------------------------------------------------------- /_site.yml: -------------------------------------------------------------------------------- 1 | name: "purrr-tutorial" 2 | output_dir: "." 3 | navbar: 4 | title: "purrr tutorial" 5 | left: 6 | - text: "Lessons and examples" 7 | href: index.html 8 | - text: "More resources" 9 | href: more-resources.html 10 | - text: "Talks" 11 | href: talks.html 12 | right: 13 | - text: "About" 14 | href: about.html 15 | output: 16 | html_document: 17 | theme: flatly 18 | includes: 19 | after_body: footer.html 20 | new_session: true 21 | -------------------------------------------------------------------------------- /about.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "About This Website" 3 | --- 4 | 5 | This is an [R Markdown website](http://rmarkdown.rstudio.com/rmarkdown_websites.html). The source can be found [on GitHub](https://github.com/jennybc/purrr-tutorial) and its home on the web is here: 6 | 7 | 8 | 9 | Who am I? 10 | 11 | * Jenny Bryan 12 | * 13 | * 14 | * 15 | 16 | Materials initially created to support: 17 | 18 | * [2016-08-12 meeting](https://github.com/minisciencegirl/studyGroup/issues/131) of the Vancouver R Study Group. 19 | * [STAT 545](http://stat545.com) Exploratory Data Analysis 20 | * [DSCI 523](http://www.calendar.ubc.ca/vancouver/courses.cfm?page=code&code=DSCI) Data Wrangling, part of the UBC Master of Data Science 21 | 22 | Versions of key packages: 23 | 24 | ```{r} 25 | devtools::session_info(c("purrr", "repurrrsive")) 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /by-nc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/by-nc.png -------------------------------------------------------------------------------- /ex20_bulk-gmail.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Send email via Gmail API" 3 | --- 4 | 5 | ```{r, echo = FALSE} 6 | knitr::opts_chunk$set( 7 | collapse = TRUE, 8 | comment = "#>" 9 | ) 10 | ``` 11 | 12 | Will be based on: 13 | 14 | 15 | 16 | Overview: 17 | 18 | * Data frame #1: email recipients 19 | - one row per email recipient 20 | - email addresses 21 | - names 22 | * Data frame #2: emails (one per piece of student homework, in example) 23 | - one per email to be sent 24 | - variables for MIME parts 25 | * Join these two data frames 26 | * Where does `purrr` come in? 27 | - `pmap(edat, mime)` to create one MIME-formatted email object per row/email 28 | - `safely(gmailr::send_message)` to create a "safe" version of the function that send messages ... so one failure doesn't derail things 29 | - `map(email, safe_send_message)` to send the email 30 | - `transpose()` and `map_lgl()` to inspect message success vs failure 31 | 32 | In the meantime, here's the clean script that is developed there: 33 | 34 | ```{r eval = FALSE} 35 | suppressPackageStartupMessages(library(gmailr)) 36 | suppressPackageStartupMessages(library(dplyr)) 37 | suppressPackageStartupMessages(library(purrr)) 38 | library(readr) 39 | 40 | addresses <- read_csv("addresses.csv") 41 | marks <- read_csv("marks.csv") 42 | my_dat <- left_join(marks, addresses) 43 | 44 | this_hw <- "The Fellowship Of The Ring" 45 | email_sender <- 'Peter Jackson ' # your Gmail address 46 | optional_bcc <- 'Anonymous ' # for me, TA address 47 | body <- "Hi, %s. 48 | 49 | Your mark for %s is %s. 50 | 51 | Thanks for participating in this film! 52 | " 53 | 54 | edat <- my_dat %>% 55 | mutate( 56 | To = sprintf('%s <%s>', name, email), 57 | Bcc = optional_bcc, 58 | From = email_sender, 59 | Subject = sprintf('Mark for %s', this_hw), 60 | body = sprintf(body, name, this_hw, mark)) %>% 61 | select(To, Bcc, From, Subject, body) 62 | edat 63 | write_csv(edat, "composed-emails.csv") 64 | 65 | emails <- edat %>% 66 | map_n(mime) 67 | 68 | ## optional: use if you've created your own client id 69 | use_secret_file("gmailr-tutorial.json") 70 | 71 | safe_send_message <- safely(send_message) 72 | sent_mail <- emails %>% 73 | map(safe_send_message) 74 | 75 | saveRDS(sent_mail, 76 | paste(gsub("\\s+", "_", this_hw), "sent-emails.rds", sep = "_")) 77 | 78 | errors <- sent_mail %>% 79 | transpose() %>% 80 | .$error %>% 81 | map_lgl(Negate(is.null)) 82 | sent_mail[errors] 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /ex20_bulk-gmail.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Send email via Gmail API 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 42 | 43 | 44 | 45 | 71 | 72 | 73 | 74 | 75 | 101 | 102 | 103 | 172 | 173 | 190 | 191 | 192 | 193 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 |
258 | 259 | 260 | 261 | 262 | 292 | 293 | 300 | 301 | 302 |

Will be based on:

303 |

https://github.com/jennybc/send-email-with-r#readme

304 |

Overview:

305 |
    306 |
  • Data frame #1: email recipients 307 |
      308 |
    • one row per email recipient
    • 309 |
    • email addresses
    • 310 |
    • names
    • 311 |
  • 312 |
  • Data frame #2: emails (one per piece of student homework, in example) 313 |
      314 |
    • one per email to be sent
    • 315 |
    • variables for MIME parts
    • 316 |
  • 317 |
  • Join these two data frames
  • 318 |
  • Where does purrr come in? 319 |
      320 |
    • pmap(edat, mime) to create one MIME-formatted email object per row/email
    • 321 |
    • safely(gmailr::send_message) to create a “safe” version of the function that send messages … so one failure doesn’t derail things
    • 322 |
    • map(email, safe_send_message) to send the email
    • 323 |
    • transpose() and map_lgl() to inspect message success vs failure
    • 324 |
  • 325 |
326 |

In the meantime, here’s the clean script that is developed there:

327 |
suppressPackageStartupMessages(library(gmailr))
328 | suppressPackageStartupMessages(library(dplyr))
329 | suppressPackageStartupMessages(library(purrr))
330 | library(readr)
331 | 
332 | addresses <- read_csv("addresses.csv")
333 | marks <- read_csv("marks.csv")
334 | my_dat <- left_join(marks, addresses)
335 | 
336 | this_hw <- "The Fellowship Of The Ring"
337 | email_sender <- 'Peter Jackson <peter@tolkien.example.org>' # your Gmail address
338 | optional_bcc <- 'Anonymous <anon@palantir.example.org>'     # for me, TA address
339 | body <- "Hi, %s.
340 | 
341 | Your mark for %s is %s.
342 | 
343 | Thanks for participating in this film!
344 | "
345 | 
346 | edat <- my_dat %>%
347 |   mutate(
348 |     To = sprintf('%s <%s>', name, email),
349 |     Bcc = optional_bcc,
350 |     From = email_sender,
351 |     Subject = sprintf('Mark for %s', this_hw),
352 |     body = sprintf(body, name, this_hw, mark)) %>%
353 |   select(To, Bcc, From, Subject, body)
354 | edat
355 | write_csv(edat, "composed-emails.csv")
356 | 
357 | emails <- edat %>%
358 |   map_n(mime)
359 | 
360 | ## optional: use if you've created your own client id
361 | use_secret_file("gmailr-tutorial.json")
362 | 
363 | safe_send_message <- safely(send_message)
364 | sent_mail <- emails %>%
365 |   map(safe_send_message)
366 | 
367 | saveRDS(sent_mail,
368 |         paste(gsub("\\s+", "_", this_hw), "sent-emails.rds", sep = "_"))
369 | 
370 | errors <- sent_mail %>%
371 |   transpose() %>%
372 |   .$error %>%
373 |   map_lgl(Negate(is.null))
374 | sent_mail[errors]
375 | 376 | 377 | 378 | 379 | 380 |
381 | 382 | 394 | 395 | 396 | 397 | 408 | 409 | 410 | 411 | 412 | 413 | 421 | 422 | 423 | 424 | -------------------------------------------------------------------------------- /ex22_github-issues-pull-requests.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Analyze GitHub issues and pull requests" 3 | --- 4 | 5 | ```{r, echo = FALSE} 6 | knitr::opts_chunk$set( 7 | collapse = TRUE, 8 | comment = "#>" 9 | ) 10 | ``` 11 | 12 | Will be based on: 13 | 14 | 15 | 16 | Overview: 17 | 18 | * Three examples of working with data from the GitHub API 19 | - API sends JSON but gets converted to R list by the [`gh` package](https://github.com/gaborcsardi/gh) 20 | * Issues for one user 21 | - tabulate open issue by repository 22 | - `map()` is used to iterate over repo 23 | * Pull requests on a repository 24 | - analyze pull requests against the books [R Packages](http://r-pkgs.had.co.nz/) and [Advanced R](http://adv-r.had.co.nz/) 25 | - Are there more PRs for earlier chapters? Do people lose their enthusiasm for reading/correcting later in the book? 26 | - `map_*()` functions used to create data frame from nested list 27 | - `map()` to iterate over PRs and get affected files from the diff 28 | - `unnest()` to go from one row per PR to one row per file modified in a PR 29 | * Issues threads on a repository 30 | - analyze class participation in the STAT 545 Discussion board 31 | - `map_*()` functions used to create data frame from nested list 32 | - `map()` to iterate over issues and retrieve follow up comments 33 | - `walk()` 34 | - `unnest()` to go from one row per issue to one row per comment on an issue 35 | 36 | Several clean scripts are there, as well as expository README. 37 | -------------------------------------------------------------------------------- /ex22_github-issues-pull-requests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Analyze GitHub issues and pull requests 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 42 | 43 | 44 | 45 | 71 | 72 | 73 | 74 | 75 | 101 | 102 | 103 | 172 | 173 | 190 | 191 | 192 | 193 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 |
258 | 259 | 260 | 261 | 262 | 292 | 293 | 300 | 301 | 302 |

Will be based on:

303 |

https://github.com/jennybc/analyze-github-stuff-with-r#readme

304 |

Overview:

305 |
    306 |
  • Three examples of working with data from the GitHub API 307 |
      308 |
    • API sends JSON but gets converted to R list by the gh package
    • 309 |
  • 310 |
  • Issues for one user 311 |
      312 |
    • tabulate open issue by repository
    • 313 |
    • map() is used to iterate over repo
    • 314 |
  • 315 |
  • Pull requests on a repository 316 |
      317 |
    • analyze pull requests against the books R Packages and Advanced R
    • 318 |
    • Are there more PRs for earlier chapters? Do people lose their enthusiasm for reading/correcting later in the book?
    • 319 |
    • map_*() functions used to create data frame from nested list
    • 320 |
    • map() to iterate over PRs and get affected files from the diff
    • 321 |
    • unnest() to go from one row per PR to one row per file modified in a PR
    • 322 |
  • 323 |
  • Issues threads on a repository 324 |
      325 |
    • analyze class participation in the STAT 545 Discussion board
    • 326 |
    • map_*() functions used to create data frame from nested list
    • 327 |
    • map() to iterate over issues and retrieve follow up comments
    • 328 |
    • walk()
    • 329 |
    • unnest() to go from one row per issue to one row per comment on an issue
    • 330 |
  • 331 |
332 |

Several clean scripts are there, as well as expository README.

333 | 334 | 335 | 336 | 337 | 338 |
339 | 340 | 352 | 353 | 354 | 355 | 366 | 367 | 368 | 369 | 370 | 371 | 379 | 380 | 381 | 382 | -------------------------------------------------------------------------------- /ex24_xml-wrangling.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Tame XML from Google Sheets API" 3 | --- 4 | 5 | ```{r, echo = FALSE} 6 | knitr::opts_chunk$set( 7 | collapse = TRUE, 8 | comment = "#>" 9 | ) 10 | ``` 11 | 12 | Will be based on: 13 | 14 | 15 | 16 | Overview: 17 | 18 | * Wrangle XML from the Google Sheets API 19 | * XPath to isolate XML nodesets as elements of a list 20 | - each element contains data for one row of a Google Sheet 21 | - wrapped in great gobs of thoroughly namespaced XML 22 | * Use `map()` on that list with more XPath to retain only nodes from `gsx` namespace 23 | - these nodes contain actual cell data 24 | * Use `map()` at depth 2 to extract sub-node names and text 25 | * `unnest()` to go from one row per Sheet row to one row per Sheet cell 26 | 27 | -------------------------------------------------------------------------------- /ex24_xml-wrangling.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Tame XML from Google Sheets API 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 42 | 43 | 44 | 45 | 71 | 72 | 73 | 74 | 75 | 101 | 102 | 103 | 172 | 173 | 190 | 191 | 192 | 193 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 |
258 | 259 | 260 | 261 | 262 | 292 | 293 | 300 | 301 | 302 |

Will be based on:

303 |

https://github.com/jennybc/manipulate-xml-with-purrr-dplyr-tidyr#readme

304 |

Overview:

305 |
    306 |
  • Wrangle XML from the Google Sheets API
  • 307 |
  • XPath to isolate XML nodesets as elements of a list 308 |
      309 |
    • each element contains data for one row of a Google Sheet
    • 310 |
    • wrapped in great gobs of thoroughly namespaced XML
    • 311 |
  • 312 |
  • Use map() on that list with more XPath to retain only nodes from gsx namespace 313 |
      314 |
    • these nodes contain actual cell data
    • 315 |
  • 316 |
  • Use map() at depth 2 to extract sub-node names and text
  • 317 |
  • unnest() to go from one row per Sheet row to one row per Sheet cell
  • 318 |
319 | 320 | 321 | 322 | 323 | 324 |
325 | 326 | 338 | 339 | 340 | 341 | 352 | 353 | 354 | 355 | 356 | 357 | 365 | 366 | 367 | 368 | -------------------------------------------------------------------------------- /foodMarkets/nys.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/foodMarkets/nys.dbf -------------------------------------------------------------------------------- /foodMarkets/nys.prj: -------------------------------------------------------------------------------- 1 | PROJCS["NAD_1983_UTM_Zone_18N",GEOGCS["GCS_North_American_1983",DATUM["D_North_American_1983",SPHEROID["GRS_1980",6378137.0,298.257222101]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["False_Easting",500000.0],PARAMETER["False_Northing",0.0],PARAMETER["Central_Meridian",-75.0],PARAMETER["Scale_Factor",0.9996],PARAMETER["Latitude_Of_Origin",0.0],UNIT["Meter",1.0]] -------------------------------------------------------------------------------- /foodMarkets/nys.sbn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/foodMarkets/nys.sbn -------------------------------------------------------------------------------- /foodMarkets/nys.sbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/foodMarkets/nys.sbx -------------------------------------------------------------------------------- /foodMarkets/nys.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/foodMarkets/nys.shp -------------------------------------------------------------------------------- /foodMarkets/nys.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/foodMarkets/nys.shx -------------------------------------------------------------------------------- /footer.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Lessons and Examples" 3 | --- 4 | 5 | ```{r setup, include = FALSE, warning = FALSE} 6 | library(tidyverse) 7 | library(stringr) 8 | library(htmltools) 9 | tagList(rmarkdown::html_dependency_font_awesome()) 10 | ``` 11 | 12 | ```{r file-list, include = FALSE, warning = FALSE} 13 | has_all_things <- function(y, things) { 14 | all(match(tolower(things), tolower(y), nomatch = 0) > 0) 15 | } 16 | parse_yaml <- function(file, n = 15) { 17 | rmarkdown:::parse_yaml_front_matter(read_lines(file, n_max = n)) 18 | } 19 | ext_file <- function(file, ext) paste(file, ext, sep = ".") 20 | cat_bullet <- function(x, pattern) { 21 | x <- x %>% 22 | filter(str_detect(basename, pattern)) 23 | cat(x$bullet) 24 | } 25 | 26 | x <- tibble(fls = list.files()) %>% 27 | mutate(basename = tools::file_path_sans_ext(fls), 28 | ext = tools::file_ext(fls)) %>% 29 | group_by(basename) %>% 30 | summarize_all(list) %>% 31 | filter(map_lgl(ext, has_all_things, things = c("Rmd", "html"))) 32 | 33 | x <- x %>% 34 | mutate( 35 | yaml = basename %>% ext_file("Rmd") %>% map(parse_yaml), 36 | title = yaml %>% map_chr("title"), 37 | comment = yaml %>% map_chr("comment", .null = ""), 38 | href = map2(ext_file(basename, "html"), title, ~ a(href = .x, .y)) %>% 39 | map_chr(as.character), 40 | bullet = map2_chr(href, comment, ~ paste("*", .x, .y, "\n")) 41 | ) 42 | ``` 43 | 44 | ### Background basics 45 | 46 | ```{r results = 'asis', echo = FALSE} 47 | cat_bullet(x, "^bk") 48 | ``` 49 | 50 | ### Core purrr lessons 51 | 52 | ```{r results = 'asis', echo = FALSE} 53 | cat_bullet(x, "^ls") 54 | ``` 55 | 56 | ### Worked examples 57 | 58 | ```{r results = 'asis', echo = FALSE} 59 | cat_bullet(x, "^ex") 60 | ``` 61 | 62 | ### Patterns and anti-patterns 63 | 64 | ```{r results = 'asis', echo = FALSE} 65 | cat_bullet(x, "^pt") 66 | ``` 67 | 68 | This work is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-sa/4.0/). 69 | 70 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Lessons and Examples 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 44 | 45 | 46 | 47 | 73 | 74 | 75 | 76 | 77 | 103 | 104 | 105 | 174 | 175 | 192 | 193 | 194 | 195 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 |
260 | 261 | 262 | 263 | 264 | 294 | 295 | 302 | 303 | 304 |
305 |

Background basics

306 | 310 |
311 |
312 |

Core purrr lessons

313 | 323 |
324 |
325 |

Worked examples

326 | 335 |
336 |
337 |

Patterns and anti-patterns

338 | 341 |

This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

342 |
343 | 344 | 345 | 346 | 347 | 348 |
349 | 350 | 362 | 363 | 364 | 365 | 376 | 377 | 378 | 379 | 380 | 381 | 389 | 390 | 391 | 392 | -------------------------------------------------------------------------------- /ls00_inspect-explore.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Explore the example lists: Wes Anderson, Game of Thrones, GitHub" 3 | comment: "*how to get to know a list*" 4 | output: 5 | html_document: 6 | toc: true 7 | toc_float: true 8 | --- 9 | 10 | ```{r, echo = FALSE} 11 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>", error = TRUE) 12 | ``` 13 | 14 | ## Load packages 15 | 16 | Load purrr and repurrrsive, which contains recursive list examples. 17 | 18 | ```{r} 19 | library(purrr) 20 | library(repurrrsive) 21 | ``` 22 | 23 | ## Inspect and explore 24 | 25 | List inspection is very important and also fairly miserable. Before you can apply a function to every element of a list, you'd better understand the list! 26 | 27 | You need to develop a toolkit for list inspection. Be on the look out for: 28 | 29 | * What is the length of the list? 30 | * Are the components homogeneous, i.e. do they have the same overall structure, albeit containing different data? 31 | * Note the length, names, and types of the constituent objects. 32 | 33 | > I have no idea what's in this list or what its structure is! Please send help. 34 | 35 | Understand this is **situation normal**, especially when your list comes from querying a poorly documented API. This is often true even when your list has been created completely within R. How many of us perfectly understand the structure of a fitted linear model object? You just have to embark on a voyage of discovery and figure out what's in there. Happy trails. 36 | 37 | ### Indexing, review 38 | 39 | Remember, there are 3 ways to pull elements out of a list: 40 | 41 | * The `$` operator. Extracts a single element by name. Name can be unquoted, if syntactic. 42 | ```{r} 43 | x <- list(a = "a", b = 2) 44 | x$a 45 | x$b 46 | ``` 47 | * `[[` a.k.a. double square bracket. Extracts a single element by name or position. Name must be quoted, if provided directly. Name or position can also be stored in a variable. 48 | ```{r} 49 | x <- list(a = "a", b = 2) 50 | x[["a"]] 51 | x[[2]] 52 | nm <- "a" 53 | x[[nm]] 54 | i <- 2 55 | x[[i]] 56 | ``` 57 | * `[` a.k.a. single square bracket. Regular vector indexing. For a list input, this always returns a list! 58 | ```{r} 59 | x <- list(a = "a", b = 2) 60 | x["a"] 61 | x[c("a", "b")] 62 | x[c(FALSE, TRUE)] 63 | ``` 64 | 65 | ### `str()` 66 | 67 | `str()` can help with basic list inspection, although it's still rather frustrating. Learn to love the `max.level` and `list.len` arguments. You can use them to keep the output of `str()` down to a manageable volume. 68 | 69 | Once you begin to suspect or trust that your list is homogeneous, i.e. consists of sub-lists with similar structure, it's often a good idea to do an in-depth study of a single element. In general, remember you can combine list inspection via `str(..., list.len = x, max.level = y)` with single `[` and double `[[` square bracket indexing. 70 | 71 | The repurrrsive package provides examples of lists. We explore them below, to lay the groundwork for other lessons, and to demonstrate list inspection strategies. 72 | 73 | ### listviewer and RStudio's Object Explorer 74 | 75 | The RStudio IDE (v1.1 and higher) offers an [Object Explorer](https://blog.rstudio.com/2017/08/22/rstudio-v1-1-preview-object-explorer/) that provides interactive inspection and code generation tools for hierarchical objects, such as lists. You can invoke it via the GUI or in code as `View(YOUR_UGLY_LIST)`. 76 | 77 | However, that won't help you expose list exploration in something like this website. I am using the [listviewer](https://CRAN.R-project.org/package=listviewer) package to do this below. It allows you to expose list exploration in a rendered `.Rmd` document. 78 | To replicate this experience locally, call, e.g., `listviewer::jsonedit(got_chars, mode = "view")`. 79 | 80 | 81 | ```{r} 82 | library(listviewer) 83 | ``` 84 | 85 | ## Wes Anderson color palettes 86 | 87 | `wesanderson` is a simple list containing color palettes from the [wesanderson package](https://cran.r-project.org/package=wesanderson). Each component is a palette, named after a movie, and contains a character vector of colors as hexadecimal triplets. 88 | 89 | ```{r} 90 | str(wesanderson) 91 | ``` 92 | 93 | ### Explore `wesanderson` 94 | 95 | ```{r echo = FALSE} 96 | jsonedit(wesanderson, mode = "view", elementId = "wesanderson") 97 | ``` 98 | 99 | You can get a similar experience in RStudio via `View(wesanderson)`. 100 | 101 | ## Game of Thrones POV characters 102 | 103 | `got_chars` is a list with information on the `r length(got_chars)` point-of-view characters from the first five books in the Song of Ice and Fire series by George R. R. Martin. Retrieved from [An API Of Ice And Fire](https://anapioficeandfire.com). Each component corresponds to one character and contains `r length(got_chars[[1]])` components which are named atomic vectors of various lengths and types. 104 | 105 | ```{r} 106 | str(got_chars, list.len = 3) 107 | str(got_chars[[1]], list.len = 8) 108 | ``` 109 | 110 | ### Explore `got_chars` 111 | 112 | ```{r echo = FALSE} 113 | jsonedit(number_unnamed(got_chars), mode = "view", elementId = "got_chars") 114 | ``` 115 | 116 | You can get a similar experience in RStudio via `View(got_chars)`. 117 | 118 | ## GitHub users and repositories 119 | 120 | `gh_users` is a list with information on 6 GitHub users. `gh_repos` is a nested list, also of length 6, where each component is another list with information on up to 30 of that user's repositories. Retrieved from the [GitHub API](https://developer.github.com/v3/). 121 | 122 | ```{r} 123 | str(gh_users, max.level = 1) 124 | ``` 125 | 126 | ### Explore `gh_users` 127 | 128 | ```{r echo = FALSE} 129 | jsonedit(number_unnamed(gh_users), mode = "view", elementId = "gh_users") 130 | ``` 131 | 132 | You can get a similar experience in RStudio via `View(gh_users)`. 133 | 134 | ### Explore `gh_repos` 135 | 136 | ```{r echo = FALSE} 137 | jsonedit(number_unnamed(gh_repos), mode = "view", elementId = "gh_repos") 138 | ``` 139 | 140 | You can get a similar experience in RStudio via `View(gh_repos)`. 141 | 142 | #### Exercises 143 | 144 | 1. Read the documentation on `str()`. What does `max.level` control? Apply `str()` to `wesanderson` and/or `got_chars` and experiment with `max.level = 0`, `max.level = 1`, and `max.level = 2`. Which will you use in practice with deeply nested lists? 145 | 1. What does the `list.len` argument of `str()` control? What is its default value? Call `str()` on `got_chars` and then on a single component of `got_chars` with `list.len` set to a value much smaller than the default. What range of values do you think you'll use in real life? 146 | 1. Call `str()` on `got_chars`, specifying both `max.level` and `list.len`. 147 | 1. Call `str()` on the first element of `got_chars`, i.e. the first Game of Thrones character. Use what you've learned to pick an appropriate combination of `max.level` and `list.len`. 148 | -------------------------------------------------------------------------------- /ls01_map-name-position-shortcuts.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Introduction to `map()`: extract elements" 3 | comment: "*name and position shortcuts, type-specific and simplifying map*" 4 | output: 5 | html_document: 6 | toc: true 7 | toc_float: true 8 | --- 9 | 10 | ```{r, echo = FALSE} 11 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>", error = TRUE) 12 | ``` 13 | 14 | ```{r} 15 | search() 16 | ``` 17 | 18 | 19 | ## Load packages 20 | 21 | Load purrr and repurrrsive, which contains recursive list examples. 22 | 23 | ```{r} 24 | library(purrr) 25 | library(repurrrsive) 26 | ``` 27 | 28 | ## Vectorized and "list-ized" operations 29 | 30 | This lesson picks up where [the primer on vectors and lists](bk00_vectors-and-lists.html#vectorized_operations) left off. Recall that many operations "just work" in a vectorized fashion in R: 31 | 32 | ```{r} 33 | (3:5) ^ 2 34 | sqrt(c(9, 16, 25)) 35 | ``` 36 | 37 | Through the magic of R, the operations "raise to the power of 2" and "take the square root" were applied to each individual element of the numeric vector input. Someone -- but not you! -- has written a `for()` loop: 38 | 39 | ```{r eval = FALSE} 40 | for (i in 1:n) { 41 | output[[i]] <- f(input[[i]]) 42 | } 43 | ``` 44 | 45 | Automatic vectorization is possible because our input is an atomic vector: the individual atoms are always of length one, always of uniform type. 46 | 47 | What if the input is a list? You have to be more intentional to apply a function `f()` to each element of a list, i.e. to "list-ize" computation. This makes sense because the data structure itself does not guarantee that it makes any sense at all to apply a common function `f()` to each element of the list. You must guarantee that. 48 | 49 | `purrr::map()` is a function for applying a function to each element of a list. The [closest base R function](bk01_base-functions.html) is `lapply()`. Here's how the square root example of the above would look if the input was in a list. 50 | 51 | ```{r} 52 | map(c(9, 16, 25), sqrt) 53 | ``` 54 | 55 | A template for basic `map()` usage: 56 | 57 | ```{r eval = FALSE} 58 | map(YOUR_LIST, YOUR_FUNCTION) 59 | ``` 60 | 61 | Below we explore these useful features of `purrr::map()` and friends: 62 | 63 | * Shortcuts for `YOUR_FUNCTION` when you want to extract list elements by name or position 64 | * Simplify and specify the type of output via `map_chr()`, `map_lgl()`, etc. 65 | 66 | This is where you begin to see the differences between `purrr::map()` and `base::lapply()`. 67 | 68 | ### Name and position shortcuts 69 | 70 | Who are these Game of Thrones characters? 71 | 72 | We want the elements with name "name", so we do this (we restrict to the first few elements purely to conserve space): 73 | 74 | ```{r} 75 | map(got_chars[1:4], "name") 76 | ``` 77 | 78 | We are exploiting one of purrr's most useful features: a shortcut to create a function that extracts an element based on its name. 79 | 80 | A companion shortcut is used if you provide a positive integer to `map()`. This creates a function that extracts an element based on position. 81 | 82 | The 3rd element of each character's list is his or her name and we get them like so: 83 | 84 | ```{r} 85 | map(got_chars[5:8], 3) 86 | ``` 87 | 88 | To recap, here are two shortcuts for making the `.f` function that `map()` will apply: 89 | 90 | * provide "TEXT" to extract the element named "TEXT" 91 | - equivalent to `function(x) x[["TEXT"]]` 92 | * provide `i` to extract the `i`-th element 93 | - equivalent to `function(x) x[[i]]` 94 | 95 | You will frequently see `map()` used together with [the pipe `%>%`](http://r4ds.had.co.nz/pipes.html). These calls produce the same result as the above. 96 | 97 | ```{r eval = FALSE} 98 | got_chars %>% 99 | map("name") 100 | got_chars %>% 101 | map(3) 102 | ``` 103 | 104 | #### Exercises 105 | 106 | 1. Use `names()` to inspect the names of the list elements associated with a single character. What is the index or position of the `playedBy` element? Use the character and position shortcuts to extract the `playedBy` elements for all characters. 107 | 1. What happens if you use the character shortcut with a string that does not appear in the lists' names? 108 | 1. What happens if you use the position shortcut with a number greater than the length of the lists? 109 | 1. What if these shortcuts did not exist? Write a function that takes a list and a string as input and returns the list element that bears the name in the string. Apply this to `got_chars` via `map()`. Do you get the same result as with the shortcut? Reflect on code length and readability. 110 | 1. Write another function that takes a list and an integer as input and returns the list element at that position. Apply this to `got_chars` via `map()`. How does this result and process compare with the shortcut? 111 | 112 | ### Type-specific map 113 | 114 | `map()` always returns a list, even if all the elements have the same flavor and are of length one. But in that case, you might prefer a simpler object: **an atomic vector**. 115 | 116 | If you expect `map()` to return output that can be turned into an atomic vector, it is best to use a type-specific variant of `map()`. This is more efficient than using `map()` to get a list and then simplifying the result in a second step. Also purrr will alert you to any problems, i.e. if one or more inputs has the wrong type or length. This is the [increased rigor about type alluded to in the section about coercion](bk00_vectors-and-lists.html#coercion). 117 | 118 | Our current examples are suitable for demonstrating `map_chr()`, since the requested elements are always character. 119 | 120 | ```{r} 121 | map_chr(got_chars[9:12], "name") 122 | map_chr(got_chars[13:16], 3) 123 | ``` 124 | 125 | Besides `map_chr()`, there are other variants of `map()`, with the target type conveyed by the name: 126 | 127 | * `map_lgl()`, `map_int()`, `map_dbl()` 128 | 129 | #### Exercises 130 | 131 | 1. For each character, the second element is named "id". This is the character's id in the [API Of Ice And Fire](https://anapioficeandfire.com). Use a type-specific form of `map()` and an extraction shortcut to extract these ids into an integer vector. 132 | 1. Use your list inspection strategies to find the list element that is logical. There is one! Use a type-specific form of `map()` and an extraction shortcut to extract these values for all characters into a logical vector. 133 | 134 | ### Extract multiple values 135 | 136 | What if you want to retrieve multiple elements? Such as the character's name and culture? First, recall how we do this with the list for a single user: 137 | 138 | ```{r} 139 | got_chars[[3]][c("name", "culture", "gender", "born")] 140 | ``` 141 | 142 | We use single square bracket indexing and a character vector to index by name. How will we ram this into the `map()` framework? To paraphrase Chambers, ["everything that happens in R is a function call"](http://adv-r.had.co.nz/Functions.html#all-calls) and indexing with `[` is no exception. 143 | 144 | It feels (and maybe looks) weird, but we can map `[` just like any other function. Recall `map()` usage: 145 | 146 | ```{r eval = FALSE} 147 | map(.x, .f, ...) 148 | ``` 149 | 150 | The function `.f` will be `[`. And we finally get to use `...`! This is where we pass the character vector of the names of our desired elements. We inspect the result for two characters. 151 | 152 | ```{r} 153 | x <- map(got_chars, `[`, c("name", "culture", "gender", "born")) 154 | str(x[16:17]) 155 | ``` 156 | 157 | Some people find this ugly and might prefer the `extract()` function from magrittr. 158 | 159 | ```{r} 160 | library(magrittr) 161 | x <- map(got_chars, extract, c("name", "culture", "gender", "born")) 162 | str(x[18:19]) 163 | ``` 164 | 165 | #### Exercises 166 | 167 | 1. Use your list inspection skills to determine the position of the elements named "name", "gender", "culture", "born", and "died". Map `[` or `magrittr::extract()` over users, requesting these elements by position instead of name. 168 | 169 | ### Data frame output 170 | 171 | We just learned how to extract multiple elements per user by mapping `[`. But, since `[` is non-simplifying, each user's elements are returned in a list. And, as it must, `map()` itself returns list. We've traded one recursive list for another recursive list, albeit a slightly less complicated one. 172 | 173 | How can we "stack up" these results row-wise, i.e. one row per user and variables for "name", "gender", etc.? A data frame would be the perfect data structure for this information. 174 | 175 | This is what `map_dfr()` is for. 176 | 177 | ```{r} 178 | map_dfr(got_chars, extract, c("name", "culture", "gender", "id", "born", "alive")) 179 | ``` 180 | 181 | Finally! A data frame! Hallelujah! 182 | 183 | Notice how the variables have been automatically type converted. It's a beautiful thing. Until it's not. When programming, it is safer, but more cumbersome, to explicitly specify type and build your data frame the usual way. 184 | 185 | ```{r} 186 | library(tibble) 187 | got_chars %>% { 188 | tibble( 189 | name = map_chr(., "name"), 190 | culture = map_chr(., "culture"), 191 | gender = map_chr(., "gender"), 192 | id = map_int(., "id"), 193 | born = map_chr(., "born"), 194 | alive = map_lgl(., "alive") 195 | ) 196 | } 197 | ``` 198 | 199 | *Syntax notes: The dot `.` above is the placeholder for the primary input: `got_chars` in this case. The curly braces `{}` surrounding the `tibble()` call prevent `got_chars` from being passed in as the first argument of `tibble()`.* 200 | 201 | #### Exercises 202 | 203 | 1. Use `map_dfr()` to create the same data frame as above, but indexing with a vector of positive integers instead of names. 204 | -------------------------------------------------------------------------------- /ls03_map-function-syntax.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Specifying the function in map() + parallel mapping" 3 | comment: "*all the purrr shortcuts and mapping over multiple lists*" 4 | output: 5 | html_document: 6 | toc: true 7 | toc_float: true 8 | editor_options: 9 | chunk_output_type: console 10 | --- 11 | 12 | ```{r, echo = FALSE} 13 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>") 14 | ``` 15 | 16 | ## Load packages 17 | 18 | Load purrr and repurrrsive, which contains recursive list examples. If you're just jumping here, the example datasets are introduced [elsewhere](https://jennybc.github.io/purrr-tutorial/ls00_inspect-explore.html), including via interactive listviewer widgets. 19 | 20 | ```{r} 21 | library(purrr) 22 | library(repurrrsive) 23 | ``` 24 | 25 | ## `map()` overview 26 | 27 | Recall the usage of purrr's core `map()` function: 28 | 29 | ```{r eval = FALSE} 30 | map(.x, .f, ...) 31 | map(VECTOR_OR_LIST_INPUT, FUNCTION_TO_APPLY, OPTIONAL_OTHER_STUFF) 32 | ``` 33 | 34 | You can provide further arguments via `...`, but you don't have to. The above expands to something like this: 35 | 36 | ```{r eval = FALSE} 37 | res <- vector(mode = "list", length = length(.x)) 38 | res[[1]] <- .f(.x[[1]], ...) 39 | res[[2]] <- .f(.x[[2]], ...) 40 | ## and so on, until the end of .x 41 | res 42 | ``` 43 | 44 | Note that any additional arguments provided via `...` are used "as is" in each call to `.f`. In other words, `map()` is not vectorized over these arguments. If you need that, check out `map2()`, `pmap()`, and friends. 45 | 46 | ## `map()` function specification 47 | 48 | One of the main reasons to use purrr is the flexible and concise syntax for specifying `.f`, the function to apply. 49 | 50 | The [shortcuts for extracting by name and position](ls01_map-name-position-shortcuts.html) are covered thoroughly elsewhere and won't be repeated here. 51 | 52 | We demonstrate three more ways to specify general `.f`: 53 | 54 | * an existing function 55 | * an anonymous function, defined on-the-fly, as usual 56 | * a formula: this is unique to purrr and provides a very concise way to define an anonymous function 57 | 58 | We work with the Game of Thrones character list, `got_chars`. Each character can have aliases, which are stored in a vector in each character's component. We pull out the aliases for three characters to use as our demo. 59 | 60 | ```{r} 61 | aliases <- set_names(map(got_chars, "aliases"), map_chr(got_chars, "name")) 62 | (aliases <- aliases[c("Theon Greyjoy", "Asha Greyjoy", "Brienne of Tarth")]) 63 | ``` 64 | 65 | ### Existing function 66 | 67 | Use a pre-existing function. Or, as here, define one ourselves, which gives a nice way to build-in our specification for the `collapse` argument. 68 | 69 | ```{r} 70 | my_fun <- function(x) paste(x, collapse = " | ") 71 | map(aliases, my_fun) 72 | ``` 73 | 74 | ### Anonymous function, conventional 75 | 76 | Define an anonymous function on-the-fly, in the conventional way. Here we put our desired value for the `collapse` argument into the function defintion itself. 77 | 78 | ```{r} 79 | map(aliases, function(x) paste(x, collapse = " | ")) 80 | ``` 81 | 82 | Alternatively you can simply name the function and provide `collapse` via `...`. 83 | 84 | ```{r} 85 | map(aliases, paste, collapse = " | ") 86 | ``` 87 | 88 | ### Anonymous function, formula 89 | 90 | We saved possibly the best for last. 91 | 92 | purrr provides a very concise way to define an anonymous function: as a formula. This should start with the `~` symbol and then look like a typical top-level expression, as you might write in a script. Use `.x` to refer to the input, i.e. an individual element of the primary vector or list. 93 | 94 | ```{r} 95 | map(aliases, ~ paste(.x, collapse = " | ")) 96 | ``` 97 | 98 | ### Workflow advice 99 | 100 | It's rare to write these calls perfect and whole the first time. You should probably pilot your idea on a single element. Then drop your **proven, working logic** into one of the above templates. When things aren't working as expected, consider: have you tried to skip too many steps? Pull out an example, get everything to work there, check it on another example, then scale back up again. 101 | 102 | A development process for the above might look like this: 103 | 104 | ```{r} 105 | (a <- map(got_chars, "aliases")[[19]]) ## OOPS! NULL --> a useless example 106 | (a <- map(got_chars, "aliases")[[16]]) ## ok good 107 | paste(a, sep = " | ") ## OOPS! not what I want 108 | paste(a, collapse = " | ") ## ok good 109 | got_chars[15:17] %>% ## I am a programming god 110 | map("aliases") %>% 111 | map_chr(paste, collapse = " | ") 112 | ``` 113 | 114 | ## List to data frame 115 | 116 | Since we've simplifed the aliases to a single string for each character, we can hold them as an atomic character vector instead of as list. Wouldn't it be nice to put that in a data frame, with another variable holding the names? The `enframe()` function from tibble takes a named vector and promotes the names to a proper variable. 117 | 118 | From the top, using four characters to conserve space: 119 | 120 | ```{r} 121 | aliases <- set_names(map(got_chars, "aliases"), map_chr(got_chars, "name")) 122 | map_chr(aliases[c(3, 10, 20, 24)], ~ paste(.x, collapse = " | ")) %>% 123 | tibble::enframe(value = "aliases") 124 | ``` 125 | 126 | Alternative way to get same data frame 127 | 128 | ```{r} 129 | tibble::tibble( 130 | name = map_chr(got_chars, "name"), 131 | aliases = got_chars %>% 132 | map("aliases") %>% 133 | map_chr(~ paste(.x, collapse = " | ")) 134 | ) %>% 135 | dplyr::slice(c(3, 10, 20, 24)) 136 | ``` 137 | 138 | This is a very typical workflow: take an unwieldy nested list and, via extraction and/or simplification, produce a more approachable data frame. 139 | 140 | ## Recap 141 | 142 | These are the different ways to specify the function `.f` in the `map()`-type functions in purrr. 143 | 144 | ```{r eval = FALSE} 145 | map(aliases, function(x) paste(x, collapse = "|")) 146 | map(aliases, paste, collapse = "|") 147 | map(aliases, ~ paste(.x, collapse = " | ")) 148 | ``` 149 | 150 | ### Exercises 151 | 152 | Each character can be allied with one of the [houses](https://anapioficeandfire.com/Documentation#houses) (or with several or with zero). These allegiances are held as a vector in each character's component. 153 | 154 | 1. Create a list `allegiances` that holds the characters' house affiliations. 155 | 1. Create a character vector `nms` that holds the characters' names. 156 | 1. Apply the names in `nms` to the `allegiances` list via `set_names`. 157 | 1. Re-use the code from above to collapse each character's vector of allegiances down to a string. 158 | 1. We said that any elements passed via `...` would be used "as is". Specifically they are not used in a vectorized fashion. What happens if you pass `collapse = c(" | ", " * ")`? Why is that? 159 | 160 | ## Parallel map 161 | 162 | ### `map2()` 163 | 164 | What if you need to map a function over two vectors or lists in parallel? 165 | 166 | You can use `map2()` for that. Here is the usage: 167 | 168 | ```{r eval = FALSE} 169 | map2(.x, .y, .f, ...) 170 | map(INPUT_ONE, INPUT_TWO, FUNCTION_TO_APPLY, OPTIONAL_OTHER_STUFF) 171 | ``` 172 | 173 | `map2()` has all the type-specific friends you would expect: `map2_chr()`, `map2_lgl()`, etc. 174 | 175 | How will we specify the function to apply? All the usual options are open. 176 | 177 | What shall our example be? Each character has a free text field, giving the date and possibly location of his or her birth. Let's paste that together with the character's name to get a sentence. 178 | 179 | First, obtain the two inputs. 180 | 181 | ```{r} 182 | nms <- got_chars %>% 183 | map_chr("name") 184 | birth <- got_chars %>% 185 | map_chr("born") 186 | ``` 187 | 188 | Now map over both with an existing function, defined by us. 189 | 190 | ```{r} 191 | my_fun <- function(x, y) paste(x, "was born", y) 192 | map2_chr(nms, birth, my_fun) %>% head() 193 | ``` 194 | 195 | Anonymous function, conventional form. 196 | 197 | ```{r} 198 | map2_chr(nms, birth, function(x, y) paste(x, "was born", y)) %>% head() 199 | ``` 200 | 201 | Anonymous function via formula. Use `.x` and `.y` to refer to the individual elements of the two primary inputs. 202 | 203 | ```{r} 204 | map2_chr(nms[16:18], birth[16:18], ~ paste(.x, "was born", .y)) %>% tail() 205 | ``` 206 | 207 | ### `pmap()` 208 | 209 | What if you need to map a function over **two or more** vectors or lists in parallel? 210 | 211 | You can use `pmap()` for that. Here is the usage: 212 | 213 | ```{r eval = FALSE} 214 | pmap(.l, .f, ...) 215 | map(LIST_OF_INPUT_LISTS, FUNCTION_TO_APPLY, OPTIONAL_OTHER_STUFF) 216 | ``` 217 | 218 | *words* 219 | 220 | ```{r} 221 | df <- got_chars %>% { 222 | tibble::tibble( 223 | name = map_chr(., "name"), 224 | aliases = map(., "aliases"), 225 | allegiances = map(., "allegiances") 226 | ) 227 | } 228 | my_fun <- function(name, aliases, allegiances) { 229 | paste(name, "has", length(aliases), "aliases and", 230 | length(allegiances), "allegiances") 231 | } 232 | df %>% 233 | pmap_chr(my_fun) %>% 234 | tail() 235 | ``` 236 | 237 | -------------------------------------------------------------------------------- /ls08_trump-tweets_cache/html/__packages: -------------------------------------------------------------------------------- 1 | base 2 | methods 3 | datasets 4 | utils 5 | grDevices 6 | graphics 7 | stats 8 | purrr 9 | dplyr 10 | tibble 11 | bindrcpp 12 | -------------------------------------------------------------------------------- /ls08_trump-tweets_cache/html/unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/ls08_trump-tweets_cache/html/unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.RData -------------------------------------------------------------------------------- /ls08_trump-tweets_cache/html/unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/ls08_trump-tweets_cache/html/unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.rdb -------------------------------------------------------------------------------- /ls08_trump-tweets_cache/html/unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.rdx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/ls08_trump-tweets_cache/html/unnamed-chunk-30_aaaed2621e0089d001dc8d28f1bd1b10.rdx -------------------------------------------------------------------------------- /ls12_different-sized-samples.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Sample from groups, n varies by group" 3 | --- 4 | 5 | ```{r, echo = FALSE} 6 | knitr::opts_chunk$set( 7 | collapse = TRUE, 8 | comment = "#>" 9 | ) 10 | ``` 11 | 12 | A challenge [tweeted by Hilary 13 | Parker](https://twitter.com/hspter/status/739886244692295680), paraphrased: 14 | 15 | > How do you sample from groups, with a different sample size for each group? 16 | 17 | Illustrated with the iris data. 18 | 19 | * `Species` = groups. 20 | * Sample from the 3 `Species` with 3 different sample sizes. 21 | 22 | How fits the template: 23 | 24 | DRAW A SAMPLE for each PAIR OF (SPECIES DATA, SPECIES SAMPLE SIZE) 25 | 26 | How to prepare the data? I need a data frame with 27 | 28 | * One row per `Species` 29 | * A variable of `Species`-specific sample sizes 30 | * A variable of "Species data", whatever that means. 31 | - Actually we know what that is: a variable of `Species`-specific data frames. A list-column! 32 | 33 | We need a *nested data frame*. 34 | 35 | ```{r} 36 | suppressMessages(library(dplyr)) 37 | library(purrr) 38 | library(tidyr) 39 | set.seed(4561) 40 | 41 | (nested_iris <- iris %>% 42 | group_by(Species) %>% # prep for work by Species 43 | nest() %>% # --> one row per Species 44 | ungroup() %>% 45 | mutate(n = c(2, 5, 3))) # add sample sizes 46 | ``` 47 | 48 | Draw the samples. 49 | 50 | * `purrr::map2()` is good since we want to operate on 2 things `(data = DATA FOR ONE SPECIES, n = SAMPLE SIZE)`. 51 | * We've already got `data = DATA FOR ONE SPECIES` and `n = SAMPLE SIZE` as variables in our data frame. 52 | * Drop them in as inputs 1 and 2 to `dplyr::sample_n(tbl, size)`. 53 | * Accept whatever comes back as a new list-column in the data frame, i.e. use `dplyr::mutate()`. Be brave and deal with it. 54 | 55 | ```{r} 56 | (sampled_iris <- nested_iris %>% 57 | mutate(samp = map2(data, n, sample_n))) 58 | ``` 59 | 60 | What came back? More `Species`-specific data frames. 61 | 62 | We are in that uncomfortable intermediate state, with two list-columns: the original `data` and the sampled data, `samp`. Let's get back to a normal data frame! 63 | 64 | * Keep only `Species` and `samp` variables. 65 | * Unnest, which essentially rowbinds the data frames in `samp` and replicates `Species` as necessary. 66 | 67 | ```{r} 68 | sampled_iris %>% 69 | select(-data) %>% 70 | unnest(samp) 71 | ``` 72 | 73 | Again, from the top, with no exposition: 74 | 75 | ```{r} 76 | iris %>% 77 | group_by(Species) %>% 78 | nest() %>% 79 | ungroup() %>% 80 | mutate(n = c(2, 5, 3)) %>% 81 | mutate(samp = map2(data, n, sample_n)) %>% 82 | select(-data) %>% 83 | unnest(samp) 84 | ``` 85 | 86 | A base R solution, with some marginal comments: 87 | 88 | ```{r} 89 | split_iris <- split(iris, iris$Species) # why can't Species be found in iris? 90 | # where else would it be found? 91 | str(split_iris) # split_iris ~= nested_iris[["data"]] 92 | (n <- c(2, 5, 3)) # Species data and n are only 'in sync' 93 | # due to my discipline / care 94 | # not locked safely into a data frame 95 | (group_sizes <- vapply(split_iris, nrow, integer(1))) # also floating free 96 | (sampled_obs <- mapply(sample, group_sizes, n)) # I'm floating free too! 97 | get_rows <- function(df, rows) df[rows, , drop = FALSE] # custom function 98 | # drop = FALSE required to avoid 99 | # nasty surprise in case of n = 1 100 | (sampled_iris <- # god help you if forget SIMPLIFY = FALSE 101 | mapply(get_rows, split_iris, sampled_obs, SIMPLIFY = FALSE)) 102 | do.call(rbind, sampled_iris) # :( do.call() 103 | ``` 104 | 105 | IMO the base R solution requires much greater facility with R programming and data structures to get it right. It feels more like programming than data analysis. 106 | -------------------------------------------------------------------------------- /ls13_list-columns.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "List columns" 3 | comment: "*creating, managing, and eliminating list-columns*" 4 | output: 5 | html_document: 6 | toc: true 7 | toc_float: true 8 | --- 9 | 10 | ```{r, echo = FALSE} 11 | knitr::opts_chunk$set(collapse = TRUE, comment = "#>") 12 | ``` 13 | 14 | Data frames are a fantastic data structure for data analysis. We usually think of them as a data receptacle for several atomic vectors with a common length and with a notion of "observation", i.e. the i-th value of each atomic vector is related to all the other i-th values. 15 | 16 | But data frame are not limited to atomic vectors. They can host general vectors, i.e. *lists* as well. This is what I call a **list-column**. 17 | 18 | List-columns and the data frame that hosts them require some special handling. In particular, it is highly advantageous if the data frame is a [tibble](https://github.com/tidyverse/tibble#readme), which anticipates list-columns. To work comfortably with list-columns, you need to develop techniques to: 19 | 20 | * **Inspect**. What have I created? 21 | * **Index**. How do I pull out specific bits by name or position? 22 | * **Compute**. How do I operate on my list-column to make another vector or list-column? 23 | * **Simplify**. How do I get rid of this list-column and back to a normal data frame? 24 | 25 | The purrr package and all the techniques depicted in the other lessons come into heavy play here. This is a collection of worked examples that show these techniques applied specifically to list-columns. 26 | 27 | ## Regex and Trump tweets 28 | 29 | ### Load packages 30 | 31 | ```{r message = FALSE} 32 | library(tidyverse) 33 | library(lubridate) 34 | library(here) 35 | ``` 36 | 37 | ### Bring tweets in 38 | 39 | Working with the same 7 tweets as [Trump Android words](ls08_trump-tweets.html) lesson. Go there for the rationale for choosing these 7 tweets. 40 | 41 | ```{r} 42 | tb_raw <- read_csv(here("talks", "trump-tweets.csv")) 43 | ``` 44 | 45 | ### Create a list-column of Trump Android words 46 | 47 | Clean a variable and create a list-column: 48 | 49 | * `source` comes in an unfriendly form. Simplify to convey if tweet came from Android or iPhone. 50 | * `twords` are what we'll call the "Trump Android words". See [Trump Android words](ls08_trump-tweets.html) lesson for backstory. **This is a list-column!** 51 | 52 | ```{r} 53 | source_regex <- "android|iphone" 54 | tword_regex <- "badly|crazy|weak|spent|strong|dumb|joke|guns|funny|dead" 55 | 56 | tb <- tb_raw %>% 57 | mutate(source = str_extract(source, source_regex), 58 | twords = str_extract_all(tweet, tword_regex)) 59 | ``` 60 | 61 | ### Derive new variables 62 | 63 | Add variables, two of which are based on the `twords` list-column. 64 | 65 | * `n`: How many twords are in the tweet? 66 | * `hour`: At which hour of the day was the tweet? 67 | * `start`: Start character of each tword. 68 | 69 | ```{r} 70 | tb <- tb %>% 71 | mutate(n = lengths(twords), 72 | hour = hour(created), 73 | start = gregexpr(tword_regex, tweet)) 74 | ``` 75 | 76 | ```{r include = FALSE} 77 | # another possibilty that would require more processing 78 | # so less useful for a talk example 79 | # but more useful IRL: 80 | # str_locate_all(tweet, tword_regex)) 81 | ``` 82 | 83 | ### Use regular data manipulation toolkit 84 | 85 | Let's isolate tweets created before 2pm, containing 1 or 2 twords, in which there's an tword that starts within the first 30 characters. 86 | 87 | ```{r} 88 | tb %>% 89 | filter(hour < 14, 90 | between(n, 1, 2), 91 | between(map_int(start, min), 0, 30)) 92 | ``` 93 | 94 | Let's isolate tweets that contain both the twords "strong" and "weak". 95 | 96 | ```{r} 97 | tb %>% 98 | filter(map_lgl(twords, ~ all(c("strong", "weak") %in% .x))) 99 | ``` 100 | 101 | ## JSON from an API and Game of Thrones 102 | 103 | ### Load packages 104 | 105 | ```{r} 106 | library(repurrrsive) 107 | library(tidyverse) 108 | library(httr) 109 | library(here) 110 | ``` 111 | 112 | ### Call the API of Ice and Fire 113 | 114 | Here's a simplified version of how we obtained the data on the Game of Thrones POV characters. This data appears as a more processed list in the [repurrrsive](https://cran.r-project.org/package=repurrrsive) package. 115 | 116 | * Get character IDs from repurrrsive. *cheating a little, humor me* 117 | * Put IDs and character names in a tibble. 118 | 119 | ```{r} 120 | pov <- set_names(map_int(got_chars, "id"), 121 | map_chr(got_chars, "name")) 122 | tail(pov, 5) 123 | ice <- pov %>% 124 | enframe(value = "id") 125 | ice 126 | ``` 127 | 128 | Request info for each character and store what comes back -- whatever that may be -- in the list-column `stuff`. 129 | 130 | ```{r} 131 | ice_and_fire_url <- "https://anapioficeandfire.com/" 132 | if (file.exists(here("talks", "ice.rds"))) { 133 | ice <- readRDS(here("talks", "ice.rds")) 134 | } else { 135 | ice <- ice %>% 136 | mutate( 137 | response = map(id, 138 | ~ GET(ice_and_fire_url, 139 | path = c("api", "characters", .x))), 140 | stuff = map(response, ~ content(.x, as = "parsed", 141 | simplifyVector = TRUE)) 142 | ) %>% 143 | select(-id, -response) 144 | saveRDS(ice, here("talks", "ice.rds")) 145 | } 146 | ice 147 | ``` 148 | 149 | Let's switch to a nicer version of `ice`, based on the list in repurrrsive, because it already has books and houses replaced with names instead of URLs. 150 | 151 | ```{r} 152 | ice2 <- tibble( 153 | name = map_chr(got_chars, "name"), 154 | stuff = got_chars 155 | ) 156 | ice2 157 | ``` 158 | 159 | Inspect the list-column. 160 | ```{r} 161 | str(ice2$stuff[[9]], max.level = 1) 162 | # if (interactive()) { 163 | # listviewer::jsonedit(ice2$stuff[[2]], mode = "view", width = 500, height = 530) 164 | # } 165 | ``` 166 | 167 | ### Use regular data manipulation toolkit 168 | 169 | Form a sentence of the form "NAME was born AT THIS TIME, IN THIS PLACE" by digging info out of the `stuff` list-column and placing into a string template. No list-columns left! 170 | 171 | ```{r} 172 | template <- "${name} was born ${born}." 173 | birth_announcements <- ice2 %>% 174 | mutate(birth = map_chr(stuff, str_interp, string = template)) %>% 175 | select(-stuff) 176 | birth_announcements 177 | ``` 178 | 179 | Extract each character's house allegiances. Keep only those with more than one allegiance. Then unnest to explode the `houses` list-column and get a tibble with one row per character * house combination. No list-columns left! 180 | 181 | ```{r} 182 | allegiances <- ice2 %>% 183 | transmute(name, 184 | houses = map(stuff, "allegiances")) %>% 185 | filter(lengths(houses) > 1) %>% 186 | unnest(houses) 187 | allegiances 188 | ``` 189 | 190 | ## Aliases and allegiances of Game of Thrones characters 191 | 192 | ### Load packages 193 | 194 | ```{r} 195 | library(tidyverse) 196 | library(repurrrsive) 197 | ``` 198 | 199 | ## Lists as variables in a data frame 200 | 201 | One row per GoT character. List columns for aliases and allegiances. 202 | 203 | ```{r} 204 | x <- tibble( 205 | name = got_chars %>% map_chr("name"), 206 | aliases = got_chars %>% map("aliases"), 207 | allegiances = got_chars %>% map("allegiances") 208 | ) 209 | x 210 | #View(x) 211 | ``` 212 | 213 | What if we only care about characters with a "Lannister" alliance? Practice operating on a list-column. 214 | 215 | ```{r} 216 | x %>% 217 | mutate(lannister = map(allegiances, str_detect, pattern = "Lannister"), 218 | lannister = map_lgl(lannister, any)) 219 | ``` 220 | 221 | Keep only the Lannisters and Starks allegiances. You can use `filter()` with list-columns, but you will need to `map()` to list-ize your operation. Once I've got the characters I want, I drop `allegiances` and use `unnest()` to get back to a simple data frame with no list columns. 222 | 223 | ```{r} 224 | x %>% 225 | filter(allegiances %>% 226 | map(str_detect, "Lannister|Stark") %>% 227 | map_lgl(any)) %>% 228 | select(-allegiances) %>% 229 | filter(lengths(aliases) > 0) %>% 230 | unnest(aliases) %>% 231 | print(n = Inf) 232 | ``` 233 | 234 | ```{r eval = FALSE, include = FALSE} 235 | x_base <- data.frame( 236 | name = vapply(got_chars, `[[`, character(1), "name"), 237 | aliases = I(lapply(got_chars, `[[`, "aliases")), 238 | allegiances = I(lapply(got_chars, `[[`, "allegiances")) 239 | ) 240 | keep1 <- vapply(x_base$allegiances, function(y) any(grepl("Lannister|Stark", y)), logical(1)) 241 | x_base <- x_base[keep1, ] 242 | x_base$allegiances <- NULL 243 | x_base 244 | data.frame( 245 | name = rep(x_base$name, lengths(x_base$aliases)), 246 | aliases = unlist(x_base$aliases) 247 | ) 248 | ``` 249 | 250 | ## Nested data frame, modelling, and Gapminder 251 | 252 | Another version of this same example is here: 253 | 254 | 255 | 256 | *mostly code at this point, more words needed* 257 | 258 | ### Load packages 259 | 260 | ```{r} 261 | library(tidyverse) 262 | library(gapminder) 263 | library(broom) 264 | ``` 265 | 266 | ### Hello, again, Gapminder 267 | 268 | ```{r} 269 | gapminder %>% 270 | ggplot(aes(year, lifeExp, group = country)) + 271 | geom_line(alpha = 1/3) 272 | ``` 273 | 274 | What if we fit a line to each country? 275 | 276 | ```{r} 277 | gapminder %>% 278 | ggplot(aes(year, lifeExp, group = country)) + 279 | geom_line(stat = "smooth", method = "lm", 280 | alpha = 1/3, se = FALSE, colour = "black") 281 | ``` 282 | 283 | What if you actually want those fits? To access estimates, p-values, etc. In that case, you need to fit them yourself. How to do that? 284 | 285 | * Put the variables needed for country-specific models into nested dataframe. In a **list-column**! 286 | * Use the usual "map inside mutate", possibly with the broom package, to pull interesting information out of the 142 fitted linear models. 287 | 288 | ### Nested data frame 289 | 290 | Nest the data frames, i.e. get one meta-row per country: 291 | 292 | ```{r} 293 | gap_nested <- gapminder %>% 294 | group_by(country) %>% 295 | nest() 296 | gap_nested 297 | gap_nested$data[[1]] 298 | ``` 299 | 300 | *Compare/contrast to a data frame grouped by country (dplyr-style) or split on country (base)*. 301 | 302 | ### Fit models, extract results 303 | 304 | Fit a model for each country. 305 | 306 | ```{r} 307 | gap_fits <- gap_nested %>% 308 | mutate(fit = map(data, ~ lm(lifeExp ~ year, data = .x))) 309 | ``` 310 | 311 | Look at one fitted model, for concreteness. 312 | 313 | ```{r} 314 | gap_fits %>% tail(3) 315 | canada <- which(gap_fits$country == "Canada") 316 | summary(gap_fits$fit[[canada]]) 317 | ``` 318 | 319 | Let's get all the r-squared values! 320 | 321 | ```{r} 322 | gap_fits %>% 323 | mutate(rsq = map_dbl(fit, ~ summary(.x)[["r.squared"]])) %>% 324 | arrange(rsq) 325 | ``` 326 | 327 | Let's use a function from broom to get the usual coefficient table from `summary.lm()` but in a friendlier form for downstream work. 328 | 329 | ```{r} 330 | library(broom) 331 | gap_fits %>% 332 | mutate(coef = map(fit, tidy)) %>% 333 | unnest(coef) 334 | ``` 335 | -------------------------------------------------------------------------------- /ls13_list-columns_files/figure-html/unnamed-chunk-22-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/ls13_list-columns_files/figure-html/unnamed-chunk-22-1.png -------------------------------------------------------------------------------- /ls13_list-columns_files/figure-html/unnamed-chunk-23-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/ls13_list-columns_files/figure-html/unnamed-chunk-23-1.png -------------------------------------------------------------------------------- /more-resources.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "More resources" 3 | --- 4 | 5 | 6 | 7 | * On CRAN: 8 | * Development on GitHub: 9 | * Coverage in [R for Data Science](http://r4ds.had.co.nz) 10 | - [Iteration](http://r4ds.had.co.nz/iteration.html) 11 | - [Handling hierarchy](http://r4ds.had.co.nz/hierarchy.html) 12 | - [Many models](http://r4ds.had.co.nz/many-models.html) 13 | * Example lists are in the repurrrsive package: 14 | - 15 | * Depicting R data structures and operations via lego: 16 | - [https://github.com/jennybc/lego-rstats](https://github.com/jennybc/lego-rstats#readme) 17 | -------------------------------------------------------------------------------- /more-resources.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | More resources 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 33 | 42 | 43 | 44 | 45 | 71 | 72 | 73 | 74 | 75 | 101 | 102 | 103 | 172 | 173 | 190 | 191 | 192 | 193 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 |
258 | 259 | 260 | 261 | 262 | 292 | 293 | 300 | 301 | 302 |

303 | 321 | 322 | 323 | 324 | 325 | 326 |
327 | 328 | 340 | 341 | 342 | 343 | 354 | 355 | 356 | 357 | 358 | 359 | 367 | 368 | 369 | 370 | -------------------------------------------------------------------------------- /pt00_gotchas.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Gotchas" 3 | --- 4 | 5 | ```{r, echo = FALSE} 6 | knitr::opts_chunk$set( 7 | collapse = TRUE, 8 | comment = "#>", 9 | error = TRUE 10 | ) 11 | ``` 12 | 13 | Mistakes I have made. And made. And made again. *Expect this page to grow as I (re)discover more gotchas.* 14 | 15 | ```{r} 16 | library(purrr) 17 | ``` 18 | 19 | ### The `magrittr` dot tension 20 | 21 | The tidyverse takes its dot `.` pronoun from the [`magrittr` package](https://github.com/smbache/magrittr#readme). It means "the thing we are operating on" and is also known as the "argument placeholder". 22 | 23 | You don't need the dot when you're using pipe-friendly functions and the planets align for you: 24 | 25 | ```{r} 26 | 8 %>% log2() 27 | ## is same as 28 | log2(8) 29 | ``` 30 | 31 | But sometimes the thing you're passing into the right-hand side (RHS) is not the first argument: 32 | 33 | ```{r} 34 | 2 %>% log(8) 35 | ## is not what I want and is not the same as 36 | 2 %>% log(8, .) 37 | ## or 38 | 2 %>% log(8, base = .) 39 | ``` 40 | 41 | And sometimes you want to prevent the left-hand side from being used as the (invisible) first argument on the RHS. So you have to enclose RHS in curly braces: 42 | 43 | ```{r} 44 | iris %>% { 45 | c(rows = nrow(.), cols = ncol(.)) 46 | } 47 | ``` 48 | 49 | One last thing ... and this leads to the gotcha. The `.` can also be used to create a *unary function*: 50 | 51 | ```{r} 52 | att <- . %>% toupper() %>% paste("ALL THE THINGS!") 53 | "open source" %>% att() 54 | "butter" %>% att() 55 | "teach" %>% att() 56 | ``` 57 | 58 | What is `att` anyway? 59 | 60 | ```{r} 61 | att 62 | ``` 63 | 64 | It is a "functional sequence". 65 | 66 | It's fairly easy to write code where you think `.` is a placeholder, but it generates a functional sequence. 67 | 68 | Watch me. 69 | 70 | ```{r} 71 | library(purrr) 72 | library(tibble) 73 | 74 | x <- list(list(int = 1L, chr = 'a'), list(int = 2L, chr = 'b')) 75 | 76 | ## YES GOOD WORKS 77 | x %>% { 78 | tibble(id = map_int(., "int"), 79 | chr = map_chr(., "chr")) 80 | } 81 | 82 | ## NO BAD DOES NOT WORK 83 | x %>% { 84 | tibble(id = . %>% map_int("int"), 85 | chr = . %>% map_chr("chr")) 86 | } 87 | ``` 88 | 89 | What went wrong? 90 | 91 | `. %>% map_int("int")` built a unary function, instead of passing `x` into `map_int()`. Do not start a pipeline with `.` unless you want a unary function. 92 | 93 | What does this have to do with `purrr`? 94 | 95 | If you've got a complicated object `x` (e.g., a deeply nested list from JSON), you might build a data frame with repeated calls to `map_*()` functions. Be careful where you put your dot `.`! 96 | 97 | ### `purrr` is strict about types 98 | 99 | `purrr`'s type checking is very strict, which is overhwhelmingly positive. But it will force you to be more aware of integer vs. double. 100 | 101 | ```{r error = TRUE} 102 | set.seed(4561) 103 | (x <- sample(1:5)) 104 | 105 | times_two <- function(x) x * 2 106 | times_two(x) 107 | 108 | x_list <- as.list(x) 109 | 110 | ## WTF? 111 | x_list %>% 112 | map_int(times_two) 113 | ``` 114 | 115 | Why can I suddenly not multiply these numbers by 2? 116 | 117 | Because we've said to expect integers back and, though the elements of `x` are integer, the result of multiplying by the *double* 2 is *double*. 118 | 119 | What can you do? Buckle down and make sure that integer stays integer, if that's appropriate. Or loosen up and use `map_dbl()` instead. 120 | 121 | ```{r} 122 | ## GOOD, in the buckle down sense 123 | times_two <- function(x) x * 2L 124 | x_list %>% 125 | map_int(times_two) 126 | 127 | ## GOOD, in the loosen up sense 128 | times_two <- function(x) x * 2 129 | x_list %>% 130 | map_dbl(times_two) 131 | ``` 132 | 133 | -------------------------------------------------------------------------------- /purrr-slides-trump-tweets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/purrr-slides-trump-tweets.png -------------------------------------------------------------------------------- /purrr-tutorial.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Website 19 | -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/Lato.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/Lato.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/LatoBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/LatoBold.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/LatoItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/LatoItalic.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/NewsCycle.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/NewsCycle.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/NewsCycleBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/NewsCycleBold.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/OpenSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/OpenSans.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/OpenSansBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/OpenSansBold.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/OpenSansBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/OpenSansBoldItalic.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/OpenSansItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/OpenSansItalic.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/OpenSansLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/OpenSansLight.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/OpenSansLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/OpenSansLightItalic.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/Raleway.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/Raleway.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/RalewayBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/RalewayBold.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/Roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/Roboto.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/RobotoBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/RobotoBold.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/RobotoLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/RobotoLight.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/RobotoMedium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/RobotoMedium.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/SourceSansPro.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/SourceSansPro.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/SourceSansProBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/SourceSansProBold.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/SourceSansProItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/SourceSansProItalic.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/SourceSansProLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/SourceSansProLight.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/css/fonts/Ubuntu.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/css/fonts/Ubuntu.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/bootstrap-3.3.5/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/shim/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | // Only run this code in IE 8 5 | if (!!window.navigator.userAgent.match("MSIE 8")) { 6 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.2",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b)}(this,document); 7 | }; 8 | -------------------------------------------------------------------------------- /site_libs/bootstrap-3.3.5/shim/respond.min.js: -------------------------------------------------------------------------------- 1 | /*! Respond.js v1.4.2: min/max-width media query polyfill * Copyright 2013 Scott Jehl 2 | * Licensed under https://github.com/scottjehl/Respond/blob/master/LICENSE-MIT 3 | * */ 4 | 5 | // Only run this code in IE 8 6 | if (!!window.navigator.userAgent.match("MSIE 8")) { 7 | !function(a){"use strict";a.matchMedia=a.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='­',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){u(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))};if(c.ajax=f,c.queue=d,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\([\s]*min\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/,maxw:/\([\s]*max\-width\s*:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var g,h,i,j=a.document,k=j.documentElement,l=[],m=[],n=[],o={},p=30,q=j.getElementsByTagName("head")[0]||k,r=j.getElementsByTagName("base")[0],s=q.getElementsByTagName("link"),t=function(){var a,b=j.createElement("div"),c=j.body,d=k.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=j.createElement("body"),c.style.background="none"),k.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&k.insertBefore(c,k.firstChild),a=b.offsetWidth,f?k.removeChild(c):c.removeChild(b),k.style.fontSize=d,e&&(c.style.fontSize=e),a=i=parseFloat(a)},u=function(b){var c="clientWidth",d=k[c],e="CSS1Compat"===j.compatMode&&d||j.body[c]||d,f={},o=s[s.length-1],r=(new Date).getTime();if(b&&g&&p>r-g)return a.clearTimeout(h),h=a.setTimeout(u,p),void 0;g=r;for(var v in l)if(l.hasOwnProperty(v)){var w=l[v],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?i||t():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?i||t():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(m[w.rules]))}for(var C in n)n.hasOwnProperty(C)&&n[C]&&n[C].parentNode===q&&q.removeChild(n[C]);n.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=j.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,q.insertBefore(E,o.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(j.createTextNode(F)),n.push(E)}},v=function(a,b,d){var e=a.replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var g=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},h=!f&&d;b.length&&(b+="/"),h&&(f=1);for(var i=0;f>i;i++){var j,k,n,o;h?(j=d,m.push(g(a))):(j=e[i].match(c.regex.findStyles)&&RegExp.$1,m.push(RegExp.$2&&g(RegExp.$2))),n=j.split(","),o=n.length;for(var p=0;o>p;p++)k=n[p],l.push({media:k.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:m.length-1,hasquery:k.indexOf("(")>-1,minw:k.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:k.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}u()},w=function(){if(d.length){var b=d.shift();f(b.href,function(c){v(c,b.href,b.media),o[b.href]=!0,a.setTimeout(function(){w()},0)})}},x=function(){for(var b=0;b 21 | 22 | code editor 23 | 24 | 25 | ## Features 26 | 27 | ### Tree editor 28 | - Edit, add, move, remove, and duplicate fields and values. 29 | - Change type of values. 30 | - Sort arrays and objects. 31 | - Colorized code. 32 | - Search & highlight text in the treeview. 33 | - Undo and redo all actions. 34 | 35 | ### Code editor 36 | - Format and compact JSON. 37 | - Colorized code (powered by Ace). 38 | - Inspect JSON (powered by Ace). 39 | 40 | ### Text editor 41 | - Format and compact JSON. 42 | 43 | 44 | ## Documentation 45 | 46 | - Documentation: 47 | - [API](https://github.com/josdejong/jsoneditor/tree/master/docs/api.md) 48 | - [Usage](https://github.com/josdejong/jsoneditor/tree/master/docs/usage.md) 49 | - [Shortcut keys](https://github.com/josdejong/jsoneditor/tree/master/docs/shortcut_keys.md) 50 | - [Examples](https://github.com/josdejong/jsoneditor/tree/master/examples) 51 | - [Source](https://github.com/josdejong/jsoneditor) 52 | - [History](https://github.com/josdejong/jsoneditor/blob/master/HISTORY.md) 53 | 54 | 55 | ## Install 56 | 57 | with npm: 58 | 59 | npm install jsoneditor 60 | 61 | with bower: 62 | 63 | bower install jsoneditor 64 | 65 | download: 66 | 67 | [http://jsoneditoronline.org/downloads/](http://jsoneditoronline.org/downloads/) 68 | 69 | 70 | ## Use 71 | 72 | ```html 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
81 | 82 | 101 | 102 | 103 | ``` 104 | 105 | 106 | ## Build 107 | 108 | The code of the JSON Editor is located in the folder `./src`. To build 109 | jsoneditor: 110 | 111 | - Install dependencies: 112 | 113 | ``` 114 | npm install 115 | ``` 116 | 117 | - Build JSON Editor: 118 | 119 | ``` 120 | npm run build 121 | ``` 122 | 123 | This will generate the files `./jsoneditor.js`, `./jsoneditor.css`, and 124 | minified versions in the root of the project. 125 | 126 | 127 | ## Custom builds 128 | 129 | The source code of JSONEditor consists of CommonJS modules. JSONEditor can be bundled in a customized way using a module bundler like [browserify](http://browserify.org/) or [webpack](http://webpack.github.io/). First, install all dependencies of jsoneditor: 130 | 131 | npm install 132 | 133 | To create a custom bundle of the source code using browserify: 134 | 135 | browserify ./index.js -o ./jsoneditor.custom.js -s JSONEditor 136 | 137 | The Ace editor, used in mode `code`, accounts for about 75% of the total 138 | size of the library. To exclude the Ace editor from the bundle: 139 | 140 | browserify ./index.js -o ./jsoneditor.custom.js -s JSONEditor -x brace -x brace/mode/json -x brace/ext/searchbox 141 | 142 | To minify the generated bundle, use [uglifyjs](https://github.com/mishoo/UglifyJS2): 143 | 144 | uglifyjs ./jsoneditor.custom.js -o ./jsoneditor.custom.min.js -m -c 145 | -------------------------------------------------------------------------------- /site_libs/jsonedit-binding-2.0.0/jsoneditor/dist/img/jsoneditor-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/jsonedit-binding-2.0.0/jsoneditor/dist/img/jsoneditor-icons.png -------------------------------------------------------------------------------- /site_libs/jsonedit-binding-2.0.0/reactjson.js: -------------------------------------------------------------------------------- 1 | HTMLWidgets.widget({ 2 | 3 | name: 'reactjson', 4 | 5 | type: 'output', 6 | 7 | factory: function(el, width, height) { 8 | 9 | // TODO: define shared variables for this instance 10 | 11 | return { 12 | 13 | renderValue: function(x) { 14 | 15 | // add scroll to widget container for better sizing behavior 16 | el.style.overflow = "auto"; 17 | 18 | ReactDOM.render( 19 | React.createElement( 20 | reactJsonView.default, 21 | { 22 | src: (typeof(x.data)==="string") ? JSON.parse(x.data) : x.data, 23 | name: null, 24 | onAdd: logChange, 25 | onEdit: logChange, 26 | onDelete: logChange 27 | } 28 | ), 29 | el 30 | ); 31 | 32 | function logChange( value ){ 33 | if(typeof(Shiny) !== "undefined" && Shiny.onInputChange){ 34 | Shiny.onInputChange(el.id + "_change", {value:value}); 35 | } 36 | } 37 | 38 | }, 39 | 40 | resize: function(width, height) { 41 | 42 | // TODO: code to re-render the widget with a new size 43 | 44 | } 45 | 46 | }; 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /site_libs/jsonedit-binding-2.0.0/reactjson.yaml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/jsonedit-binding-2.0.0/reactjson.yaml -------------------------------------------------------------------------------- /site_libs/jsonedit-binding-2.0.0/reactjson/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Mac Gainor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /site_libs/jsonedit-binding-2.0.0/reactjson/README.md: -------------------------------------------------------------------------------- 1 | ![alt text](https://github.com/mac-s-g/react-json-view/blob/master/doc/rjv-icon-alt.png?raw=true) 2 | 3 | [![npm](https://img.shields.io/npm/v/react-json-view.svg)](https://www.npmjs.com/package/react-json-view) [![npm](https://img.shields.io/npm/l/react-json-view.svg)](https://github.com/mac-s-g/react-json-view/blob/master/LISCENSE) [![Build Status](https://travis-ci.org/mac-s-g/react-json-view.svg)](https://travis-ci.org/mac-s-g/react-json-view) [![Coverage Status](https://coveralls.io/repos/github/mac-s-g/react-json-view/badge.svg?branch=master)](https://coveralls.io/github/mac-s-g/react-json-view?branch=master) [![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/react-json-view) 4 | 5 | # react-json-view 6 | RJV is a react component for displaying and editing javascript **arrays** and **JSON objects**. 7 | 8 | This component provides a responsive interface for displaying arrays or JSON in a web browser. NPM offers a distribution of the source that's transpiled to ES5; so you can include this component with *any web-based javascript application*. 9 | 10 | [Check out the Interactive Demo](https://mac-s-g.github.io/react-json-view/demo/dist/) 11 | 12 | 13 | ### Implementation Example 14 | ```js 15 | // import the react-json-view component 16 | import ReactJson from 'react-json-view' 17 | 18 | // use the component in your app! 19 | 20 | ``` 21 | 22 | ### Output Examples 23 | #### Default Theme 24 | ![alt text](https://github.com/mac-s-g/react-json-view/blob/master/doc/output-example-15.png?raw=true "Output Example 1") 25 | #### Hopscotch Theme, with Triangle Icons: 26 | ![alt text](https://github.com/mac-s-g/react-json-view/blob/master/doc/output-example-14.png?raw=true "Output Example 2") 27 | 28 | ### Installation Instructions 29 | Install this component with [NPM](https://www.npmjs.com/package/react-json-view). 30 | ```shell 31 | npm install --save react-json-view 32 | ``` 33 | Or add to your package.json config file: 34 | ```json 35 | "dependencies": { 36 | "react-json-view": "latest" 37 | } 38 | ``` 39 | 40 | ### Props 41 | Name|Type|Default|Description 42 | |:---|:---|:---|:--- 43 | `src`|`JSON Object`|None|This property contains your input JSON 44 | `name`|`string` or `false`|"root"|Contains the name of your root node. Use `null` or `false` for no name. 45 | `theme`|`string`|"rjv-default"|RJV supports base-16 themes. Check out the list of supported themes [in the demo](https://mac-s-g.github.io/react-json-view/demo/dist/). A custom "rjv-default" theme applies by default. 46 | `style`|`object`|`{}`|Style attributes for react-json-view container. Explicit style attributes will override attributes provided by a theme. 47 | `iconStyle`|`string`|"circle"| Style of expand/collapse icons. Accepted values are "circle", triangle" or "square". 48 | `indentWidth`|`integer`|4|Set the indent-width for nested objects 49 | `collapsed`|`boolean` or `integer`|`false`|When set to `true`, all nodes will be collapsed by default. Use an integer value to collapse at a particular depth. 50 | `collapseStringsAfterLength`|`integer`|`false`|When an integer value is assigned, strings will be cut off at that length. Collapsed strings are followed by an ellipsis. String content can be expanded and collapsed by clicking on the string value. 51 | `shouldCollapse`|`(field)=>{}`|`false`|Callback function to provide control over what objects and arrays should be collapsed by default. An object is passed to the callback containing `name`, `src`, `type` ("array" or "object") and `namespace`. 52 | `groupArraysAfterLength`|`integer`|`100`|When an integer value is assigned, arrays will be displayed in groups by count of the value. Groups are displayed with brakcet notation and can be expanded and collapsed by clickong on the brackets. 53 | `enableClipboard`|`boolean` or `(copy)=>{}`|`true`|When prop is not `false`, the user can copy objects and arrays to clipboard by clicking on the clipboard icon. Copy callbacks are supported. 54 | `displayObjectSize`|`boolean`|`true`|When set to `true`, objects and arrays are labeled with size 55 | `displayDataTypes`|`boolean`|`true`|When set to `true`, data type labels prefix values 56 | `onEdit`|`(edit)=>{}`|`false`|When a callback function is passed in, `edit` functionality is enabled. The callback is invoked before edits are completed. Returning `false` from `onEdit` will prevent the change from being made. [see: onEdit docs](#onedit-onadd-and-ondelete-interaction) 57 | `onAdd`|`(add)=>{}`|`false`|When a callback function is passed in, `add` functionality is enabled. The callback is invoked before additions are completed. Returning `false` from `onAdd` will prevent the change from being made. [see: onAdd docs](#onedit-onadd-and-ondelete-interaction) 58 | `onDelete`|`(delete)=>{}`|`false`|When a callback function is passed in, `delete` functionality is enabled. The callback is invoked before deletions are completed. Returning `false` from `onDelete` will prevent the change from being made. [see: onDelete docs](#onedit-onadd-and-ondelete-interaction) 59 | `onSelect`|`(select)=>{}`|`false`|When a function is passed in, clicking a value triggers the `onSelect` method to be called. 60 | `validationMessage`|`string`|"Validation Error"|Custom message for validation failures to `onEdit`, `onAdd`, or `onDelete` callbacks 61 | 62 | ### Features 63 | * `onEdit`, `onAdd` and `onDelete` props allow users to edit the `src` variable 64 | * Object, array, string and function values can be collapsed and expanded 65 | * Object and array nodes display length 66 | * Object and array nodes support a "Copy to Clipboard" feature 67 | * String values can be truncated after a specified length 68 | * Arrays can be subgrouped after a specified length 69 | * Base-16 Theme Support 70 | * When `onEdit` is enabled: 71 | * `Ctrl+Click` Edit Mode 72 | * `Ctrl+Enter` Submit 73 | 74 | ### Customizing Style 75 | #### Stock Themes 76 | RJV now supports base-16 themes! 77 | 78 | You can specify a `theme` name or object when you instantiate your rjv component. 79 | ```jsx 80 | 81 | ``` 82 | Check out the list of supported themes [in the component demo](https://mac-s-g.github.io/react-json-view/demo/dist/). 83 | 84 | #### Monokai theme example 85 | ![alt text](https://github.com/mac-s-g/react-json-view/blob/master/doc/output-example-monokai-2.png?raw=true "Base-16 Theme Example") 86 | 87 | #### Solarized theme example 88 | ![alt text](https://github.com/mac-s-g/react-json-view/blob/master/doc/output-example-solarized-2.png?raw=true "Base-16 Theme Example") 89 | 90 | #### Use Your Own Theme 91 | You can supply your own base-16 theme object. 92 | 93 | To better understand custom themes, take a look at [my example implementation](https://github.com/mac-s-g/react-json-view/blob/master/dev-server/dev-server.js#L81) and the [base-16 theme styling guidelines](https://github.com/chriskempson/base16/blob/master/styling.md). 94 | 95 | ### onEdit, onAdd and onDelete Interaction 96 | Pass callback methods to `onEdit`, `onAdd` and `onDelete` props. Your method will be invoked when a user attempts to update your `src` object. 97 | 98 | The following object will be passed to your method: 99 | ```js 100 | { 101 | updated_src: src, //new src value 102 | name: name, //new var name 103 | namespace: namespace, //list, namespace indicating var location 104 | new_value: new_value, //new variable value 105 | existing_value: existing_value, //existing variable value 106 | } 107 | ``` 108 | 109 | Returning `false` from a callback method will prevent the src from being affected. 110 | 111 | ### Contributing to the source code 112 | #### Run the Dev Server 113 | 114 | ```bash 115 | # clone this repository 116 | git clone git@github.com:mac-s-g/react-json-view.git && cd react-json-view 117 | # install dependencies 118 | npm install --save-dev 119 | # run the dev server with hot reloading 120 | npm run dev 121 | ``` 122 | Webpack Dev Server should automatically open up http://localhost:2000 in your web browser. If it does not, open a browser and navigate to port 2000. The hot reloader will automatically reload when files are modified in the `/src/` directory. 123 | 124 | #### Run the Production Build 125 | 126 | ```bash 127 | # run the build (note: you may need to use `sudo` priveledges to run the build successfully) 128 | npm run build 129 | ``` 130 | Please add tests for your code before posting a pull request. 131 | 132 | You can run the test suite with `npm run test` or `npm run test:watch` to automatically reload when files are modified. 133 | 134 | #### Docker Tools 135 | 136 | I recommend using docker for development because it enforces environmental consistency. 137 | 138 | For information about contributing with Docker, see the [README in ./docker](https://github.com/mac-s-g/react-json-view/blob/master/docker/README.md#contributing-to-this-project-using-docker). 139 | 140 | 141 | ### Inspiration 142 | I drew a ton of design ideas from [react-json-tree](https://github.com/alexkuz/react-json-tree). Thanks to the RJT contributors for putting together an awesome component! 143 | 144 | I'm also inspired by users who come up with interesting feature requests. Reach out to me with ideas for this project or other projects you want to collaborate on. My email address is listed on my [github user page](https://github.com/mac-s-g). -------------------------------------------------------------------------------- /site_libs/jsonedit-binding-2.0.0/reactjson/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-json-view", 3 | "description": "Interactive react component for displaying javascript arrays and JSON objects.", 4 | "version": "1.15.2", 5 | "main": "dist/main.js", 6 | "files": [ 7 | "dist/" 8 | ], 9 | "dependencies": { 10 | "flux": "^3.1.3", 11 | "react-base16-styling": "^0.5.3", 12 | "react-textarea-autosize": "^5.1.0" 13 | }, 14 | "devDependencies": { 15 | "babel-core": "^6.25.0", 16 | "babel-loader": "^7.1.1", 17 | "babel-plugin-react-html-attrs": "^2.0.0", 18 | "babel-plugin-transform-class-properties": "^6.24.1", 19 | "babel-plugin-transform-function-bind": "^6.22.0", 20 | "babel-plugin-transform-node-env-inline": "^0.2.0", 21 | "babel-preset-es2015": "^6.24.1", 22 | "babel-preset-react": "^6.24.1", 23 | "babel-preset-stage-0": "^6.24.1", 24 | "chai": "^4.1.2", 25 | "coveralls": "^3.0.0", 26 | "css-loader": "^0.28.4", 27 | "enzyme": "^3.2.0", 28 | "enzyme-adapter-react-16": "^1.1.1", 29 | "html-webpack-plugin": "2.30.1", 30 | "ignore-styles": "^5.0.1", 31 | "istanbul": "^0.4.5", 32 | "jsdom": "^11.5.0", 33 | "mocha": "^4.0.1", 34 | "node": "8.4.0", 35 | "node-sass": "^4.5.3", 36 | "nyc": "^11.4.1", 37 | "react": "^16.2.0", 38 | "react-dom": "^16.2.0", 39 | "react-hot-loader": "^3.0.0-beta.6", 40 | "react-select": "^1.1.0", 41 | "react-test-renderer": "^16.2.0", 42 | "sass-loader": "^6.0.6", 43 | "sinon": "^4.1.3", 44 | "style-loader": "^0.18.2", 45 | "webpack": "^3.10.0", 46 | "webpack-bundle-size-analyzer": "^2.7.0", 47 | "webpack-dev-server": "^2.9.7" 48 | }, 49 | "peerDependencies": { 50 | "react": "^16.0.0 || ^15.5.4", 51 | "react-dom": "^16.0.0 || ^15.5.4" 52 | }, 53 | "scripts": { 54 | "build": "webpack --config webpack/webpack.config.js -p --display-error-details --progress --optimize-minimize", 55 | "prebuild": "npm run test:unit", 56 | "build:demo": "webpack --config webpack/webpack.config-demo.js -p --display-error-details --progress --optimize-minimize", 57 | "dev": "webpack-dev-server --config webpack/webpack.config-dev.js --open", 58 | "modules:debug": "./docker/debug.sh", 59 | "modules:tree": "webpack --config webpack/webpack.config.js --json ", 60 | "modules:size-analyzer": "webpack --config webpack/webpack.config.js --json | webpack-bundle-size-analyzer", 61 | "test": "npm run test:unit && npm run test:coverage", 62 | "test:unit": "nyc mocha test/**/*-test.js", 63 | "test:coverage": "nyc report --reporter=text-lcov | coveralls", 64 | "test:watch": "nyc mocha -w test/**/*-test.js" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "https://github.com/mac-s-g/react-json-view.git" 69 | }, 70 | "keywords": [ 71 | "array-viewer", 72 | "base-16", 73 | "component", 74 | "interactive", 75 | "interactive-json", 76 | "json", 77 | "json-component", 78 | "json-display", 79 | "json-tree", 80 | "json-view", 81 | "json-viewer", 82 | "json-inspector", 83 | "json-tree", 84 | "react", 85 | "react-component", 86 | "react-json", 87 | "theme", 88 | "tree", 89 | "tree-view", 90 | "treeview" 91 | ], 92 | "license": "MIT", 93 | "author": "Mac Gainor", 94 | "bugs": { 95 | "url": "https://github.com/mac-s-g/react-json-view/issues" 96 | }, 97 | "homepage": "https://github.com/mac-s-g/react-json-view", 98 | "directories": { 99 | "docs": "docs", 100 | "test": "test" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /site_libs/jsonedit-binding-2.1.0/jsonedit.js: -------------------------------------------------------------------------------- 1 | HTMLWidgets.widget({ 2 | 3 | name: 'jsonedit', 4 | 5 | type: 'output', 6 | 7 | initialize: function(el, width, height) { 8 | 9 | return { editor: null } 10 | 11 | }, 12 | 13 | renderValue: function(el, x, instance) { 14 | 15 | // clear out our element in case of dynamic 16 | // probably a sloppy way of doing it but easy and dependency-free 17 | // feel free to make suggestions 18 | el.innerHTML = ""; 19 | 20 | // create our editor 21 | var editor = new JSONEditor( el, x.options, (typeof(x.data)==="string") ? JSON.parse(x.data) : x.data ); 22 | 23 | // use expando property to store editor for change callback potential 24 | instance.editor = editor; 25 | 26 | }, 27 | 28 | resize: function(el, width, height, instance) { 29 | 30 | } 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /site_libs/jsoneditor-5.25.6/img/jsoneditor-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/jsoneditor-5.25.6/img/jsoneditor-icons.png -------------------------------------------------------------------------------- /site_libs/jsoneditor-5.5.5/img/jsoneditor-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/site_libs/jsoneditor-5.5.5/img/jsoneditor-icons.png -------------------------------------------------------------------------------- /site_libs/navigation-1.1/codefolding.js: -------------------------------------------------------------------------------- 1 | 2 | window.initializeCodeFolding = function(show) { 3 | 4 | // handlers for show-all and hide all 5 | $("#rmd-show-all-code").click(function() { 6 | $('div.r-code-collapse').each(function() { 7 | $(this).collapse('show'); 8 | }); 9 | }); 10 | $("#rmd-hide-all-code").click(function() { 11 | $('div.r-code-collapse').each(function() { 12 | $(this).collapse('hide'); 13 | }); 14 | }); 15 | 16 | // index for unique code element ids 17 | var currentIndex = 1; 18 | 19 | // select all R code blocks 20 | var rCodeBlocks = $('pre.r, pre.python, pre.bash, pre.sql, pre.cpp, pre.stan, pre.julia'); 21 | rCodeBlocks.each(function() { 22 | 23 | // create a collapsable div to wrap the code in 24 | var div = $('
'); 25 | if (show || $(this)[0].classList.contains('fold-show')) 26 | div.addClass('in'); 27 | var id = 'rcode-643E0F36' + currentIndex++; 28 | div.attr('id', id); 29 | $(this).before(div); 30 | $(this).detach().appendTo(div); 31 | 32 | // add a show code button right above 33 | var showCodeText = $('' + (show ? 'Hide' : 'Code') + ''); 34 | var showCodeButton = $(''); 35 | showCodeButton.append(showCodeText); 36 | showCodeButton 37 | .attr('data-toggle', 'collapse') 38 | .attr('data-target', '#' + id) 39 | .attr('aria-expanded', show) 40 | .attr('aria-controls', id); 41 | 42 | var buttonRow = $('
'); 43 | var buttonCol = $('
'); 44 | 45 | buttonCol.append(showCodeButton); 46 | buttonRow.append(buttonCol); 47 | 48 | div.before(buttonRow); 49 | 50 | // update state of button on show/hide 51 | div.on('hidden.bs.collapse', function () { 52 | showCodeText.text('Code'); 53 | }); 54 | div.on('show.bs.collapse', function () { 55 | showCodeText.text('Hide'); 56 | }); 57 | }); 58 | 59 | } 60 | -------------------------------------------------------------------------------- /site_libs/navigation-1.1/sourceembed.js: -------------------------------------------------------------------------------- 1 | 2 | window.initializeSourceEmbed = function(filename) { 3 | $("#rmd-download-source").click(function() { 4 | var src = $("#rmd-source-code").html(); 5 | var a = document.createElement('a'); 6 | a.href = "data:text/x-r-markdown;base64," + src; 7 | a.download = filename; 8 | document.body.appendChild(a); 9 | a.click(); 10 | document.body.removeChild(a); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /site_libs/navigation-1.1/tabsets.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | * jQuery Plugin: Sticky Tabs 5 | * 6 | * @author Aidan Lister 7 | * adapted by Ruben Arslan to activate parent tabs too 8 | * http://www.aidanlister.com/2014/03/persisting-the-tab-state-in-bootstrap/ 9 | */ 10 | (function($) { 11 | "use strict"; 12 | $.fn.rmarkdownStickyTabs = function() { 13 | var context = this; 14 | // Show the tab corresponding with the hash in the URL, or the first tab 15 | var showStuffFromHash = function() { 16 | var hash = window.location.hash; 17 | var selector = hash ? 'a[href="' + hash + '"]' : 'li.active > a'; 18 | var $selector = $(selector, context); 19 | if($selector.data('toggle') === "tab") { 20 | $selector.tab('show'); 21 | // walk up the ancestors of this element, show any hidden tabs 22 | $selector.parents('.section.tabset').each(function(i, elm) { 23 | var link = $('a[href="#' + $(elm).attr('id') + '"]'); 24 | if(link.data('toggle') === "tab") { 25 | link.tab("show"); 26 | } 27 | }); 28 | } 29 | }; 30 | 31 | 32 | // Set the correct tab when the page loads 33 | showStuffFromHash(context); 34 | 35 | // Set the correct tab when a user uses their back/forward button 36 | $(window).on('hashchange', function() { 37 | showStuffFromHash(context); 38 | }); 39 | 40 | // Change the URL when tabs are clicked 41 | $('a', context).on('click', function(e) { 42 | history.pushState(null, null, this.href); 43 | showStuffFromHash(context); 44 | }); 45 | 46 | return this; 47 | }; 48 | }(jQuery)); 49 | 50 | window.buildTabsets = function(tocID) { 51 | 52 | // build a tabset from a section div with the .tabset class 53 | function buildTabset(tabset) { 54 | 55 | // check for fade and pills options 56 | var fade = tabset.hasClass("tabset-fade"); 57 | var pills = tabset.hasClass("tabset-pills"); 58 | var navClass = pills ? "nav-pills" : "nav-tabs"; 59 | 60 | // determine the heading level of the tabset and tabs 61 | var match = tabset.attr('class').match(/level(\d) /); 62 | if (match === null) 63 | return; 64 | var tabsetLevel = Number(match[1]); 65 | var tabLevel = tabsetLevel + 1; 66 | 67 | // find all subheadings immediately below 68 | var tabs = tabset.find("div.section.level" + tabLevel); 69 | if (!tabs.length) 70 | return; 71 | 72 | // create tablist and tab-content elements 73 | var tabList = $(''); 74 | $(tabs[0]).before(tabList); 75 | var tabContent = $('
'); 76 | $(tabs[0]).before(tabContent); 77 | 78 | // build the tabset 79 | var activeTab = 0; 80 | tabs.each(function(i) { 81 | 82 | // get the tab div 83 | var tab = $(tabs[i]); 84 | 85 | // get the id then sanitize it for use with bootstrap tabs 86 | var id = tab.attr('id'); 87 | 88 | // see if this is marked as the active tab 89 | if (tab.hasClass('active')) 90 | activeTab = i; 91 | 92 | // remove any table of contents entries associated with 93 | // this ID (since we'll be removing the heading element) 94 | $("div#" + tocID + " li a[href='#" + id + "']").parent().remove(); 95 | 96 | // sanitize the id for use with bootstrap tabs 97 | id = id.replace(/[.\/?&!#<>]/g, '').replace(/\s/g, '_'); 98 | tab.attr('id', id); 99 | 100 | // get the heading element within it, grab it's text, then remove it 101 | var heading = tab.find('h' + tabLevel + ':first'); 102 | var headingText = heading.html(); 103 | heading.remove(); 104 | 105 | // build and append the tab list item 106 | var a = $('' + headingText + ''); 107 | a.attr('href', '#' + id); 108 | a.attr('aria-controls', id); 109 | var li = $('
  • '); 110 | li.append(a); 111 | tabList.append(li); 112 | 113 | // set it's attributes 114 | tab.attr('role', 'tabpanel'); 115 | tab.addClass('tab-pane'); 116 | tab.addClass('tabbed-pane'); 117 | if (fade) 118 | tab.addClass('fade'); 119 | 120 | // move it into the tab content div 121 | tab.detach().appendTo(tabContent); 122 | }); 123 | 124 | // set active tab 125 | $(tabList.children('li')[activeTab]).addClass('active'); 126 | var active = $(tabContent.children('div.section')[activeTab]); 127 | active.addClass('active'); 128 | if (fade) 129 | active.addClass('in'); 130 | 131 | if (tabset.hasClass("tabset-sticky")) 132 | tabset.rmarkdownStickyTabs(); 133 | } 134 | 135 | // convert section divs with the .tabset class to tabsets 136 | var tabsets = $("div.section.tabset"); 137 | tabsets.each(function(i) { 138 | buildTabset($(tabsets[i])); 139 | }); 140 | }; 141 | 142 | -------------------------------------------------------------------------------- /site_libs/tocify-1.9.1/jquery.tocify.css: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.tocify.css 1.9.1 3 | * Author: @gregfranko 4 | */ 5 | 6 | /* The Table of Contents container element */ 7 | .tocify { 8 | width: 20%; 9 | max-height: 90%; 10 | overflow: auto; 11 | margin-left: 2%; 12 | position: fixed; 13 | border: 1px solid #ccc; 14 | border-radius: 6px; 15 | } 16 | 17 | /* The Table of Contents is composed of multiple nested unordered lists. These styles remove the default styling of an unordered list because it is ugly. */ 18 | .tocify ul, .tocify li { 19 | list-style: none; 20 | margin: 0; 21 | padding: 0; 22 | border: none; 23 | line-height: 30px; 24 | } 25 | 26 | /* Top level header elements */ 27 | .tocify-header { 28 | text-indent: 10px; 29 | } 30 | 31 | /* Top level subheader elements. These are the first nested items underneath a header element. */ 32 | .tocify-subheader { 33 | text-indent: 20px; 34 | display: none; 35 | } 36 | 37 | /* Makes the font smaller for all subheader elements. */ 38 | .tocify-subheader li { 39 | font-size: 12px; 40 | } 41 | 42 | /* Further indents second level subheader elements. */ 43 | .tocify-subheader .tocify-subheader { 44 | text-indent: 30px; 45 | } 46 | .tocify-subheader .tocify-subheader .tocify-subheader { 47 | text-indent: 40px; 48 | } 49 | .tocify-subheader .tocify-subheader .tocify-subheader .tocify-subheader { 50 | text-indent: 50px; 51 | } 52 | .tocify-subheader .tocify-subheader .tocify-subheader .tocify-subheader .tocify-subheader { 53 | text-indent: 60px; 54 | } 55 | 56 | /* Twitter Bootstrap Override Style */ 57 | .tocify .tocify-item > a, .tocify .nav-list .nav-header { 58 | margin: 0px; 59 | } 60 | 61 | /* Twitter Bootstrap Override Styles */ 62 | .tocify .tocify-item a, .tocify .list-group-item { 63 | padding: 5px; 64 | } 65 | 66 | .tocify .nav-pills > li { 67 | float: none; 68 | } 69 | 70 | /* We don't override the bootstrap colors because this gives us the 71 | wrong selection colors when using bootstrap themes 72 | 73 | .tocify .list-group-item:hover, .tocify .list-group-item:focus { 74 | background-color: #f5f5f5; 75 | } 76 | 77 | .tocify .list-group-item.active:hover, .tocify .list-group-item.active:focus { 78 | background-color: #428bca; 79 | } 80 | */ 81 | 82 | /* End Twitter Bootstrap Override Styles */ 83 | -------------------------------------------------------------------------------- /talks.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Talks" 3 | --- 4 | 5 | Talks related to purrr. 6 | 7 | ### Hands-on workshop 8 | 9 | I've given 2 - 4 hour hands-on workshops on purrr, e.g., at Latin-R in Buenos Aires, September 2018. I use these slides to complement the live coding. 10 | 11 | 12 | 13 | ### rstudio::conf 2017 14 | 15 | 16 | 17 | Scripts 18 | 19 | * Trump tweet preparation: [trump-tweets-setup.R](talks/trump-tweets-setup.R) 20 | * Trump tweet list-column example: [trump-tweets.R](talks/trump-tweets.R) 21 | * Game of Thrones POV character list-column example: [got-pov.R](talks/got-pov.R) 22 | * Gapminder nested data frame example *no separate script* 23 | * All 3 are now incorporated into [List-columns](ls13_list-columns.html). 24 | 25 | ### Data Rectangling 26 | 27 | Various versions given at EARL Boston 2015, Plotcon NY 2016, Johns Hopkins Biostat Seminar 28 | 29 | 30 | 31 | ### 2016 summer/fall teaching 32 | 33 | Selected slides I've shown before and during live-coding in class: 34 | 35 | 36 | -------------------------------------------------------------------------------- /talks/2018-09_latinr/.gitignore: -------------------------------------------------------------------------------- 1 | *.key 2 | *.pdf 3 | -------------------------------------------------------------------------------- /talks/2018-09_latinr/00_purrr-motivation.R: -------------------------------------------------------------------------------- 1 | #+ include = FALSE 2 | R.version 3 | 4 | #+ include = FALSE 5 | library(gapminder) 6 | library(tidyverse) 7 | 8 | #+ include = FALSE 9 | gapminder 10 | 11 | #+ include = FALSE 12 | gapminder %>% 13 | count(continent) 14 | 15 | #+ eval = FALSE, include = FALSE 16 | africa <- gapminder[gapminder$continent == "Africa", ] 17 | africa_mm <- max(africa$lifeExp) - min(africa$lifeExp) 18 | 19 | americas <- gapminder[gapminder$continent == "Americas", ] 20 | americas_mm <- max(americas$lifeExp) - min(americas$lifeExp) 21 | 22 | asia <- gapminder[gapminder$continent == "Asia", ] 23 | asia_mm <- max(asia$lifeExp) - min(africa$lifeExp) 24 | 25 | europe <- gapminder[gapminder$continent == "Europe", ] 26 | europe_mm <- max(europe$lifeExp) - min(europe$lifeExp) 27 | 28 | oceania <- gapminder[gapminder$continent == "Oceania", ] 29 | oceania_mm <- max(europe$lifeExp) - min(oceania$lifeExp) 30 | 31 | cbind( 32 | continent = c("Africa", "Asias", "Europe", "Oceania"), 33 | max_minus_min = c(africa_mm, americas_mm, asia_mm, 34 | europe_mm, oceania_mm) 35 | ) 36 | 37 | #+ eval = FALSE 38 | gapminder %>% 39 | group_by(continent) %>% 40 | summarize(max_minus_min = max(lifeExp) - min(lifeExp)) 41 | 42 | #+ eval = TRUE 43 | child <- c("Reed", "Wesley", "Eli", "Toby") 44 | age <- c( 14, 12, 12, 1) 45 | 46 | s <- rep_len("", length(child)) 47 | for (i in seq_along(s)) { 48 | s[i] <- paste(child[i], "is", age[i], "years old") 49 | } 50 | s 51 | 52 | paste(child, "is", age, "years old") 53 | glue::glue("{child} is {age} years old") 54 | 55 | #+ eval = FALSE 56 | install.packages("tidyverse") # <-- install purrr + much more 57 | install.packages("purrr") # <-- installs only purrr 58 | 59 | library(tidyverse) # <-- loads purrr + much more 60 | library(purrr) # <-- loads only purrr 61 | 62 | #+ eval = FALSE 63 | .x <- SOME VECTOR OR LIST 64 | out <- vector(mode = "list", length = length(.x)) 65 | for (i in seq_along(out)) { 66 | out[[i]] <- .f(.x[[i]]) 67 | } 68 | out 69 | -------------------------------------------------------------------------------- /talks/2018-09_latinr/01_list-exploration.R: -------------------------------------------------------------------------------- 1 | library(purrr) 2 | library(repurrrsive) 3 | help(package = "repurrrsive") 4 | 5 | length(got_chars) 6 | got_chars[[9]] 7 | View(got_chars[[9]]) 8 | 9 | 10 | View(got_chars) 11 | -------------------------------------------------------------------------------- /talks/2018-09_latinr/02_basic-map-workflow.R: -------------------------------------------------------------------------------- 1 | #+ include = FALSE 2 | library(purrr) 3 | library(repurrrsive) 4 | got_chars_named <- set_names(got_chars, map_chr(got_chars, "name")) 5 | 6 | #+ include = FALSE 7 | daenerys <- got_chars[[9]] 8 | ## View(daenerys) 9 | 10 | daenerys[["aliases"]] 11 | 12 | length(daenerys[["aliases"]]) 13 | 14 | #+ include = FALSE 15 | asha <- got_chars[[13]] 16 | ## View(asha) 17 | 18 | asha[["aliases"]] 19 | 20 | length(asha[["aliases"]]) 21 | 22 | #+ eval = FALSE, include = FALSE 23 | .x <- got_chars[[i]] 24 | 25 | length(.x[["aliases"]]) 26 | 27 | #+ include = FALSE, include = FALSE 28 | map(got_chars, ~ length(.x[["aliases"]])) 29 | 30 | #+ include = FALSE 31 | map_int(got_chars, ~ length(.x[["aliases"]])) 32 | 33 | #+ eval = FALSE, include = FALSE 34 | # What's each character's name? 35 | map(got_chars, ~.x[["name"]]) 36 | map(sw_people, ~.x[["name"]]) 37 | 38 | # What color is each SW character's hair? 39 | map(sw_people, ~ .x[["hair_color"]]) 40 | 41 | # Is the GoT character alive? 42 | map(got_chars, ~ .x[["alive"]]) 43 | 44 | # Is the SW character female? 45 | map(sw_people, ~ .x[["gender"]] == "female") 46 | 47 | # How heavy is each SW character? 48 | map(sw_people, ~ .x[["mass"]]) 49 | 50 | 51 | 52 | #+ eval = FALSE, include = FALSE 53 | # What's each character's name? 54 | map(got_chars, ~.x[["name"]]) 55 | map(sw_people, ~.x[["name"]]) 56 | 57 | # What color is each SW character's hair? 58 | map(sw_people, ~ .x[["hair_color"]]) 59 | 60 | # Is the GoT character alive? 61 | map(got_chars, ~ .x[["alive"]]) 62 | 63 | # Is the SW character female? 64 | map(sw_people, ~ .x[["gender"]] == "female") 65 | 66 | # How heavy is each SW character? 67 | map(sw_people, ~ .x[["mass"]]) 68 | 69 | #+ eval = FALSE, include = FALSE 70 | map_lgl(sw_people, ~ .x[["gender"]] == "female") 71 | 72 | map_int(got_chars, ~ length(.x$allegiances)) 73 | 74 | map_chr(got_chars, ~ .x[["name"]]) 75 | 76 | #+ eval = FALSE, include = FALSE 77 | map_chr(got_chars, ~ .x[["name"]]) 78 | 79 | map_chr(got_chars, "name") 80 | 81 | #+ eval = FALSE, include = FALSE 82 | map_??(got_chars, ??) 83 | map_??(sw_??, ??) 84 | 85 | #+ eval = FALSE, include = FALSE 86 | map(sw_people, ~ names(.x)) %>% 87 | flatten_chr() %>% 88 | table() 89 | map(got_chars, ~ names(.x)) %>% 90 | flatten_chr() %>% 91 | table() 92 | 93 | #+ eval = TRUE, include = FALSE 94 | map(sw_vehicles, "pilots", .default = NA) 95 | 96 | #+ eval = TRUE, include = FALSE 97 | map_chr(sw_vehicles, list("pilots", 1), .default = NA) 98 | 99 | #+ eval = FALSE, include = FALSE 100 | map(got_chars, c(14, 1)) 101 | map_chr(sw_vehicles, list("pilots", 1), .default = NA) 102 | 103 | #+ eval = TRUE, include = FALSE 104 | map_chr(got_chars, "name") 105 | 106 | got_chars_named <- set_names(got_chars, map_chr(got_chars, "name")) 107 | 108 | got_chars_named %>% 109 | map_lgl("alive") 110 | 111 | #+ include = TRUE 112 | allegiances <- map(got_chars_named, "allegiances") 113 | tibble::enframe(allegiances, value = "allegiances") 114 | 115 | #+ include = FALSE 116 | books <- map(got_chars_named, "books") 117 | map_chr(books[1:2], paste, collapse = ", ") 118 | map_chr(books[1:2], ~ paste(.x, collapse = ", ")) 119 | 120 | map(got_chars, o) 121 | -------------------------------------------------------------------------------- /talks/2018-09_latinr/03_gapminder-walk-map-dfrow.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(gapminder) 3 | 4 | countries <- c("Argentina", "Brazil", "Canada") 5 | gap_small <- gapminder %>% 6 | filter(country %in% countries, year > 1996) 7 | gap_small 8 | 9 | write_one <- function(x) { 10 | filename <- paste0(x, ".csv") 11 | dataset <- filter(gap_small, country == x) 12 | write_csv(dataset, filename) 13 | } 14 | 15 | walk(countries, write_one) 16 | list.files(pattern = "*.csv") 17 | 18 | library(tidyverse) 19 | 20 | csv_files <- list.files(pattern = "*.csv") 21 | csv_files 22 | 23 | map_dfr(csv_files, ~ read_csv(.x)) 24 | 25 | 26 | -------------------------------------------------------------------------------- /talks/2018-09_latinr/99_pipe.R: -------------------------------------------------------------------------------- 1 | #+ eval = FALSE 2 | filter(gapminder, country == “Canada”) 3 | gapminder %>% 4 | filter (country == “Canada”) 5 | 6 | mean(x) 7 | x %>% mean() 8 | -------------------------------------------------------------------------------- /talks/2018-09_latinr/README.md: -------------------------------------------------------------------------------- 1 | The `.R` files here are fodder for code snippets that appear in the slides accompanying a live purrr workshop. I render them with `reprex::reprex(venue = "rtf")`, then use in Keynote slides. 2 | -------------------------------------------------------------------------------- /talks/earl-examples.R: -------------------------------------------------------------------------------- 1 | #' --- 2 | #' output: rtf_document 3 | #' --- 4 | 5 | library(tidyverse) 6 | library(repurrrsive) 7 | library(gapminder) 8 | library(stringr) 9 | library(listviewer) 10 | 11 | titles <- got_chars %>% { 12 | tibble(name = map_chr(., "name"), 13 | titles = map(., "titles")) 14 | } 15 | titles 16 | View(titles) 17 | titles %>% 18 | filter(name %in% c("Daenerys Targaryen", 19 | "Asha Greyjoy", "Cersei Lannister")) %>% 20 | View() 21 | # {set_names(.$titles, .$name)} 22 | 23 | map(got_chars, "name") 24 | map_chr(got_chars, "name") 25 | map_df(got_chars, `[`, c("name", "culture", "gender", "born")) 26 | 27 | got_chars %>% { 28 | tibble(name = map_chr(., "name"), 29 | houses = map(., "allegiances")) 30 | } %>% 31 | filter(lengths(houses) > 1) %>% 32 | unnest() 33 | 34 | 35 | gap_nested <- gapminder %>% 36 | group_by(country, continent) %>% 37 | nest() 38 | gap_nested 39 | gap_nested %>% 40 | mutate(fit = map(data, ~ lm(lifeExp ~ year, data = .x))) %>% 41 | filter(continent == "Oceania") %>% 42 | mutate(coefs = map(fit, coef)) 43 | gap_nested %>% 44 | mutate(fit = map(data, ~ lm(lifeExp ~ year, data = .x))) %>% 45 | filter(continent == "Oceania") %>% 46 | mutate(coefs = map(fit, coef)) %>% 47 | mutate(intercept = map_dbl(coefs, 1), 48 | slope = map_dbl(coefs, 2)) %>% 49 | select(country, continent, intercept, slope) 50 | 51 | library(gapminder) 52 | library(tidyr) 53 | library(dplyr) 54 | gap_nested %>% 55 | group_by(country) %>% 56 | summarize(lifeExp = map_dbl(data, ~ mean(.x$lifeExp))) 57 | gapminder %>% 58 | group_by(country) %>% 59 | summarize(lifeExp = mean(lifeExp)) 60 | -------------------------------------------------------------------------------- /talks/got-pov.R: -------------------------------------------------------------------------------- 1 | library(repurrrsive) 2 | library(tidyverse) 3 | library(httr) 4 | library(stringr) 5 | library(here) 6 | 7 | pov <- set_names(map_int(got_chars, "id"), 8 | map_chr(got_chars, "name")) 9 | 10 | tail(pov, 5) 11 | 12 | ice <- pov %>% 13 | enframe(value = "id") 14 | ice 15 | 16 | ice_and_fire_url <- "https://anapioficeandfire.com/" 17 | if (file.exists(here("talks", "ice.rds"))) { 18 | ice <- readRDS(here("talks", "ice.rds")) 19 | } else { 20 | ice <- ice %>% 21 | mutate( 22 | response = map(id, 23 | ~ GET(ice_and_fire_url, 24 | path = c("api", "characters", .x))), 25 | stuff = map(response, ~ content(.x, as = "parsed", 26 | simplifyVector = TRUE)) 27 | ) %>% 28 | select(-id, -response) 29 | saveRDS(ice, here("talks", "ice.rds")) 30 | } 31 | ice 32 | 33 | str(ice$stuff[[9]], max.level = 1) ## list.len also good stuff! 34 | if (interactive()) { 35 | listviewer::jsonedit(ice$stuff[[2]], mode = "view", width = 500, height = 530) 36 | } 37 | 38 | ## at this point, I switched to a version of ice taken from the list in my 39 | ## repurrrsive package, where I have resolved, e.g., houses 40 | ## i.e. they are actual houses instead of API URLs 41 | 42 | ice2 <- tibble( 43 | name = map_chr(got_chars, "name"), 44 | stuff = got_chars 45 | ) 46 | ice2 47 | str(ice2$stuff[[9]], max.level = 1) ## list.len also good stuff! 48 | if (interactive()) { 49 | listviewer::jsonedit(ice2$stuff[[2]], mode = "view", width = 500, height = 530) 50 | } 51 | 52 | template <- "${name} was born ${born}." 53 | birth_announcements <- ice2 %>% 54 | mutate(birth = map_chr(stuff, str_interp, string = template)) %>% 55 | select(-stuff) 56 | if (interactive()) View(birth_announcements) 57 | birth_announcements 58 | 59 | allegiances <- ice2 %>% 60 | transmute(name, 61 | houses = map(stuff, "allegiances")) %>% 62 | filter(lengths(houses) > 1) %>% 63 | unnest() 64 | if (interactive()) View(allegiances) 65 | allegiances 66 | -------------------------------------------------------------------------------- /talks/ice.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/talks/ice.rds -------------------------------------------------------------------------------- /talks/trump-tweets-setup.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(here) 3 | 4 | if (!file.exists(here("trump_tweets_df.rda"))) { 5 | download.file("http://varianceexplained.org/files/trump_tweets_df.rda", 6 | "trump_tweets_df.rda") 7 | } 8 | 9 | load(here("trump_tweets_df.rda")) 10 | regex <- "badly|crazy|weak|spent|strong|dumb|joke|guns|funny|dead" 11 | tdf <- tibble( 12 | tweet = trump_tweets_df$text, 13 | source = trump_tweets_df$statusSource, 14 | created = trump_tweets_df$created, 15 | matches = gregexpr(regex, tweet), 16 | n_matches = map_int(matches, ~ sum(.x > 0)) 17 | ) 18 | tdf %>% 19 | count(n_matches) 20 | ## # A tibble: 4 x 2 21 | ## n_matches n 22 | ## 23 | ## 1 0 1429 24 | ## 2 1 72 25 | ## 3 2 10 26 | ## 4 3 1 27 | tdf %>% 28 | mutate(row_num = seq_len(nrow(.))) %>% 29 | group_by(n_matches) %>% 30 | top_n(2, created) %>% 31 | .$row_num 32 | ## [1] 1 2 5 6 198 347 919 33 | 34 | keepers <- tdf[c(1, 2, 5, 6, 198, 347, 919), ] 35 | if (interactive()) View(keepers) 36 | keepers %>% 37 | select(tweet, source, created) %>% 38 | write_csv(here("talks", "trump-tweets.csv")) 39 | -------------------------------------------------------------------------------- /talks/trump-tweets.R: -------------------------------------------------------------------------------- 1 | library(tidyverse) 2 | library(stringr) 3 | library(lubridate) 4 | library(here) 5 | 6 | tb_raw <- read_csv(here("talks", "trump-tweets.csv")) 7 | tb_raw 8 | if (interactive()) View(tb_raw) 9 | 10 | source_regex <- "android|iphone" 11 | tword_regex <- "badly|crazy|weak|spent|strong|dumb|joke|guns|funny|dead" 12 | 13 | tb <- tb_raw %>% 14 | mutate(source = str_extract(source, source_regex), 15 | twords = str_extract_all(tweet, tword_regex)) 16 | 17 | tb$tweet 18 | 19 | tb %>% select(-tweet) 20 | if (interactive()) View(tb) 21 | 22 | if (interactive()) str_view_all(tb$tweet, tword_regex) 23 | 24 | tb <- tb %>% 25 | mutate(n = lengths(twords), 26 | hour = hour(created), 27 | start = gregexpr(tword_regex, tweet)) 28 | # another possibilty that would require more processing 29 | # so less useful for a talk example 30 | # but more useful IRL: 31 | # str_locate_all(tweet, tword_regex)) 32 | 33 | if (interactive()) View(tb) 34 | 35 | tb$twords[c(4, 7)] 36 | tb$start[[7]] 37 | 38 | tb %>% 39 | filter(hour < 14, ## created before 2pm 40 | between(n, 1, 2), ## containing 1 or 2 Android words 41 | between(map_int(start, min), 0, 30)) ## Android word in 1st 30 chars 42 | 43 | tb %>% 44 | filter(map_lgl(twords, ~ all(c("strong", "weak") %in% .x))) 45 | -------------------------------------------------------------------------------- /talks/trump-tweets.csv: -------------------------------------------------------------------------------- 1 | tweet,source,created 2 | My economic policy speech will be carried live at 12:15 P.M. Enjoy!,"Twitter for Android",2016-08-08T15:20:44Z 3 | "Join me in Fayetteville, North Carolina tomorrow evening at 6pm. Tickets now available at: https://t.co/Z80d4MYIg8","Twitter for iPhone",2016-08-08T13:28:20Z 4 | "The media is going crazy. They totally distort so many things on purpose. Crimea, nuclear, ""the baby"" and so much more. Very dishonest!","Twitter for Android",2016-08-07T21:31:46Z 5 | I see where Mayor Stephanie Rawlings-Blake of Baltimore is pushing Crooked hard. Look at the job she has done in Baltimore. She is a joke!,"Twitter for Android",2016-08-07T13:49:29Z 6 | "Bernie Sanders started off strong, but with the selection of Kaine for V.P., is ending really weak. So much for a movement! TOTAL DISRESPECT","Twitter for Android",2016-07-24T11:25:06Z 7 | Crooked Hillary Clinton is unfit to serve as President of the U.S. Her temperament is weak and her opponents are strong. BAD JUDGEMENT!,"Twitter for Android",2016-07-06T04:36:31Z 8 | "The Cruz-Kasich pact is under great strain. This joke of a deal is falling apart, not being honored and almost dead. Very dumb!","Twitter for Android",2016-04-26T10:42:39Z 9 | -------------------------------------------------------------------------------- /trump_tweets_df.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jennybc/purrr-tutorial/c3c5c2ab7fcbb5021ea6503465da722aec260dde/trump_tweets_df.rda --------------------------------------------------------------------------------