├── .DS_Store ├── .Rbuildignore ├── .github ├── .gitignore ├── CODE_OF_CONDUCT.md ├── dependabot.yaml └── workflows │ ├── bookdown.yaml │ └── check-link-rot.yaml ├── .gitignore ├── Advanced-R-exercises.Rproj ├── Big-picture.Rmd ├── Conditions.Rmd ├── Control-flow.Rmd ├── DESCRIPTION ├── Debugging.Rmd ├── Environments.Rmd ├── Evaluation.Rmd ├── Expressions.Rmd ├── Function-factories.Rmd ├── Function-operators.Rmd ├── Functionals.Rmd ├── Functions.Rmd ├── Introduction.Rmd ├── Makevars ├── Names-values.Rmd ├── OO-tradeoffs.Rmd ├── Perf-improve.Rmd ├── Perf-measure.Rmd ├── Quotation.Rmd ├── R6.Rmd ├── README.md ├── Rcpp.Rmd ├── S3.Rmd ├── S4.Rmd ├── Subsetting.Rmd ├── Translation.Rmd ├── Vectors.Rmd ├── _bookdown.yml ├── _output.yml ├── assets ├── combined.jpg └── solutions_cover.png ├── base-types.Rmd ├── book.bib ├── chicago-fullnote-bibliography.csl ├── common.R ├── cover.png ├── diagrams ├── environments.graffle ├── environments │ ├── binding-2.png │ ├── binding.png │ ├── bindings.png │ ├── calling.png │ ├── closure-call.png │ ├── closure.png │ ├── execution.png │ ├── loop.png │ ├── namespace-bind.png │ ├── namespace-env.png │ ├── namespace.png │ ├── parents-empty.png │ ├── parents.png │ ├── recursive-1.png │ ├── recursive-2.png │ ├── search-path-2.png │ ├── search-path.png │ └── where-ex.png ├── evaluation.graffle ├── expressions.graffle ├── expressions │ ├── ambig-order.png │ ├── call-call.png │ ├── complicated.png │ ├── prefix.png │ └── simple.png ├── fp.graffle ├── fp.png ├── function-factories.graffle ├── function-factories │ ├── counter-1.png │ ├── counter-2.png │ ├── power-exec.png │ ├── power-full.png │ └── power-simple.png ├── functionals.graffle ├── functionals │ ├── invoke_map-recycle.png │ ├── invoke_map.png │ ├── map-arg-flipped.png │ ├── map-arg-recycle.png │ ├── map-arg.png │ ├── map-list.png │ ├── map.png │ ├── map2-arg.png │ ├── map2-recycle.png │ ├── map2.png │ ├── pmap-3.png │ ├── pmap-arg.png │ ├── pmap.png │ ├── reduce-arg.png │ ├── reduce-init.png │ ├── reduce.png │ ├── reduce2-init.png │ ├── reduce2.png │ ├── walk.png │ └── walk2.png ├── functions.graffle ├── functions │ ├── components.png │ ├── env.png │ └── first-class.png ├── name-value.graffle ├── name-value │ ├── binding-1.png │ ├── binding-2.png │ ├── binding-3.png │ ├── binding-f1.png │ ├── binding-f2.png │ ├── character-2.png │ ├── character.png │ ├── d-modify-c.png │ ├── d-modify-r.png │ ├── dataframe.png │ ├── e-modify-1.png │ ├── e-modify-2.png │ ├── e-self.png │ ├── l-modify-1.png │ ├── l-modify-2.png │ ├── list.png │ ├── unbinding-1.png │ ├── unbinding-2.png │ ├── unbinding-3.png │ ├── v-inplace-1.png │ └── v-inplace-2.png ├── oo-venn.png ├── oo.graffle ├── quotation.graffle ├── quotation │ ├── bang-bang-bang.png │ ├── bang-bang.png │ ├── fun.png │ ├── infix-bad.png │ ├── infix.png │ └── simple.png ├── s4.graffle ├── s4 │ ├── Matrix.png │ ├── emoji.png │ ├── multiple-all.png │ ├── multiple-ambig-2.png │ ├── multiple-ambig.png │ ├── multiple-any.png │ ├── multiple-multiple.png │ ├── multiple.png │ ├── single-any.png │ ├── single-multiple.png │ ├── single-single-ambig.png │ ├── single-single.png │ └── single.png ├── subsetting.graffle ├── subsetting │ ├── train-multiple.png │ ├── train-single.png │ └── train.png ├── vectors.graffle └── vectors │ ├── atomic.png │ ├── attr-dim-1.png │ ├── attr-dim-2.png │ ├── attr-names-1.png │ ├── attr-names-2.png │ ├── attr.png │ ├── data-frame-1.png │ ├── data-frame-2.png │ ├── data-frame-list.png │ ├── data-frame-matrix.png │ ├── factor.png │ ├── list-c.png │ ├── list-recursive.png │ ├── list.png │ ├── summary-tree-atomic.png │ ├── summary-tree-s3-1.png │ ├── summary-tree-s3-2.png │ └── summary-tree.png ├── dsl-html-attributes.R ├── index.Rmd ├── packages.bib ├── preamble.tex ├── profiling-exercises.R ├── profvis-script.R └── style.css /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/.DS_Store -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^\.github$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at patilindrajeet.science@gmail.com. 63 | All complaints will be reviewed and investigated promptly and fairly. 64 | 65 | All community leaders are obligated to respect the privacy and security of the 66 | reporter of any incident. 67 | 68 | ## Enforcement Guidelines 69 | 70 | Community leaders will follow these Community Impact Guidelines in determining 71 | the consequences for any action they deem in violation of this Code of Conduct: 72 | 73 | ### 1. Correction 74 | 75 | **Community Impact**: Use of inappropriate language or other behavior deemed 76 | unprofessional or unwelcome in the community. 77 | 78 | **Consequence**: A private, written warning from community leaders, providing 79 | clarity around the nature of the violation and an explanation of why the 80 | behavior was inappropriate. A public apology may be requested. 81 | 82 | ### 2. Warning 83 | 84 | **Community Impact**: A violation through a single incident or series of 85 | actions. 86 | 87 | **Consequence**: A warning with consequences for continued behavior. No 88 | interaction with the people involved, including unsolicited interaction with 89 | those enforcing the Code of Conduct, for a specified period of time. This 90 | includes avoiding interactions in community spaces as well as external channels 91 | like social media. Violating these terms may lead to a temporary or permanent 92 | ban. 93 | 94 | ### 3. Temporary Ban 95 | 96 | **Community Impact**: A serious violation of community standards, including 97 | sustained inappropriate behavior. 98 | 99 | **Consequence**: A temporary ban from any sort of interaction or public 100 | communication with the community for a specified period of time. No public or 101 | private interaction with the people involved, including unsolicited interaction 102 | with those enforcing the Code of Conduct, is allowed during this period. 103 | Violating these terms may lead to a permanent ban. 104 | 105 | ### 4. Permanent Ban 106 | 107 | **Community Impact**: Demonstrating a pattern of violation of community 108 | standards, including sustained inappropriate behavior, harassment of an 109 | individual, or aggression toward or disparagement of classes of individuals. 110 | 111 | **Consequence**: A permanent ban from any sort of public interaction within the 112 | community. 113 | 114 | ## Attribution 115 | 116 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 117 | version 2.1, available at 118 | . 119 | 120 | Community Impact Guidelines were inspired by 121 | [Mozilla's code of conduct enforcement ladder][https://github.com/mozilla/inclusion]. 122 | 123 | For answers to common questions about this code of conduct, see the FAQ at 124 | . Translations are available at . 125 | 126 | [homepage]: https://www.contributor-covenant.org 127 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | # Keep dependencies for GitHub Actions up-to-date 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | -------------------------------------------------------------------------------- /.github/workflows/bookdown.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | schedule: 9 | # * is a special character in YAML so you have to quote this string 10 | # Trigger once a week, on a Sunday (0) 11 | - cron: "0 0 * * 0" 12 | workflow_dispatch: 13 | 14 | name: bookdown 15 | 16 | jobs: 17 | bookdown: 18 | runs-on: ubuntu-latest 19 | 20 | env: 21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - uses: r-lib/actions/setup-pandoc@v2 27 | with: 28 | pandoc-version: "latest" 29 | 30 | - uses: r-lib/actions/setup-tinytex@v2 31 | - run: | 32 | tlmgr --version 33 | 34 | - uses: r-lib/actions/setup-r@v2 35 | with: 36 | use-public-rspm: true 37 | 38 | - uses: r-lib/actions/setup-r-dependencies@v2 39 | 40 | - name: Cache bookdown results 41 | uses: actions/cache@v4 42 | with: 43 | path: _bookdown_files 44 | key: bookdown-${{ hashFiles('**/*Rmd') }} 45 | restore-keys: bookdown- 46 | 47 | - name: Build site 48 | run: | 49 | library(bookdown) 50 | render_book("index.Rmd", output_format = "all", quiet = FALSE) 51 | shell: Rscript {0} 52 | 53 | - name: Deploy to GitHub pages 🚀 54 | if: github.event_name != 'pull_request' 55 | uses: JamesIves/github-pages-deploy-action@v4.7.3 56 | with: 57 | branch: gh-pages 58 | folder: _book 59 | -------------------------------------------------------------------------------- /.github/workflows/check-link-rot.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main, master] 4 | pull_request: 5 | branches: [main, master] 6 | schedule: 7 | # * is a special character in YAML so you have to quote this string 8 | # Trigger once a week at 00:00 on Sunday 9 | - cron: "0 0 * * SUN" 10 | 11 | name: check-link-rot 12 | 13 | jobs: 14 | check-link-rot: 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | R_KEEP_PKG_SOURCE: yes 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: r-lib/actions/setup-pandoc@v2 23 | with: 24 | pandoc-version: "3.1.8" 25 | 26 | - uses: r-lib/actions/setup-r@v2 27 | with: 28 | r-version: "devel" 29 | http-user-agent: "release" 30 | use-public-rspm: true 31 | 32 | - uses: r-lib/actions/setup-r-dependencies@v2 33 | with: 34 | pak-version: devel 35 | dependencies: '"hard"' 36 | extra-packages: | 37 | any::rcmdcheck 38 | any::urlchecker 39 | needs: check 40 | 41 | - name: Run URL checker 42 | run: | 43 | options(crayon.enabled = TRUE) 44 | rotten_links <- urlchecker::url_check(progress = FALSE) 45 | print(rotten_links) 46 | if (length(rotten_links$URL) > 0L) { 47 | cli::cli_abort("Some URLs are outdated and need to be updated.") 48 | } 49 | shell: Rscript {0} 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | *_cache/ 32 | /cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | 38 | # R Environment Variables 39 | .Renviron 40 | 41 | rsconnect 42 | .github/.DS_Store 43 | _bookdown_files 44 | .DS_Store 45 | _book 46 | _main.* 47 | -------------------------------------------------------------------------------- /Advanced-R-exercises.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: No 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Website 19 | 20 | QuitChildProcessesOnExit: Yes 21 | DisableExecuteRprofile: Yes 22 | -------------------------------------------------------------------------------- /Big-picture.Rmd: -------------------------------------------------------------------------------- 1 | # Big Picture 2 | 3 | No exercises. 4 | -------------------------------------------------------------------------------- /Control-flow.Rmd: -------------------------------------------------------------------------------- 1 | # Control flow 2 | 3 | ```{r Control-flow-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | ## Choices (Exercises 5.2.4) 8 | 9 | **Q1.** What type of vector does each of the following calls to `ifelse()` return? 10 | 11 | ```{r Control-flow-2, eval = FALSE} 12 | ifelse(TRUE, 1, "no") 13 | ifelse(FALSE, 1, "no") 14 | ifelse(NA, 1, "no") 15 | ``` 16 | 17 | Read the documentation and write down the rules in your own words. 18 | 19 | **A1.** Here are the rules about what a call to `ifelse()` might return: 20 | 21 | - It is type unstable, i.e. the type of return will depend on the type of which condition is true (`yes` or `no`, i.e.): 22 | 23 | ```{r Control-flow-3} 24 | ifelse(TRUE, 1, "no") # `numeric` returned 25 | ifelse(FALSE, 1, "no") # `character` returned 26 | ``` 27 | 28 | - It works only for cases where `test` argument evaluates to a `logical` type: 29 | 30 | ```{r Control-flow-4} 31 | ifelse(NA_real_, 1, "no") 32 | ifelse(NaN, 1, "no") 33 | ``` 34 | 35 | - If `test` is argument is of logical type, but `NA`, it will return `NA`: 36 | 37 | ```{r Control-flow-5} 38 | ifelse(NA, 1, "no") 39 | ``` 40 | 41 | - If the `test` argument doesn't resolve to `logical` type, it will try to coerce the output to a `logical` type: 42 | 43 | ```{r Control-flow-6} 44 | # will work 45 | ifelse("TRUE", 1, "no") 46 | ifelse("false", 1, "no") 47 | 48 | # won't work 49 | ifelse("tRuE", 1, "no") 50 | ifelse(NaN, 1, "no") 51 | ``` 52 | 53 | This is also clarified in the docs for this function: 54 | 55 | > A vector of the same length and attributes (including dimensions and `"class"`) as `test` and data values from the values of `yes` or `no`. The mode of the answer will be coerced from logical to accommodate first any values taken from yes and then any values taken from `no`. 56 | 57 | **Q2.** Why does the following code work? 58 | 59 | ```{r Control-flow-7} 60 | x <- 1:10 61 | if (length(x)) "not empty" else "empty" 62 | 63 | x <- numeric() 64 | if (length(x)) "not empty" else "empty" 65 | ``` 66 | 67 | **A2.** The code works because the conditional expressions in `if()` - even though of `numeric` type - can be successfully coerced to a `logical` type. 68 | 69 | ```{r Control-flow-8} 70 | as.logical(length(1:10)) 71 | 72 | as.logical(length(numeric())) 73 | ``` 74 | 75 | ## Loops (Exercises 5.3.3) 76 | 77 | **Q1.** Why does this code succeed without errors or warnings? 78 | 79 | ```{r Control-flow-9, results = FALSE} 80 | x <- numeric() 81 | out <- vector("list", length(x)) 82 | for (i in 1:length(x)) { 83 | out[i] <- x[i]^2 84 | } 85 | out 86 | ``` 87 | 88 | **A1.** This works because `1:length(x)` works in both positive and negative directions. 89 | 90 | ```{r Control-flow-10} 91 | 1:2 92 | 1:0 93 | 1:-3 94 | ``` 95 | 96 | In this case, since `x` is of length `0`, `i` will go from `1` to `0`. 97 | 98 | Additionally, since out-of-bound (OOB) value for atomic vectors is `NA`, all related operations with OOB values will also produce `NA`. 99 | 100 | ```{r Control-flow-11} 101 | x <- numeric() 102 | out <- vector("list", length(x)) 103 | 104 | for (i in 1:length(x)) { 105 | print(paste("i:", i, ", x[i]:", x[i], ", out[i]:", out[i])) 106 | 107 | out[i] <- x[i]^2 108 | } 109 | 110 | out 111 | ``` 112 | 113 | A way to do avoid this unintended behavior is to use `seq_along()` instead: 114 | 115 | ```{r Control-flow-12} 116 | x <- numeric() 117 | out <- vector("list", length(x)) 118 | 119 | for (i in seq_along(x)) { 120 | out[i] <- x[i]^2 121 | } 122 | 123 | out 124 | ``` 125 | 126 | **Q2.** When the following code is evaluated, what can you say about the vector being iterated? 127 | 128 | ```{r Control-flow-13} 129 | xs <- c(1, 2, 3) 130 | for (x in xs) { 131 | xs <- c(xs, x * 2) 132 | } 133 | xs 134 | ``` 135 | 136 | **A2.** The iterator variable `x` initially takes all values of the vector `xs`. We can check this by printing `x` for each iteration: 137 | 138 | ```{r Control-flow-14} 139 | xs <- c(1, 2, 3) 140 | for (x in xs) { 141 | cat("x:", x, "\n") 142 | xs <- c(xs, x * 2) 143 | cat("xs:", paste(xs), "\n") 144 | } 145 | ``` 146 | 147 | It is worth noting that `x` is not updated *after* each iteration; otherwise, it will take increasingly bigger values of `xs`, and the loop will never end executing. 148 | 149 | **Q3.** What does the following code tell you about when the index is updated? 150 | 151 | ```{r Control-flow-15} 152 | for (i in 1:3) { 153 | i <- i * 2 154 | print(i) 155 | } 156 | ``` 157 | 158 | **A3.** In a `for()` loop the index is updated in the **beginning** of each iteration. Otherwise, we will encounter an infinite loop. 159 | 160 | ```{r Control-flow-16} 161 | for (i in 1:3) { 162 | cat("before: ", i, "\n") 163 | i <- i * 2 164 | cat("after: ", i, "\n") 165 | } 166 | ``` 167 | 168 | Also, worth contrasting the behavior of `for()` loop with that of `while()` loop: 169 | 170 | ```{r Control-flow-17} 171 | i <- 1 172 | while (i < 4) { 173 | cat("before: ", i, "\n") 174 | i <- i * 2 175 | cat("after: ", i, "\n") 176 | } 177 | ``` 178 | 179 | ## Session information 180 | 181 | ```{r Control-flow-18} 182 | sessioninfo::session_info(include_base = TRUE) 183 | ``` 184 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Type: Book 2 | Package: Advanced-R-exercises 3 | Title: Solutions to exercises from Hadley Wickham's _Advanced R_ (2nd 4 | edition) 5 | Version: 0.1.0 6 | Authors@R: 7 | person("Indrajeet", "Patil", , "patilindrajeet.science@gmail.com", role = c("cre", "aut"), 8 | comment = c(ORCID = "0000-0003-1995-6531")) 9 | Description: This book is solutions manual for Hadley Wickham's 'Advanced 10 | R' (2nd edition). This is not a package, and the DESCRIPTION file is 11 | included to make it easier to install required packages in GitHub 12 | action workflows. 13 | License: CC0 1.0 Universal 14 | URL: https://github.com/IndrajeetPatil/advanced-r-exercises, 15 | https://indrajeetpatil.github.io/advanced-r-exercises/ 16 | BugReports: https://github.com/IndrajeetPatil/advanced-r-exercises/issues 17 | Depends: 18 | R (>= 4.1.0) 19 | Imports: 20 | bench, 21 | biglm, 22 | emoji, 23 | fastmatch, 24 | fasttime, 25 | gapminder, 26 | ggbeeswarm, 27 | lobstr, 28 | MASS, 29 | memoise, 30 | profvis, 31 | Rcpp, 32 | RcppEigen, 33 | RSQLite, 34 | sloop, 35 | speedglm, 36 | testthat, 37 | tidyverse, 38 | zeallot 39 | Suggests: 40 | bookdown, 41 | bslib, 42 | desc, 43 | downlit, 44 | jsonlite, 45 | knitr, 46 | png, 47 | sessioninfo, 48 | xml2 49 | Encoding: UTF-8 50 | LazyData: false 51 | Roxygen: list(markdown = TRUE) 52 | RoxygenNote: 7.3.2 53 | -------------------------------------------------------------------------------- /Debugging.Rmd: -------------------------------------------------------------------------------- 1 | # Debugging 2 | 3 | No exercises. 4 | -------------------------------------------------------------------------------- /Environments.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: 3 | pdf_document: default 4 | html_document: default 5 | --- 6 | # Environments 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading the needed libraries: 15 | 16 | ```{r Environments-1, warning=FALSE, message=FALSE} 17 | library(rlang, warn.conflicts = FALSE) 18 | ``` 19 | 20 | ## Environment basics (Exercises 7.2.7) 21 | 22 | **Q1.** List three ways in which an environment differs from a list. 23 | 24 | **A1.** As mentioned in the book, here are a few ways in which environments differ from lists: 25 | 26 | | Property | List | Environment | 27 | | :-----------------: | :-----: | :---------: | 28 | | semantics | value | reference | 29 | | data structure | linear | non-linear | 30 | | duplicated names | allowed | not allowed | 31 | | can have parents? | false | true | 32 | | can contain itself? | false | true | 33 | 34 | **Q2.** Create an environment as illustrated by this picture. 35 | 36 | ```{r Environments-2, echo = FALSE} 37 | knitr::include_graphics("diagrams/environments/recursive-1.png") 38 | ``` 39 | 40 | **A2.** Creating the environment illustrated in the picture: 41 | 42 | ```{r Environments-3} 43 | library(rlang) 44 | 45 | e <- env() 46 | e$loop <- e 47 | env_print(e) 48 | ``` 49 | 50 | The binding `loop` should have the same memory address as the environment `e`: 51 | 52 | ```{r Environments-4} 53 | lobstr::ref(e$loop) 54 | ``` 55 | 56 | **Q3.** Create a pair of environments as illustrated by this picture. 57 | 58 | ```{r Environments-5, echo = FALSE} 59 | knitr::include_graphics("diagrams/environments/recursive-2.png") 60 | ``` 61 | 62 | **A3.** Creating the specified environment: 63 | 64 | ```{r Environments-6} 65 | e1 <- env() 66 | e2 <- env() 67 | 68 | e1$loop <- e2 69 | e2$deloop <- e1 70 | 71 | # following should be the same 72 | lobstr::obj_addrs(list(e1, e2$deloop)) 73 | lobstr::obj_addrs(list(e2, e1$loop)) 74 | ``` 75 | 76 | **Q4.** Explain why `e[[1]]` and `e[c("a", "b")]` don't make sense when `e` is an environment. 77 | 78 | **A4.** An environment is a non-linear data structure, and has no concept of ordered elements. Therefore, indexing it (e.g. `e[[1]]`) doesn't make sense. 79 | 80 | Subsetting a list or a vector returns a subset of the underlying data structure. For example, subsetting a vector returns another vector. But it's unclear what subsetting an environment (e.g. `e[c("a", "b")]`) should return because there is no data structure to contain its returns. It can't be another environment since environments have reference semantics. 81 | 82 | **Q5.** Create a version of `env_poke()` that will only bind new names, never re-bind old names. Some programming languages only do this, and are known as [single assignment languages](https://en.wikipedia.org/wiki/Assignment_(computer_science)#Single_assignment). 83 | 84 | **A5.** Create a version of `env_poke()` that doesn't allow re-binding old names: 85 | 86 | ```{r Environments-7} 87 | env_poke2 <- function(env, nm, value) { 88 | if (env_has(env, nm)) { 89 | abort("Can't re-bind existing names.") 90 | } 91 | 92 | env_poke(env, nm, value) 93 | } 94 | ``` 95 | 96 | Making sure that it behaves as expected: 97 | 98 | ```{r Environments-8, error=TRUE} 99 | e <- env(a = 1, b = 2, c = 3) 100 | 101 | # re-binding old names not allowed 102 | env_poke2(e, "b", 4) 103 | 104 | # binding new names allowed 105 | env_poke2(e, "d", 8) 106 | e$d 107 | ``` 108 | 109 | Contrast this behavior with the following: 110 | 111 | ```{r Environments-9} 112 | e <- env(a = 1, b = 2, c = 3) 113 | 114 | e$b 115 | 116 | # re-binding old names allowed 117 | env_poke(e, "b", 4) 118 | e$b 119 | ``` 120 | 121 | **Q6.** What does this function do? How does it differ from `<<-` and why might you prefer it? 122 | 123 | ```{r Environments-10, error = TRUE} 124 | rebind <- function(name, value, env = caller_env()) { 125 | if (identical(env, empty_env())) { 126 | stop("Can't find `", name, "`", call. = FALSE) 127 | } else if (env_has(env, name)) { 128 | env_poke(env, name, value) 129 | } else { 130 | rebind(name, value, env_parent(env)) 131 | } 132 | } 133 | rebind("a", 10) 134 | a <- 5 135 | rebind("a", 10) 136 | a 137 | ``` 138 | 139 | **A6.** The downside of `<<-` is that it will create a new binding if it doesn't exist in the given environment, which is something that we may not wish: 140 | 141 | ```{r Environments-11} 142 | # `x` doesn't exist 143 | exists("x") 144 | 145 | # so `<<-` will create one for us 146 | { 147 | x <<- 5 148 | } 149 | 150 | # in the global environment 151 | env_has(global_env(), "x") 152 | x 153 | ``` 154 | 155 | But `rebind()` function will let us know if the binding doesn't exist, which is much safer: 156 | 157 | ```{r Environments-12, error=TRUE} 158 | rebind <- function(name, value, env = caller_env()) { 159 | if (identical(env, empty_env())) { 160 | stop("Can't find `", name, "`", call. = FALSE) 161 | } else if (env_has(env, name)) { 162 | env_poke(env, name, value) 163 | } else { 164 | rebind(name, value, env_parent(env)) 165 | } 166 | } 167 | 168 | # doesn't exist 169 | exists("abc") 170 | 171 | # so function will produce an error instead of creating it for us 172 | rebind("abc", 10) 173 | 174 | # but it will work as expected when the variable already exists 175 | abc <- 5 176 | rebind("abc", 10) 177 | abc 178 | ``` 179 | 180 | ## Recursing over environments (Exercises 7.3.1) 181 | 182 | **Q1.** Modify `where()` to return _all_ environments that contain a binding for `name`. Carefully think through what type of object the function will need to return. 183 | 184 | **A1.** Here is a modified version of `where()` that returns _all_ environments that contain a binding for `name`. 185 | 186 | Since we anticipate more than one environment, we dynamically update a list each time an environment with the specified binding is found. It is important to initialize to an empty list since that signifies that given binding is not found in any of the environments. 187 | 188 | ```{r Environments-13} 189 | where <- function(name, env = caller_env()) { 190 | env_list <- list() 191 | 192 | while (!identical(env, empty_env())) { 193 | if (env_has(env, name)) { 194 | env_list <- append(env_list, env) 195 | } 196 | 197 | env <- env_parent(env) 198 | } 199 | 200 | return(env_list) 201 | } 202 | ``` 203 | 204 | Let's try it out: 205 | 206 | ```{r Environments-14} 207 | where("yyy") 208 | 209 | x <- 5 210 | where("x") 211 | 212 | where("mean") 213 | 214 | library(dplyr, warn.conflicts = FALSE) 215 | where("filter") 216 | detach("package:dplyr") 217 | ``` 218 | 219 | **Q2.** Write a function called `fget()` that finds only function objects. It should have two arguments, `name` and `env`, and should obey the regular scoping rules for functions: if there's an object with a matching name that's not a function, look in the parent. For an added challenge, also add an `inherits` argument which controls whether the function recurses up the parents or only looks in one environment. 220 | 221 | **A2.** Here is a function that recursively looks for function objects: 222 | 223 | ```{r Environments-15} 224 | fget <- function(name, env = caller_env(), inherits = FALSE) { 225 | # we need only function objects 226 | f_value <- mget(name, 227 | envir = env, 228 | mode = "function", 229 | inherits = FALSE, # since we have our custom argument 230 | ifnotfound = list(NULL) 231 | ) 232 | 233 | if (!is.null(f_value[[1]])) { 234 | # success case 235 | f_value[[1]] 236 | } else { 237 | if (inherits && !identical(env, empty_env())) { 238 | # recursive case 239 | env <- env_parent(env) 240 | fget(name, env, inherits = TRUE) 241 | } else { 242 | # base case 243 | stop("No function objects with matching name was found.", call. = FALSE) 244 | } 245 | } 246 | } 247 | ``` 248 | 249 | Let's try it out: 250 | 251 | ```{r Environments-16, error=TRUE} 252 | fget("mean", inherits = FALSE) 253 | 254 | fget("mean", inherits = TRUE) 255 | 256 | mean <- 5 257 | fget("mean", inherits = FALSE) 258 | 259 | mean <- function() NULL 260 | fget("mean", inherits = FALSE) 261 | rm("mean") 262 | ``` 263 | 264 | ## Special environments (Exercises 7.4.5) 265 | 266 | **Q1.** How is `search_envs()` different from `env_parents(global_env())`? 267 | 268 | **A1.** The `search_envs()` lists a chain of environments currently attached to the search path and contains exported functions from these packages. The search path always ends at the `{base}` package environment. The search path also includes the global environment. 269 | 270 | ```{r Environments-17} 271 | search_envs() 272 | ``` 273 | 274 | The `env_parents()` lists all parent environments up until the empty environment. Of course, the global environment itself is not included in this list. 275 | 276 | ```{r Environments-18} 277 | env_parents(global_env()) 278 | ``` 279 | 280 | **Q2.** Draw a diagram that shows the enclosing environments of this function: 281 | 282 | ```{r Environments-19, eval = FALSE} 283 | f1 <- function(x1) { 284 | f2 <- function(x2) { 285 | f3 <- function(x3) { 286 | x1 + x2 + x3 287 | } 288 | f3(3) 289 | } 290 | f2(2) 291 | } 292 | f1(1) 293 | ``` 294 | 295 | **A2.** I don't have access to the graphics software used to create diagrams in the book, so I am linking the diagram from the [official solutions manual](https://advanced-r-solutions.rbind.io/environments.html#special-environments), where you will also find a more detailed description for the figure: 296 | 297 | ```{r Environments-20, echo=FALSE, out.width = '100%', eval=!knitr::is_latex_output()} 298 | knitr::include_graphics("https://raw.githubusercontent.com/Tazinho/Advanced-R-Solutions/main/images/environments/function_environments_corrected.png", auto_pdf = TRUE) 299 | ``` 300 | 301 | **Q3.** Write an enhanced version of `str()` that provides more information about functions. Show where the function was found and what environment it was defined in. 302 | 303 | **A3.** To write the required function, we can first re-purpose the `fget()` function we wrote above to return the environment in which it was found and its enclosing environment: 304 | 305 | ```{r Environments-21} 306 | fget2 <- function(name, env = caller_env()) { 307 | # we need only function objects 308 | f_value <- mget(name, 309 | envir = env, 310 | mode = "function", 311 | inherits = FALSE, 312 | ifnotfound = list(NULL) 313 | ) 314 | 315 | if (!is.null(f_value[[1]])) { 316 | # success case 317 | list( 318 | "where" = env, 319 | "enclosing" = fn_env(f_value[[1]]) 320 | ) 321 | } else { 322 | if (!identical(env, empty_env())) { 323 | # recursive case 324 | env <- env_parent(env) 325 | fget2(name, env) 326 | } else { 327 | # base case 328 | stop("No function objects with matching name was found.", call. = FALSE) 329 | } 330 | } 331 | } 332 | ``` 333 | 334 | Let's try it out: 335 | 336 | ```{r Environments-22} 337 | fget2("mean") 338 | 339 | mean <- function() NULL 340 | fget2("mean") 341 | rm("mean") 342 | ``` 343 | 344 | We can now write the new version of `str()` as a wrapper around this function. We only need to foresee that the users might enter the function name either as a symbol or a string. 345 | 346 | ```{r Environments-23} 347 | str_function <- function(.f) { 348 | fget2(as_string(ensym(.f))) 349 | } 350 | ``` 351 | 352 | Let's first try it with `base::mean()`: 353 | 354 | ```{r Environments-24} 355 | str_function(mean) 356 | 357 | str_function("mean") 358 | ``` 359 | 360 | And then with our variant present in the global environment: 361 | 362 | ```{r Environments-25} 363 | mean <- function() NULL 364 | 365 | str_function(mean) 366 | 367 | str_function("mean") 368 | 369 | rm("mean") 370 | ``` 371 | 372 | ## Call stacks (Exercises 7.5.5) 373 | 374 | **Q1.** Write a function that lists all the variables defined in the environment in which it was called. It should return the same results as `ls()`. 375 | 376 | **A1.** Here is a function that lists all the variables defined in the environment in which it was called: 377 | 378 | ```{r Environments-26} 379 | # let's first remove everything that exists in the global environment right now 380 | # to test with only newly defined objects 381 | rm(list = ls()) 382 | rm(.Random.seed, envir = globalenv()) 383 | 384 | ls_env <- function(env = rlang::caller_env()) { 385 | sort(rlang::env_names(env)) 386 | } 387 | ``` 388 | 389 | The workhorse here is `rlang::caller_env()`, so let's also have a look at its definition: 390 | 391 | ```{r Environments-27} 392 | rlang::caller_env 393 | ``` 394 | 395 | Let's try it out: 396 | 397 | - In global environment: 398 | 399 | ```{r Environments-28} 400 | x <- "a" 401 | y <- 1 402 | 403 | ls_env() 404 | 405 | ls() 406 | ``` 407 | 408 | - In function environment: 409 | 410 | ```{r Environments-29} 411 | foo <- function() { 412 | a <- "x" 413 | b <- 2 414 | 415 | print(ls_env()) 416 | 417 | print(ls()) 418 | } 419 | 420 | foo() 421 | ``` 422 | 423 | ## Session information 424 | 425 | ```{r Environments-30} 426 | sessioninfo::session_info(include_base = TRUE) 427 | ``` 428 | 429 | -------------------------------------------------------------------------------- /Evaluation.Rmd: -------------------------------------------------------------------------------- 1 | # Evaluation 2 | 3 | ```{r Evaluation-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | Attaching the needed libraries: 8 | 9 | ```{r Evaluation-2, warning=FALSE, message=FALSE} 10 | library(rlang) 11 | ``` 12 | 13 | ## Evaluation basics (Exercises 20.2.4) 14 | 15 | --- 16 | 17 | **Q1.** Carefully read the documentation for `source()`. What environment does it use by default? What if you supply `local = TRUE`? How do you provide a custom environment? 18 | 19 | **A1.** The parameter `local` for `source()` decides the environment in which the parsed expressions are evaluated. 20 | 21 | By default `local = FALSE`, this corresponds to the user's workspace (the global environment, i.e.). 22 | 23 | ```{r Evaluation-3} 24 | withr::with_tempdir( 25 | code = { 26 | f <- tempfile() 27 | writeLines("rlang::env_print()", f) 28 | foo <- function() source(f, local = FALSE) 29 | foo() 30 | } 31 | ) 32 | ``` 33 | 34 | If `local = TRUE`, then the environment from which `source()` is called will be used. 35 | 36 | ```{r Evaluation-4} 37 | withr::with_tempdir( 38 | code = { 39 | f <- tempfile() 40 | writeLines("rlang::env_print()", f) 41 | foo <- function() source(f, local = TRUE) 42 | foo() 43 | } 44 | ) 45 | ``` 46 | 47 | To specify a custom environment, the `sys.source()` function can be used, which provides an `envir` parameter. 48 | 49 | --- 50 | 51 | **Q2.** Predict the results of the following lines of code: 52 | 53 | ```{r Evaluation-5, eval = FALSE} 54 | eval(expr(eval(expr(eval(expr(2 + 2)))))) 55 | eval(eval(expr(eval(expr(eval(expr(2 + 2))))))) 56 | expr(eval(expr(eval(expr(eval(expr(2 + 2))))))) 57 | ``` 58 | 59 | **A2.** Correctly predicted 😉 60 | 61 | ```{r Evaluation-6} 62 | eval(expr(eval(expr(eval(expr(2 + 2)))))) 63 | 64 | eval(eval(expr(eval(expr(eval(expr(2 + 2))))))) 65 | 66 | expr(eval(expr(eval(expr(eval(expr(2 + 2))))))) 67 | ``` 68 | 69 | --- 70 | 71 | **Q3.** Fill in the function bodies below to re-implement `get()` using `sym()` and `eval()`, and `assign()` using `sym()`, `expr()`, and `eval()`. Don't worry about the multiple ways of choosing an environment that `get()` and `assign()` support; assume that the user supplies it explicitly. 72 | 73 | ```{r Evaluation-7} 74 | # name is a string 75 | get2 <- function(name, env) {} 76 | assign2 <- function(name, value, env) {} 77 | ``` 78 | 79 | **A3.** Here are the required re-implementations: 80 | 81 | - `get()` 82 | 83 | ```{r Evaluation-8} 84 | get2 <- function(name, env = caller_env()) { 85 | name <- sym(name) 86 | eval(name, env) 87 | } 88 | 89 | x <- 2 90 | 91 | get2("x") 92 | get("x") 93 | 94 | y <- 1:4 95 | assign("y[1]", 2) 96 | 97 | get2("y[1]") 98 | get("y[1]") 99 | ``` 100 | 101 | - `assign()` 102 | 103 | ```{r Evaluation-9} 104 | assign2 <- function(name, value, env = caller_env()) { 105 | name <- sym(name) 106 | eval(expr(!!name <- !!value), env) 107 | } 108 | 109 | assign("y1", 4) 110 | y1 111 | 112 | assign2("y2", 4) 113 | y2 114 | ``` 115 | 116 | --- 117 | 118 | **Q4.** Modify `source2()` so it returns the result of *every* expression, not just the last one. Can you eliminate the for loop? 119 | 120 | **A4.** We can use `purrr::map()` to iterate over every expression and return result of every expression: 121 | 122 | ```{r Evaluation-10} 123 | source2 <- function(path, env = caller_env()) { 124 | file <- paste(readLines(path, warn = FALSE), collapse = "\n") 125 | exprs <- parse_exprs(file) 126 | purrr::map(exprs, ~ eval(.x, env)) 127 | } 128 | 129 | withr::with_tempdir( 130 | code = { 131 | f <- tempfile(fileext = ".R") 132 | writeLines("1 + 1; 2 + 4", f) 133 | source2(f) 134 | } 135 | ) 136 | ``` 137 | 138 | --- 139 | 140 | **Q5.** We can make `base::local()` slightly easier to understand by spreading out over multiple lines: 141 | 142 | ```{r Evaluation-11} 143 | local3 <- function(expr, envir = new.env()) { 144 | call <- substitute(eval(quote(expr), envir)) 145 | eval(call, envir = parent.frame()) 146 | } 147 | ``` 148 | 149 | Explain how `local()` works in words. (Hint: you might want to `print(call)` to help understand what `substitute()` is doing, and read the documentation to remind yourself what environment `new.env()` will inherit from.) 150 | 151 | **A5.** In order to figure out how this function works, let's add the suggested `print(call)`: 152 | 153 | ```{r Evaluation-12} 154 | local3 <- function(expr, envir = new.env()) { 155 | call <- substitute(eval(quote(expr), envir)) 156 | print(call) 157 | 158 | eval(call, envir = parent.frame()) 159 | } 160 | 161 | local3({ 162 | x <- 10 163 | y <- 200 164 | x + y 165 | }) 166 | ``` 167 | 168 | As docs for `substitute()` mention: 169 | 170 | > Substituting and quoting often cause confusion when the argument is expression(...). The result is a call to the expression constructor function and needs to be evaluated with eval to give the actual expression object. 171 | 172 | Thus, to get the actual expression object, quoted expression needs to be evaluated using `eval()`: 173 | 174 | ```{r Evaluation-13} 175 | is_expression(eval(quote({ 176 | x <- 10 177 | y <- 200 178 | x + y 179 | }), new.env())) 180 | ``` 181 | 182 | Finally, the generated `call` is evaluated in the caller environment. So the final function call looks like the following: 183 | 184 | ```{r Evaluation-14, eval=FALSE} 185 | # outer environment 186 | eval( 187 | # inner environment 188 | eval(quote({ 189 | x <- 10 190 | y <- 200 191 | x + y 192 | }), new.env()), 193 | envir = parent.frame() 194 | ) 195 | ``` 196 | 197 | Note here that the bindings for `x` and `y` are found in the inner environment, while bindings for functions `eval()`, `quote()`, etc. are found in the outer environment. 198 | 199 | --- 200 | 201 | ## Quosures (Exercises 20.3.6) 202 | 203 | --- 204 | 205 | **Q1.** Predict what each of the following quosures will return if evaluated. 206 | 207 | ```{r Evaluation-15} 208 | q1 <- new_quosure(expr(x), env(x = 1)) 209 | q1 210 | q2 <- new_quosure(expr(x + !!q1), env(x = 10)) 211 | q2 212 | q3 <- new_quosure(expr(x + !!q2), env(x = 100)) 213 | q3 214 | ``` 215 | 216 | **A1.** Correctly predicted 😉 217 | 218 | ```{r Evaluation-16} 219 | q1 <- new_quosure(expr(x), env(x = 1)) 220 | eval_tidy(q1) 221 | 222 | q2 <- new_quosure(expr(x + !!q1), env(x = 10)) 223 | eval_tidy(q2) 224 | 225 | q3 <- new_quosure(expr(x + !!q2), env(x = 100)) 226 | eval_tidy(q3) 227 | ``` 228 | 229 | --- 230 | 231 | **Q2.** Write an `enenv()` function that captures the environment associated with an argument. (Hint: this should only require two function calls.) 232 | 233 | **A2.** We can make use of the `get_env()` helper to get the environment associated with an argument: 234 | 235 | ```{r Evaluation-17} 236 | enenv <- function(x) { 237 | x <- enquo(x) 238 | get_env(x) 239 | } 240 | 241 | enenv(x) 242 | 243 | foo <- function(x) enenv(x) 244 | foo() 245 | ``` 246 | 247 | --- 248 | 249 | ## Data masks (Exercises 20.4.6) 250 | 251 | --- 252 | 253 | **Q1.** Why did I use a `for` loop in `transform2()` instead of `map()`? Consider `transform2(df, x = x * 2, x = x * 2)`. 254 | 255 | **A1.** To see why `map()` is not appropriate for this function, let's create a version of the function with `map()` and see what happens. 256 | 257 | ```{r Evaluation-18} 258 | transform2 <- function(.data, ...) { 259 | dots <- enquos(...) 260 | 261 | for (i in seq_along(dots)) { 262 | name <- names(dots)[[i]] 263 | dot <- dots[[i]] 264 | 265 | .data[[name]] <- eval_tidy(dot, .data) 266 | } 267 | 268 | .data 269 | } 270 | 271 | transform3 <- function(.data, ...) { 272 | dots <- enquos(...) 273 | 274 | purrr::map(dots, function(x, .data = .data) { 275 | name <- names(x) 276 | dot <- x 277 | 278 | .data[[name]] <- eval_tidy(dot, .data) 279 | 280 | .data 281 | }) 282 | } 283 | ``` 284 | 285 | When we use a `for()` loop, in each iteration, we are updating the `x` column with the current expression under evaluation. That is, repeatedly modifying the same column works. 286 | 287 | ```{r Evaluation-19} 288 | df <- data.frame(x = 1:3) 289 | transform2(df, x = x * 2, x = x * 2) 290 | ``` 291 | 292 | If we use `map()` instead, we are trying to evaluate all expressions at the same time; i.e., the same column is being attempted to modify on using multiple expressions. 293 | 294 | ```{r Evaluation-20, error=TRUE} 295 | df <- data.frame(x = 1:3) 296 | transform3(df, x = x * 2, x = x * 2) 297 | ``` 298 | 299 | --- 300 | 301 | **Q2.** Here's an alternative implementation of `subset2()`: 302 | 303 | ```{r Evaluation-21, results = FALSE} 304 | subset3 <- function(data, rows) { 305 | rows <- enquo(rows) 306 | eval_tidy(expr(data[!!rows, , drop = FALSE]), data = data) 307 | } 308 | df <- data.frame(x = 1:3) 309 | subset3(df, x == 1) 310 | ``` 311 | 312 | Compare and contrast `subset3()` to `subset2()`. What are its advantages and disadvantages? 313 | 314 | **A2.** Let's first juxtapose these functions and their outputs so that we can compare them better. 315 | 316 | ```{r Evaluation-22} 317 | subset2 <- function(data, rows) { 318 | rows <- enquo(rows) 319 | rows_val <- eval_tidy(rows, data) 320 | stopifnot(is.logical(rows_val)) 321 | 322 | data[rows_val, , drop = FALSE] 323 | } 324 | 325 | df <- data.frame(x = 1:3) 326 | subset2(df, x == 1) 327 | ``` 328 | 329 | ```{r Evaluation-23} 330 | subset3 <- function(data, rows) { 331 | rows <- enquo(rows) 332 | eval_tidy(expr(data[!!rows, , drop = FALSE]), data = data) 333 | } 334 | 335 | subset3(df, x == 1) 336 | ``` 337 | 338 | **Disadvantages of `subset3()` over `subset2()`** 339 | 340 | When the filtering conditions specified in `rows` don't evaluate to a logical, the function doesn't fail informatively. Indeed, it silently returns incorrect result. 341 | 342 | ```{r Evaluation-24, error=TRUE} 343 | rm("x") 344 | exists("x") 345 | 346 | subset2(df, x + 1) 347 | 348 | subset3(df, x + 1) 349 | ``` 350 | 351 | **Advantages of `subset3()` over `subset2()`** 352 | 353 | Some might argue that the function being shorter is an advantage, but this is very much a subjective preference. 354 | 355 | --- 356 | 357 | **Q3.** The following function implements the basics of `dplyr::arrange()`. Annotate each line with a comment explaining what it does. Can you explain why `!!.na.last` is strictly correct, but omitting the `!!` is unlikely to cause problems? 358 | 359 | ```{r Evaluation-25} 360 | arrange2 <- function(.df, ..., .na.last = TRUE) { 361 | args <- enquos(...) 362 | order_call <- expr(order(!!!args, na.last = !!.na.last)) 363 | ord <- eval_tidy(order_call, .df) 364 | stopifnot(length(ord) == nrow(.df)) 365 | .df[ord, , drop = FALSE] 366 | } 367 | ``` 368 | 369 | **A3.** Annotated version of the function: 370 | 371 | ```{r Evaluation-26} 372 | arrange2 <- function(.df, ..., .na.last = TRUE) { 373 | # capture user-supplied expressions (and corresponding environments) as quosures 374 | args <- enquos(...) 375 | 376 | # create a call object by splicing a list of quosures 377 | order_call <- expr(order(!!!args, na.last = !!.na.last)) 378 | 379 | # and evaluate the constructed call in the data frame 380 | ord <- eval_tidy(order_call, .df) 381 | 382 | # sanity check 383 | stopifnot(length(ord) == nrow(.df)) 384 | 385 | .df[ord, , drop = FALSE] 386 | } 387 | ``` 388 | 389 | To see why it doesn't matter whether whether we unquote the `.na.last` argument or not, let's have a look at this smaller example: 390 | 391 | ```{r Evaluation-27} 392 | x <- TRUE 393 | eval(expr(c(x = !!x))) 394 | eval(expr(c(x = x))) 395 | ``` 396 | 397 | As can be seen: 398 | 399 | - without unquoting, `.na.last` is found in the function environment 400 | - with unquoting, `.na.last` is included in the `order` call object itself 401 | 402 | --- 403 | 404 | ## Using tidy evaluation (Exercises 20.5.4) 405 | 406 | --- 407 | 408 | **Q1.** I've included an alternative implementation of `threshold_var()` below. What makes it different to the approach I used above? What makes it harder? 409 | 410 | ```{r Evaluation-28} 411 | threshold_var <- function(df, var, val) { 412 | var <- ensym(var) 413 | subset2(df, `$`(.data, !!var) >= !!val) 414 | } 415 | ``` 416 | 417 | **A1.** First, let's compare the two definitions for the same function and make sure that they produce the same output: 418 | 419 | ```{r Evaluation-29} 420 | threshold_var_old <- function(df, var, val) { 421 | var <- as_string(ensym(var)) 422 | subset2(df, .data[[var]] >= !!val) 423 | } 424 | 425 | threshold_var_new <- threshold_var 426 | 427 | df <- data.frame(x = 1:10) 428 | 429 | identical( 430 | threshold_var(df, x, 8), 431 | threshold_var(df, x, 8) 432 | ) 433 | ``` 434 | 435 | The key difference is in the subsetting operator used: 436 | 437 | - The old version uses non-quoting `[[` operator. Thus, `var` argument first needs to be converted to a string. 438 | - The new version uses quoting `$` operator. Thus, `var` argument is first quoted and then unquoted (using `!!`). 439 | 440 | --- 441 | 442 | ## Base evaluation (Exercises 20.6.3) 443 | 444 | --- 445 | 446 | **Q1.** Why does this function fail? 447 | 448 | ```{r Evaluation-30, eval = FALSE} 449 | lm3a <- function(formula, data) { 450 | formula <- enexpr(formula) 451 | lm_call <- expr(lm(!!formula, data = data)) 452 | eval(lm_call, caller_env()) 453 | } 454 | 455 | lm3a(mpg ~ disp, mtcars)$call 456 | #> Error in as.data.frame.default(data, optional = TRUE): 457 | #> cannot coerce class ‘"function"’ to a data.frame 458 | ``` 459 | 460 | **A1.** This doesn't work because when `lm_call` call is evaluated in `caller_env()`, it finds a binding for `base::data()` function, and not `data` from execution environment. 461 | 462 | To make it work, we need to unquote `data` into the expression: 463 | 464 | ```{r Evaluation-31} 465 | lm3a <- function(formula, data) { 466 | formula <- enexpr(formula) 467 | lm_call <- expr(lm(!!formula, data = !!data)) 468 | eval(lm_call, caller_env()) 469 | } 470 | 471 | is_call(lm3a(mpg ~ disp, mtcars)$call) 472 | ``` 473 | 474 | --- 475 | 476 | **Q2.** When model building, typically the response and data are relatively constant while you rapidly experiment with different predictors. Write a small wrapper that allows you to reduce duplication in the code below. 477 | 478 | ```{r Evaluation-32, eval = FALSE} 479 | lm(mpg ~ disp, data = mtcars) 480 | lm(mpg ~ I(1 / disp), data = mtcars) 481 | lm(mpg ~ disp * cyl, data = mtcars) 482 | ``` 483 | 484 | **A2.** Here is a small wrapper that allows you to enter only the predictors: 485 | 486 | ```{r Evaluation-33} 487 | lm_custom <- function(data = mtcars, x, y = mpg) { 488 | x <- enexpr(x) 489 | y <- enexpr(y) 490 | data <- enexpr(data) 491 | 492 | lm_call <- expr(lm(formula = !!y ~ !!x, data = !!data)) 493 | 494 | eval(lm_call, caller_env()) 495 | } 496 | 497 | identical( 498 | lm_custom(x = disp), 499 | lm(mpg ~ disp, data = mtcars) 500 | ) 501 | 502 | identical( 503 | lm_custom(x = I(1 / disp)), 504 | lm(mpg ~ I(1 / disp), data = mtcars) 505 | ) 506 | 507 | identical( 508 | lm_custom(x = disp * cyl), 509 | lm(mpg ~ disp * cyl, data = mtcars) 510 | ) 511 | ``` 512 | 513 | But the function is flexible enough to also allow changing both the data and the dependent variable: 514 | 515 | ```{r Evaluation-34} 516 | lm_custom(data = iris, x = Sepal.Length, y = Petal.Width) 517 | ``` 518 | 519 | --- 520 | 521 | **Q3.** Another way to write `resample_lm()` would be to include the resample expression (`data[sample(nrow(data), replace = TRUE), , drop = FALSE]`) in the data argument. Implement that approach. What are the advantages? What are the disadvantages? 522 | 523 | **A3.** In this variant of `resample_lm()`, we are providing the resampled data as an argument. 524 | 525 | ```{r Evaluation-35} 526 | resample_lm3 <- function(formula, 527 | data, 528 | resample_data = data[sample(nrow(data), replace = TRUE), , drop = FALSE], 529 | env = current_env()) { 530 | formula <- enexpr(formula) 531 | lm_call <- expr(lm(!!formula, data = resample_data)) 532 | expr_print(lm_call) 533 | eval(lm_call, env) 534 | } 535 | 536 | df <- data.frame(x = 1:10, y = 5 + 3 * (1:10) + round(rnorm(10), 2)) 537 | resample_lm3(y ~ x, data = df) 538 | ``` 539 | 540 | This makes use of R's lazy evaluation of function arguments. That is, `resample_data` argument will be evaluated only when it is needed in the function. 541 | 542 | --- 543 | 544 | ## Session information 545 | 546 | ```{r Evaluation-36} 547 | sessioninfo::session_info(include_base = TRUE) 548 | ``` 549 | -------------------------------------------------------------------------------- /Function-factories.Rmd: -------------------------------------------------------------------------------- 1 | # Function factories 2 | 3 | ```{r Function-factories-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | Attaching the needed libraries: 8 | 9 | ```{r Function-factories-2, warning=FALSE, message=FALSE} 10 | library(rlang, warn.conflicts = FALSE) 11 | library(ggplot2, warn.conflicts = FALSE) 12 | ``` 13 | 14 | ## Factory fundamentals (Exercises 10.2.6) 15 | 16 | --- 17 | 18 | **Q1.** The definition of `force()` is simple: 19 | 20 | ```{r Function-factories-3} 21 | force 22 | ``` 23 | 24 | Why is it better to `force(x)` instead of just `x`? 25 | 26 | **A1.** Due to lazy evaluation, argument to a function won't be evaluated until its value is needed. But sometimes we may want to have eager evaluation, and using `force()` makes this intent clearer. 27 | 28 | --- 29 | 30 | **Q2.** Base R contains two function factories, `approxfun()` and `ecdf()`. Read their documentation and experiment to figure out what the functions do and what they return. 31 | 32 | **A2.** About the two function factories- 33 | 34 | - `approxfun()` 35 | 36 | This function factory returns a function performing the linear (or constant) interpolation. 37 | 38 | ```{r Function-factories-4} 39 | x <- 1:10 40 | y <- rnorm(10) 41 | f <- approxfun(x, y) 42 | f 43 | f(x) 44 | curve(f(x), 0, 11) 45 | ``` 46 | 47 | - `ecdf()` 48 | 49 | This function factory computes an empirical cumulative distribution function. 50 | 51 | ```{r Function-factories-5} 52 | x <- rnorm(12) 53 | f <- ecdf(x) 54 | f 55 | f(seq(-2, 2, by = 0.1)) 56 | ``` 57 | 58 | --- 59 | 60 | **Q3.** Create a function `pick()` that takes an index, `i`, as an argument and returns a function with an argument `x` that subsets `x` with `i`. 61 | 62 | ```{r Function-factories-6, eval = FALSE} 63 | pick(1)(x) 64 | # should be equivalent to 65 | x[[1]] 66 | 67 | lapply(mtcars, pick(5)) 68 | # should be equivalent to 69 | lapply(mtcars, function(x) x[[5]]) 70 | ``` 71 | 72 | **A3.** To write desired function, we just need to make sure that the argument `i` is eagerly evaluated. 73 | 74 | ```{r Function-factories-7} 75 | pick <- function(i) { 76 | force(i) 77 | function(x) x[[i]] 78 | } 79 | ``` 80 | 81 | Testing it with specified test cases: 82 | 83 | ```{r Function-factories-8} 84 | x <- list("a", "b", "c") 85 | identical(x[[1]], pick(1)(x)) 86 | 87 | identical( 88 | lapply(mtcars, pick(5)), 89 | lapply(mtcars, function(x) x[[5]]) 90 | ) 91 | ``` 92 | 93 | --- 94 | 95 | **Q4.** Create a function that creates functions that compute the i^th^ [central moment](http://en.wikipedia.org/wiki/Central_moment) of a numeric vector. You can test it by running the following code: 96 | 97 | ```{r Function-factories-9, eval = FALSE} 98 | m1 <- moment(1) 99 | m2 <- moment(2) 100 | x <- runif(100) 101 | stopifnot(all.equal(m1(x), 0)) 102 | stopifnot(all.equal(m2(x), var(x) * 99 / 100)) 103 | ``` 104 | 105 | **A4.** The following function satisfied the specified requirements: 106 | 107 | ```{r Function-factories-10} 108 | moment <- function(k) { 109 | force(k) 110 | 111 | function(x) (sum((x - mean(x))^k)) / length(x) 112 | } 113 | ``` 114 | 115 | Testing it with specified test cases: 116 | 117 | ```{r Function-factories-11} 118 | m1 <- moment(1) 119 | m2 <- moment(2) 120 | x <- runif(100) 121 | 122 | stopifnot(all.equal(m1(x), 0)) 123 | stopifnot(all.equal(m2(x), var(x) * 99 / 100)) 124 | ``` 125 | 126 | --- 127 | 128 | **Q5.** What happens if you don't use a closure? Make predictions, then verify with the code below. 129 | 130 | ```{r Function-factories-12} 131 | i <- 0 132 | new_counter2 <- function() { 133 | i <<- i + 1 134 | i 135 | } 136 | ``` 137 | 138 | **A5.** In case closures are not used in this context, the counts are stored in a global variable, which can be modified by other processes or even deleted. 139 | 140 | ```{r Function-factories-13} 141 | new_counter2() 142 | 143 | new_counter2() 144 | 145 | new_counter2() 146 | 147 | i <- 20 148 | new_counter2() 149 | ``` 150 | 151 | --- 152 | 153 | **Q6.** What happens if you use `<-` instead of `<<-`? Make predictions, then verify with the code below. 154 | 155 | ```{r Function-factories-14} 156 | new_counter3 <- function() { 157 | i <- 0 158 | function() { 159 | i <- i + 1 160 | i 161 | } 162 | } 163 | ``` 164 | 165 | **A6.** In this case, the function will always return `1`. 166 | 167 | ```{r Function-factories-15} 168 | new_counter3() 169 | 170 | new_counter3() 171 | ``` 172 | 173 | --- 174 | 175 | ## Graphical factories (Exercises 10.3.4) 176 | 177 | --- 178 | 179 | **Q1.** Compare and contrast `ggplot2::label_bquote()` with `scales::number_format()`. 180 | 181 | **A1.** To compare and contrast, let's first look at the source code for these functions: 182 | 183 | - `ggplot2::label_bquote()` 184 | 185 | ```{r Function-factories-16} 186 | ggplot2::label_bquote 187 | ``` 188 | 189 | - `scales::number_format()` 190 | 191 | ```{r Function-factories-17} 192 | scales::number_format 193 | ``` 194 | 195 | Both of these functions return formatting functions used to style the facets labels and other labels to have the desired format in `{ggplot2}` plots. 196 | 197 | For example, using plotmath expression in the facet label: 198 | 199 | ```{r Function-factories-18} 200 | library(ggplot2) 201 | 202 | p <- ggplot(mtcars, aes(wt, mpg)) + 203 | geom_point() 204 | p + facet_grid(. ~ vs, labeller = label_bquote(cols = alpha^.(vs))) 205 | ``` 206 | 207 | Or to display axes labels in the desired format: 208 | 209 | ```{r Function-factories-19} 210 | library(scales) 211 | 212 | ggplot(mtcars, aes(wt, mpg)) + 213 | geom_point() + 214 | scale_y_continuous(labels = number_format(accuracy = 0.01, decimal.mark = ",")) 215 | ``` 216 | 217 | The `ggplot2::label_bquote()` adds an additional class to the returned function. 218 | 219 | The `scales::number_format()` function is a simple pass-through method that forces evaluation of all its parameters and passes them on to the underlying `scales::number()` function. 220 | 221 | --- 222 | 223 | ## Statistical factories (Exercises 10.4.4) 224 | 225 | --- 226 | 227 | **Q1.** In `boot_model()`, why don't I need to force the evaluation of `df` or `model`? 228 | 229 | **A1.** We don’t need to force the evaluation of `df` or `model` because these arguments are automatically evaluated by `lm()`: 230 | 231 | ```{r Function-factories-20} 232 | boot_model <- function(df, formula) { 233 | mod <- lm(formula, data = df) 234 | fitted <- unname(fitted(mod)) 235 | resid <- unname(resid(mod)) 236 | rm(mod) 237 | 238 | function() { 239 | fitted + sample(resid) 240 | } 241 | } 242 | ``` 243 | 244 | --- 245 | 246 | **Q2.** Why might you formulate the Box-Cox transformation like this? 247 | 248 | ```{r Function-factories-21} 249 | boxcox3 <- function(x) { 250 | function(lambda) { 251 | if (lambda == 0) { 252 | log(x) 253 | } else { 254 | (x^lambda - 1) / lambda 255 | } 256 | } 257 | } 258 | ``` 259 | 260 | **A2.** To see why we formulate this transformation like above, we can compare it to the one mentioned in the book: 261 | 262 | ```{r Function-factories-22} 263 | boxcox2 <- function(lambda) { 264 | if (lambda == 0) { 265 | function(x) log(x) 266 | } else { 267 | function(x) (x^lambda - 1) / lambda 268 | } 269 | } 270 | ``` 271 | 272 | Let's have a look at one example with each: 273 | 274 | ```{r Function-factories-23} 275 | boxcox2(1) 276 | 277 | boxcox3(mtcars$wt) 278 | ``` 279 | 280 | As can be seen: 281 | 282 | - in `boxcox2()`, we can vary `x` for the same value of `lambda`, while 283 | - in `boxcox3()`, we can vary `lambda` for the same vector. 284 | 285 | Thus, `boxcox3()` can be handy while exploring different transformations across inputs. 286 | 287 | --- 288 | 289 | **Q3.** Why don't you need to worry that `boot_permute()` stores a copy of the data inside the function that it generates? 290 | 291 | **A3.** If we look at the source code generated by the function factory, we notice that the exact data frame (`mtcars`) is not referenced: 292 | 293 | ```{r Function-factories-24} 294 | boot_permute <- function(df, var) { 295 | n <- nrow(df) 296 | force(var) 297 | 298 | function() { 299 | col <- df[[var]] 300 | col[sample(n, replace = TRUE)] 301 | } 302 | } 303 | 304 | boot_permute(mtcars, "mpg") 305 | ``` 306 | 307 | This is why we don't need to worry about a copy being made because the `df` in the function environment points to the memory address of the data frame. We can confirm this by comparing their memory addresses: 308 | 309 | ```{r Function-factories-25} 310 | boot_permute_env <- rlang::fn_env(boot_permute(mtcars, "mpg")) 311 | rlang::env_print(boot_permute_env) 312 | 313 | identical( 314 | lobstr::obj_addr(boot_permute_env$df), 315 | lobstr::obj_addr(mtcars) 316 | ) 317 | ``` 318 | 319 | We can also check that the values of these bindings are the same as what we entered into the function factory: 320 | 321 | ```{r Function-factories-26} 322 | identical(boot_permute_env$df, mtcars) 323 | identical(boot_permute_env$var, "mpg") 324 | ``` 325 | 326 | --- 327 | 328 | **Q4.** How much time does `ll_poisson2()` save compared to `ll_poisson1()`? Use `bench::mark()` to see how much faster the optimisation occurs. How does changing the length of `x` change the results? 329 | 330 | **A4.** Let's first compare the performance of these functions with the example in the book: 331 | 332 | ```{r Function-factories-27} 333 | ll_poisson1 <- function(x) { 334 | n <- length(x) 335 | 336 | function(lambda) { 337 | log(lambda) * sum(x) - n * lambda - sum(lfactorial(x)) 338 | } 339 | } 340 | 341 | ll_poisson2 <- function(x) { 342 | n <- length(x) 343 | sum_x <- sum(x) 344 | c <- sum(lfactorial(x)) 345 | 346 | function(lambda) { 347 | log(lambda) * sum_x - n * lambda - c 348 | } 349 | } 350 | 351 | x1 <- c(41, 30, 31, 38, 29, 24, 30, 29, 31, 38) 352 | 353 | bench::mark( 354 | "LL1" = optimise(ll_poisson1(x1), c(0, 100), maximum = TRUE), 355 | "LL2" = optimise(ll_poisson2(x1), c(0, 100), maximum = TRUE) 356 | ) 357 | ``` 358 | 359 | As can be seen, the second version is much faster than the first version. 360 | 361 | We can also vary the length of the vector and confirm that across a wide range of vector lengths, this performance advantage is observed. 362 | 363 | ```{r Function-factories-28} 364 | generate_ll_benches <- function(n) { 365 | x_vec <- sample.int(n, n) 366 | 367 | bench::mark( 368 | "LL1" = optimise(ll_poisson1(x_vec), c(0, 100), maximum = TRUE), 369 | "LL2" = optimise(ll_poisson2(x_vec), c(0, 100), maximum = TRUE) 370 | )[1:4] %>% 371 | dplyr::mutate(length = n, .before = expression) 372 | } 373 | 374 | (df_bench <- purrr::map_dfr( 375 | .x = c(10, 20, 50, 100, 1000), 376 | .f = ~ generate_ll_benches(n = .x) 377 | )) 378 | 379 | ggplot( 380 | df_bench, 381 | aes( 382 | x = as.numeric(length), 383 | y = median, 384 | group = as.character(expression), 385 | color = as.character(expression) 386 | ) 387 | ) + 388 | geom_point() + 389 | geom_line() + 390 | labs( 391 | x = "Vector length", 392 | y = "Median Execution Time", 393 | colour = "Function used" 394 | ) 395 | ``` 396 | 397 | --- 398 | 399 | ## Function factories + functionals (Exercises 10.5.1) 400 | 401 | **Q1.** Which of the following commands is equivalent to `with(x, f(z))`? 402 | 403 | (a) `x$f(x$z)`. 404 | (b) `f(x$z)`. 405 | (c) `x$f(z)`. 406 | (d) `f(z)`. 407 | (e) It depends. 408 | 409 | **A1.** It depends on whether `with()` is used with a data frame or a list. 410 | 411 | ```{r Function-factories-29} 412 | f <- mean 413 | z <- 1 414 | x <- list(f = mean, z = 1) 415 | 416 | identical(with(x, f(z)), x$f(x$z)) 417 | 418 | identical(with(x, f(z)), f(x$z)) 419 | 420 | identical(with(x, f(z)), x$f(z)) 421 | 422 | identical(with(x, f(z)), f(z)) 423 | ``` 424 | 425 | --- 426 | 427 | **Q2.** Compare and contrast the effects of `env_bind()` vs. `attach()` for the following code. 428 | 429 | **A2.** Let's compare and contrast the effects of `env_bind()` vs. `attach()`. 430 | 431 | - `attach()` adds `funs` to the search path. Since these functions have the same names as functions in `{base}` package, the attached names mask the ones in the `{base}` package. 432 | 433 | ```{r Function-factories-30} 434 | funs <- list( 435 | mean = function(x) mean(x, na.rm = TRUE), 436 | sum = function(x) sum(x, na.rm = TRUE) 437 | ) 438 | 439 | attach(funs) 440 | 441 | mean 442 | head(search()) 443 | 444 | mean <- function(x) stop("Hi!") 445 | mean 446 | head(search()) 447 | 448 | detach(funs) 449 | ``` 450 | 451 | - `env_bind()` adds the functions in `funs` to the global environment, instead of masking the names in the `{base}` package. 452 | 453 | ```{r Function-factories-31} 454 | env_bind(globalenv(), !!!funs) 455 | mean 456 | 457 | mean <- function(x) stop("Hi!") 458 | mean 459 | env_unbind(globalenv(), names(funs)) 460 | ``` 461 | 462 | Note that there is no `"funs"` in this output. 463 | 464 | --- 465 | 466 | ## Session information 467 | 468 | ```{r Function-factories-32} 469 | sessioninfo::session_info(include_base = TRUE) 470 | ``` 471 | 472 | -------------------------------------------------------------------------------- /Function-operators.Rmd: -------------------------------------------------------------------------------- 1 | # Function operators 2 | 3 | ```{r Function-operators-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | Attaching the needed libraries: 8 | 9 | ```{r Function-operators-2, warning=FALSE, message=FALSE} 10 | library(purrr, warn.conflicts = FALSE) 11 | ``` 12 | 13 | ## Existing function operators (Exercises 11.2.3) 14 | 15 | --- 16 | 17 | **Q1.** Base R provides a function operator in the form of `Vectorize()`. What does it do? When might you use it? 18 | 19 | **A1.** `Vectorize()` function creates a function that vectorizes the action of the provided function over specified arguments (i.e., it acts on each element of the vector). We will see its utility by solving a problem that otherwise would be difficult to solve. 20 | 21 | The problem is to find indices of matching numeric values for the given threshold by creating a hybrid of the following functions: 22 | 23 | - `%in%` (which doesn't provide any way to provide tolerance when comparing numeric values), 24 | - `dplyr::near()` (which is vectorized element-wise and thus expects two vectors of equal length) 25 | 26 | ```{r Function-operators-3} 27 | which_near <- function(x, y, tolerance) { 28 | # Vectorize `dplyr::near()` function only over the `y` argument. 29 | # `Vectorize()` is a function operator and will return a function. 30 | customNear <- Vectorize(dplyr::near, vectorize.args = c("y"), SIMPLIFY = FALSE) 31 | 32 | # Apply the vectorized function to vector arguments and then check where the 33 | # comparisons are equal (i.e. `TRUE`) using `which()`. 34 | # 35 | # Use `compact()` to remove empty elements from the resulting list. 36 | index_list <- purrr::compact(purrr::map(customNear(x, y, tol = tolerance), which)) 37 | 38 | # If there are any matches, return the indices as an atomic vector of integers. 39 | if (length(index_list) > 0L) { 40 | index_vector <- purrr::simplify(index_list, "integer") 41 | return(index_vector) 42 | } 43 | 44 | # If there are no matches 45 | return(integer(0L)) 46 | } 47 | ``` 48 | 49 | Let's use it: 50 | 51 | ```{r Function-operators-4} 52 | x1 <- c(2.1, 3.3, 8.45, 8, 6) 53 | x2 <- c(6, 8.40, 3) 54 | 55 | which_near(x1, x2, tolerance = 0.1) 56 | ``` 57 | 58 | Note that we needed to create a new function for this because neither of the existing functions do what we want. 59 | 60 | ```{r} 61 | which(x1 %in% x2) 62 | 63 | which(dplyr::near(x1, x2, tol = 0.1)) 64 | ``` 65 | 66 | We solved a complex task here using the `Vectorize()` function! 67 | 68 | --- 69 | 70 | **Q2.** Read the source code for `possibly()`. How does it work? 71 | 72 | **A2.** Let's have a look at the source code for this function: 73 | 74 | ```{r Function-operators-5} 75 | possibly 76 | ``` 77 | 78 | Looking at this code, we can see that `possibly()`: 79 | 80 | - uses `tryCatch()` for error handling 81 | - has a parameter `otherwise` to specify default value in case an error occurs 82 | - has a parameter `quiet` to suppress error message (if needed) 83 | 84 | --- 85 | 86 | **Q3.** Read the source code for `safely()`. How does it work? 87 | 88 | **A3.** Let's have a look at the source code for this function: 89 | 90 | ```{r Function-operators-6} 91 | safely 92 | 93 | purrr:::capture_error 94 | ``` 95 | 96 | Looking at this code, we can see that `safely()`: 97 | 98 | - uses a list to save both the results (if the function executes successfully) and the error (if it fails) 99 | - uses `tryCatch()` for error handling 100 | - has a parameter `otherwise` to specify default value in case an error occurs 101 | - has a parameter `quiet` to suppress error message (if needed) 102 | 103 | --- 104 | 105 | ## Case study: Creating your own function operators (Exercises 11.3.1) 106 | 107 | --- 108 | 109 | **Q1.** Weigh the pros and cons of `download.file %>% dot_every(10) %>% delay_by(0.1)` versus `download.file %>% delay_by(0.1) %>% dot_every(10)`. 110 | 111 | **A1.** Although both of these chains of piped operations produce the same number of dots and would need the same amount of time, there is a subtle difference in how they do this. 112 | 113 | - `download.file %>% dot_every(10) %>% delay_by(0.1)` 114 | 115 | Here, the printing of the dot is also delayed, and the first dot is printed when the 10th URL download starts. 116 | 117 | - `download.file %>% delay_by(0.1) %>% dot_every(10)` 118 | 119 | Here, the first dot is printed after the 9th download is finished, and the 10th download starts after a short delay. 120 | 121 | --- 122 | 123 | **Q2.** Should you memoise `download.file()`? Why or why not? 124 | 125 | **A2.** Since `download.file()` is meant to download files from the Internet, memoising it is not recommended for the following reasons: 126 | 127 | - Memoization is helpful when giving the same input the function returns the same output. This is not necessarily the case for webpages since they constantly change, and you may continue to "download" an outdated version of the webpage. 128 | 129 | - Memoization works by caching results, which can take up a significant amount of memory. 130 | 131 | --- 132 | 133 | **Q3.** Create a function operator that reports whenever a file is created or deleted in the working directory, using `dir()` and `setdiff()`. What other global function effects might you want to track? 134 | 135 | **A3.** First, let's create helper functions to compare and print added or removed filenames: 136 | 137 | ```{r Function-operators-7} 138 | print_multiple_entries <- function(header, entries) { 139 | message(paste0(header, ":\n"), paste0(entries, collapse = "\n")) 140 | } 141 | 142 | file_comparator <- function(old, new) { 143 | if (setequal(old, new)) { 144 | return() 145 | } 146 | 147 | removed <- setdiff(old, new) 148 | added <- setdiff(new, old) 149 | 150 | if (length(removed) > 0L) print_multiple_entries("- File removed", removed) 151 | if (length(added) > 0L) print_multiple_entries("- File added", added) 152 | } 153 | ``` 154 | 155 | We can then write a function operator and use it to create functions that will do the necessary tracking: 156 | 157 | ```{r Function-operators-8} 158 | dir_tracker <- function(f) { 159 | force(f) 160 | function(...) { 161 | old_files <- dir() 162 | on.exit(file_comparator(old_files, dir()), add = TRUE) 163 | 164 | f(...) 165 | } 166 | } 167 | 168 | file_creation_tracker <- dir_tracker(file.create) 169 | file_deletion_tracker <- dir_tracker(file.remove) 170 | ``` 171 | 172 | Let's try it out: 173 | 174 | ```{r Function-operators-9} 175 | file_creation_tracker(c("a.txt", "b.txt")) 176 | 177 | file_deletion_tracker(c("a.txt", "b.txt")) 178 | ``` 179 | 180 | Other global function effects we might want to track: 181 | 182 | - working directory 183 | - environment variables 184 | - connections 185 | - library paths 186 | - graphics devices 187 | - [etc.](https://withr.r-lib.org/reference/index.html) 188 | 189 | --- 190 | 191 | **Q4.** Write a function operator that logs a timestamp and message to a file every time a function is run. 192 | 193 | **A4.** The following function operator logs a timestamp and message to a file every time a function is run: 194 | 195 | ```{r Function-operators-10} 196 | # helper function to write to a file connection 197 | write_line <- function(filepath, ...) { 198 | cat(..., "\n", sep = "", file = filepath, append = TRUE) 199 | } 200 | 201 | # function operator 202 | logger <- function(f, filepath) { 203 | force(f) 204 | force(filepath) 205 | 206 | write_line(filepath, "Function created at: ", as.character(Sys.time())) 207 | 208 | function(...) { 209 | write_line(filepath, "Function called at: ", as.character(Sys.time())) 210 | f(...) 211 | } 212 | } 213 | 214 | # check that the function works as expected with a tempfile 215 | withr::with_tempfile("logfile", code = { 216 | logged_runif <- logger(runif, logfile) 217 | 218 | Sys.sleep(sample.int(10, 1)) 219 | logged_runif(1) 220 | 221 | Sys.sleep(sample.int(10, 1)) 222 | logged_runif(2) 223 | 224 | Sys.sleep(sample.int(10, 1)) 225 | logged_runif(3) 226 | 227 | cat(readLines(logfile), sep = "\n") 228 | }) 229 | ``` 230 | 231 | --- 232 | 233 | **Q5.** Modify `delay_by()` so that instead of delaying by a fixed amount of time, it ensures that a certain amount of time has elapsed since the function was last called. That is, if you called `g <- delay_by(1, f); g(); Sys.sleep(2); g()` there shouldn't be an extra delay. 234 | 235 | **A5.** Modified version of the function meeting the specified requirements: 236 | 237 | ```{r Function-operators-11} 238 | delay_by_atleast <- function(f, amount) { 239 | force(f) 240 | force(amount) 241 | 242 | # the last time the function was run 243 | last_time <- NULL 244 | 245 | function(...) { 246 | if (!is.null(last_time)) { 247 | wait <- (last_time - Sys.time()) + amount 248 | if (wait > 0) Sys.sleep(wait) 249 | } 250 | 251 | # update the time in the parent frame for the next run when the function finishes 252 | on.exit(last_time <<- Sys.time()) 253 | 254 | f(...) 255 | } 256 | } 257 | ``` 258 | 259 | --- 260 | 261 | ## Session information 262 | 263 | ```{r Function-operators-12} 264 | sessioninfo::session_info(include_base = TRUE) 265 | ``` 266 | -------------------------------------------------------------------------------- /Introduction.Rmd: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | No exercises. 4 | 5 | -------------------------------------------------------------------------------- /Makevars: -------------------------------------------------------------------------------- 1 | CC=clang 2 | CXX=clang++ 3 | -------------------------------------------------------------------------------- /Names-values.Rmd: -------------------------------------------------------------------------------- 1 | # Names and values 2 | 3 | ```{r Names-values-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | Loading the needed libraries: 8 | 9 | ```{r Names-values-2, warning=FALSE, message=FALSE} 10 | library(lobstr) 11 | ``` 12 | 13 | ## Binding basics (Exercise 2.2.2) 14 | 15 | --- 16 | 17 | **Q1.** Explain the relationship between `a`, `b`, `c` and `d` in the following code: 18 | 19 | ```{r Names-values-3} 20 | a <- 1:10 21 | b <- a 22 | c <- b 23 | d <- 1:10 24 | ``` 25 | 26 | **A1.** The names (`a`, `b`, and `c`) have same values and point to the same object in memory, as can be seen by their identical memory addresses: 27 | 28 | ```{r Names-values-4} 29 | obj_addrs <- obj_addrs(list(a, b, c)) 30 | unique(obj_addrs) 31 | ``` 32 | 33 | Except `d`, which is a different object, even if it has the same value as `a`, `b`, and `c`: 34 | 35 | ```{r Names-values-5} 36 | obj_addr(d) 37 | ``` 38 | 39 | --- 40 | 41 | **Q2.** The following code accesses the mean function in multiple ways. Do they all point to the same underlying function object? Verify this with `lobstr::obj_addr()`. 42 | 43 | ```{r Names-values-6, eval = FALSE} 44 | mean 45 | base::mean 46 | get("mean") 47 | evalq(mean) 48 | match.fun("mean") 49 | ``` 50 | 51 | **A2.** All listed function calls point to the same underlying function object in memory, as shown by this object's memory address: 52 | 53 | ```{r Names-values-7} 54 | obj_addrs <- obj_addrs(list( 55 | mean, 56 | base::mean, 57 | get("mean"), 58 | evalq(mean), 59 | match.fun("mean") 60 | )) 61 | 62 | unique(obj_addrs) 63 | ``` 64 | 65 | --- 66 | 67 | **Q3.** By default, base R data import functions, like `read.csv()`, will automatically convert non-syntactic names to syntactic ones. Why might this be problematic? What option allows you to suppress this behaviour? 68 | 69 | **A3.** The conversion of non-syntactic names to syntactic ones can sometimes corrupt the data. Some datasets may require non-syntactic names. 70 | 71 | To suppress this behavior, one can set `check.names = FALSE`. 72 | 73 | --- 74 | 75 | **Q4.** What rules does `make.names()` use to convert non-syntactic names into syntactic ones? 76 | 77 | **A4.** `make.names()` uses following rules to convert non-syntactic names into syntactic ones: 78 | 79 | - it prepends non-syntactic names with `X` 80 | - it converts invalid characters (like `@`) to `.` 81 | - it adds a `.` as a suffix if the name is a [reserved keyword](https://stat.ethz.ch/R-manual/R-devel/library/base/html/Reserved.html) 82 | 83 | ```{r Names-values-8} 84 | make.names(c("123abc", "@me", "_yu", " gh", "else")) 85 | ``` 86 | 87 | --- 88 | 89 | **Q5.** I slightly simplified the rules that govern syntactic names. Why is `.123e1` not a syntactic name? Read `?make.names` for the full details. 90 | 91 | **A5.** `.123e1` is not a syntacti name because it is parsed as a number, and not as a string: 92 | 93 | ```{r Names-values-9} 94 | typeof(.123e1) 95 | ``` 96 | 97 | And as the docs mention (emphasis mine): 98 | 99 | > A syntactically valid name consists of letters, numbers and the dot or underline characters and starts with a letter or **the dot not followed by a number**. 100 | 101 | --- 102 | 103 | ## Copy-on-modify (Exercise 2.3.6) 104 | 105 | --- 106 | 107 | **Q1.** Why is `tracemem(1:10)` not useful? 108 | 109 | **A1.** `tracemem()` traces copying of objects in R. For example: 110 | 111 | ```{r Names-values-10} 112 | x <- 1:10 113 | 114 | tracemem(x) 115 | 116 | x <- x + 1 117 | 118 | untracemem(x) 119 | ``` 120 | 121 | But since the object created in memory by `1:10` is not assigned a name, it can't be addressed or modified from R, and so there is nothing to trace. 122 | 123 | ```{r Names-values-11} 124 | obj_addr(1:10) 125 | 126 | tracemem(1:10) 127 | ``` 128 | 129 | --- 130 | 131 | **Q2.** Explain why `tracemem()` shows two copies when you run this code. Hint: carefully look at the difference between this code and the code shown earlier in the section. 132 | 133 | ```{r Names-values-12, results = FALSE} 134 | x <- c(1L, 2L, 3L) 135 | tracemem(x) 136 | 137 | x[[3]] <- 4 138 | untracemem(x) 139 | ``` 140 | 141 | **A2.** This is because the initial atomic vector is of type `integer`, but `4` (and not `4L`) is of type `double`. This is why a new copy is created. 142 | 143 | ```{r Names-values-13} 144 | x <- c(1L, 2L, 3L) 145 | typeof(x) 146 | tracemem(x) 147 | 148 | x[[3]] <- 4 149 | untracemem(x) 150 | 151 | typeof(x) 152 | ``` 153 | 154 | Trying with an integer should not create another copy: 155 | 156 | ```{r Names-values-14} 157 | x <- c(1L, 2L, 3L) 158 | typeof(x) 159 | tracemem(x) 160 | 161 | x[[3]] <- 4L 162 | untracemem(x) 163 | 164 | typeof(x) 165 | ``` 166 | 167 | To understand why this still produces a copy, here is an explanation from the [official solutions manual](https://advanced-r-solutions.rbind.io/names-and-values.html#copy-on-modify): 168 | 169 | > Please be aware that running this code in RStudio will result in additional copies because of the reference from the environment pane. 170 | 171 | --- 172 | 173 | **Q3.** Sketch out the relationship between the following objects: 174 | 175 | ```{r Names-values-15} 176 | a <- 1:10 177 | b <- list(a, a) 178 | c <- list(b, a, 1:10) 179 | ``` 180 | 181 | **A3.** We can understand the relationship between these objects by looking at their memory addresses: 182 | 183 | ```{r Names-values-16} 184 | a <- 1:10 185 | b <- list(a, a) 186 | c <- list(b, a, 1:10) 187 | 188 | ref(a) 189 | 190 | ref(b) 191 | 192 | ref(c) 193 | ``` 194 | 195 | Here is what we learn: 196 | 197 | - The name `a` references object `1:10` in the memory. 198 | - The name `b` is bound to a list of two references to the memory address of `a`. 199 | - The name `c` is also bound to a list of references to `a` and `b`, and `1:10` object (not bound to any name). 200 | 201 | --- 202 | 203 | **Q4.** What happens when you run this code? 204 | 205 | ```{r Names-values-17, eval=FALSE} 206 | x <- list(1:10) 207 | x[[2]] <- x 208 | ``` 209 | 210 | Draw a picture. 211 | 212 | **A4.** 213 | 214 | ```{r Names-values-18} 215 | x <- list(1:10) 216 | x 217 | obj_addr(x) 218 | 219 | x[[2]] <- x 220 | x 221 | obj_addr(x) 222 | 223 | ref(x) 224 | ``` 225 | 226 | I don't have access to OmniGraffle software, so I am including here the figure from the [official solution manual](https://advanced-r-solutions.rbind.io/names-and-values.html#copy-on-modify): 227 | 228 | ```{r Names-values-19, echo=FALSE, out.width='180pt', eval=!knitr::is_latex_output()} 229 | knitr::include_graphics("https://raw.githubusercontent.com/Tazinho/Advanced-R-Solutions/main/images/names_values/copy_on_modify_fig2.png", auto_pdf = TRUE) 230 | ``` 231 | 232 | --- 233 | 234 | ## Object size (Exercise 2.4.1) 235 | 236 | --- 237 | 238 | **Q1.** In the following example, why are `object.size(y)` and `obj_size(y)` so radically different? Consult the documentation of `object.size()`. 239 | 240 | ```{r Names-values-20, eval=FALSE} 241 | y <- rep(list(runif(1e4)), 100) 242 | 243 | object.size(y) 244 | obj_size(y) 245 | ``` 246 | 247 | **A1.** As mentioned in the docs for `object.size()`: 248 | 249 | > This function...does not detect if elements of a list are shared. 250 | 251 | This is why the sizes are so different: 252 | 253 | ```{r Names-values-21} 254 | y <- rep(list(runif(1e4)), 100) 255 | 256 | object.size(y) 257 | 258 | obj_size(y) 259 | ``` 260 | 261 | --- 262 | 263 | **Q2.** Take the following list. Why is its size somewhat misleading? 264 | 265 | ```{r Names-values-22, eval=FALSE} 266 | funs <- list(mean, sd, var) 267 | obj_size(funs) 268 | ``` 269 | 270 | **A2.** These functions are not externally created objects in R, but are always available as part of base packages, so doesn't make much sense to measure their size because they are never going to be *not* available. 271 | 272 | ```{r Names-values-23} 273 | funs <- list(mean, sd, var) 274 | obj_size(funs) 275 | ``` 276 | 277 | --- 278 | 279 | **Q3.** Predict the output of the following code: 280 | 281 | ```{r Names-values-24, eval = FALSE} 282 | a <- runif(1e6) 283 | obj_size(a) 284 | 285 | b <- list(a, a) 286 | obj_size(b) 287 | obj_size(a, b) 288 | 289 | b[[1]][[1]] <- 10 290 | obj_size(b) 291 | obj_size(a, b) 292 | 293 | b[[2]][[1]] <- 10 294 | obj_size(b) 295 | obj_size(a, b) 296 | ``` 297 | 298 | **A3.** Correctly predicted 😉 299 | 300 | ```{r Names-values-25} 301 | a <- runif(1e6) 302 | obj_size(a) 303 | 304 | b <- list(a, a) 305 | obj_size(b) 306 | obj_size(a, b) 307 | 308 | b[[1]][[1]] <- 10 309 | obj_size(b) 310 | obj_size(a, b) 311 | 312 | b[[2]][[1]] <- 10 313 | obj_size(b) 314 | obj_size(a, b) 315 | ``` 316 | 317 | Key pieces of information to keep in mind to make correct predictions: 318 | 319 | - Size of empty vector 320 | 321 | ```{r Names-values-26} 322 | obj_size(double()) 323 | ``` 324 | 325 | - Size of a single double: 8 bytes 326 | 327 | ```{r Names-values-27} 328 | obj_size(double(1)) 329 | ``` 330 | 331 | - Copy-on-modify semantics 332 | 333 | --- 334 | 335 | ## Modify-in-place (Exercise 2.5.3) 336 | 337 | --- 338 | 339 | **Q1.** Explain why the following code doesn't create a circular list. 340 | 341 | ```{r Names-values-28, eval = FALSE} 342 | x <- list() 343 | x[[1]] <- x 344 | ``` 345 | 346 | **A1.** Copy-on-modify prevents the creation of a circular list. 347 | 348 | ```{r Names-values-29} 349 | x <- list() 350 | 351 | obj_addr(x) 352 | 353 | tracemem(x) 354 | 355 | x[[1]] <- x 356 | 357 | obj_addr(x[[1]]) 358 | ``` 359 | 360 | --- 361 | 362 | **Q2.** Wrap the two methods for subtracting medians into two functions, then use the 'bench' package to carefully compare their speeds. How does performance change as the number of columns increase? 363 | 364 | **A2.** Let's first microbenchmark functions that do and do not create copies for varying lengths of number of columns. 365 | 366 | ```{r Names-values-30, message=FALSE, warning=FALSE} 367 | library(bench) 368 | library(tidyverse) 369 | 370 | generateDataFrame <- function(ncol) { 371 | as.data.frame(matrix(runif(100 * ncol), nrow = 100)) 372 | } 373 | 374 | withCopy <- function(ncol) { 375 | x <- generateDataFrame(ncol) 376 | medians <- vapply(x, median, numeric(1)) 377 | 378 | for (i in seq_along(medians)) { 379 | x[[i]] <- x[[i]] - medians[[i]] 380 | } 381 | 382 | return(x) 383 | } 384 | 385 | withoutCopy <- function(ncol) { 386 | x <- generateDataFrame(ncol) 387 | medians <- vapply(x, median, numeric(1)) 388 | 389 | y <- as.list(x) 390 | 391 | for (i in seq_along(medians)) { 392 | y[[i]] <- y[[i]] - medians[[i]] 393 | } 394 | 395 | return(y) 396 | } 397 | 398 | benchComparison <- function(ncol) { 399 | bench::mark( 400 | withCopy(ncol), 401 | withoutCopy(ncol), 402 | iterations = 100, 403 | check = FALSE 404 | ) %>% 405 | dplyr::select(expression:total_time) 406 | } 407 | 408 | nColList <- list(1, 10, 50, 100, 250, 500, 1000) 409 | 410 | names(nColList) <- as.character(nColList) 411 | 412 | benchDf <- purrr::map_dfr( 413 | .x = nColList, 414 | .f = benchComparison, 415 | .id = "nColumns" 416 | ) 417 | ``` 418 | 419 | Plotting these benchmarks reveals how the performance gets increasingly worse as the number of data frames increases: 420 | 421 | ```{r Names-values-31} 422 | ggplot( 423 | benchDf, 424 | aes( 425 | x = as.numeric(nColumns), 426 | y = median, 427 | group = as.character(expression), 428 | color = as.character(expression) 429 | ) 430 | ) + 431 | geom_line() + 432 | labs( 433 | x = "Number of Columns", 434 | y = "Median Execution Time (ms)", 435 | colour = "Type of function" 436 | ) 437 | ``` 438 | 439 | --- 440 | 441 | **Q3.** What happens if you attempt to use `tracemem()` on an environment? 442 | 443 | **A3.** It doesn't work and the documentation for `tracemem()` makes it clear why: 444 | 445 | > It is not useful to trace `NULL`, environments, promises, weak references, or external pointer objects, as these are not duplicated 446 | 447 | ```{r Names-values-32, error=TRUE} 448 | e <- rlang::env(a = 1, b = "3") 449 | tracemem(e) 450 | ``` 451 | 452 | --- 453 | 454 | ## Session information 455 | 456 | ```{r Names-values-33} 457 | sessioninfo::session_info(include_base = TRUE) 458 | ``` 459 | -------------------------------------------------------------------------------- /OO-tradeoffs.Rmd: -------------------------------------------------------------------------------- 1 | # Trade-offs 2 | 3 | No exercises. 4 | -------------------------------------------------------------------------------- /Perf-improve.Rmd: -------------------------------------------------------------------------------- 1 | # Improving performance 2 | 3 | ```{r Perf-improve-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | Attaching the needed libraries: 8 | 9 | ```{r Perf-improve-2, warning=FALSE, message=FALSE} 10 | library(ggplot2) 11 | library(dplyr) 12 | library(purrr) 13 | ``` 14 | 15 | ## Exercises 24.3.1 16 | 17 | **Q1.** What are faster alternatives to `lm()`? Which are specifically designed to work with larger datasets? 18 | 19 | **A1.** Faster alternatives to `lm()` can be found by visiting [CRAN Task View: High-Performance and Parallel Computing with R](https://cran.r-project.org/web/views/HighPerformanceComputing.html) page. 20 | 21 | Here are some of the available options: 22 | 23 | - `speedglm::speedlm()` (for large datasets) 24 | 25 | - `biglm::biglm()` (specifically designed for data too large to fit in memory) 26 | 27 | - `RcppEigen::fastLm()` (using the `Eigen` linear algebra library) 28 | 29 | High performances can be obtained with these packages especially if R is linked against an optimized BLAS, such as ATLAS. You can check this information using `sessionInfo()`: 30 | 31 | ```{r Perf-improve-3} 32 | sessInfo <- sessionInfo() 33 | sessInfo$matprod 34 | sessInfo$LAPACK 35 | ``` 36 | 37 | Comparing performance of different alternatives: 38 | 39 | ```{r Perf-improve-4} 40 | library(gapminder) 41 | 42 | # having a look at the data 43 | glimpse(gapminder) 44 | 45 | bench::mark( 46 | "lm" = stats::lm(lifeExp ~ continent * gdpPercap, gapminder), 47 | "speedglm" = speedglm::speedlm(lifeExp ~ continent * gdpPercap, gapminder), 48 | "biglm" = biglm::biglm(lifeExp ~ continent * gdpPercap, gapminder), 49 | "fastLm" = RcppEigen::fastLm(lifeExp ~ continent * gdpPercap, gapminder), 50 | check = FALSE, 51 | iterations = 1000 52 | )[1:5] 53 | ``` 54 | 55 | The results might change depending on the size of the dataset, with the performance benefits accruing bigger the dataset. 56 | 57 | You will have to experiment with different algorithms and find the one that fits the needs of your dataset the best. 58 | 59 | **Q2.** What package implements a version of `match()` that's faster for repeated look ups? How much faster is it? 60 | 61 | **A2.** The package (and the respective function) is `fastmatch::fmatch()`^[In addition to Google search, you can also try [packagefinder](https://www.zuckarelli.de/packagefinder/tutorial.html) to search for CRAN packages.]. 62 | 63 | The documentation for this function notes: 64 | 65 | > It is slightly faster than the built-in version because it uses more specialized code, but in addition it retains the hash table within the table object such that it can be re-used, dramatically reducing the look-up time especially for large table. 66 | 67 | With a small vector, `fmatch()` is only slightly faster, but of the same order of magnitude. 68 | 69 | ```{r Perf-improve-5} 70 | library(fastmatch, warn.conflicts = FALSE) 71 | 72 | small_vec <- c("a", "b", "x", "m", "n", "y") 73 | 74 | length(small_vec) 75 | 76 | bench::mark( 77 | "base" = match(c("x", "y"), small_vec), 78 | "fastmatch" = fmatch(c("x", "y"), small_vec) 79 | )[1:5] 80 | ``` 81 | 82 | But, with a larger vector, `fmatch()` is orders of magnitude faster! ⚡ 83 | 84 | ```{r Perf-improve-6} 85 | large_vec <- c(rep(c("a", "b"), 1e4), "x", rep(c("m", "n"), 1e6), "y") 86 | 87 | length(large_vec) 88 | 89 | bench::mark( 90 | "base" = match(c("x", "y"), large_vec), 91 | "fastmatch" = fmatch(c("x", "y"), large_vec) 92 | )[1:5] 93 | ``` 94 | 95 | We can also look at the hash table: 96 | 97 | ```{r Perf-improve-7} 98 | fmatch.hash(c("x", "y"), small_vec) 99 | ``` 100 | 101 | Additionally, `{fastmatch}` provides equivalent of the familiar infix operator: 102 | 103 | ```{r Perf-improve-8} 104 | library(fastmatch) 105 | 106 | small_vec <- c("a", "b", "x", "m", "n", "y") 107 | 108 | c("x", "y") %in% small_vec 109 | 110 | c("x", "y") %fin% small_vec 111 | ``` 112 | 113 | **Q3.** List four functions (not just those in base R) that convert a string into a date time object. What are their strengths and weaknesses? 114 | 115 | **A3.** Here are four functions that convert a string into a date time object: 116 | 117 | - `base::as.POSIXct()` 118 | 119 | ```{r Perf-improve-9} 120 | base::as.POSIXct("2022-05-05 09:23:22") 121 | ``` 122 | 123 | - `base::as.POSIXlt()` 124 | 125 | ```{r Perf-improve-10} 126 | base::as.POSIXlt("2022-05-05 09:23:22") 127 | ``` 128 | 129 | - `lubridate::ymd_hms()` 130 | 131 | ```{r Perf-improve-11} 132 | lubridate::ymd_hms("2022-05-05-09-23-22") 133 | ``` 134 | 135 | - `fasttime::fastPOSIXct()` 136 | 137 | ```{r Perf-improve-12} 138 | fasttime::fastPOSIXct("2022-05-05 09:23:22") 139 | ``` 140 | 141 | We can also compare their performance: 142 | 143 | ```{r Perf-improve-13} 144 | bench::mark( 145 | "as.POSIXct" = base::as.POSIXct("2022-05-05 09:23:22"), 146 | "as.POSIXlt" = base::as.POSIXlt("2022-05-05 09:23:22"), 147 | "ymd_hms" = lubridate::ymd_hms("2022-05-05-09-23-22"), 148 | "fastPOSIXct" = fasttime::fastPOSIXct("2022-05-05 09:23:22"), 149 | check = FALSE, 150 | iterations = 1000 151 | ) 152 | ``` 153 | 154 | There are many more packages that implement a way to convert from string to a date time object. For more, see [CRAN Task View: Time Series Analysis](https://cran.r-project.org/web/views/TimeSeries.html) 155 | 156 | **Q4.** Which packages provide the ability to compute a rolling mean? 157 | 158 | **A4.** Here are a few packages and respective functions that provide a way to compute a rolling mean: 159 | 160 | - `RcppRoll::roll_mean()` 161 | - `data.table::frollmean()` 162 | - `roll::roll_mean()` 163 | - `zoo::rollmean()` 164 | - `slider::slide_dbl()` 165 | 166 | **Q5.** What are the alternatives to `optim()`? 167 | 168 | **A5.** The `optim()` function provides general-purpose optimization. As noted in its docs: 169 | 170 | > General-purpose optimization based on Nelder–Mead, quasi-Newton and conjugate-gradient algorithms. It includes an option for box-constrained optimization and simulated annealing. 171 | 172 | There are many alternatives and the exact one you would want to choose would depend on the type of optimization you would like to do. 173 | 174 | Most available options can be seen at [CRAN Task View: Optimization and Mathematical Programming](https://cran.r-project.org/web/views/Optimization.html). 175 | 176 | ## Exercises 24.4.3 177 | 178 | **Q1.** What's the difference between `rowSums()` and `.rowSums()`? 179 | 180 | **A1.** The documentation for these functions state: 181 | 182 | > The versions with an initial dot in the name (.colSums() etc) are ‘bare-bones’ versions for use in programming: they apply only to numeric (like) matrices and do not name the result. 183 | 184 | Looking at the source code, 185 | 186 | - `rowSums()` function does a number of checks to validate if the arguments are acceptable 187 | 188 | ```{r Perf-improve-14} 189 | rowSums 190 | ``` 191 | 192 | - `.rowSums()` directly proceeds to computation using an internal code which is built in to the R interpreter 193 | 194 | ```{r Perf-improve-15} 195 | .rowSums 196 | ``` 197 | 198 | But they have comparable performance: 199 | 200 | ```{r Perf-improve-16} 201 | x <- cbind(x1 = 3, x2 = c(4:1e4, 2:1e5)) 202 | 203 | bench::mark( 204 | "rowSums" = rowSums(x), 205 | ".rowSums" = .rowSums(x, dim(x)[[1]], dim(x)[[2]]) 206 | )[1:5] 207 | ``` 208 | 209 | **Q2.** Make a faster version of `chisq.test()` that only computes the chi-square test statistic when the input is two numeric vectors with no missing values. You can try simplifying `chisq.test()` or by coding from the [mathematical definition](http://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test). 210 | 211 | **A2.** If the function is supposed to accept only two numeric vectors without missing values, then we can make `chisq.test()` do less work by removing code corresponding to the following : 212 | 213 | - checks for data frame and matrix inputs 214 | - goodness-of-fit test 215 | - simulating *p*-values 216 | - checking for missing values 217 | 218 | This leaves us with a much simpler, bare bones implementation: 219 | 220 | ```{r Perf-improve-17} 221 | my_chisq_test <- function(x, y) { 222 | x <- table(x, y) 223 | n <- sum(x) 224 | 225 | nr <- as.integer(nrow(x)) 226 | nc <- as.integer(ncol(x)) 227 | 228 | sr <- rowSums(x) 229 | sc <- colSums(x) 230 | E <- outer(sr, sc, "*") / n 231 | v <- function(r, c, n) c * r * (n - r) * (n - c) / n^3 232 | V <- outer(sr, sc, v, n) 233 | dimnames(E) <- dimnames(x) 234 | 235 | STATISTIC <- sum((abs(x - E))^2 / E) 236 | PARAMETER <- (nr - 1L) * (nc - 1L) 237 | PVAL <- pchisq(STATISTIC, PARAMETER, lower.tail = FALSE) 238 | 239 | names(STATISTIC) <- "X-squared" 240 | names(PARAMETER) <- "df" 241 | 242 | structure( 243 | list( 244 | statistic = STATISTIC, 245 | parameter = PARAMETER, 246 | p.value = PVAL, 247 | method = "Pearson's Chi-squared test", 248 | observed = x, 249 | expected = E, 250 | residuals = (x - E) / sqrt(E), 251 | stdres = (x - E) / sqrt(V) 252 | ), 253 | class = "htest" 254 | ) 255 | } 256 | ``` 257 | 258 | And, indeed, this custom function performs slightly better^[Deliberately choosing a larger dataset to stress test the new function.] than its base equivalent: 259 | 260 | ```{r Perf-improve-18, warning=FALSE} 261 | m <- c(rep("a", 1000), rep("b", 9000)) 262 | n <- c(rep(c("x", "y"), 5000)) 263 | 264 | bench::mark( 265 | "base" = chisq.test(m, n)$statistic[[1]], 266 | "custom" = my_chisq_test(m, n)$statistic[[1]] 267 | )[1:5] 268 | ``` 269 | 270 | **Q3.** Can you make a faster version of `table()` for the case of an input of two integer vectors with no missing values? Can you use it to speed up your chi-square test? 271 | 272 | **A3.** In order to make a leaner version of `table()`, we can take a similar approach and trim the unnecessary input checks in light of our new API of accepting just two vectors without missing values. We can remove the following components from the code: 273 | 274 | - extracting data from objects entered in `...` argument 275 | - dealing with missing values 276 | - other input validation checks 277 | 278 | In addition to this removal, we can also use `fastmatch::fmatch()` instead of `match()`: 279 | 280 | ```{r Perf-improve-19} 281 | my_table <- function(x, y) { 282 | x_sorted <- sort(unique(x)) 283 | y_sorted <- sort(unique(y)) 284 | 285 | x_length <- length(x_sorted) 286 | y_length <- length(y_sorted) 287 | 288 | bin <- 289 | fastmatch::fmatch(x, x_sorted) + 290 | x_length * fastmatch::fmatch(y, y_sorted) - 291 | x_length 292 | 293 | y <- tabulate(bin, x_length * y_length) 294 | 295 | y <- array( 296 | y, 297 | dim = c(x_length, y_length), 298 | dimnames = list(x = x_sorted, y = y_sorted) 299 | ) 300 | 301 | class(y) <- "table" 302 | y 303 | } 304 | ``` 305 | 306 | The custom function indeed performs slightly better: 307 | 308 | ```{r Perf-improve-20} 309 | x <- c(rep("a", 1000), rep("b", 9000)) 310 | y <- c(rep(c("x", "y"), 5000)) 311 | 312 | # `check = FALSE` because the custom function has an additional attribute: 313 | # ".match.hash" 314 | bench::mark( 315 | "base" = table(x, y), 316 | "custom" = my_table(x, y), 317 | check = FALSE 318 | )[1:5] 319 | ``` 320 | 321 | We can also use this function in our custom chi-squared test function and see if the performance improves any further: 322 | 323 | ```{r Perf-improve-21} 324 | my_chisq_test2 <- function(x, y) { 325 | x <- my_table(x, y) 326 | n <- sum(x) 327 | 328 | nr <- as.integer(nrow(x)) 329 | nc <- as.integer(ncol(x)) 330 | 331 | sr <- rowSums(x) 332 | sc <- colSums(x) 333 | E <- outer(sr, sc, "*") / n 334 | v <- function(r, c, n) c * r * (n - r) * (n - c) / n^3 335 | V <- outer(sr, sc, v, n) 336 | dimnames(E) <- dimnames(x) 337 | 338 | STATISTIC <- sum((abs(x - E))^2 / E) 339 | PARAMETER <- (nr - 1L) * (nc - 1L) 340 | PVAL <- pchisq(STATISTIC, PARAMETER, lower.tail = FALSE) 341 | 342 | names(STATISTIC) <- "X-squared" 343 | names(PARAMETER) <- "df" 344 | 345 | structure( 346 | list( 347 | statistic = STATISTIC, 348 | parameter = PARAMETER, 349 | p.value = PVAL, 350 | method = "Pearson's Chi-squared test", 351 | observed = x, 352 | expected = E, 353 | residuals = (x - E) / sqrt(E), 354 | stdres = (x - E) / sqrt(V) 355 | ), 356 | class = "htest" 357 | ) 358 | } 359 | ``` 360 | 361 | And, indeed, this new version of the custom function performs even better than it previously did: 362 | 363 | ```{r Perf-improve-22, warning=FALSE} 364 | m <- c(rep("a", 1000), rep("b", 9000)) 365 | n <- c(rep(c("x", "y"), 5000)) 366 | 367 | bench::mark( 368 | "base" = chisq.test(m, n)$statistic[[1]], 369 | "custom" = my_chisq_test2(m, n)$statistic[[1]] 370 | )[1:5] 371 | ``` 372 | 373 | ## Exercises 24.5.1 374 | 375 | **Q1.** The density functions, e.g., `dnorm()`, have a common interface. Which arguments are vectorised over? What does `rnorm(10, mean = 10:1)` do? 376 | 377 | **A1.** The density function family has the following interface: 378 | 379 | ```{r Perf-improve-23, eval=FALSE} 380 | dnorm(x, mean = 0, sd = 1, log = FALSE) 381 | pnorm(q, mean = 0, sd = 1, lower.tail = TRUE, log.p = FALSE) 382 | qnorm(p, mean = 0, sd = 1, lower.tail = TRUE, log.p = FALSE) 383 | rnorm(n, mean = 0, sd = 1) 384 | ``` 385 | 386 | Reading the documentation reveals that the following parameters are vectorized: 387 | `x`, `q`, `p`, `mean`, `sd`. 388 | 389 | This means that something like the following will work: 390 | 391 | ```{r Perf-improve-24} 392 | rnorm(c(1, 2, 3), mean = c(0, -1, 5)) 393 | ``` 394 | 395 | But, for functions that don't have multiple vectorized parameters, it won't. For example, 396 | 397 | ```{r Perf-improve-25, error=TRUE} 398 | pnorm(c(1, 2, 3), mean = c(0, -1, 5), log.p = c(FALSE, TRUE, TRUE)) 399 | ``` 400 | 401 | The following function call generates 10 random numbers (since `n = 10`) with 10 different distributions with means supplied by the vector `10:1`. 402 | 403 | ```{r Perf-improve-26} 404 | rnorm(n = 10, mean = 10:1) 405 | ``` 406 | 407 | **Q2.** Compare the speed of `apply(x, 1, sum)` with `rowSums(x)` for varying sizes of `x`. 408 | 409 | **A2.** We can write a custom function to vary number of rows in a matrix and extract a data frame comparing performance of these two functions. 410 | 411 | ```{r Perf-improve-27, warning=FALSE} 412 | benc_perform <- function(nRow, nCol = 100) { 413 | x <- matrix(data = rnorm(nRow * nCol), nrow = nRow, ncol = nCol) 414 | 415 | bench::mark( 416 | rowSums(x), 417 | apply(x, 1, sum) 418 | )[1:5] 419 | } 420 | 421 | nRowList <- list(10, 100, 500, 1000, 5000, 10000, 50000, 100000) 422 | 423 | names(nRowList) <- as.character(nRowList) 424 | 425 | benchDF <- map_dfr( 426 | .x = nRowList, 427 | .f = ~ benc_perform(.x), 428 | .id = "nRows" 429 | ) %>% 430 | mutate(nRows = as.numeric(nRows)) 431 | ``` 432 | 433 | Plotting this data reveals that `rowSums(x)` has *O*(1) behavior, while *O*(n) behavior. 434 | 435 | ```{r Perf-improve-28} 436 | ggplot( 437 | benchDF, 438 | aes( 439 | x = as.numeric(nRows), 440 | y = median, 441 | group = as.character(expression), 442 | color = as.character(expression) 443 | ) 444 | ) + 445 | geom_point() + 446 | geom_line() + 447 | labs( 448 | x = "Number of Rows", 449 | y = "Median Execution Time", 450 | colour = "Function used" 451 | ) 452 | ``` 453 | 454 | **Q3.** How can you use `crossprod()` to compute a weighted sum? How much faster is it than the naive `sum(x * w)`? 455 | 456 | **A3.** Both of these functions provide a way to compute a weighted sum: 457 | 458 | ```{r Perf-improve-29} 459 | x <- c(1:6, 2, 3) 460 | w <- rnorm(length(x)) 461 | 462 | crossprod(x, w)[[1]] 463 | sum(x * w)[[1]] 464 | ``` 465 | 466 | But benchmarking their performance reveals that the latter is significantly faster than the former! 467 | 468 | ```{r Perf-improve-30} 469 | bench::mark( 470 | crossprod(x, w)[[1]], 471 | sum(x * w)[[1]], 472 | iterations = 1e6 473 | )[1:5] 474 | ``` 475 | -------------------------------------------------------------------------------- /Perf-measure.Rmd: -------------------------------------------------------------------------------- 1 | # Measuring performance 2 | 3 | ```{r Perf-measure-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | Attaching the needed libraries: 8 | 9 | ```{r Perf-measure-2, warning=FALSE, message=FALSE} 10 | library(profvis, warn.conflicts = FALSE) 11 | library(dplyr, warn.conflicts = FALSE) 12 | ``` 13 | 14 | ## Profiling (Exercises 23.2.4) 15 | 16 | --- 17 | 18 | **Q1.** Profile the following function with `torture = TRUE`. What is surprising? Read the source code of `rm()` to figure out what's going on. 19 | 20 | ```{r Perf-measure-3, eval = FALSE} 21 | f <- function(n = 1e5) { 22 | x <- rep(1, n) 23 | rm(x) 24 | } 25 | ``` 26 | 27 | **A1.** Let's source the functions mentioned in exercises. 28 | 29 | ```{r Perf-measure-4, warning=FALSE} 30 | source("profiling-exercises.R") 31 | ``` 32 | 33 | First, we try without `torture = TRUE`: it returns no meaningful results. 34 | 35 | ```{r Perf-measure-5, error=TRUE} 36 | profvis(f()) 37 | ``` 38 | 39 | As mentioned in the docs, setting `torture = TRUE` 40 | 41 | > Triggers garbage collection after every torture memory allocation call. 42 | 43 | This process somehow never seems to finish and crashes the RStudio session when it stops! 44 | 45 | ```{r Perf-measure-7, eval = FALSE} 46 | profvis(f(), torture = TRUE) 47 | ``` 48 | 49 | The question says that documentation for `rm()` may provide clues: 50 | 51 | ```{r Perf-measure-8} 52 | rm 53 | ``` 54 | 55 | I still couldn't figure out why. I would recommend checking out the [official answer](https://advanced-r-solutions.rbind.io/measuring-performance.html#profiling). 56 | 57 | --- 58 | 59 | ## Microbenchmarking (Exercises 23.3.3) 60 | 61 | --- 62 | 63 | **Q1.** Instead of using `bench::mark()`, you could use the built-in function `system.time()`. But `system.time()` is much less precise, so you'll need to repeat each operation many times with a loop, and then divide to find the average time of each operation, as in the code below. 64 | 65 | ```{r Perf-measure-9, eval = FALSE} 66 | n <- 1e6 67 | system.time(for (i in 1:n) sqrt(x)) / n 68 | system.time(for (i in 1:n) x^0.5) / n 69 | ``` 70 | 71 | How do the estimates from `system.time()` compare to those from `bench::mark()`? Why are they different? 72 | 73 | **A1.** Let's benchmark first using these two approaches: 74 | 75 | ```{r Perf-measure-10} 76 | n <- 1e6 77 | x <- runif(100) 78 | 79 | # bench ------------------- 80 | 81 | bench_df <- bench::mark( 82 | sqrt(x), 83 | x^0.5, 84 | iterations = n, 85 | time_unit = "us" 86 | ) 87 | 88 | t_bench_df <- bench_df %>% 89 | select(expression, time) %>% 90 | rowwise() %>% 91 | mutate(bench_mean = mean(unlist(time))) %>% 92 | ungroup() %>% 93 | select(-time) 94 | 95 | # system.time ------------------- 96 | 97 | # garbage collection performed immediately before the timing 98 | t1_systime_gc <- system.time(for (i in 1:n) sqrt(x), gcFirst = TRUE) / n 99 | t2_systime_gc <- system.time(for (i in 1:n) x^0.5, gcFirst = TRUE) / n 100 | 101 | # garbage collection not performed immediately before the timing 102 | t1_systime_nogc <- system.time(for (i in 1:n) sqrt(x), gcFirst = FALSE) / n 103 | t2_systime_nogc <- system.time(for (i in 1:n) x^0.5, gcFirst = FALSE) / n 104 | 105 | t_systime_df <- tibble( 106 | "expression" = bench_df$expression, 107 | "systime_with_gc" = c(t1_systime_gc["elapsed"], t2_systime_gc["elapsed"]), 108 | "systime_with_nogc" = c(t1_systime_nogc["elapsed"], t2_systime_nogc["elapsed"]) 109 | ) %>% 110 | mutate( 111 | systime_with_gc = systime_with_gc * 1e6, # in microseconds 112 | systime_with_nogc = systime_with_nogc * 1e6 # in microseconds 113 | ) 114 | ``` 115 | 116 | Now we can compare results from these alternatives: 117 | 118 | ```{r Perf-measure-11} 119 | # note that system time columns report time in microseconds 120 | full_join(t_bench_df, t_systime_df, by = "expression") 121 | ``` 122 | 123 | The comparison reveals that these two approaches yield quite similar results. Slight differences in exact values is possibly due to differences in the precision of timers used internally by these functions. 124 | 125 | --- 126 | 127 | **Q2.** Here are two other ways to compute the square root of a vector. Which do you think will be fastest? Which will be slowest? Use microbenchmarking to test your answers. 128 | 129 | ```{r Perf-measure-12, eval = FALSE} 130 | x^(1 / 2) 131 | exp(log(x) / 2) 132 | ``` 133 | 134 | --- 135 | 136 | **A2.** Microbenchmarking all ways to compute square root of a vector mentioned in this chapter. 137 | 138 | ```{r Perf-measure-13} 139 | x <- runif(1000) 140 | 141 | bench::mark( 142 | sqrt(x), 143 | x^0.5, 144 | x^(1 / 2), 145 | exp(log(x) / 2), 146 | iterations = 1000 147 | ) %>% 148 | select(expression, median) %>% 149 | arrange(median) 150 | ``` 151 | 152 | The specialized primitive function `sqrt()` (written in `C`) is the fastest way to compute square root. 153 | 154 | --- 155 | 156 | ## Session information 157 | 158 | ```{r Perf-measure-14} 159 | sessioninfo::session_info(include_base = TRUE) 160 | ``` 161 | -------------------------------------------------------------------------------- /R6.Rmd: -------------------------------------------------------------------------------- 1 | # R6 2 | 3 | ```{r R6-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | Loading the needed libraries: 8 | 9 | ```{r R6-2, warning=FALSE, message=FALSE} 10 | library(R6) 11 | ``` 12 | 13 | ## Classes and methods (Exercises 14.2.6) 14 | 15 | **Q1.** Create a bank account R6 class that stores a balance and allows you to deposit and withdraw money. Create a subclass that throws an error if you attempt to go into overdraft. Create another subclass that allows you to go into overdraft, but charges you a fee. Create the superclass and make sure it works as expected. 16 | 17 | **A1.** First, let's create a bank account R6 class that stores a balance and allows you to deposit and withdraw money: 18 | 19 | ```{r R6-3} 20 | library(R6) 21 | 22 | bankAccount <- R6::R6Class( 23 | "bankAccount", 24 | public = list( 25 | balance = 0, 26 | initialize = function(balance) { 27 | self$balance <- balance 28 | }, 29 | deposit = function(amount) { 30 | self$balance <- self$balance + amount 31 | message(paste0("Current balance is: ", self$balance)) 32 | invisible(self) 33 | }, 34 | withdraw = function(amount) { 35 | self$balance <- self$balance - amount 36 | message(paste0("Current balance is: ", self$balance)) 37 | invisible(self) 38 | } 39 | ) 40 | ) 41 | ``` 42 | 43 | Let's try it out: 44 | 45 | ```{r R6-4} 46 | indra <- bankAccount$new(balance = 100) 47 | 48 | indra$deposit(20) 49 | 50 | indra$withdraw(10) 51 | ``` 52 | 53 | Create a subclass that errors if you attempt to overdraw: 54 | 55 | ```{r R6-5} 56 | bankAccountStrict <- R6::R6Class( 57 | "bankAccountStrict", 58 | inherit = bankAccount, 59 | public = list( 60 | withdraw = function(amount) { 61 | if (self$balance - amount < 0) { 62 | stop( 63 | paste0("Can't withdraw more than your current balance: ", self$balance), 64 | call. = FALSE 65 | ) 66 | } 67 | 68 | super$withdraw(amount) 69 | } 70 | ) 71 | ) 72 | ``` 73 | 74 | Let's try it out: 75 | 76 | ```{r R6-6, error=TRUE} 77 | Pritesh <- bankAccountStrict$new(balance = 100) 78 | 79 | Pritesh$deposit(20) 80 | 81 | Pritesh$withdraw(150) 82 | ``` 83 | 84 | Now let's create a subclass that charges a fee if account is overdrawn: 85 | 86 | ```{r R6-7} 87 | bankAccountFee <- R6::R6Class( 88 | "bankAccountFee", 89 | inherit = bankAccount, 90 | public = list( 91 | withdraw = function(amount) { 92 | super$withdraw(amount) 93 | 94 | if (self$balance) { 95 | self$balance <- self$balance - 10 96 | message("You're withdrawing more than your current balance. You will be charged a fee of 10 euros.") 97 | } 98 | } 99 | ) 100 | ) 101 | ``` 102 | 103 | Let's try it out: 104 | 105 | ```{r R6-8} 106 | Mangesh <- bankAccountFee$new(balance = 100) 107 | 108 | Mangesh$deposit(20) 109 | 110 | Mangesh$withdraw(150) 111 | ``` 112 | 113 | **Q2.** Create an R6 class that represents a shuffled deck of cards. You should be able to draw cards from the deck with `$draw(n)`, and return all cards to the deck and reshuffle with `$reshuffle()`. Use the following code to make a vector of cards. 114 | 115 | ```{r R6-9} 116 | suit <- c("♠", "♥", "♦", "♣") 117 | value <- c("A", 2:10, "J", "Q", "K") 118 | cards <- paste0(rep(value, 4), suit) 119 | ``` 120 | 121 | **A2.** Let's create needed class that represents a shuffled deck of cards: 122 | 123 | ```{r R6-10} 124 | suit <- c("♠", "♥", "♦", "♣") 125 | value <- c("A", 2:10, "J", "Q", "K") 126 | cards <- paste(rep(value, 4), suit) 127 | 128 | Deck <- R6::R6Class( 129 | "Deck", 130 | public = list( 131 | initialize = function(deck) { 132 | private$cards <- sample(deck) 133 | }, 134 | draw = function(n) { 135 | if (n > length(private$cards)) { 136 | stop( 137 | paste0("Can't draw more than remaining number of cards: ", length(private$cards)), 138 | call. = FALSE 139 | ) 140 | } 141 | 142 | drawn_cards <- sample(private$cards, n) 143 | private$cards <- private$cards[-which(private$cards %in% drawn_cards)] 144 | message(paste0("Remaining number of cards: ", length(private$cards))) 145 | 146 | return(drawn_cards) 147 | }, 148 | reshuffle = function() { 149 | private$cards <- sample(private$cards) 150 | invisible(self) 151 | } 152 | ), 153 | private = list( 154 | cards = NULL 155 | ) 156 | ) 157 | ``` 158 | 159 | Let's try it out: 160 | 161 | ```{r R6-11, error=TRUE} 162 | myDeck <- Deck$new(cards) 163 | 164 | myDeck$draw(4) 165 | 166 | myDeck$reshuffle()$draw(5) 167 | 168 | myDeck$draw(50) 169 | ``` 170 | 171 | **Q3.** Why can't you model a bank account or a deck of cards with an S3 class? 172 | 173 | **A3.** We can't model a bank account or a deck of cards with an `S3` class because instances of these classes are *immutable*. 174 | 175 | On the other hand, `R6` classes encapsulate data and represent its *state*, which can change over the course of object's lifecycle. In other words, these objects are *mutable* and well-suited to model a bank account. 176 | 177 | **Q4.** Create an R6 class that allows you to get and set the current time zone. You can access the current time zone with `Sys.timezone()` and set it with `Sys.setenv(TZ = "newtimezone")`. When setting the time zone, make sure the new time zone is in the list provided by `OlsonNames()`. 178 | 179 | **A4.** Here is an `R6` class that manages the current time zone: 180 | 181 | ```{r R6-12} 182 | CurrentTimeZone <- R6::R6Class("CurrentTimeZone", 183 | public = list( 184 | setTimeZone = function(tz) { 185 | stopifnot(tz %in% OlsonNames()) 186 | Sys.setenv(TZ = tz) 187 | }, 188 | getTimeZone = function() { 189 | Sys.timezone() 190 | } 191 | ) 192 | ) 193 | ``` 194 | 195 | Let's try it out: 196 | 197 | ```{r R6-13} 198 | myCurrentTimeZone <- CurrentTimeZone$new() 199 | 200 | myCurrentTimeZone$getTimeZone() 201 | 202 | myCurrentTimeZone$setTimeZone("Asia/Kolkata") 203 | myCurrentTimeZone$getTimeZone() 204 | 205 | myCurrentTimeZone$setTimeZone("Europe/Berlin") 206 | ``` 207 | 208 | **Q5.** Create an R6 class that manages the current working directory. It should have `$get()` and `$set()` methods. 209 | 210 | **A5.** Here is an `R6` class that manages the current working directory: 211 | 212 | ```{r R6-14} 213 | ManageDirectory <- R6::R6Class("ManageDirectory", 214 | public = list( 215 | setWorkingDirectory = function(dir) { 216 | setwd(dir) 217 | }, 218 | getWorkingDirectory = function() { 219 | getwd() 220 | } 221 | ) 222 | ) 223 | ``` 224 | 225 | Let's create an instance of this class and check if the methods work as expected: 226 | 227 | ```{r R6-15, eval=FALSE} 228 | myDirManager <- ManageDirectory$new() 229 | 230 | # current working directory 231 | myDirManager$getWorkingDirectory() 232 | 233 | # change and check if that worked 234 | myDirManager$setWorkingDirectory("..") 235 | myDirManager$getWorkingDirectory() 236 | 237 | # revert this change 238 | myDirManager$setWorkingDirectory("/Advanced-R-exercises") 239 | ``` 240 | 241 | **Q6.** Why can't you model the time zone or current working directory with an S3 class? 242 | 243 | **A6.** Same as answer to **Q3**: 244 | 245 | Objects that represent these real-life entities need to be mutable and `S3` class instances are not mutable. 246 | 247 | **Q7.** What base type are R6 objects built on top of? What attributes do they have? 248 | 249 | **A7.** Let's create an example class and create instance of that class: 250 | 251 | ```{r R6-16} 252 | Example <- R6::R6Class("Example") 253 | myExample <- Example$new() 254 | ``` 255 | 256 | The `R6` objects are built on top of environment: 257 | 258 | ```{r R6-17} 259 | typeof(myExample) 260 | 261 | rlang::env_print(myExample) 262 | ``` 263 | 264 | And it has only `class` attribute, which is a character vector with the `"R6"` being the last element and the superclasses being other elements: 265 | 266 | ```{r R6-18} 267 | attributes(myExample) 268 | ``` 269 | 270 | ## Controlling access (Exercises 14.3.3) 271 | 272 | **Q1.** Create a bank account class that prevents you from directly setting the account balance, but you can still withdraw from and deposit to. Throw an error if you attempt to go into overdraft. 273 | 274 | **A1.** Here is a bank account class that satisfies the specified requirements: 275 | 276 | ```{r R6-19} 277 | SafeBankAccount <- R6::R6Class( 278 | classname = "SafeBankAccount", 279 | public = list( 280 | deposit = function(deposit_amount) { 281 | private$.balance <- private$.balance + deposit_amount 282 | print(paste("Current balance:", private$.balance)) 283 | 284 | invisible(self) 285 | }, 286 | withdraw = function(withdrawal_amount) { 287 | if (withdrawal_amount > private$.balance) { 288 | stop("You can't withdraw more than your current balance.", call. = FALSE) 289 | } 290 | 291 | private$.balance <- private$.balance - withdrawal_amount 292 | print(paste("Current balance:", private$.balance)) 293 | 294 | invisible(self) 295 | } 296 | ), 297 | private = list( 298 | .balance = 0 299 | ) 300 | ) 301 | ``` 302 | 303 | Let's check if it works as expected: 304 | 305 | ```{r R6-20, error=TRUE} 306 | mySafeBankAccount <- SafeBankAccount$new() 307 | 308 | mySafeBankAccount$deposit(100) 309 | 310 | mySafeBankAccount$withdraw(50) 311 | 312 | mySafeBankAccount$withdraw(100) 313 | ``` 314 | 315 | **Q2.** Create a class with a write-only `$password` field. It should have `$check_password(password)` method that returns `TRUE` or `FALSE`, but there should be no way to view the complete password. 316 | 317 | **A2.** Here is an implementation of the class with the needed properties: 318 | 319 | ```{r R6-21} 320 | library(R6) 321 | 322 | checkCredentials <- R6Class( 323 | "checkCredentials", 324 | public = list( 325 | # setter 326 | set_password = function(password) { 327 | private$.password <- password 328 | }, 329 | 330 | # checker 331 | check_password = function(password) { 332 | if (is.null(private$.password)) { 333 | stop("No password set to check against.") 334 | } 335 | 336 | identical(password, private$.password) 337 | }, 338 | 339 | # the default print method prints the private fields as well 340 | print = function() { 341 | cat("Password: XXXX") 342 | 343 | # for method chaining 344 | invisible(self) 345 | } 346 | ), 347 | private = list( 348 | .password = NULL 349 | ) 350 | ) 351 | 352 | myCheck <- checkCredentials$new() 353 | 354 | myCheck$set_password("1234") 355 | print(myCheck) 356 | 357 | myCheck$check_password("abcd") 358 | myCheck$check_password("1234") 359 | ``` 360 | 361 | But, of course, everything is possible: 362 | 363 | ```{r R6-22} 364 | myCheck$.__enclos_env__$private$.password 365 | ``` 366 | 367 | **Q3.** Extend the `Rando` class with another active binding that allows you to access the previous random value. Ensure that active binding is the only way to access the value. 368 | 369 | **A3.** Here is a modified version of the `Rando` class to meet the specified requirements: 370 | 371 | ```{r R6-23} 372 | Rando <- R6::R6Class("Rando", 373 | active = list( 374 | random = function(value) { 375 | if (missing(value)) { 376 | newValue <- runif(1) 377 | private$.previousRandom <- private$.currentRandom 378 | private$.currentRandom <- newValue 379 | return(private$.currentRandom) 380 | } else { 381 | stop("Can't set `$random`", call. = FALSE) 382 | } 383 | }, 384 | previousRandom = function(value) { 385 | if (missing(value)) { 386 | if (is.null(private$.previousRandom)) { 387 | message("No random value has been generated yet.") 388 | } else { 389 | return(private$.previousRandom) 390 | } 391 | } else { 392 | stop("Can't set `$previousRandom`", call. = FALSE) 393 | } 394 | } 395 | ), 396 | private = list( 397 | .currentRandom = NULL, 398 | .previousRandom = NULL 399 | ) 400 | ) 401 | ``` 402 | 403 | Let's try it out: 404 | 405 | ```{r R6-24} 406 | myRando <- Rando$new() 407 | 408 | # first time 409 | myRando$random 410 | myRando$previousRandom 411 | 412 | # second time 413 | myRando$random 414 | myRando$previousRandom 415 | 416 | # third time 417 | myRando$random 418 | myRando$previousRandom 419 | ``` 420 | 421 | **Q4.** Can subclasses access private fields/methods from their parent? Perform an experiment to find out. 422 | 423 | **A4.** Unlike common OOP in other languages (e.g. C++), R6 subclasses (or derived classes) also have access to the private methods in superclass (or base class). 424 | 425 | For instance, in the following example, the `Duck` class has a private method `$quack()`, but its subclass `Mallard` can access it using `super$quack()`. 426 | 427 | ```{r R6-25} 428 | Duck <- R6Class("Duck", 429 | private = list(quack = function() print("Quack Quack")) 430 | ) 431 | 432 | Mallard <- R6Class("Mallard", 433 | inherit = Duck, 434 | public = list(quack = function() super$quack()) 435 | ) 436 | 437 | myMallard <- Mallard$new() 438 | myMallard$quack() 439 | ``` 440 | 441 | ## Reference semantics (Exercises 14.4.4) 442 | 443 | **Q1.** Create a class that allows you to write a line to a specified file. You should open a connection to the file in `$initialize()`, append a line using `cat()` in `$append_line()`, and close the connection in `$finalize()`. 444 | 445 | **A1.** Here is a class that allows you to write a line to a specified file: 446 | 447 | ```{r R6-26} 448 | fileEditor <- R6Class( 449 | "fileEditor", 450 | public = list( 451 | initialize = function(filePath) { 452 | private$.connection <- file(filePath, open = "wt") 453 | }, 454 | append_line = function(text) { 455 | cat( 456 | text, 457 | file = private$.connection, 458 | sep = "\n", 459 | append = TRUE 460 | ) 461 | } 462 | ), 463 | private = list( 464 | .connection = NULL, 465 | # according to R6 docs, the destructor method should be private 466 | finalize = function() { 467 | print("Closing the file connection!") 468 | close(private$.connection) 469 | } 470 | ) 471 | ) 472 | ``` 473 | 474 | Let's check if it works as expected: 475 | 476 | ```{r R6-27} 477 | greetMom <- function() { 478 | f <- tempfile() 479 | myfileEditor <- fileEditor$new(f) 480 | 481 | readLines(f) 482 | 483 | myfileEditor$append_line("Hi mom!") 484 | myfileEditor$append_line("It's a beautiful day!") 485 | 486 | readLines(f) 487 | } 488 | 489 | greetMom() 490 | 491 | # force garbage collection 492 | gc() 493 | ``` 494 | 495 | ## Session information 496 | 497 | ```{r R6-28} 498 | sessioninfo::session_info(include_base = TRUE) 499 | ``` 500 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This book provides solutions to exercises from Hadley Wickham's _Advanced R_ (2nd edition) [book](https://adv-r.hadley.nz/). 2 | 3 | I started working on this book as part of my process to learn by solving each of the book's exercises. While comparing solutions to the [official solutions manual](https://advanced-r-solutions.rbind.io/index.html), I realized that some solutions took different approaches or were at least explained differently. I'm sharing these solutions in case others might find another perspective or explanation than the official solution manual helpful for building understanding. 4 | 5 | Although I have tried to make sure that all solutions are correct, the blame for any inaccuracies lies solely with me. I'd very much appreciate [any suggestions or corrections](https://github.com/IndrajeetPatil/advanced-r-exercises/issues). 6 | 7 | 8 | 9 | ## Code of Conduct 10 | 11 | Please note that the Advanced-R-exercises project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. 12 | -------------------------------------------------------------------------------- /S4.Rmd: -------------------------------------------------------------------------------- 1 | # S4 2 | 3 | ```{r setup, include = FALSE} 4 | library(lubridate) 5 | source("common.R") 6 | 7 | code <- function(...) paste0("`", ..., "`") 8 | ``` 9 | 10 | ## Basics (Exercises 15.2.1) 11 | 12 | --- 13 | 14 | **Q1.** `lubridate::period()` returns an S4 class. What slots does it have? What class is each slot? What accessors does it provide? 15 | 16 | **A1.** Let's first create an instance of `Period` class: 17 | 18 | ```{r S4-1} 19 | library(lubridate) 20 | x <- lubridate::period(c(2, 43, 6), c("hour", "second", "minute")) 21 | x 22 | ``` 23 | 24 | It has the following slots: 25 | 26 | ```{r S4-2} 27 | slotNames(x) 28 | ``` 29 | 30 | Additionally, the base type of each slot (`numeric`) can be seen in `str()` output: 31 | 32 | ```{r S4-3} 33 | str(x) 34 | ``` 35 | 36 | The `{lubridate}` package provides accessors for all slots: 37 | 38 | ```{r S4-4} 39 | year(x) 40 | month(x) 41 | day(x) 42 | hour(x) 43 | minute(x) 44 | second(x) 45 | ``` 46 | 47 | --- 48 | 49 | **Q2.** What other ways can you find help for a method? Read `?"?"` and summarise the details. 50 | 51 | **A2.** The `"?"` operator allows access to documentation in three ways. To demonstrate different ways to access documentation, let's define a new `S4` class. 52 | 53 | ```{r S4-5} 54 | pow <- function(x, exp) c(x, exp) 55 | setGeneric("pow") 56 | setMethod("pow", c("numeric", "numeric"), function(x, exp) x^exp) 57 | ``` 58 | 59 | Ways to access documentation: 60 | 61 | - The general documentation for a generic can be found with `?topic`: 62 | 63 | ```{r S4-6, eval=FALSE} 64 | ?pow 65 | ``` 66 | 67 | - The expression `type?topic` will look for the overall documentation methods for the function `f`. 68 | 69 | ```{r S4-7, eval=FALSE} 70 | ?pow # produces the function documentation 71 | 72 | methods?pow # looks for the overall methods documentation 73 | ``` 74 | 75 | --- 76 | 77 | ## Classes (Exercises 15.3.6) 78 | 79 | --- 80 | 81 | **Q1.** Extend the Person class with fields to match `utils::person()`. Think about what slots you will need, what class each slot should have, and what you'll need to check in your validity method. 82 | 83 | **A1.** The code below extends the `Person` class described in the book to match more closely with `utils::person()`. 84 | 85 | ```{r S4-8} 86 | setClass("Person", 87 | slots = c( 88 | age = "numeric", 89 | given = "character", 90 | family = "character", 91 | middle = "character", 92 | email = "character", 93 | role = "character", 94 | comment = "character" 95 | ), 96 | prototype = list( 97 | age = NA_real_, 98 | given = NA_character_, 99 | family = NA_character_, 100 | middle = NA_character_, 101 | email = NA_character_, 102 | role = NA_character_, 103 | comment = NA_character_ 104 | ) 105 | ) 106 | 107 | # Helper function to create an instance of the `Person` class 108 | Person <- function(given, 109 | family, 110 | middle = NA_character_, 111 | age = NA_real_, 112 | email = NA_character_, 113 | role = NA_character_, 114 | comment = NA_character_) { 115 | age <- as.double(age) 116 | 117 | new("Person", 118 | age = age, 119 | given = given, 120 | family = family, 121 | middle = middle, 122 | email = email, 123 | role = role, 124 | comment = comment 125 | ) 126 | } 127 | 128 | # Validator to ensure that each slot is of length one and that the specified 129 | # role is one of the possible roles 130 | setValidity("Person", function(object) { 131 | invalid_length <- NULL 132 | slot_lengths <- c( 133 | length(object@age), 134 | length(object@given), 135 | length(object@middle), 136 | length(object@family), 137 | length(object@email), 138 | length(object@comment) 139 | ) 140 | 141 | if (any(slot_lengths > 1L)) { 142 | invalid_length <- "\nFollowing slots must be of length 1:\n @age, @given, @family, @middle, @email, @comment" 143 | } 144 | 145 | possible_roles <- c( 146 | NA_character_, 147 | "aut", 148 | "com", 149 | "cph", 150 | "cre", 151 | "ctb", 152 | "ctr", 153 | "dtc", 154 | "fnd", 155 | "rev", 156 | "ths", 157 | "trl" 158 | ) 159 | 160 | if (any(!object@role %in% possible_roles)) { 161 | invalid_length <- paste( 162 | invalid_length, 163 | "\nSlot @role(s) must be one of the following:\n", 164 | paste(possible_roles, collapse = ", ") 165 | ) 166 | } 167 | 168 | if (!is.null(invalid_length)) { 169 | return(invalid_length) 170 | } else { 171 | return(TRUE) 172 | } 173 | }) 174 | ``` 175 | 176 | Let's make sure that validation works as expected: 177 | 178 | ```{r S4-9, error=TRUE} 179 | # length of first argument not 1 180 | Person(c("Indrajeet", "Surendra"), "Patil") 181 | 182 | # role not recognized 183 | Person("Indrajeet", "Patil", role = "xyz") 184 | 185 | # all okay 186 | Person("Indrajeet", "Patil", role = c("aut", "cph")) 187 | ``` 188 | 189 | --- 190 | 191 | **Q2.** What happens if you define a new S4 class that doesn't have any slots? (Hint: read about virtual classes in `?setClass`.) 192 | 193 | **A2.** If you define a new `S4` class that doesn't have any slots, it will create *virtual* classes: 194 | 195 | ```{r S4-10, error=TRUE} 196 | setClass("Empty") 197 | 198 | isVirtualClass("Empty") 199 | ``` 200 | 201 | You can't create an instance of this class: 202 | 203 | ```{r S4-11, error=TRUE} 204 | new("Empty") 205 | ``` 206 | 207 | So how is this useful? As mentioned in `?setClass` docs: 208 | 209 | > Classes exist for which no actual objects can be created, the virtual classes. 210 | > 211 | > The most common and useful form of virtual class is the class union, a virtual class that is defined in a call to `setClassUnion()` rather than a call to `setClass()`. 212 | 213 | So virtual classes can still be inherited: 214 | 215 | ```{r S4-12, eval=FALSE} 216 | setClass("Nothing", contains = "Empty") 217 | ``` 218 | 219 | In addition to not specifying any slots, here is another way to create virtual classes: 220 | 221 | > Calls to `setClass()` will also create a virtual class, either when only the Class argument is supplied (no slots or superclasses) or when the `contains=` argument includes the special class name `"VIRTUAL"`. 222 | 223 | --- 224 | 225 | **Q3.** Imagine you were going to reimplement factors, dates, and data frames in S4. Sketch out the `setClass()` calls that you would use to define the classes. Think about appropriate `slots` and `prototype`. 226 | 227 | **A3.** The reimplementation of following classes in `S4` might have definitions like the following. 228 | 229 | - `factor` 230 | 231 | For simplicity, we won't provide all options that `factor()` provides. Note that `x` has pseudo-class `ANY` to accept objects of any type. 232 | 233 | ```{r S4-13} 234 | setClass("Factor", 235 | slots = c( 236 | x = "ANY", 237 | levels = "character", 238 | ordered = "logical" 239 | ), 240 | prototype = list( 241 | x = character(), 242 | levels = character(), 243 | ordered = FALSE 244 | ) 245 | ) 246 | 247 | new("Factor", x = letters[1:3], levels = LETTERS[1:3]) 248 | 249 | new("Factor", x = 1:3, levels = letters[1:3]) 250 | 251 | new("Factor", x = c(TRUE, FALSE, TRUE), levels = c("x", "y", "x")) 252 | ``` 253 | 254 | - `Date` 255 | 256 | Just like the base-R version, this will have only integer values. 257 | 258 | ```{r S4-14} 259 | setClass("Date2", 260 | slots = list( 261 | data = "integer" 262 | ), 263 | prototype = list( 264 | data = integer() 265 | ) 266 | ) 267 | 268 | new("Date2", data = 1342L) 269 | ``` 270 | 271 | - `data.frame` 272 | 273 | The tricky part is supporting the `...` argument of `data.frame()`. For this, we can let the users pass a (named) list. 274 | 275 | ```{r S4-15} 276 | setClass("DataFrame", 277 | slots = c( 278 | data = "list", 279 | row.names = "character" 280 | ), 281 | prototype = list( 282 | data = list(), 283 | row.names = character(0L) 284 | ) 285 | ) 286 | 287 | new("DataFrame", data = list(x = c("a", "b"), y = c(1L, 2L))) 288 | ``` 289 | 290 | --- 291 | 292 | ## Generics and methods (Exercises 15.4.5) 293 | 294 | --- 295 | 296 | **Q1.** Add `age()` accessors for the `Person` class. 297 | 298 | **A1.** We first should define a generic and then a method for our class: 299 | 300 | ```{r S4-16} 301 | Indra <- Person("Indrajeet", "Patil", role = c("aut", "cph"), age = 34) 302 | 303 | setGeneric("age", function(x) standardGeneric("age")) 304 | setMethod("age", "Person", function(x) x@age) 305 | 306 | age(Indra) 307 | ``` 308 | 309 | --- 310 | 311 | **Q2.** In the definition of the generic, why is it necessary to repeat the name of the generic twice? 312 | 313 | **A2.** Let's look at the generic we just defined; the generic name `"age"` is repeated twice. 314 | 315 | ```{r S4-17, eval=FALSE} 316 | setGeneric(name = "age", def = function(x) standardGeneric("age")) 317 | ``` 318 | 319 | This is because: 320 | 321 | - the `"age"` passed to argument `name` provides the name for the generic 322 | - the `"age"` passed to argument `def` supplies the method dispatch 323 | 324 | This is reminiscent of how we defined `S3` generic, where we also had to repeat the name twice: 325 | 326 | ```{r S4-18, eval=FALSE} 327 | age <- function(x) { 328 | UseMethod("age") 329 | } 330 | ``` 331 | 332 | --- 333 | 334 | **Q3.** Why does the `show()` method defined in Section [Show method](https://adv-r.hadley.nz/s4.html#show-method) use `is(object)[[1]]`? (Hint: try printing the employee subclass.) 335 | 336 | **A3.** Because we wish to define `show()` method for a specific class, we need to disregard the other super-/sub-classes. 337 | 338 | ```{r S4-19, echo=FALSE} 339 | setClass("Person", 340 | slots = c( 341 | name = "character", 342 | age = "numeric" 343 | ), 344 | prototype = list( 345 | name = NA_character_, 346 | age = NA_real_ 347 | ) 348 | ) 349 | 350 | setClass("Employee", 351 | contains = "Person", 352 | slots = c( 353 | boss = "Person" 354 | ), 355 | prototype = list( 356 | boss = new("Person") 357 | ) 358 | ) 359 | ``` 360 | 361 | Always using the first element ensures that the method will be defined for the class in question: 362 | 363 | ```{r S4-20} 364 | Alice <- new("Employee") 365 | 366 | is(Alice) 367 | 368 | is(Alice)[[1]] 369 | ``` 370 | 371 | --- 372 | 373 | **Q4.** What happens if you define a method with different argument names to the generic? 374 | 375 | **A4.** Let's experiment with the method we defined in **Q1.** to study this behavior. 376 | 377 | The original method that worked as expected since the argument name between generic and method matched: 378 | 379 | ```{r S4-21} 380 | setMethod("age", "Person", function(x) x@age) 381 | ``` 382 | 383 | If this is not the case, we either get a warning or get an error depending on which and how many arguments have been specified: 384 | 385 | ```{r S4-22, error=TRUE} 386 | setMethod("age", "Person", function(object) object@age) 387 | 388 | setMethod("age", "Person", function(object, x) object@age) 389 | 390 | setMethod("age", "Person", function(...) ...elt(1)@age) 391 | 392 | setMethod("age", "Person", function(x, ...) x@age) 393 | ``` 394 | 395 | --- 396 | 397 | ## Method dispatch (Exercises 15.5.5) 398 | 399 | --- 400 | 401 | **Q1.** Draw the method graph for `r paste0(code("f("), emojis[["sweat_smile"]], ", ", emojis[["kissing_cat"]], code(")"))`. 402 | 403 | **A1.** I don't how to prepare the visual illustrations used in the book, so I am linking to the illustration in the [official solution manual](https://advanced-r-solutions.rbind.io/s4.html#method-dispatch): 404 | 405 | ```{r S4-23, echo=FALSE, eval=!knitr::is_latex_output()} 406 | knitr::include_graphics("https://raw.githubusercontent.com/Tazinho/Advanced-R-Solutions/main/images/s4/method_dispatch1.png", auto_pdf = TRUE) 407 | ``` 408 | 409 | --- 410 | 411 | **Q2.** Draw the method graph for `r paste0(code("f("), emojis[["smiley"]], ", ", emojis[["wink"]], ", ", emojis[["kissing_smiling_eyes"]], code(")"))`. 412 | 413 | **A2.** I don't have access to the software used to prepare the visual illustrations used in the book, so I am linking to the illustration in the [official solution manual](https://advanced-r-solutions.rbind.io/s4.html#method-dispatch): 414 | 415 | ```{r S4-24, echo=FALSE, eval=!knitr::is_latex_output()} 416 | knitr::include_graphics("https://raw.githubusercontent.com/Tazinho/Advanced-R-Solutions/main/images/s4/method_dispatch2.png", auto_pdf = TRUE) 417 | ``` 418 | 419 | --- 420 | 421 | **Q3.** Take the last example which shows multiple dispatch over two classes that use multiple inheritance. What happens if you define a method for all terminal classes? Why does method dispatch not save us much work here? 422 | 423 | **A3.** Because one class has distance of 2 to all terminal nodes and the other four have distance of 1 to two terminal nodes each, this will introduce ambiguity. 424 | 425 | Method dispatch not save us much work here because to resolve this ambiguity we have to define five more methods (one per class combination). 426 | 427 | --- 428 | 429 | ## `S4` and `S3` (Exercises 15.6.3) 430 | 431 | --- 432 | 433 | **Q1.** What would a full `setOldClass()` definition look like for an ordered factor (i.e. add `slots` and `prototype` the definition above)? 434 | 435 | **A1.** We can register the old-style/`S3` `ordered` class to a formally defined class using `setOldClass()`. 436 | 437 | ```{r S4-25} 438 | setClass("factor", 439 | contains = "integer", 440 | slots = c( 441 | levels = "character" 442 | ), 443 | prototype = structure( 444 | integer(), 445 | levels = character() 446 | ) 447 | ) 448 | setOldClass("factor", S4Class = "factor") 449 | 450 | setClass("Ordered", 451 | contains = "factor", 452 | slots = c( 453 | levels = "character", 454 | ordered = "logical" 455 | ), 456 | prototype = structure( 457 | integer(), 458 | levels = character(), 459 | ordered = logical() 460 | ) 461 | ) 462 | 463 | setOldClass("ordered", S4Class = "Ordered") 464 | ``` 465 | 466 | Let's use it to see if it works as expected. 467 | 468 | ```{r S4-26} 469 | x <- new("Ordered", 1L:4L, levels = letters[1:4], ordered = TRUE) 470 | 471 | x 472 | 473 | str(x) 474 | 475 | class(x) 476 | ``` 477 | 478 | --- 479 | 480 | **Q2.** Define a `length` method for the `Person` class. 481 | 482 | **A2.** Because our `Person` class can be used to create objects that represent multiple people, let's say the `length()` method returns how many persons are in the object. 483 | 484 | ```{r S4-27} 485 | Friends <- new("Person", name = c("Vishu", "Aditi")) 486 | ``` 487 | 488 | We can define an `S3` method for this class: 489 | 490 | ```{r S4-28} 491 | length.Person <- function(x) length(x@name) 492 | 493 | length(Friends) 494 | ``` 495 | 496 | Alternatively, we can also write `S4` method: 497 | 498 | ```{r S4-29} 499 | setMethod("length", "Person", function(x) length(x@name)) 500 | 501 | length(Friends) 502 | ``` 503 | 504 | --- 505 | 506 | ## Session information 507 | 508 | ```{r S4-30} 509 | sessioninfo::session_info(include_base = TRUE) 510 | ``` 511 | 512 | -------------------------------------------------------------------------------- /Subsetting.Rmd: -------------------------------------------------------------------------------- 1 | # Subsetting 2 | 3 | ```{r Subsetting-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | Attaching the needed libraries: 8 | 9 | ```{r Subsetting-2, warning=FALSE, message=FALSE} 10 | library(tibble) 11 | ``` 12 | 13 | ## Selecting multiple elements (Exercises 4.2.6) 14 | 15 | **Q1.** Fix each of the following common data frame subsetting errors: 16 | 17 | ```{r Subsetting-3, eval = FALSE} 18 | # styler: off 19 | mtcars[mtcars$cyl = 4, ] 20 | mtcars[-1:4, ] 21 | mtcars[mtcars$cyl <= 5] 22 | mtcars[mtcars$cyl == 4 | 6, ] 23 | # styler: on 24 | ``` 25 | 26 | **A1.** Fixed versions of these commands: 27 | 28 | ```{r Subsetting-4, eval=FALSE} 29 | # `==` instead of `=` 30 | mtcars[mtcars$cyl == 4, ] 31 | 32 | # `-(1:4)` instead of `-1:4` 33 | mtcars[-(1:4), ] 34 | 35 | # `,` was missing 36 | mtcars[mtcars$cyl <= 5, ] 37 | 38 | # correct subsetting syntax 39 | mtcars[mtcars$cyl == 4 | mtcars$cyl == 6, ] 40 | mtcars[mtcars$cyl %in% c(4, 6), ] 41 | ``` 42 | 43 | **Q2.** Why does the following code yield five missing values? 44 | 45 | ```{r Subsetting-5} 46 | x <- 1:5 47 | x[NA] 48 | ``` 49 | 50 | **A2.** This is because of two reasons: 51 | 52 | - The default type of `NA` in R is of `logical` type. 53 | 54 | ```{r Subsetting-6} 55 | typeof(NA) 56 | ``` 57 | 58 | - R recycles indexes to match the length of the vector. 59 | 60 | ```{r Subsetting-7} 61 | x <- 1:5 62 | x[c(TRUE, FALSE)] # recycled to c(TRUE, FALSE, TRUE, FALSE, TRUE) 63 | ``` 64 | 65 | **Q3.** What does `upper.tri()` return? How does subsetting a matrix with it work? Do we need any additional subsetting rules to describe its behaviour? 66 | 67 | ```{r Subsetting-8, eval = FALSE} 68 | x <- outer(1:5, 1:5, FUN = "*") 69 | x[upper.tri(x)] 70 | ``` 71 | 72 | **A3.** The documentation for `upper.tri()` states- 73 | 74 | > Returns a matrix of logicals the same size of a given matrix with entries `TRUE` in the **upper triangle** 75 | 76 | ```{r Subsetting-9} 77 | (x <- outer(1:5, 1:5, FUN = "*")) 78 | 79 | upper.tri(x) 80 | ``` 81 | 82 | When used with a matrix for subsetting, elements corresponding to `TRUE` in the subsetting matrix are selected. But, instead of a matrix, this returns a vector: 83 | 84 | ```{r Subsetting-10} 85 | x[upper.tri(x)] 86 | ``` 87 | 88 | **Q4.** Why does `mtcars[1:20]` return an error? How does it differ from the similar `mtcars[1:20, ]`? 89 | 90 | **A4.** When indexed like a list, data frame columns at given indices will be selected. 91 | 92 | ```{r Subsetting-11} 93 | head(mtcars[1:2]) 94 | ``` 95 | 96 | `mtcars[1:20]` doesn't work because there are only `r length(mtcars)` columns in `mtcars` dataset. 97 | 98 | On the other hand, `mtcars[1:20, ]` indexes a dataframe like a matrix, and because there are indeed 20 rows in `mtcars`, all columns with these rows are selected. 99 | 100 | ```{r Subsetting-12} 101 | nrow(mtcars[1:20, ]) 102 | ``` 103 | 104 | **Q5.** Implement your own function that extracts the diagonal entries from a matrix (it should behave like `diag(x)` where `x` is a matrix). 105 | 106 | **A5.** We can combine the existing functions to our advantage: 107 | 108 | ```{r Subsetting-13} 109 | x[!upper.tri(x) & !lower.tri(x)] 110 | 111 | diag(x) 112 | ``` 113 | 114 | **Q6.** What does `df[is.na(df)] <- 0` do? How does it work? 115 | 116 | **A6.** This expression replaces every instance of `NA` in `df` with `0`. 117 | 118 | `is.na(df)` produces a matrix of logical values, which provides a way of subsetting. 119 | 120 | ```{r Subsetting-14} 121 | (df <- tibble(x = c(1, 2, NA), y = c(NA, 5, NA))) 122 | 123 | is.na(df) 124 | 125 | class(is.na(df)) 126 | ``` 127 | 128 | ## Selecting a single element (Exercises 4.3.5) 129 | 130 | **Q1.** Brainstorm as many ways as possible to extract the third value from the `cyl` variable in the `mtcars` dataset. 131 | 132 | **A1.** Possible ways to to extract the third value from the `cyl` variable in the `mtcars` dataset: 133 | 134 | ```{r Subsetting-15} 135 | mtcars[["cyl"]][[3]] 136 | mtcars[[c(2, 3)]] 137 | mtcars[3, ][["cyl"]] 138 | mtcars[3, ]$cyl 139 | mtcars[3, "cyl"] 140 | mtcars[, "cyl"][[3]] 141 | mtcars[3, 2] 142 | mtcars$cyl[[3]] 143 | ``` 144 | 145 | **Q2.** Given a linear model, e.g., `mod <- lm(mpg ~ wt, data = mtcars)`, extract the residual degrees of freedom. Then extract the R squared from the model summary (`summary(mod)`) 146 | 147 | **A2.** Given that objects of class `lm` are lists, we can use subsetting operators to extract elements we want. 148 | 149 | ```{r Subsetting-16} 150 | mod <- lm(mpg ~ wt, data = mtcars) 151 | class(mod) 152 | typeof(mod) 153 | ``` 154 | 155 | - extracting the residual degrees of freedom 156 | 157 | ```{r Subsetting-17} 158 | mod$df.residual 159 | mod[["df.residual"]] 160 | ``` 161 | 162 | - extracting the R squared from the model summary 163 | 164 | ```{r Subsetting-18} 165 | summary(mod)$r.squared 166 | summary(mod)[["r.squared"]] 167 | ``` 168 | 169 | ## Applications (Exercises 4.5.9) 170 | 171 | **Q1.** How would you randomly permute the columns of a data frame? (This is an important technique in random forests.) Can you simultaneously permute the rows and columns in one step? 172 | 173 | **A1.** Let's create a small data frame to work with. 174 | 175 | ```{r Subsetting-19} 176 | df <- head(mtcars) 177 | 178 | # original 179 | df 180 | ``` 181 | 182 | To randomly permute the columns of a data frame, we can combine `[` and `sample()` as follows: 183 | 184 | - randomly permute columns 185 | 186 | ```{r Subsetting-20} 187 | df[sample.int(ncol(df))] 188 | ``` 189 | 190 | - randomly permute rows 191 | 192 | ```{r Subsetting-21} 193 | df[sample.int(nrow(df)), ] 194 | ``` 195 | 196 | - randomly permute columns and rows 197 | 198 | ```{r Subsetting-22} 199 | df[sample.int(nrow(df)), sample.int(ncol(df))] 200 | ``` 201 | 202 | **Q2.** How would you select a random sample of `m` rows from a data frame? What if the sample had to be contiguous (i.e., with an initial row, a final row, and every row in between)? 203 | 204 | **A2.** Let's create a small data frame to work with. 205 | 206 | ```{r Subsetting-23} 207 | df <- head(mtcars) 208 | 209 | # original 210 | df 211 | 212 | # number of rows to sample 213 | m <- 2L 214 | ``` 215 | 216 | To select a random sample of `m` rows from a data frame, we can combine `[` and `sample()` as follows: 217 | 218 | - random and non-contiguous sample of `m` rows from a data frame 219 | 220 | ```{r Subsetting-24} 221 | df[sample(nrow(df), m), ] 222 | ``` 223 | 224 | - random and contiguous sample of `m` rows from a data frame 225 | 226 | ```{r Subsetting-25} 227 | # select a random starting position from available number of rows 228 | start_row <- sample(nrow(df) - m + 1, size = 1) 229 | 230 | # adjust ending position while avoiding off-by-one error 231 | end_row <- start_row + m - 1 232 | 233 | df[start_row:end_row, ] 234 | ``` 235 | 236 | **Q3.** How could you put the columns in a data frame in alphabetical order? 237 | 238 | **A3.** we can sort columns in a data frame in the alphabetical order using `[` with `order()`: 239 | 240 | ```{r Subsetting-26} 241 | # columns in original order 242 | names(mtcars) 243 | 244 | # columns in alphabetical order 245 | names(mtcars[order(names(mtcars))]) 246 | ``` 247 | 248 | ## Session information 249 | 250 | ```{r Subsetting-27} 251 | sessioninfo::session_info(include_base = TRUE) 252 | ``` 253 | -------------------------------------------------------------------------------- /Translation.Rmd: -------------------------------------------------------------------------------- 1 | # Translation 2 | 3 | ```{r Translation-1, include = FALSE} 4 | source("common.R") 5 | source("dsl-html-attributes.R") 6 | ``` 7 | 8 | Needed libraries: 9 | 10 | ```{r Translation-2, warning=FALSE,message=FALSE} 11 | library(rlang) 12 | library(purrr) 13 | ``` 14 | 15 | ## HTML (Exercises 21.2.6) 16 | 17 | --- 18 | 19 | **Q1.** The escaping rules for `` so that the tag isn't closed too early. For example, `script("''")`, shouldn't generate this: 20 | 21 | ```html 22 | ' 23 | ``` 24 | 25 | But 26 | 27 | ```html 28 | 29 | ``` 30 | 31 | Adapt the `escape()` to follow these rules when a new argument `script` is set to `TRUE`. 32 | 33 | **A1.** Let's first start with the boilerplate code included in the book: 34 | 35 | ```{r Translation-3} 36 | escape <- function(x, ...) UseMethod("escape") 37 | 38 | escape.character <- function(x, script = FALSE) { 39 | if (script) { 40 | x <- gsub("", "<\\/script>", x, fixed = TRUE) 41 | } else { 42 | x <- gsub("&", "&", x) 43 | x <- gsub("<", "<", x) 44 | x <- gsub(">", ">", x) 45 | } 46 | 47 | html(x) 48 | } 49 | 50 | escape.advr_html <- function(x, ...) x 51 | ``` 52 | 53 | We will also need to tweak the boilerplate to pass this additional parameter to `escape()`: 54 | 55 | ```{r Translation-4} 56 | html <- function(x) structure(x, class = "advr_html") 57 | 58 | print.advr_html <- function(x, ...) { 59 | out <- paste0(" ", x) 60 | cat(paste(strwrap(out), collapse = "\n"), "\n", sep = "") 61 | } 62 | 63 | dots_partition <- function(...) { 64 | dots <- list2(...) 65 | 66 | if (is.null(names(dots))) { 67 | is_named <- rep(FALSE, length(dots)) 68 | } else { 69 | is_named <- names(dots) != "" 70 | } 71 | 72 | list( 73 | named = dots[is_named], 74 | unnamed = dots[!is_named] 75 | ) 76 | } 77 | 78 | tag <- function(tag, script = FALSE) { 79 | force(script) 80 | new_function( 81 | exprs(... = ), 82 | expr({ 83 | dots <- dots_partition(...) 84 | attribs <- html_attributes(dots$named) 85 | children <- map_chr(.x = dots$unnamed, .f = ~ escape(.x, !!script)) 86 | 87 | html(paste0( 88 | !!paste0("<", tag), 89 | attribs, 90 | ">", 91 | paste(children, collapse = ""), 92 | !!paste0("") 93 | )) 94 | }), 95 | caller_env() 96 | ) 97 | } 98 | 99 | void_tag <- function(tag) { 100 | new_function( 101 | exprs(... = ), 102 | expr({ 103 | dots <- dots_partition(...) 104 | if (length(dots$unnamed) > 0) { 105 | abort(!!paste0("<", tag, "> must not have unnamed arguments")) 106 | } 107 | attribs <- html_attributes(dots$named) 108 | 109 | html(paste0(!!paste0("<", tag), attribs, " />")) 110 | }), 111 | caller_env() 112 | ) 113 | } 114 | 115 | p <- tag("p") 116 | script <- tag("script", script = TRUE) 117 | ``` 118 | 119 | ```{r Translation-5} 120 | script("''") 121 | ``` 122 | 123 | --- 124 | 125 | **Q2.** The use of `...` for all functions has some big downsides. There's no input validation and there will be little information in the documentation or autocomplete about how they are used in the function. Create a new function that, when given a named list of tags and their attribute names (like below), creates tag functions with named arguments. 126 | 127 | ```{r Translation-6, eval = FALSE} 128 | list( 129 | a = c("href"), 130 | img = c("src", "width", "height") 131 | ) 132 | ``` 133 | 134 | All tags should get `class` and `id` attributes. 135 | 136 | --- 137 | 138 | **Q3.** Reason about the following code that calls `with_html()` referencing objects from the environment. Will it work or fail? Why? Run the code to verify your predictions. 139 | 140 | ```{r Translation-7, eval = FALSE} 141 | greeting <- "Hello!" 142 | with_html(p(greeting)) 143 | p <- function() "p" 144 | address <- "123 anywhere street" 145 | with_html(p(address)) 146 | ``` 147 | 148 | **A3.** To work with this, we first need to copy-paste relevant code from the book: 149 | 150 | ```{r Translation-8} 151 | tags <- c( 152 | "a", 153 | "abbr", 154 | "address", 155 | "article", 156 | "aside", 157 | "audio", 158 | "b", 159 | "bdi", 160 | "bdo", 161 | "blockquote", 162 | "body", 163 | "button", 164 | "canvas", 165 | "caption", 166 | "cite", 167 | "code", 168 | "colgroup", 169 | "data", 170 | "datalist", 171 | "dd", 172 | "del", 173 | "details", 174 | "dfn", 175 | "div", 176 | "dl", 177 | "dt", 178 | "em", 179 | "eventsource", 180 | "fieldset", 181 | "figcaption", 182 | "figure", 183 | "footer", 184 | "form", 185 | "h1", 186 | "h2", 187 | "h3", 188 | "h4", 189 | "h5", 190 | "h6", 191 | "head", 192 | "header", 193 | "hgroup", 194 | "html", 195 | "i", 196 | "iframe", 197 | "ins", 198 | "kbd", 199 | "label", 200 | "legend", 201 | "li", 202 | "mark", 203 | "map", 204 | "menu", 205 | "meter", 206 | "nav", 207 | "noscript", 208 | "object", 209 | "ol", 210 | "optgroup", 211 | "option", 212 | "output", 213 | "p", 214 | "pre", 215 | "progress", 216 | "q", 217 | "ruby", 218 | "rp", 219 | "rt", 220 | "s", 221 | "samp", 222 | "script", 223 | "section", 224 | "select", 225 | "small", 226 | "span", 227 | "strong", 228 | "style", 229 | "sub", 230 | "summary", 231 | "sup", 232 | "table", 233 | "tbody", 234 | "td", 235 | "textarea", 236 | "tfoot", 237 | "th", 238 | "thead", 239 | "time", 240 | "title", 241 | "tr", 242 | "u", 243 | "ul", 244 | "var", 245 | "video" 246 | ) 247 | 248 | void_tags <- c( 249 | "area", 250 | "base", 251 | "br", 252 | "col", 253 | "command", 254 | "embed", 255 | "hr", 256 | "img", 257 | "input", 258 | "keygen", 259 | "link", 260 | "meta", 261 | "param", 262 | "source", 263 | "track", 264 | "wbr" 265 | ) 266 | 267 | html_tags <- c( 268 | tags %>% set_names() %>% map(tag), 269 | void_tags %>% set_names() %>% map(void_tag) 270 | ) 271 | 272 | with_html <- function(code) { 273 | code <- enquo(code) 274 | eval_tidy(code, html_tags) 275 | } 276 | ``` 277 | 278 | Note that `with_html()` uses `eval_tidy()`, and therefore `code` argument is evaluated first in the `html_tags` named list, which acts as a data mask, and if no object is found in the data mask, searches in the caller environment. 279 | 280 | For this reason, the first example code will work: 281 | 282 | ```{r Translation-9} 283 | greeting <- "Hello!" 284 | with_html(p(greeting)) 285 | ``` 286 | 287 | The following code, however, is not going to work because there is already `address` element in the data mask, and so `p()` will take a function `address()` as an input, and `escape()` doesn't know how to deal with objects of `function` type: 288 | 289 | ```{r Translation-10, error=TRUE} 290 | "address" %in% names(html_tags) 291 | 292 | p <- function() "p" 293 | address <- "123 anywhere street" 294 | with_html(p(address)) 295 | ``` 296 | 297 | --- 298 | 299 | **Q4.** Currently the HTML doesn't look terribly pretty, and it's hard to see the structure. How could you adapt `tag()` to do indenting and formatting? (You may need to do some research into block and inline tags.) 300 | 301 | **A4.** Let's first have a look at what it currently looks like: 302 | 303 | ```{r Translation-11} 304 | with_html( 305 | body( 306 | h1("A heading", id = "first"), 307 | p("Some text &", b("some bold text.")), 308 | img(src = "myimg.png", width = 100, height = 100) 309 | ) 310 | ) 311 | ``` 312 | 313 | We can improve this to follow the [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html#HTML_Formatting_Rules). 314 | 315 | For this, we need to create a new function to indent the code conditionally: 316 | 317 | ```{r} 318 | print.advr_html <- function(x, ...) { 319 | cat(paste("", x, sep = "\n")) 320 | } 321 | 322 | indent <- function(x) { 323 | paste0(" ", gsub("\n", "\n ", x)) 324 | } 325 | 326 | format_code <- function(children, indent = FALSE) { 327 | if (indent) { 328 | paste0("\n", paste0(indent(children), collapse = "\n"), "\n") 329 | } else { 330 | paste(children, collapse = "") 331 | } 332 | } 333 | ``` 334 | 335 | We can then update the `body()` function to use this new helper: 336 | 337 | ```{r} 338 | html_tags$body <- function(...) { 339 | dots <- dots_partition(...) 340 | attribs <- html_attributes(dots$named) 341 | children <- map_chr(dots$unnamed, escape) 342 | 343 | html(paste0( 344 | "", 347 | format_code(children, indent = TRUE), 348 | "" 349 | )) 350 | } 351 | ``` 352 | 353 | The new formatting looks much better: 354 | 355 | ```{r} 356 | with_html( 357 | body( 358 | h1("A heading", id = "first"), 359 | p("Some text &", b("some bold text.")), 360 | img(src = "myimg.png", width = 100, height = 100) 361 | ) 362 | ) 363 | ``` 364 | 365 | --- 366 | 367 | ## LaTeX (Exercises 21.3.8) 368 | 369 | I didn't manage to solve these exercises, and so I'd recommend checking out the solutions in the [official solutions manual](https://advanced-r-solutions.rbind.io/translating-r-code.html#latex). 370 | 371 | --- 372 | 373 | **Q1.** Add escaping. The special symbols that should be escaped by adding a backslash in front of them are `\`, `$`, and `%`. Just as with HTML, you'll need to make sure you don't end up double-escaping. So you'll need to create a small S3 class and then use that in function operators. That will also allow you to embed arbitrary LaTeX if needed. 374 | 375 | --- 376 | 377 | **Q2.** Complete the DSL to support all the functions that `plotmath` supports. 378 | 379 | --- 380 | 381 | ## Session information 382 | 383 | ```{r Translation-12} 384 | sessioninfo::session_info(include_base = TRUE) 385 | ``` 386 | -------------------------------------------------------------------------------- /Vectors.Rmd: -------------------------------------------------------------------------------- 1 | # Vectors 2 | 3 | ```{r Vectors-1, include = FALSE} 4 | source("common.R") 5 | ``` 6 | 7 | ## Atomic vectors (Exercises 3.2.5) 8 | 9 | **Q1.** How do you create raw and complex scalars? (See `?raw` and `?complex`.) 10 | 11 | **A1.** In R, scalars are nothing but vectors of length 1, and can be created using the same constructor. 12 | 13 | - Raw vectors 14 | 15 | The raw type holds raw bytes, and can be created using `charToRaw()`. For example, 16 | 17 | ```{r Vectors-2} 18 | x <- "A string" 19 | 20 | (y <- charToRaw(x)) 21 | 22 | typeof(y) 23 | ``` 24 | 25 | An alternative is to use `as.raw()`: 26 | 27 | ```{r Vectors-3} 28 | as.raw("–") # en-dash 29 | as.raw("—") # em-dash 30 | ``` 31 | 32 | - Complex vectors 33 | 34 | Complex vectors are used to represent (surprise!) complex numbers. 35 | 36 | Example of a complex scalar: 37 | 38 | ```{r Vectors-4} 39 | (x <- complex(length.out = 1, real = 1, imaginary = 8)) 40 | 41 | typeof(x) 42 | ``` 43 | 44 | **Q2.** Test your knowledge of the vector coercion rules by predicting the output of the following uses of `c()`: 45 | 46 | ```{r Vectors-5, eval=FALSE} 47 | c(1, FALSE) 48 | c("a", 1) 49 | c(TRUE, 1L) 50 | ``` 51 | 52 | **A2.** The vector coercion rules dictate that the data type with smaller size will be converted to data type with bigger size. 53 | 54 | ```{r Vectors-6} 55 | c(1, FALSE) 56 | 57 | c("a", 1) 58 | 59 | c(TRUE, 1L) 60 | ``` 61 | 62 | **Q3.** Why is `1 == "1"` true? Why is `-1 < FALSE` true? Why is `"one" < 2` false? 63 | 64 | **A3.** The coercion rules for vectors reveal why some of these comparisons return the results that they do. 65 | 66 | ```{r Vectors-7} 67 | 1 == "1" 68 | 69 | c(1, "1") 70 | ``` 71 | 72 | ```{r Vectors-8} 73 | -1 < FALSE 74 | 75 | c(-1, FALSE) 76 | ``` 77 | 78 | ```{r Vectors-9} 79 | "one" < 2 80 | 81 | c("one", 2) 82 | 83 | sort(c("one", 2)) 84 | ``` 85 | 86 | **Q4.** Why is the default missing value, `NA`, a logical vector? What's special about logical vectors? (Hint: think about `c(FALSE, NA_character_)`.) 87 | 88 | **A4.** The `"logical"` type is the lowest in the coercion hierarchy. 89 | 90 | So `NA` defaulting to any other type (e.g. `"numeric"`) would mean that any time there is a missing element in a vector, rest of the elements would be converted to a type higher in hierarchy, which would be problematic for types lower in hierarchy. 91 | 92 | ```{r Vectors-10} 93 | typeof(NA) 94 | 95 | c(FALSE, NA_character_) 96 | ``` 97 | 98 | **Q5.** Precisely what do `is.atomic()`, `is.numeric()`, and `is.vector()` test for? 99 | 100 | **A5.** Let's discuss them one-by-one. 101 | 102 | - `is.atomic()` 103 | 104 | This function checks if the object is a vector of atomic *type* (or `NULL`). 105 | 106 | Quoting docs: 107 | 108 | > `is.atomic` is true for the atomic types ("logical", "integer", "numeric", "complex", "character" and "raw") and `NULL`. 109 | 110 | ```{r Vectors-11} 111 | is.atomic(NULL) 112 | 113 | is.atomic(list(NULL)) 114 | ``` 115 | 116 | - `is.numeric()` 117 | 118 | Its documentation says: 119 | 120 | > `is.numeric` should only return true if the base type of the class is `double` or `integer` and values can reasonably be regarded as `numeric` 121 | 122 | Therefore, this function only checks for `double` and `integer` base types and not other types based on top of these types (`factor`, `Date`, `POSIXt`, or `difftime`). 123 | 124 | ```{r Vectors-12} 125 | is.numeric(1L) 126 | 127 | is.numeric(factor(1L)) 128 | ``` 129 | 130 | - `is.vector()` 131 | 132 | As per its documentation: 133 | 134 | > `is.vector` returns `TRUE` if `x` is a vector of the specified mode having no attributes *other than names*. It returns `FALSE` otherwise. 135 | 136 | Thus, the function can be incorrectif the object has attributes other than `names`. 137 | 138 | ```{r Vectors-13} 139 | x <- c("x" = 1, "y" = 2) 140 | 141 | is.vector(x) 142 | 143 | attr(x, "m") <- "abcdef" 144 | 145 | is.vector(x) 146 | ``` 147 | 148 | A better way to check for a vector: 149 | 150 | ```{r Vectors-14} 151 | is.null(dim(x)) 152 | ``` 153 | 154 | ## Attributes (Exercises 3.3.4) 155 | 156 | **Q1.** How is `setNames()` implemented? How is `unname()` implemented? Read the source code. 157 | 158 | **A1.** Let's have a look at implementations for these functions. 159 | 160 | - `setNames()` 161 | 162 | ```{r Vectors-15} 163 | setNames 164 | ``` 165 | 166 | Given this function signature, we can see why, when no first argument is given, the result is still a named vector. 167 | 168 | ```{r Vectors-16} 169 | setNames(, c("a", "b")) 170 | 171 | setNames(c(1, 2), c("a", "b")) 172 | ``` 173 | 174 | - `unname()` 175 | 176 | ```{r Vectors-17} 177 | unname 178 | ``` 179 | 180 | `unname()` removes existing names (or dimnames) by setting them to `NULL`. 181 | 182 | ```{r Vectors-18} 183 | unname(setNames(, c("a", "b"))) 184 | ``` 185 | 186 | **Q2.** What does `dim()` return when applied to a 1-dimensional vector? When might you use `NROW()` or `NCOL()`? 187 | 188 | **A2.** Dimensions for a 1-dimensional vector are `NULL`. For example, 189 | 190 | ```{r Vectors-19} 191 | dim(c(1, 2)) 192 | ``` 193 | 194 | 195 | `NROW()` and `NCOL()` are helpful for getting dimensions for 1D vectors by treating them as if they were matrices or dataframes. 196 | 197 | ```{r Vectors-20} 198 | # example-1 199 | x <- character(0) 200 | 201 | dim(x) 202 | 203 | nrow(x) 204 | NROW(x) 205 | 206 | ncol(x) 207 | NCOL(x) 208 | 209 | # example-2 210 | y <- 1:4 211 | 212 | dim(y) 213 | 214 | nrow(y) 215 | NROW(y) 216 | 217 | ncol(y) 218 | NCOL(y) 219 | ``` 220 | 221 | **Q3.** How would you describe the following three objects? What makes them different from `1:5`? 222 | 223 | ```{r Vectors-21} 224 | x1 <- array(1:5, c(1, 1, 5)) 225 | x2 <- array(1:5, c(1, 5, 1)) 226 | x3 <- array(1:5, c(5, 1, 1)) 227 | ``` 228 | 229 | **A3.** `x1`, `x2`, and `x3` are one-dimensional **array**s, but with different "orientations", if we were to mentally visualize them. 230 | 231 | `x1` has 5 entries in the third dimension, `x2` in the second dimension, while `x1` in the first dimension. 232 | 233 | **Q4.** An early draft used this code to illustrate `structure()`: 234 | 235 | ```{r Vectors-22} 236 | structure(1:5, comment = "my attribute") 237 | ``` 238 | 239 | But when you print that object you don't see the comment attribute. Why? Is the attribute missing, or is there something else special about it? (Hint: try using help.) 240 | 241 | **A4.** From `?attributes` (emphasis mine): 242 | 243 | > Note that some attributes (namely class, **comment**, dim, dimnames, names, row.names and tsp) are treated specially and have restrictions on the values which can be set. 244 | 245 | ```{r Vectors-23} 246 | structure(1:5, x = "my attribute") 247 | 248 | structure(1:5, comment = "my attribute") 249 | ``` 250 | 251 | ## S3 atomic vectors (Exercises 3.4.5) 252 | 253 | **Q1.** What sort of object does `table()` return? What is its type? What attributes does it have? How does the dimensionality change as you tabulate more variables? 254 | 255 | **A1.** `table()` returns an array of `integer` type and its dimensions scale with the number of variables present. 256 | 257 | ```{r Vectors-24} 258 | (x <- table(mtcars$am)) 259 | (y <- table(mtcars$am, mtcars$cyl)) 260 | (z <- table(mtcars$am, mtcars$cyl, mtcars$vs)) 261 | 262 | # type 263 | purrr::map(list(x, y, z), typeof) 264 | 265 | # attributes 266 | purrr::map(list(x, y, z), attributes) 267 | ``` 268 | 269 | **Q2.** What happens to a factor when you modify its levels? 270 | 271 | ```{r Vectors-25, results = FALSE} 272 | f1 <- factor(letters) 273 | levels(f1) <- rev(levels(f1)) 274 | ``` 275 | 276 | **A2.** Its levels change but the underlying integer values remain the same. 277 | 278 | ```{r Vectors-26} 279 | f1 <- factor(letters) 280 | f1 281 | as.integer(f1) 282 | 283 | levels(f1) <- rev(levels(f1)) 284 | f1 285 | as.integer(f1) 286 | ``` 287 | 288 | **Q3.** What does this code do? How do `f2` and `f3` differ from `f1`? 289 | 290 | ```{r Vectors-27, results = FALSE} 291 | f2 <- rev(factor(letters)) 292 | f3 <- factor(letters, levels = rev(letters)) 293 | ``` 294 | 295 | **A3.** In this code: 296 | 297 | - `f2`: Only the underlying integers are reversed, but levels remain unchanged. 298 | 299 | ```{r Vectors-28} 300 | f2 <- rev(factor(letters)) 301 | f2 302 | as.integer(f2) 303 | ``` 304 | 305 | - `f3`: Both the levels and the underlying integers are reversed. 306 | 307 | ```{r Vectors-29} 308 | f3 <- factor(letters, levels = rev(letters)) 309 | f3 310 | as.integer(f3) 311 | ``` 312 | 313 | ## Lists (Exercises 3.5.4) 314 | 315 | **Q1.** List all the ways that a list differs from an atomic vector. 316 | 317 | **A1.** Here is a table of comparison: 318 | 319 | | feature | atomic vector | list (aka generic vector) | 320 | | :----------------------------- | :----------------------------------------------- | :----------------------------------------------------- | 321 | | element type | unique | mixed^[a list can contain a mix of types] | 322 | | recursive? | no | yes^[a list can contain itself] | 323 | | return for out-of-bounds index | `NA` | `NULL` | 324 | | memory address | single memory reference^[`lobstr::ref(c(1, 2))`] | reference per list element^[`lobstr::ref(list(1, 2))`] | 325 | 326 | **Q2.** Why do you need to use `unlist()` to convert a list to an atomic vector? Why doesn't `as.vector()` work? 327 | 328 | **A2.** A list already *is* a (generic) vector, so `as.vector()` is not going to change anything, and there is no `as.atomic.vector`. Thus, we need to use `unlist()`. 329 | 330 | ```{r Vectors-30} 331 | x <- list(a = 1, b = 2) 332 | 333 | is.vector(x) 334 | is.atomic(x) 335 | 336 | # still a list 337 | as.vector(x) 338 | 339 | # now a vector 340 | unlist(x) 341 | ``` 342 | 343 | **Q3.** Compare and contrast `c()` and `unlist()` when combining a date and date-time into a single vector. 344 | 345 | **A3.** Let's first create a date and datetime object 346 | 347 | ```{r Vectors-31} 348 | date <- as.Date("1947-08-15") 349 | datetime <- as.POSIXct("1950-01-26 00:01", tz = "UTC") 350 | ``` 351 | 352 | And check their attributes and underlying `double` representation: 353 | 354 | ```{r Vectors-32} 355 | attributes(date) 356 | attributes(datetime) 357 | 358 | as.double(date) # number of days since the Unix epoch 1970-01-01 359 | as.double(datetime) # number of seconds since then 360 | ``` 361 | 362 | - Behavior with `c()` 363 | 364 | Since `S3` method for `c()` dispatches on the first argument, the resulting class of the vector is going to be the same as the first argument. Because of this, some attributes will be lost. 365 | 366 | ```{r Vectors-33} 367 | c(date, datetime) 368 | 369 | attributes(c(date, datetime)) 370 | 371 | c(datetime, date) 372 | 373 | attributes(c(datetime, date)) 374 | ``` 375 | 376 | - Behavior with `unlist()` 377 | 378 | It removes all attributes and we are left only with the underlying double representations of these objects. 379 | 380 | ```{r Vectors-34} 381 | unlist(list(date, datetime)) 382 | 383 | unlist(list(datetime, date)) 384 | ``` 385 | 386 | ## Data frames and tibbles (Exercises 3.6.8) 387 | 388 | **Q1.** Can you have a data frame with zero rows? What about zero columns? 389 | 390 | **A1.** Data frame with 0 rows is possible. This is basically a list with a vector of length 0. 391 | 392 | ```{r Vectors-35} 393 | data.frame(x = numeric(0)) 394 | ``` 395 | 396 | Data frame with 0 columns is also possible. This will be an empty list. 397 | 398 | ```{r Vectors-36} 399 | data.frame(row.names = 1) 400 | ``` 401 | 402 | And, finally, data frame with 0 rows *and* columns is also possible: 403 | 404 | ```{r Vectors-37} 405 | data.frame() 406 | 407 | dim(data.frame()) 408 | ``` 409 | 410 | Although, it might not be common to *create* such data frames, they can be results of subsetting. For example, 411 | 412 | ```{r Vectors-38} 413 | BOD[0, ] 414 | 415 | BOD[, 0] 416 | 417 | BOD[0, 0] 418 | ``` 419 | 420 | **Q2.** What happens if you attempt to set rownames that are not unique? 421 | 422 | **A2.** If you attempt to set data frame rownames that are not unique, it will not work. 423 | 424 | ```{r Vectors-39, error=TRUE} 425 | data.frame(row.names = c(1, 1)) 426 | ``` 427 | 428 | **Q3.** If `df` is a data frame, what can you say about `t(df)`, and `t(t(df))`? Perform some experiments, making sure to try different column types. 429 | 430 | **A3.** Transposing a data frame: 431 | 432 | - transforms it into a matrix 433 | - coerces all its elements to be of the same type 434 | 435 | ```{r Vectors-40} 436 | # original 437 | (df <- head(iris)) 438 | 439 | # transpose 440 | t(df) 441 | 442 | # transpose of a transpose 443 | t(t(df)) 444 | 445 | # is it a dataframe? 446 | is.data.frame(df) 447 | is.data.frame(t(df)) 448 | is.data.frame(t(t(df))) 449 | 450 | # check type 451 | typeof(df) 452 | typeof(t(df)) 453 | typeof(t(t(df))) 454 | 455 | # check dimensions 456 | dim(df) 457 | dim(t(df)) 458 | dim(t(t(df))) 459 | ``` 460 | 461 | **Q4.** What does `as.matrix()` do when applied to a data frame with columns of different types? How does it differ from `data.matrix()`? 462 | 463 | **A4.** The return type of `as.matrix()` depends on the data frame column types. 464 | 465 | As docs for `as.matrix()` mention: 466 | 467 | > The method for data frames will return a character matrix if there is only atomic columns and any non-(numeric/logical/complex) column, applying as.vector to factors and format to other non-character columns. Otherwise the usual coercion hierarchy (logical < integer < double < complex) will be used, e.g. all-logical data frames will be coerced to a logical matrix, mixed logical-integer will give an integer matrix, etc. 468 | 469 | Let's experiment: 470 | 471 | ```{r Vectors-41} 472 | # example with mixed types (coerced to character) 473 | (df <- head(iris)) 474 | 475 | as.matrix(df) 476 | 477 | str(as.matrix(df)) 478 | 479 | # another example (no such coercion) 480 | BOD 481 | 482 | as.matrix(BOD) 483 | ``` 484 | 485 | On the other hand, `data.matrix()` always returns a numeric matrix. 486 | 487 | From documentation of `data.matrix()`: 488 | 489 | > Return the matrix obtained by converting all the variables in a data frame to numeric mode and then binding them together as the columns of a matrix. Factors and ordered factors are replaced by their internal codes. [...] Character columns are first converted to factors and then to integers. 490 | 491 | Let's experiment: 492 | 493 | ```{r Vectors-42} 494 | data.matrix(df) 495 | 496 | str(data.matrix(df)) 497 | ``` 498 | 499 | ## Session information 500 | 501 | ```{r Vectors-43} 502 | sessioninfo::session_info(include_base = TRUE) 503 | ``` 504 | -------------------------------------------------------------------------------- /_bookdown.yml: -------------------------------------------------------------------------------- 1 | new_session: true 2 | before_chapter_script: common.R 3 | delete_merged_file: true 4 | language: 5 | ui: 6 | chapter_name: "Chapter " 7 | rmd_files: 8 | [ 9 | "index.Rmd", 10 | 11 | "Introduction.Rmd", 12 | "Names-values.Rmd", 13 | "Vectors.Rmd", 14 | "Subsetting.Rmd", 15 | "Control-flow.Rmd", 16 | "Functions.Rmd", 17 | "Environments.Rmd", 18 | "Conditions.Rmd", 19 | 20 | "Functionals.Rmd", 21 | "Function-factories.Rmd", 22 | "Function-operators.Rmd", 23 | 24 | "base-types.Rmd", 25 | "S3.Rmd", 26 | "R6.Rmd", 27 | "S4.Rmd", 28 | "OO-tradeoffs.Rmd", 29 | 30 | "Big-picture.Rmd", 31 | "Expressions.Rmd", 32 | "Quotation.Rmd", 33 | "Evaluation.Rmd", 34 | "Translation.Rmd", 35 | 36 | "Debugging.Rmd", 37 | "Perf-measure.Rmd", 38 | "Perf-improve.Rmd", 39 | "Rcpp.Rmd" 40 | ] 41 | -------------------------------------------------------------------------------- /_output.yml: -------------------------------------------------------------------------------- 1 | bookdown::bs4_book: 2 | css: bs4_style.css 3 | footnotes_inline: true 4 | theme: 5 | primary: "#096B72" 6 | base_font: 7 | google: "Roboto" 8 | heading_font: 9 | google: "Roboto Slab" 10 | code_font: 11 | google: "JetBrains Mono" 12 | repo: https://github.com/IndrajeetPatil/advanced-r-exercises 13 | 14 | bookdown::pdf_book: 15 | includes: 16 | in_header: preamble.tex 17 | latex_engine: xelatex 18 | citation_package: natbib 19 | 20 | bookdown::epub_book: default 21 | -------------------------------------------------------------------------------- /assets/combined.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/assets/combined.jpg -------------------------------------------------------------------------------- /assets/solutions_cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/assets/solutions_cover.png -------------------------------------------------------------------------------- /base-types.Rmd: -------------------------------------------------------------------------------- 1 | # Base Types 2 | 3 | No exercises. 4 | -------------------------------------------------------------------------------- /book.bib: -------------------------------------------------------------------------------- 1 | @Book{xie2015, 2 | title = {Dynamic Documents with {R} and knitr}, 3 | author = {Yihui Xie}, 4 | publisher = {Chapman and Hall/CRC}, 5 | address = {Boca Raton, Florida}, 6 | year = {2015}, 7 | edition = {2nd}, 8 | note = {ISBN 978-1498716963}, 9 | url = {http://yihui.org/knitr/}, 10 | } 11 | -------------------------------------------------------------------------------- /common.R: -------------------------------------------------------------------------------- 1 | # example R options set globally 2 | options( 3 | width = 60, 4 | tibble.width = Inf, 5 | pillar.bold = TRUE, 6 | pillar.neg = TRUE, 7 | pillar.subtle_num = TRUE, 8 | pillar.min_chars = Inf 9 | ) 10 | 11 | # example chunk options set globally 12 | knitr::opts_chunk$set( 13 | comment = "#>", 14 | collapse = TRUE, 15 | strip.white = FALSE, 16 | out.width = "100%" 17 | ) 18 | 19 | # for reproducibility 20 | set.seed(1024) 21 | 22 | # needed for the pipe operator 23 | library(magrittr) 24 | options(crayon.enabled = FALSE) 25 | 26 | emojis <- emoji::emoji_name 27 | -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/cover.png -------------------------------------------------------------------------------- /diagrams/environments.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments.graffle -------------------------------------------------------------------------------- /diagrams/environments/binding-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/binding-2.png -------------------------------------------------------------------------------- /diagrams/environments/binding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/binding.png -------------------------------------------------------------------------------- /diagrams/environments/bindings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/bindings.png -------------------------------------------------------------------------------- /diagrams/environments/calling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/calling.png -------------------------------------------------------------------------------- /diagrams/environments/closure-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/closure-call.png -------------------------------------------------------------------------------- /diagrams/environments/closure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/closure.png -------------------------------------------------------------------------------- /diagrams/environments/execution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/execution.png -------------------------------------------------------------------------------- /diagrams/environments/loop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/loop.png -------------------------------------------------------------------------------- /diagrams/environments/namespace-bind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/namespace-bind.png -------------------------------------------------------------------------------- /diagrams/environments/namespace-env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/namespace-env.png -------------------------------------------------------------------------------- /diagrams/environments/namespace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/namespace.png -------------------------------------------------------------------------------- /diagrams/environments/parents-empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/parents-empty.png -------------------------------------------------------------------------------- /diagrams/environments/parents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/parents.png -------------------------------------------------------------------------------- /diagrams/environments/recursive-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/recursive-1.png -------------------------------------------------------------------------------- /diagrams/environments/recursive-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/recursive-2.png -------------------------------------------------------------------------------- /diagrams/environments/search-path-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/search-path-2.png -------------------------------------------------------------------------------- /diagrams/environments/search-path.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/search-path.png -------------------------------------------------------------------------------- /diagrams/environments/where-ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/environments/where-ex.png -------------------------------------------------------------------------------- /diagrams/evaluation.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/evaluation.graffle -------------------------------------------------------------------------------- /diagrams/expressions.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/expressions.graffle -------------------------------------------------------------------------------- /diagrams/expressions/ambig-order.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/expressions/ambig-order.png -------------------------------------------------------------------------------- /diagrams/expressions/call-call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/expressions/call-call.png -------------------------------------------------------------------------------- /diagrams/expressions/complicated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/expressions/complicated.png -------------------------------------------------------------------------------- /diagrams/expressions/prefix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/expressions/prefix.png -------------------------------------------------------------------------------- /diagrams/expressions/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/expressions/simple.png -------------------------------------------------------------------------------- /diagrams/fp.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/fp.graffle -------------------------------------------------------------------------------- /diagrams/fp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/fp.png -------------------------------------------------------------------------------- /diagrams/function-factories.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/function-factories.graffle -------------------------------------------------------------------------------- /diagrams/function-factories/counter-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/function-factories/counter-1.png -------------------------------------------------------------------------------- /diagrams/function-factories/counter-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/function-factories/counter-2.png -------------------------------------------------------------------------------- /diagrams/function-factories/power-exec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/function-factories/power-exec.png -------------------------------------------------------------------------------- /diagrams/function-factories/power-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/function-factories/power-full.png -------------------------------------------------------------------------------- /diagrams/function-factories/power-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/function-factories/power-simple.png -------------------------------------------------------------------------------- /diagrams/functionals.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals.graffle -------------------------------------------------------------------------------- /diagrams/functionals/invoke_map-recycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/invoke_map-recycle.png -------------------------------------------------------------------------------- /diagrams/functionals/invoke_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/invoke_map.png -------------------------------------------------------------------------------- /diagrams/functionals/map-arg-flipped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/map-arg-flipped.png -------------------------------------------------------------------------------- /diagrams/functionals/map-arg-recycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/map-arg-recycle.png -------------------------------------------------------------------------------- /diagrams/functionals/map-arg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/map-arg.png -------------------------------------------------------------------------------- /diagrams/functionals/map-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/map-list.png -------------------------------------------------------------------------------- /diagrams/functionals/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/map.png -------------------------------------------------------------------------------- /diagrams/functionals/map2-arg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/map2-arg.png -------------------------------------------------------------------------------- /diagrams/functionals/map2-recycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/map2-recycle.png -------------------------------------------------------------------------------- /diagrams/functionals/map2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/map2.png -------------------------------------------------------------------------------- /diagrams/functionals/pmap-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/pmap-3.png -------------------------------------------------------------------------------- /diagrams/functionals/pmap-arg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/pmap-arg.png -------------------------------------------------------------------------------- /diagrams/functionals/pmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/pmap.png -------------------------------------------------------------------------------- /diagrams/functionals/reduce-arg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/reduce-arg.png -------------------------------------------------------------------------------- /diagrams/functionals/reduce-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/reduce-init.png -------------------------------------------------------------------------------- /diagrams/functionals/reduce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/reduce.png -------------------------------------------------------------------------------- /diagrams/functionals/reduce2-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/reduce2-init.png -------------------------------------------------------------------------------- /diagrams/functionals/reduce2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/reduce2.png -------------------------------------------------------------------------------- /diagrams/functionals/walk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/walk.png -------------------------------------------------------------------------------- /diagrams/functionals/walk2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functionals/walk2.png -------------------------------------------------------------------------------- /diagrams/functions.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functions.graffle -------------------------------------------------------------------------------- /diagrams/functions/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functions/components.png -------------------------------------------------------------------------------- /diagrams/functions/env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functions/env.png -------------------------------------------------------------------------------- /diagrams/functions/first-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/functions/first-class.png -------------------------------------------------------------------------------- /diagrams/name-value.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value.graffle -------------------------------------------------------------------------------- /diagrams/name-value/binding-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/binding-1.png -------------------------------------------------------------------------------- /diagrams/name-value/binding-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/binding-2.png -------------------------------------------------------------------------------- /diagrams/name-value/binding-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/binding-3.png -------------------------------------------------------------------------------- /diagrams/name-value/binding-f1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/binding-f1.png -------------------------------------------------------------------------------- /diagrams/name-value/binding-f2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/binding-f2.png -------------------------------------------------------------------------------- /diagrams/name-value/character-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/character-2.png -------------------------------------------------------------------------------- /diagrams/name-value/character.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/character.png -------------------------------------------------------------------------------- /diagrams/name-value/d-modify-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/d-modify-c.png -------------------------------------------------------------------------------- /diagrams/name-value/d-modify-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/d-modify-r.png -------------------------------------------------------------------------------- /diagrams/name-value/dataframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/dataframe.png -------------------------------------------------------------------------------- /diagrams/name-value/e-modify-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/e-modify-1.png -------------------------------------------------------------------------------- /diagrams/name-value/e-modify-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/e-modify-2.png -------------------------------------------------------------------------------- /diagrams/name-value/e-self.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/e-self.png -------------------------------------------------------------------------------- /diagrams/name-value/l-modify-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/l-modify-1.png -------------------------------------------------------------------------------- /diagrams/name-value/l-modify-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/l-modify-2.png -------------------------------------------------------------------------------- /diagrams/name-value/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/list.png -------------------------------------------------------------------------------- /diagrams/name-value/unbinding-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/unbinding-1.png -------------------------------------------------------------------------------- /diagrams/name-value/unbinding-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/unbinding-2.png -------------------------------------------------------------------------------- /diagrams/name-value/unbinding-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/unbinding-3.png -------------------------------------------------------------------------------- /diagrams/name-value/v-inplace-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/v-inplace-1.png -------------------------------------------------------------------------------- /diagrams/name-value/v-inplace-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/name-value/v-inplace-2.png -------------------------------------------------------------------------------- /diagrams/oo-venn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/oo-venn.png -------------------------------------------------------------------------------- /diagrams/oo.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/oo.graffle -------------------------------------------------------------------------------- /diagrams/quotation.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/quotation.graffle -------------------------------------------------------------------------------- /diagrams/quotation/bang-bang-bang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/quotation/bang-bang-bang.png -------------------------------------------------------------------------------- /diagrams/quotation/bang-bang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/quotation/bang-bang.png -------------------------------------------------------------------------------- /diagrams/quotation/fun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/quotation/fun.png -------------------------------------------------------------------------------- /diagrams/quotation/infix-bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/quotation/infix-bad.png -------------------------------------------------------------------------------- /diagrams/quotation/infix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/quotation/infix.png -------------------------------------------------------------------------------- /diagrams/quotation/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/quotation/simple.png -------------------------------------------------------------------------------- /diagrams/s4.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4.graffle -------------------------------------------------------------------------------- /diagrams/s4/Matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/Matrix.png -------------------------------------------------------------------------------- /diagrams/s4/emoji.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/emoji.png -------------------------------------------------------------------------------- /diagrams/s4/multiple-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/multiple-all.png -------------------------------------------------------------------------------- /diagrams/s4/multiple-ambig-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/multiple-ambig-2.png -------------------------------------------------------------------------------- /diagrams/s4/multiple-ambig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/multiple-ambig.png -------------------------------------------------------------------------------- /diagrams/s4/multiple-any.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/multiple-any.png -------------------------------------------------------------------------------- /diagrams/s4/multiple-multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/multiple-multiple.png -------------------------------------------------------------------------------- /diagrams/s4/multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/multiple.png -------------------------------------------------------------------------------- /diagrams/s4/single-any.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/single-any.png -------------------------------------------------------------------------------- /diagrams/s4/single-multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/single-multiple.png -------------------------------------------------------------------------------- /diagrams/s4/single-single-ambig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/single-single-ambig.png -------------------------------------------------------------------------------- /diagrams/s4/single-single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/single-single.png -------------------------------------------------------------------------------- /diagrams/s4/single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/s4/single.png -------------------------------------------------------------------------------- /diagrams/subsetting.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/subsetting.graffle -------------------------------------------------------------------------------- /diagrams/subsetting/train-multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/subsetting/train-multiple.png -------------------------------------------------------------------------------- /diagrams/subsetting/train-single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/subsetting/train-single.png -------------------------------------------------------------------------------- /diagrams/subsetting/train.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/subsetting/train.png -------------------------------------------------------------------------------- /diagrams/vectors.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors.graffle -------------------------------------------------------------------------------- /diagrams/vectors/atomic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/atomic.png -------------------------------------------------------------------------------- /diagrams/vectors/attr-dim-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/attr-dim-1.png -------------------------------------------------------------------------------- /diagrams/vectors/attr-dim-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/attr-dim-2.png -------------------------------------------------------------------------------- /diagrams/vectors/attr-names-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/attr-names-1.png -------------------------------------------------------------------------------- /diagrams/vectors/attr-names-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/attr-names-2.png -------------------------------------------------------------------------------- /diagrams/vectors/attr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/attr.png -------------------------------------------------------------------------------- /diagrams/vectors/data-frame-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/data-frame-1.png -------------------------------------------------------------------------------- /diagrams/vectors/data-frame-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/data-frame-2.png -------------------------------------------------------------------------------- /diagrams/vectors/data-frame-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/data-frame-list.png -------------------------------------------------------------------------------- /diagrams/vectors/data-frame-matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/data-frame-matrix.png -------------------------------------------------------------------------------- /diagrams/vectors/factor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/factor.png -------------------------------------------------------------------------------- /diagrams/vectors/list-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/list-c.png -------------------------------------------------------------------------------- /diagrams/vectors/list-recursive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/list-recursive.png -------------------------------------------------------------------------------- /diagrams/vectors/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/list.png -------------------------------------------------------------------------------- /diagrams/vectors/summary-tree-atomic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/summary-tree-atomic.png -------------------------------------------------------------------------------- /diagrams/vectors/summary-tree-s3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/summary-tree-s3-1.png -------------------------------------------------------------------------------- /diagrams/vectors/summary-tree-s3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/summary-tree-s3-2.png -------------------------------------------------------------------------------- /diagrams/vectors/summary-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IndrajeetPatil/advanced-r-exercises/5614aaa69df20f3f4cc4e26cec02af2726e4fbad/diagrams/vectors/summary-tree.png -------------------------------------------------------------------------------- /dsl-html-attributes.R: -------------------------------------------------------------------------------- 1 | html_attributes <- function(list) { 2 | if (length(list) == 0) { 3 | return("") 4 | } 5 | 6 | attr <- map2_chr(names(list), list, html_attribute) 7 | paste0(" ", unlist(attr), collapse = "") 8 | } 9 | html_attribute <- function(name, value = NULL) { 10 | if (length(value) == 0) { 11 | return(name) 12 | } # for attributes with no value 13 | if (length(value) != 1) stop("`value` must be NULL or length 1") 14 | 15 | if (is.logical(value)) { 16 | # Convert T and F to true and false 17 | value <- tolower(value) 18 | } else { 19 | value <- escape_attr(value) 20 | } 21 | paste0(name, "='", value, "'") 22 | } 23 | escape_attr <- function(x) { 24 | x <- escape.character(x) 25 | x <- gsub("\'", "'", x) 26 | x <- gsub("\"", """, x) 27 | x <- gsub("\r", " ", x) 28 | x <- gsub("\n", " ", x) 29 | x 30 | } 31 | -------------------------------------------------------------------------------- /index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Advanced R Exercises" 3 | author: 'Indrajeet Patil' 4 | date: "`r Sys.Date()`" 5 | site: bookdown::bookdown_site 6 | documentclass: book 7 | bibliography: [book.bib, packages.bib] 8 | url: https://bookdown.org/IndrajeetPatil/advanced-r-exercises/ 9 | cover-image: cover.png 10 | description: | 11 | Solutions to exercises in Hadley Wickham's *Advanced R* (2nd edition) book. 12 | biblio-style: apalike 13 | csl: chicago-fullnote-bibliography.csl 14 | --- 15 | 16 | # About {-} 17 | 18 | This book provides solutions to exercises from Hadley Wickham's _Advanced R_ (2nd edition) [book](https://adv-r.hadley.nz/). 19 | 20 | I started working on this book as part of my process to learn by solving each of the book's exercises. While comparing solutions to the [official solutions manual](https://advanced-r-solutions.rbind.io/index.html), I realized that some solutions took different approaches or were at least explained differently. I'm sharing these solutions in case others might find another perspective or explanation than the official solution manual helpful for building understanding. 21 | 22 | Although I have tried to make sure that all solutions are correct, the blame for any inaccuracies lies solely with me. I'd very much appreciate [any suggestions or corrections](https://github.com/IndrajeetPatil/advanced-r-exercises/issues). 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages.bib: -------------------------------------------------------------------------------- 1 | @Manual{R-base, 2 | title = {R: A Language and Environment for Statistical Computing}, 3 | author = {{R Core Team}}, 4 | organization = {R Foundation for Statistical Computing}, 5 | address = {Vienna, Austria}, 6 | year = {2021}, 7 | url = {https://www.R-project.org/}, 8 | } 9 | 10 | @Manual{R-bookdown, 11 | title = {bookdown: Authoring Books and Technical Documents with R Markdown}, 12 | author = {Yihui Xie}, 13 | year = {2021}, 14 | note = {R package version 0.24}, 15 | url = {https://CRAN.R-project.org/package=bookdown}, 16 | } 17 | 18 | @Manual{R-knitr, 19 | title = {knitr: A General-Purpose Package for Dynamic Report Generation in R}, 20 | author = {Yihui Xie}, 21 | year = {2021}, 22 | note = {R package version 1.34}, 23 | url = {https://yihui.org/knitr/}, 24 | } 25 | 26 | @Manual{R-rmarkdown, 27 | title = {rmarkdown: Dynamic Documents for R}, 28 | author = {JJ Allaire and Yihui Xie and Jonathan McPherson and Javier Luraschi and Kevin Ushey and Aron Atkins and Hadley Wickham and Joe Cheng and Winston Chang and Richard Iannone}, 29 | year = {2021}, 30 | note = {R package version 2.10}, 31 | url = {https://CRAN.R-project.org/package=rmarkdown}, 32 | } 33 | 34 | @Book{bookdown2016, 35 | title = {bookdown: Authoring Books and Technical Documents with {R} Markdown}, 36 | author = {Yihui Xie}, 37 | publisher = {Chapman and Hall/CRC}, 38 | address = {Boca Raton, Florida}, 39 | year = {2016}, 40 | note = {ISBN 978-1138700109}, 41 | url = {https://bookdown.org/yihui/bookdown}, 42 | } 43 | 44 | @Book{knitr2015, 45 | title = {Dynamic Documents with {R} and knitr}, 46 | author = {Yihui Xie}, 47 | publisher = {Chapman and Hall/CRC}, 48 | address = {Boca Raton, Florida}, 49 | year = {2015}, 50 | edition = {2nd}, 51 | note = {ISBN 978-1498716963}, 52 | url = {https://yihui.org/knitr/}, 53 | } 54 | 55 | @InCollection{knitr2014, 56 | booktitle = {Implementing Reproducible Computational Research}, 57 | editor = {Victoria Stodden and Friedrich Leisch and Roger D. Peng}, 58 | title = {knitr: A Comprehensive Tool for Reproducible Research in {R}}, 59 | author = {Yihui Xie}, 60 | publisher = {Chapman and Hall/CRC}, 61 | year = {2014}, 62 | note = {ISBN 978-1466561595}, 63 | url = {http://www.crcpress.com/product/isbn/9781466561595}, 64 | } 65 | 66 | @Book{rmarkdown2018, 67 | title = {R Markdown: The Definitive Guide}, 68 | author = {Yihui Xie and J.J. Allaire and Garrett Grolemund}, 69 | publisher = {Chapman and Hall/CRC}, 70 | address = {Boca Raton, Florida}, 71 | year = {2018}, 72 | note = {ISBN 9781138359338}, 73 | url = {https://bookdown.org/yihui/rmarkdown}, 74 | } 75 | 76 | @Book{rmarkdown2020, 77 | title = {R Markdown Cookbook}, 78 | author = {Yihui Xie and Christophe Dervieux and Emily Riederer}, 79 | publisher = {Chapman and Hall/CRC}, 80 | address = {Boca Raton, Florida}, 81 | year = {2020}, 82 | note = {ISBN 9780367563837}, 83 | url = {https://bookdown.org/yihui/rmarkdown-cookbook}, 84 | } 85 | 86 | -------------------------------------------------------------------------------- /preamble.tex: -------------------------------------------------------------------------------- 1 | \usepackage{booktabs} 2 | \usepackage{fvextra} 3 | \DefineVerbatimEnvironment{Highlighting}{Verbatim}{ 4 | showspaces = false, 5 | showtabs = false, 6 | breaklines, 7 | commandchars=\\\{\} 8 | } 9 | -------------------------------------------------------------------------------- /profiling-exercises.R: -------------------------------------------------------------------------------- 1 | f <- function(n = 1e5) { 2 | x <- rep(1, n) 3 | rm(x) 4 | } 5 | -------------------------------------------------------------------------------- /profvis-script.R: -------------------------------------------------------------------------------- 1 | library(profvis) 2 | 3 | source("profiling-exercises.R") 4 | 5 | profvis(f()) 6 | 7 | profvis(f(), torture = TRUE) 8 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | p.caption { 2 | color: #777; 3 | margin-top: 10px; 4 | } 5 | p code { 6 | white-space: inherit; 7 | } 8 | pre { 9 | word-break: normal; 10 | word-wrap: normal; 11 | } 12 | pre code { 13 | white-space: inherit; 14 | } 15 | --------------------------------------------------------------------------------