├── renv ├── .gitignore ├── settings.dcf └── activate.R ├── .gitignore ├── docs ├── doge.png ├── changes.png ├── motivation.png ├── test_workflow.png └── index.Rmd ├── workflow.png ├── results ├── tab │ └── README.md └── img │ └── README.md ├── data └── README.md ├── .Rprofile ├── make_tutorial.Rproj ├── scripts ├── helpers │ ├── fix_data.R │ ├── get_more_data.R │ └── get_data.R ├── 00_clean_data.R ├── 03_regression.R ├── 01_figure_1.R └── 02_figure_2.R ├── Makefile ├── draft.Rmd ├── README.md └── renv.lock /renv/.gitignore: -------------------------------------------------------------------------------- 1 | library/ 2 | python/ 3 | staging/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | -------------------------------------------------------------------------------- /docs/doge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcvdav/make_tutorial/HEAD/docs/doge.png -------------------------------------------------------------------------------- /workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcvdav/make_tutorial/HEAD/workflow.png -------------------------------------------------------------------------------- /docs/changes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcvdav/make_tutorial/HEAD/docs/changes.png -------------------------------------------------------------------------------- /docs/motivation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcvdav/make_tutorial/HEAD/docs/motivation.png -------------------------------------------------------------------------------- /docs/test_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jcvdav/make_tutorial/HEAD/docs/test_workflow.png -------------------------------------------------------------------------------- /results/tab/README.md: -------------------------------------------------------------------------------- 1 | # Tables 2 | 3 | There shouldn't be any tables here yet. We'll create them with `scripts/03_regression.R`. -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | This folder should be empty, that's ok. To create the data, simply run: `get_data()` (no arguments needed) -------------------------------------------------------------------------------- /results/img/README.md: -------------------------------------------------------------------------------- 1 | # Figures 2 | 3 | There shouldn't be any figures here yet. We'll create them with `scripts/01_figure_1.R` and `scripts/02_figure_2.R`. -------------------------------------------------------------------------------- /.Rprofile: -------------------------------------------------------------------------------- 1 | source("renv/activate.R") 2 | source("scripts/helpers/get_data.R") 3 | source("scripts/helpers/fix_data.R") 4 | source("scripts/helpers/get_more_data.R") 5 | -------------------------------------------------------------------------------- /renv/settings.dcf: -------------------------------------------------------------------------------- 1 | external.libraries: 2 | ignored.packages: 3 | package.dependency.fields: Imports, Depends, LinkingTo 4 | snapshot.type: implicit 5 | use.cache: TRUE 6 | vcs.ignore.library: TRUE 7 | -------------------------------------------------------------------------------- /make_tutorial.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | BuildType: Makefile 16 | -------------------------------------------------------------------------------- /scripts/helpers/fix_data.R: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # fix fish data # 3 | ###################################################### 4 | # 5 | # This function is a wraper around get_data. It calls 6 | # it with a different list of species to simulate a 7 | # fix of the data. 8 | # 9 | ###################################################### 10 | 11 | fix_data <- function(fish = c("red fish", "green fish"), ...) { 12 | get_data(fish = fish, ...) 13 | } 14 | -------------------------------------------------------------------------------- /scripts/helpers/get_more_data.R: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # get more fish data # 3 | ###################################################### 4 | # 5 | # This function sumulates adding an extra year to the 6 | # FIXED data. Imagine you went out to the fild and 7 | # simply collected new data. 8 | # 9 | ###################################################### 10 | 11 | get_more_data <- function(years = c(2015:2021)) { 12 | fix_data(years = years) 13 | } 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | draft.html: draft.Rmd results/img/time_series.png results/img/first_year.png results/tab/reg_table.html 2 | Rscript -e "rmarkdown::render('draft.Rmd', quiet = T)" 3 | 4 | results/img/time_series.png: scripts/01_figure_1.R data/clean_fish_data.rds 5 | Rscript scripts/01_figure_1.R 6 | 7 | results/img/first_year.png: scripts/02_figure_2.R data/clean_fish_data.rds 8 | Rscript scripts/02_figure_2.R 9 | 10 | results/tab/reg_table.html: scripts/03_regression.R data/clean_fish_data.rds 11 | Rscript scripts/03_regression.R 12 | 13 | data/clean_fish_data.rds: scripts/00_clean_data.R data/raw_fish_data.rds 14 | Rscript scripts/00_clean_data.R -------------------------------------------------------------------------------- /scripts/helpers/get_data.R: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # create fish data # 3 | ###################################################### 4 | # 5 | # This function creates a fake dataset with fish types 6 | # and fish counts. We'll build on it to generate other 7 | # version of of the data later on. 8 | # 9 | ###################################################### 10 | 11 | get_data <- function(seed = 20, years = c(2015:2020), fish = c("red fish", "blue fish")) { 12 | # How many years do we have? 13 | n_years <- length(years) 14 | n_fish <- length(fish) 15 | n <- n_years * n_fish * 4 16 | 17 | set.seed(seed) 18 | data <- expand.grid(year = years, 19 | transect = 1:4, 20 | fish = fish) 21 | 22 | data$count <- rpois(n = n, lambda = 10) 23 | 24 | saveRDS(object = data, file = here::here("data", "raw_fish_data.rds")) 25 | } 26 | -------------------------------------------------------------------------------- /draft.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Fishy analysis" 3 | author: "EcoDataScience" 4 | date: "4/27/2021" 5 | output: html_document 6 | --- 7 | 8 | ```{r setup, include=FALSE} 9 | knitr::opts_chunk$set(echo = FALSE, 10 | warning = FALSE, 11 | message = FALSE) 12 | 13 | suppressPackageStartupMessages({ 14 | library(here) 15 | }) 16 | ``` 17 | 18 | # Intro 19 | 20 | Intro goes here 21 | 22 | # Methods 23 | 24 | We make plots and regressions 25 | 26 | # Results 27 | 28 | ## Abundance through time 29 | 30 | ```{r} 31 | knitr::include_graphics(here("results", "img", "time_series.png")) 32 | ``` 33 | 34 | 35 | ## Latest abundance 36 | 37 | ```{r} 38 | knitr::include_graphics(here("results", "img", "first_year.png")) 39 | ``` 40 | 41 | 42 | ```{r} 43 | # htmltools::includeMarkdown() 44 | htmltools::includeHTML(path = here("results", "tab", "reg_table.html")) 45 | ``` 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /scripts/00_clean_data.R: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # clean data # 3 | ###################################################### 4 | # 5 | # This script reads in the raw data, cleans it, and 6 | # then exports it ready for analysis and visualization 7 | # 8 | ###################################################### 9 | 10 | # Load packages 11 | suppressPackageStartupMessages({ 12 | library(here) 13 | library(tidyverse) 14 | }) 15 | 16 | # Read-in data 17 | raw_fish_data <- 18 | readRDS(file = here("data", "raw_fish_data.rds")) 19 | 20 | # Inspect the data in your preferred way 21 | 22 | # Clean the data 23 | clean_fish_data <- raw_fish_data %>% 24 | arrange(year, transect, fish) %>% # Arrange by year, transect, and species 25 | mutate(fish = str_remove_all(fish, " fish")) # Remove all mentions of "fish" 26 | 27 | 28 | # Export the clean data 29 | saveRDS(object = clean_fish_data, 30 | file = here("data", "clean_fish_data.rds")) 31 | -------------------------------------------------------------------------------- /scripts/03_regression.R: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # regress things # 3 | ###################################################### 4 | # 5 | # Fit a statistically obscene regression 6 | # 7 | ###################################################### 8 | 9 | # Load packages 10 | suppressPackageStartupMessages({ 11 | library(here) 12 | library(tidyverse) 13 | library(modelsummary) 14 | }) 15 | 16 | # Read-in the data 17 | fish_data <- readRDS(here("data", "clean_fish_data.rds")) 18 | 19 | # Fith the model 20 | model <- lm(count ~ factor(year) + fish, data = fish_data) 21 | 22 | # Create table and export it 23 | modelsummary(models = model, 24 | statistic_vertical = F, # Add t-stat on horizontal 25 | gof_omit = "IC|Adj|Log", # Remove goodness of fit measures 26 | stars = T, # Add "significance stars" 27 | output = here("results", "tab", "reg_table.html")) # Export to folder 28 | -------------------------------------------------------------------------------- /scripts/01_figure_1.R: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # make figure 1 # 3 | ###################################################### 4 | # 5 | # Let's make a time-series of average count per species 6 | # 7 | ###################################################### 8 | 9 | # Load packages 10 | suppressPackageStartupMessages({ 11 | library(here) 12 | library(tidyverse) 13 | }) 14 | 15 | # Read-in the data 16 | fish_data <- readRDS(here("data", "clean_fish_data.rds")) 17 | 18 | # Create figure 1 19 | p <- ggplot(data = fish_data, 20 | mapping = aes(x = year, y = count, fill = fish, group = fish)) + 21 | stat_summary(geom = "line", fun = "mean") + # Add lines that follow the mean 22 | stat_summary(geom = "errorbar", fun.data = mean_se, width = 0.1) + # Add errorbars of +- 1 SD 23 | stat_summary(geom = "point", fun = "mean", size = 4, shape = 21) + # Add average points 24 | scale_fill_brewer(palette = "Set1", direction = -1) + # Change colors 25 | theme_minimal() + # Change theme 26 | labs(x = "Year", y = "Count (Mean +- SD)") # Change axis 27 | 28 | 29 | # Export the plot 30 | ggsave(filename = here("results", "img", "time_series.png"), 31 | plot = p, 32 | width = 6, 33 | height = 4) 34 | -------------------------------------------------------------------------------- /scripts/02_figure_2.R: -------------------------------------------------------------------------------- 1 | ###################################################### 2 | # make figure 2 # 3 | ###################################################### 4 | # 5 | # Let's make an abundance chart for all the most recent 6 | # transects 7 | # 8 | ###################################################### 9 | 10 | # Load packages 11 | suppressPackageStartupMessages({ 12 | library(here) 13 | library(tidyverse) 14 | }) 15 | 16 | # Read-in the data 17 | fish_data <- readRDS(here("data", "clean_fish_data.rds")) %>% 18 | filter(year == min(year)) %>% # Keep the data for the first year only 19 | group_by(transect) %>% # Group by transect 20 | summarize(count = sum(count)) # And sum abundances across all species 21 | 22 | # Create figure 2 23 | p <- ggplot(data = fish_data, 24 | mapping = aes(x = transect, 25 | y = count)) + 26 | geom_col(fill = "steelblue", color = "black") + # Add columns 27 | theme_minimal() + # Minimal theme 28 | labs(x = "Transect", y = "Abundance (all species)") # And axis labels 29 | 30 | # Save the figure 31 | ggsave(filename = here("results", "img", "first_year.png"), 32 | plot = p, 33 | width = 6, 34 | height = 4.5) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `Make` tutorial 2 | 3 | End-to-end reproducibility 4 | 5 | ------------- 6 | 7 | ## TL; DR 8 | 9 | - Clone this repo (make sure to start a new Rproject associated with it) 10 | - Check that you have `GNU Make` on your device 11 | - Make sure you have these three packages installed: `here`, `tidyverse`, `modelsummary` 12 | 13 | ------------- 14 | 15 | ## The goal 16 | 17 | We'll simulate everyone's nightmare: 18 | 19 | - You have built your "coding pipeline" (see `scripts` and `draft.Rmd`) and are ready to do something with it 20 | - Then, you (or a colleague) finds an error in the raw data (crisis!!!) 21 | - You fix the error, and now have to rerun the ENTIRE analysis (which script did what, again? What depended on what?) 22 | - By the time you are done figuring this out, you've also gotten your hands on more data so... gotta re-run everything again 23 | 24 | The solution to this: one file (`Makefile`) and one command: `Make` 25 | 26 | ## What you'll need 27 | 28 | ### Get the materials 29 | 30 | This repository contains everything we'll need for the session. Having `git` installed will make it easier (simply clone this repo). But, if you don't have it yet you can either [follow this other tutorial](https://github.com/eco-data-science/github-intro) or, simply download the entire thing as a compressed folder. 31 | 32 | ### Do I have `GNU make`? 33 | 34 | Open a terminal (or use the `Terminal` pane in RStudio) and type: 35 | 36 | ``` 37 | make -v #checks version of GNU make 38 | ``` 39 | 40 | Should show something like this (really, anything that is not an error _should_ work): 41 | 42 | ``` 43 | >GNU Make 3.81 44 | >Copyright (C) 2006 Free Software Foundation, Inc. 45 | >This is free software; see the source for copying conditions. 46 | >There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A 47 | >PARTICULAR PURPOSE. 48 | ``` 49 | 50 | Don't have it? This might help: 51 | 52 | - [Mac homebrew formulae](https://formulae.brew.sh/formula/make) 53 | - [Windows solutions](https://stackoverflow.com/questions/32127524/how-to-install-and-use-make-in-windows) 54 | 55 | ### R packages 56 | 57 | There are two options to make sure you have all the dependencies for this project. 58 | 59 | Simply use the included `renv` environment: 60 | 61 | ``` 62 | renv::restore() # Follow the instructions 63 | ``` 64 | 65 | OR, manually install them if you don't already have them 66 | 67 | ``` 68 | renv::deactivate() # Deactivate the renv environment first 69 | pkgs <- c("tidyverse", "here", "modelsummary") # List of packages needed 70 | lapply(pkgs, install.packages) # Install all packages 71 | ``` 72 | 73 | ### Optional dependencies 74 | 75 | - [makefile2graph](https://github.com/lindenb/makefile2graph.git) To convert your makefile into a graph 76 | - [graphviz](https://www.graphviz.org/) To visualize that graph 77 | 78 | To install both, follow the instructions [here](https://gist.github.com/carlislerainey/9a1e49cb195076165a4f07a683ce05a7#setting-up) 79 | 80 | ## Other resources 81 | 82 | ### General 83 | 84 | - [GNU Make documentation](https://www.gnu.org/software/make/) 85 | - [Great example from simple to complex](https://kbroman.org/minimal_make/) 86 | - [Cool short example of makefile2graph and graphviz](https://gist.github.com/carlislerainey/9a1e49cb195076165a4f07a683ce05a7) 87 | 88 | ### R-specific: 89 | 90 | - [drake](https://github.com/ropensci/drake) 91 | - [targets](https://docs.ropensci.org/targets/) 92 | - [There is an `R` in `R`eproducibility](https://towardsdatascience.com/there-is-an-r-in-reproducibility-b9120712742f) 93 | 94 | -------------------------------------------------------------------------------- /docs/index.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "GNU `make` and `Makefile` for rerpoducibility" 3 | author: "Juan Carlos Villaseñor-Derbez" 4 | date: "4/27/2021" 5 | output: ioslides_presentation 6 | --- 7 | 8 | ```{r setup, include=FALSE} 9 | knitr::opts_chunk$set(echo = TRUE, 10 | fig.align="center") 11 | ``` 12 | 13 | # TOC 14 | 15 | - Motivation: the problem 16 | - Common solutions 17 | - GNU Make 18 | - Work together 19 | 20 | ## Disclaimer 21 | 22 | >- This is not the solution to all [coding] problems 23 | >- It is just another tool in your DataScience toolbox (albeit, a useful one!) 24 | 25 | ## Motivation: the problem 26 | 27 | >- Coding is rarely linear 28 | >- We often have to go back and change data, assumptions, transformations, filters, colors, font types 29 | >- Projects can get really big really fast... and re-running an huge Rmd file is not an option 30 | >- We rarely work alone (new data, new approaches, new questions...) 31 | 32 | ## Motivation: an example 33 | 34 | We'll simulate everyone's nightmare: 35 | 36 | >- You have built your "coding pipeline" and are ready to share results 37 | >- Then, you (or a colleague) finds an error in the raw data (crisis!!!) 38 | >- You fix the error, and now have to rerun the ENTIRE analysis. Chaos: 39 | >- _which script did what, again?_ 40 | >- _What depended on what?_ 41 | 42 | >- Yes, organization solves a lot of this (but who's got time for that?) 43 | 44 | # [Self-]Motivation: 45 | 46 | > ~~The solution lies in the problem~~ 47 | 48 | > The problem lies in the solution to the original problem 49 | 50 | # {data-background="motivation.png"} 51 | 52 | ## Consider a case 53 | 54 | ```{bash, eval = F} 55 | data 56 | |_raw_data.csv 57 | |_clean_data.csv 58 | scripts 59 | |_clean_data.R 60 | |_plot_data.R 61 | results 62 | |_figure1.png 63 | ``` 64 | 65 | ```{r, echo = F, out.width="50%", fig.align="center"} 66 | knitr::include_graphics("test_workflow.png") 67 | ``` 68 | 69 | ## A potential solution: `lapply` 70 | 71 | ```{r, eval = F} 72 | # Identify all scripts 73 | scripts <- list.files(path = "scripts", 74 | pattern = "*.R") 75 | 76 | # Run them all 77 | lapply(scripts, source) 78 | ``` 79 | 80 | 81 | ## A potential solution: `purrr` + `furrr` 82 | 83 | ```{r, eval = F} 84 | # Identify all scripts 85 | scripts <- list.files(path = "scripts", 86 | pattern = "*.R") 87 | 88 | # Run them all... in parallel 89 | plan(multisession, workers = 4) # Use four cores 90 | future_walk(scripts, source) # Walk through files 91 | ``` 92 | 93 | ```{r, out.width="35%", echo = F, fig.align="center"} 94 | knitr::include_graphics("doge.png") 95 | ``` 96 | 97 | ## A potential solution: `run_all.R` 98 | 99 | Have a script: 100 | 101 | ```{r, eval = F} 102 | # Run all scripts 103 | # This script runs all the code in my project, from scratch 104 | source("scripts/clean_data.R") # To clean the data 105 | source("scripts/plot_data.R") # To plot the data 106 | ``` 107 | 108 | And either call `source(run_all.R)` or manually source the ones that we *think* we need to run. 109 | 110 | ## Problems with these 111 | 112 | - Do I even need to actually run all? 113 | 114 | - What if variables / values are left in my environment? 115 | 116 | - It worked when I wrote it, but not anymore 117 | 118 | - What if the timing isn't correct? 119 | 120 | ## Existing solutions 121 | 122 | ### Many R packages 123 | 124 | >- [drake](https://books.ropensci.org/drake/) 125 | 126 | >- [targets](https://docs.ropensci.org/targets/) 127 | 128 | >- Many, many, many other great solutions... 129 | 130 | ## Existing solutions 131 | 132 | ### But some shortcommings 133 | 134 | >- R-specific (hinders collaboration) 135 | >- Things keep changing (for better, but still) 136 | 137 | ## Existing solutions 138 | 139 | ### But some shortcommings 140 | 141 | - R-specific (hinders collaboration) 142 | - Things keep changing (for better, but still) 143 | 144 | ```{r, echo = F} 145 | knitr::include_graphics("changes.png") 146 | ``` 147 | 148 | >- They really are just leveraging an existing infrastructure 149 | 150 | # Enter `make` 151 | 152 | ## Overview of `make` and `Makefile` 153 | 154 | From [`GNU`'s website](https://www.gnu.org/software/make/): 155 | 156 | >"GNU Make is a **tool** which **controls the generation** of executables and other non-source files of a program from the program's **source files.**" 157 | 158 | ## How does it work? 159 | 160 | >- `make` "looks" for a file called `Makefile` 161 | - You write that `Makefile`, listing all the good stuff 162 | - What's _"the good stuff"_? 163 | 164 | ## The good stuff: 165 | 166 | >- _Targets_: What needs to be created 167 | - _Prerequisites_: The dependencies 168 | - _Commands_: How do we go from dependency to target? 169 | - Together, these make a _rule_ 170 | - We specify them as: 171 | 172 | ## Three things to keep in mind: 173 | 174 | - _Targets_: What needs to be created 175 | - _Prerequisites_: The dependencies 176 | - _Commands_: How do we go from dependency to target? 177 | - Together, these make a _rule_ 178 | - We specify them as: 179 | 180 | ```{bash, eval = F} 181 | target: prerequisite 182 | command 183 | ``` 184 | 185 | >- Note on notation, indentation... 186 | 187 | ## A lame example 188 | 189 | ```{bash, eval = F} 190 | taco: recipe fridge/tortilla fridge/meat fridge/salsa 191 | follow recipe 192 | 193 | happiness: taco #A target can be a prerequisite 194 | eat taco 195 | ``` 196 | 197 | 198 | # Short example 199 | 200 | ## Remember our previous case? 201 | 202 | ```{bash, eval = F} 203 | data 204 | |_raw_data.csv 205 | |_clean_data.csv 206 | scripts 207 | |_clean_data.R 208 | |_plot_data.R 209 | results 210 | |_figure1.png 211 | ``` 212 | 213 | 214 | ```{r, echo = F, out.width="50%", fig.align="center"} 215 | knitr::include_graphics("test_workflow.png") 216 | ``` 217 | 218 | ## What's the `Makefile` for this project? 219 | 220 | ### Recall: 221 | 222 | ```{bash, eval = F} 223 | target: prerequisites 224 | command 225 | ``` 226 | 227 | ### So, then: 228 | 229 | ```{bash, eval = F} 230 | results/figure1.png: scripts/plot_data.R data/clean_data.csv 231 | Rscript scripts/plot_data.R 232 | 233 | data/clean_data.csv: scripts/clean_data.R data/raw_data.csv 234 | Rscript scripts/clean_data.R 235 | ``` 236 | 237 | And one command does it all: `make` 238 | 239 | # Cool things 240 | 241 | ## Making it into a graph 242 | 243 | For this, we need [graphviz](https://www.graphviz.org/) and [makefile2graph](https://github.com/lindenb/makefile2graph.git) 244 | 245 | ```{bash, eval = F} 246 | make -Bnd | make2graph | dot -Tpng -o makefile-dag.png 247 | ``` 248 | 249 | >- `-Bnd` tells `make` to "`B`: Unconditionally make all targets, `n`: just print, `d`: print debug info" 250 | - `|` is just the OG pipe 251 | - `-Tpng` tells `dot` "`T` use the following output format: `png`" 252 | 253 | ## Making it into a graph 254 | 255 | ```{bash, eval = F} 256 | make -Bnd | make2graph | dot -Tpng -o makefile-dag.png 257 | ``` 258 | 259 | ```{r, echo = F, out.width="100%"} 260 | knitr::include_graphics("test_workflow.png") 261 | ``` 262 | 263 | 264 | ## Automatic variables 265 | 266 | - `$@`: the file name of the target 267 | - `$<`: the name of the first prerequisite 268 | - `$^`: the names of all prerequisites 269 | - `$(@D)`: the directory part of the target 270 | - `$(@F)`: the file part of the target 271 | - `$(- `make` the project 377 | - Simulate getting a "fixed" version of the data and re-`make`ing the project _(end-to-end update)_ 378 | - Simulate making one change _(partial update)_ 379 | - Simulate getting NEW (or more?) data and re-`make`int the project once again _(end-to-end update)_ 380 | - Finally, create you own code and add a pair of target / prerequisites _(partial update)_ 381 | - If we have time, we'll generalize some of the code (less typing!) 382 | - If we have time, we'll automate the generation of the workflow image 383 | 384 | ## Other resources 385 | 386 | - [Miles McBain "That feeling of workflowing" video lecture](https://youtu.be/jU1Zv21GvT4) 387 | - [There is an `R` in `R`eproducibility](https://towardsdatascience.com/there-is-an-r-in-reproducibility-b9120712742f) 388 | - [Great example from simple to complex with GNU](https://kbroman.org/minimal_make/) 389 | - [GNU Make documentation](https://www.gnu.org/software/make/) 390 | - [Cool short example of makefile2graph and graphviz](https://gist.github.com/carlislerainey/9a1e49cb195076165a4f07a683ce05a7) 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | -------------------------------------------------------------------------------- /renv/activate.R: -------------------------------------------------------------------------------- 1 | 2 | local({ 3 | 4 | # the requested version of renv 5 | version <- "0.11.0" 6 | 7 | # the project directory 8 | project <- getwd() 9 | 10 | # avoid recursion 11 | if (!is.na(Sys.getenv("RENV_R_INITIALIZING", unset = NA))) 12 | return(invisible(TRUE)) 13 | 14 | # signal that we're loading renv during R startup 15 | Sys.setenv("RENV_R_INITIALIZING" = "true") 16 | on.exit(Sys.unsetenv("RENV_R_INITIALIZING"), add = TRUE) 17 | 18 | # signal that we've consented to use renv 19 | options(renv.consent = TRUE) 20 | 21 | # load the 'utils' package eagerly -- this ensures that renv shims, which 22 | # mask 'utils' packages, will come first on the search path 23 | library(utils, lib.loc = .Library) 24 | 25 | # check to see if renv has already been loaded 26 | if ("renv" %in% loadedNamespaces()) { 27 | 28 | # if renv has already been loaded, and it's the requested version of renv, 29 | # nothing to do 30 | spec <- .getNamespaceInfo(.getNamespace("renv"), "spec") 31 | if (identical(spec[["version"]], version)) 32 | return(invisible(TRUE)) 33 | 34 | # otherwise, unload and attempt to load the correct version of renv 35 | unloadNamespace("renv") 36 | 37 | } 38 | 39 | # load bootstrap tools 40 | bootstrap <- function(version, library) { 41 | 42 | # read repos (respecting override if set) 43 | repos <- Sys.getenv("RENV_CONFIG_REPOS_OVERRIDE", unset = NA) 44 | if (is.na(repos)) 45 | repos <- getOption("repos") 46 | 47 | # fix up repos 48 | on.exit(options(repos = repos), add = TRUE) 49 | repos[repos == "@CRAN@"] <- "https://cloud.r-project.org" 50 | options(repos = repos) 51 | 52 | # attempt to download renv 53 | tarball <- tryCatch(renv_bootstrap_download(version), error = identity) 54 | if (inherits(tarball, "error")) 55 | stop("failed to download renv ", version) 56 | 57 | # now attempt to install 58 | status <- tryCatch(renv_bootstrap_install(version, tarball, library), error = identity) 59 | if (inherits(status, "error")) 60 | stop("failed to install renv ", version) 61 | 62 | } 63 | 64 | renv_bootstrap_download_impl <- function(url, destfile) { 65 | 66 | mode <- "wb" 67 | 68 | # https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17715 69 | fixup <- 70 | Sys.info()[["sysname"]] == "Windows" && 71 | substring(url, 1L, 5L) == "file:" 72 | 73 | if (fixup) 74 | mode <- "w+b" 75 | 76 | download.file( 77 | url = url, 78 | destfile = destfile, 79 | mode = mode, 80 | quiet = TRUE 81 | ) 82 | 83 | } 84 | 85 | renv_bootstrap_download <- function(version) { 86 | 87 | methods <- list( 88 | renv_bootstrap_download_cran_latest, 89 | renv_bootstrap_download_cran_archive, 90 | renv_bootstrap_download_github 91 | ) 92 | 93 | for (method in methods) { 94 | path <- tryCatch(method(version), error = identity) 95 | if (is.character(path) && file.exists(path)) 96 | return(path) 97 | } 98 | 99 | stop("failed to download renv ", version) 100 | 101 | } 102 | 103 | renv_bootstrap_download_cran_latest <- function(version) { 104 | 105 | # check for renv on CRAN matching this version 106 | db <- as.data.frame(available.packages(), stringsAsFactors = FALSE) 107 | 108 | entry <- db[db$Package %in% "renv" & db$Version %in% version, ] 109 | if (nrow(entry) == 0) { 110 | fmt <- "renv %s is not available from your declared package repositories" 111 | stop(sprintf(fmt, version)) 112 | } 113 | 114 | message("* Downloading renv ", version, " from CRAN ... ", appendLF = FALSE) 115 | 116 | info <- tryCatch( 117 | download.packages("renv", destdir = tempdir()), 118 | condition = identity 119 | ) 120 | 121 | if (inherits(info, "condition")) { 122 | message("FAILED") 123 | return(FALSE) 124 | } 125 | 126 | message("OK") 127 | info[1, 2] 128 | 129 | } 130 | 131 | renv_bootstrap_download_cran_archive <- function(version) { 132 | 133 | name <- sprintf("renv_%s.tar.gz", version) 134 | repos <- getOption("repos") 135 | urls <- file.path(repos, "src/contrib/Archive/renv", name) 136 | destfile <- file.path(tempdir(), name) 137 | 138 | message("* Downloading renv ", version, " from CRAN archive ... ", appendLF = FALSE) 139 | 140 | for (url in urls) { 141 | 142 | status <- tryCatch( 143 | renv_bootstrap_download_impl(url, destfile), 144 | condition = identity 145 | ) 146 | 147 | if (identical(status, 0L)) { 148 | message("OK") 149 | return(destfile) 150 | } 151 | 152 | } 153 | 154 | message("FAILED") 155 | return(FALSE) 156 | 157 | } 158 | 159 | renv_bootstrap_download_github <- function(version) { 160 | 161 | enabled <- Sys.getenv("RENV_BOOTSTRAP_FROM_GITHUB", unset = "TRUE") 162 | if (!identical(enabled, "TRUE")) 163 | return(FALSE) 164 | 165 | # prepare download options 166 | pat <- Sys.getenv("GITHUB_PAT") 167 | if (nzchar(Sys.which("curl")) && nzchar(pat)) { 168 | fmt <- "--location --fail --header \"Authorization: token %s\"" 169 | extra <- sprintf(fmt, pat) 170 | saved <- options("download.file.method", "download.file.extra") 171 | options(download.file.method = "curl", download.file.extra = extra) 172 | on.exit(do.call(base::options, saved), add = TRUE) 173 | } else if (nzchar(Sys.which("wget")) && nzchar(pat)) { 174 | fmt <- "--header=\"Authorization: token %s\"" 175 | extra <- sprintf(fmt, pat) 176 | saved <- options("download.file.method", "download.file.extra") 177 | options(download.file.method = "wget", download.file.extra = extra) 178 | on.exit(do.call(base::options, saved), add = TRUE) 179 | } 180 | 181 | message("* Downloading renv ", version, " from GitHub ... ", appendLF = FALSE) 182 | 183 | url <- file.path("https://api.github.com/repos/rstudio/renv/tarball", version) 184 | name <- sprintf("renv_%s.tar.gz", version) 185 | destfile <- file.path(tempdir(), name) 186 | 187 | status <- tryCatch( 188 | renv_bootstrap_download_impl(url, destfile), 189 | condition = identity 190 | ) 191 | 192 | if (!identical(status, 0L)) { 193 | message("FAILED") 194 | return(FALSE) 195 | } 196 | 197 | message("Done!") 198 | return(destfile) 199 | 200 | } 201 | 202 | renv_bootstrap_install <- function(version, tarball, library) { 203 | 204 | # attempt to install it into project library 205 | message("* Installing renv ", version, " ... ", appendLF = FALSE) 206 | dir.create(library, showWarnings = FALSE, recursive = TRUE) 207 | 208 | # invoke using system2 so we can capture and report output 209 | bin <- R.home("bin") 210 | exe <- if (Sys.info()[["sysname"]] == "Windows") "R.exe" else "R" 211 | r <- file.path(bin, exe) 212 | args <- c("--vanilla", "CMD", "INSTALL", "-l", shQuote(library), shQuote(tarball)) 213 | output <- system2(r, args, stdout = TRUE, stderr = TRUE) 214 | message("Done!") 215 | 216 | # check for successful install 217 | status <- attr(output, "status") 218 | if (is.numeric(status) && !identical(status, 0L)) { 219 | header <- "Error installing renv:" 220 | lines <- paste(rep.int("=", nchar(header)), collapse = "") 221 | text <- c(header, lines, output) 222 | writeLines(text, con = stderr()) 223 | } 224 | 225 | status 226 | 227 | } 228 | 229 | renv_bootstrap_prefix <- function() { 230 | 231 | # construct version prefix 232 | version <- paste(R.version$major, R.version$minor, sep = ".") 233 | prefix <- paste("R", numeric_version(version)[1, 1:2], sep = "-") 234 | 235 | # include SVN revision for development versions of R 236 | # (to avoid sharing platform-specific artefacts with released versions of R) 237 | devel <- 238 | identical(R.version[["status"]], "Under development (unstable)") || 239 | identical(R.version[["nickname"]], "Unsuffered Consequences") 240 | 241 | if (devel) 242 | prefix <- paste(prefix, R.version[["svn rev"]], sep = "-r") 243 | 244 | # build list of path components 245 | components <- c(prefix, R.version$platform) 246 | 247 | # include prefix if provided by user 248 | prefix <- Sys.getenv("RENV_PATHS_PREFIX") 249 | if (nzchar(prefix)) 250 | components <- c(prefix, components) 251 | 252 | # build prefix 253 | paste(components, collapse = "/") 254 | 255 | } 256 | 257 | renv_bootstrap_library_root <- function(project) { 258 | 259 | path <- Sys.getenv("RENV_PATHS_LIBRARY", unset = NA) 260 | if (!is.na(path)) 261 | return(path) 262 | 263 | path <- Sys.getenv("RENV_PATHS_LIBRARY_ROOT", unset = NA) 264 | if (!is.na(path)) 265 | return(file.path(path, basename(project))) 266 | 267 | file.path(project, "renv/library") 268 | 269 | } 270 | 271 | renv_bootstrap_validate_version <- function(version) { 272 | 273 | loadedversion <- utils::packageDescription("renv", fields = "Version") 274 | if (version == loadedversion) 275 | return(TRUE) 276 | 277 | # assume four-component versions are from GitHub; three-component 278 | # versions are from CRAN 279 | components <- strsplit(loadedversion, "[.-]")[[1]] 280 | remote <- if (length(components) == 4L) 281 | paste("rstudio/renv", loadedversion, sep = "@") 282 | else 283 | paste("renv", loadedversion, sep = "@") 284 | 285 | fmt <- paste( 286 | "renv %1$s was loaded from project library, but renv %2$s is recorded in lockfile.", 287 | "Use `renv::record(\"%3$s\")` to record this version in the lockfile.", 288 | "Use `renv::restore(packages = \"renv\")` to install renv %2$s into the project library.", 289 | sep = "\n" 290 | ) 291 | 292 | msg <- sprintf(fmt, loadedversion, version, remote) 293 | warning(msg, call. = FALSE) 294 | 295 | FALSE 296 | 297 | } 298 | 299 | renv_bootstrap_load <- function(project, libpath, version) { 300 | 301 | # try to load renv from the project library 302 | if (!requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) 303 | return(FALSE) 304 | 305 | # warn if the version of renv loaded does not match 306 | renv_bootstrap_validate_version(version) 307 | 308 | # load the project 309 | renv::load(project) 310 | 311 | TRUE 312 | 313 | } 314 | 315 | # construct path to library root 316 | root <- renv_bootstrap_library_root(project) 317 | 318 | # construct library prefix for platform 319 | prefix <- renv_bootstrap_prefix() 320 | 321 | # construct full libpath 322 | libpath <- file.path(root, prefix) 323 | 324 | # attempt to load 325 | if (renv_bootstrap_load(project, libpath, version)) 326 | return(TRUE) 327 | 328 | # load failed; attempt to bootstrap 329 | bootstrap(version, libpath) 330 | 331 | # exit early if we're just testing bootstrap 332 | if (!is.na(Sys.getenv("RENV_BOOTSTRAP_INSTALL_ONLY", unset = NA))) 333 | return(TRUE) 334 | 335 | # try again to load 336 | if (requireNamespace("renv", lib.loc = libpath, quietly = TRUE)) { 337 | message("Successfully installed and loaded renv ", version, ".") 338 | return(renv::load()) 339 | } 340 | 341 | # failed to download or load renv; warn the user 342 | msg <- c( 343 | "Failed to find an renv installation: the project will not be loaded.", 344 | "Use `renv::activate()` to re-initialize the project." 345 | ) 346 | 347 | warning(paste(msg, collapse = "\n"), call. = FALSE) 348 | 349 | }) 350 | -------------------------------------------------------------------------------- /renv.lock: -------------------------------------------------------------------------------- 1 | { 2 | "R": { 3 | "Version": "4.0.4", 4 | "Repositories": [ 5 | { 6 | "Name": "CRAN", 7 | "URL": "https://cran.rstudio.com" 8 | } 9 | ] 10 | }, 11 | "Packages": { 12 | "BH": { 13 | "Package": "BH", 14 | "Version": "1.75.0-0", 15 | "Source": "Repository", 16 | "Repository": "CRAN", 17 | "Hash": "e4c04affc2cac20c8fec18385cd14691" 18 | }, 19 | "DBI": { 20 | "Package": "DBI", 21 | "Version": "1.1.1", 22 | "Source": "Repository", 23 | "Repository": "CRAN", 24 | "Hash": "030aaec5bc6553f35347cbb1e70b1a17" 25 | }, 26 | "MASS": { 27 | "Package": "MASS", 28 | "Version": "7.3-53", 29 | "Source": "Repository", 30 | "Repository": "CRAN", 31 | "Hash": "d1bc1c8e9c0ace57ec9ffea01021d45f" 32 | }, 33 | "Matrix": { 34 | "Package": "Matrix", 35 | "Version": "1.3-2", 36 | "Source": "Repository", 37 | "Repository": "CRAN", 38 | "Hash": "ff280503079ad8623d3c4b1519b24ea2" 39 | }, 40 | "R6": { 41 | "Package": "R6", 42 | "Version": "2.5.0", 43 | "Source": "Repository", 44 | "Repository": "CRAN", 45 | "Hash": "b203113193e70978a696b2809525649d" 46 | }, 47 | "RColorBrewer": { 48 | "Package": "RColorBrewer", 49 | "Version": "1.1-2", 50 | "Source": "Repository", 51 | "Repository": "CRAN", 52 | "Hash": "e031418365a7f7a766181ab5a41a5716" 53 | }, 54 | "Rcpp": { 55 | "Package": "Rcpp", 56 | "Version": "1.0.6", 57 | "Source": "Repository", 58 | "Repository": "CRAN", 59 | "Hash": "dbb5e436998a7eba5a9d682060533338" 60 | }, 61 | "askpass": { 62 | "Package": "askpass", 63 | "Version": "1.1", 64 | "Source": "Repository", 65 | "Repository": "CRAN", 66 | "Hash": "e8a22846fff485f0be3770c2da758713" 67 | }, 68 | "assertthat": { 69 | "Package": "assertthat", 70 | "Version": "0.2.1", 71 | "Source": "Repository", 72 | "Repository": "CRAN", 73 | "Hash": "50c838a310445e954bc13f26f26a6ecf" 74 | }, 75 | "backports": { 76 | "Package": "backports", 77 | "Version": "1.2.0", 78 | "Source": "Repository", 79 | "Repository": "CRAN", 80 | "Hash": "fb0efa7042b45dac543dd3995a6dac0b" 81 | }, 82 | "base64enc": { 83 | "Package": "base64enc", 84 | "Version": "0.1-3", 85 | "Source": "Repository", 86 | "Repository": "CRAN", 87 | "Hash": "543776ae6848fde2f48ff3816d0628bc" 88 | }, 89 | "blob": { 90 | "Package": "blob", 91 | "Version": "1.2.1", 92 | "Source": "Repository", 93 | "Repository": "RSPM", 94 | "Hash": "9addc7e2c5954eca5719928131fed98c" 95 | }, 96 | "broom": { 97 | "Package": "broom", 98 | "Version": "0.7.0", 99 | "Source": "Repository", 100 | "Repository": "RSPM", 101 | "Hash": "2ca5ae42f3bfd149504d63c833c2be26" 102 | }, 103 | "callr": { 104 | "Package": "callr", 105 | "Version": "3.5.1", 106 | "Source": "Repository", 107 | "Repository": "CRAN", 108 | "Hash": "b7d7f1e926dfcd57c74ce93f5c048e80" 109 | }, 110 | "cellranger": { 111 | "Package": "cellranger", 112 | "Version": "1.1.0", 113 | "Source": "Repository", 114 | "Repository": "CRAN", 115 | "Hash": "f61dbaec772ccd2e17705c1e872e9e7c" 116 | }, 117 | "checkmate": { 118 | "Package": "checkmate", 119 | "Version": "2.0.0", 120 | "Source": "Repository", 121 | "Repository": "CRAN", 122 | "Hash": "a667800d5f0350371bedeb8b8b950289" 123 | }, 124 | "cli": { 125 | "Package": "cli", 126 | "Version": "2.3.1", 127 | "Source": "Repository", 128 | "Repository": "CRAN", 129 | "Hash": "3e3f28efcadfda442cd18651fbcbbecf" 130 | }, 131 | "clipr": { 132 | "Package": "clipr", 133 | "Version": "0.7.1", 134 | "Source": "Repository", 135 | "Repository": "CRAN", 136 | "Hash": "ebaa97ac99cc2daf04e77eecc7b781d7" 137 | }, 138 | "colorspace": { 139 | "Package": "colorspace", 140 | "Version": "2.0-0", 141 | "Source": "Repository", 142 | "Repository": "CRAN", 143 | "Hash": "abea3384649ef37f60ef51ce002f3547" 144 | }, 145 | "cpp11": { 146 | "Package": "cpp11", 147 | "Version": "0.2.5", 148 | "Source": "Repository", 149 | "Repository": "CRAN", 150 | "Hash": "8b9a10255e862eb99e38fa8b0caa6c0d" 151 | }, 152 | "crayon": { 153 | "Package": "crayon", 154 | "Version": "1.4.1", 155 | "Source": "Repository", 156 | "Repository": "CRAN", 157 | "Hash": "e75525c55c70e5f4f78c9960a4b402e9" 158 | }, 159 | "curl": { 160 | "Package": "curl", 161 | "Version": "4.3", 162 | "Source": "Repository", 163 | "Repository": "CRAN", 164 | "Hash": "2b7d10581cc730804e9ed178c8374bd6" 165 | }, 166 | "dbplyr": { 167 | "Package": "dbplyr", 168 | "Version": "2.1.0", 169 | "Source": "Repository", 170 | "Repository": "CRAN", 171 | "Hash": "f30247ffeeebe8d9842dc68fe63e043b" 172 | }, 173 | "digest": { 174 | "Package": "digest", 175 | "Version": "0.6.27", 176 | "Source": "Repository", 177 | "Repository": "CRAN", 178 | "Hash": "a0cbe758a531d054b537d16dff4d58a1" 179 | }, 180 | "dplyr": { 181 | "Package": "dplyr", 182 | "Version": "1.0.5", 183 | "Source": "Repository", 184 | "Repository": "CRAN", 185 | "Hash": "d0d76c11ec807eb3f000eba4e3eb0f68" 186 | }, 187 | "ellipsis": { 188 | "Package": "ellipsis", 189 | "Version": "0.3.1", 190 | "Source": "Repository", 191 | "Repository": "RSPM", 192 | "Hash": "fd2844b3a43ae2d27e70ece2df1b4e2a" 193 | }, 194 | "evaluate": { 195 | "Package": "evaluate", 196 | "Version": "0.14", 197 | "Source": "Repository", 198 | "Repository": "CRAN", 199 | "Hash": "ec8ca05cffcc70569eaaad8469d2a3a7" 200 | }, 201 | "fansi": { 202 | "Package": "fansi", 203 | "Version": "0.4.2", 204 | "Source": "Repository", 205 | "Repository": "CRAN", 206 | "Hash": "fea074fb67fe4c25d47ad09087da847d" 207 | }, 208 | "farver": { 209 | "Package": "farver", 210 | "Version": "2.1.0", 211 | "Source": "Repository", 212 | "Repository": "CRAN", 213 | "Hash": "c98eb5133d9cb9e1622b8691487f11bb" 214 | }, 215 | "forcats": { 216 | "Package": "forcats", 217 | "Version": "0.5.0", 218 | "Source": "Repository", 219 | "Repository": "CRAN", 220 | "Hash": "1cb4279e697650f0bd78cd3601ee7576" 221 | }, 222 | "fs": { 223 | "Package": "fs", 224 | "Version": "1.5.0", 225 | "Source": "Repository", 226 | "Repository": "CRAN", 227 | "Hash": "44594a07a42e5f91fac9f93fda6d0109" 228 | }, 229 | "generics": { 230 | "Package": "generics", 231 | "Version": "0.1.0", 232 | "Source": "Repository", 233 | "Repository": "CRAN", 234 | "Hash": "4d243a9c10b00589889fe32314ffd902" 235 | }, 236 | "ggplot2": { 237 | "Package": "ggplot2", 238 | "Version": "3.3.3", 239 | "Source": "Repository", 240 | "Repository": "CRAN", 241 | "Hash": "3eb6477d01eb5bbdc03f7d5f70f2733e" 242 | }, 243 | "glue": { 244 | "Package": "glue", 245 | "Version": "1.4.2", 246 | "Source": "Repository", 247 | "Repository": "CRAN", 248 | "Hash": "6efd734b14c6471cfe443345f3e35e29" 249 | }, 250 | "gtable": { 251 | "Package": "gtable", 252 | "Version": "0.3.0", 253 | "Source": "Repository", 254 | "Repository": "CRAN", 255 | "Hash": "ac5c6baf7822ce8732b343f14c072c4d" 256 | }, 257 | "haven": { 258 | "Package": "haven", 259 | "Version": "2.3.1", 260 | "Source": "Repository", 261 | "Repository": "RSPM", 262 | "Hash": "221d0ad75dfa03ebf17b1a4cc5c31dfc" 263 | }, 264 | "here": { 265 | "Package": "here", 266 | "Version": "0.1", 267 | "Source": "Repository", 268 | "Repository": "CRAN", 269 | "Hash": "2c0406b8e0a4c3516ab37be62da74e3c" 270 | }, 271 | "highr": { 272 | "Package": "highr", 273 | "Version": "0.8", 274 | "Source": "Repository", 275 | "Repository": "CRAN", 276 | "Hash": "4dc5bb88961e347a0f4d8aad597cbfac" 277 | }, 278 | "hms": { 279 | "Package": "hms", 280 | "Version": "1.0.0", 281 | "Source": "Repository", 282 | "Repository": "CRAN", 283 | "Hash": "bf552cdd96f5969873afdac7311c7d0d" 284 | }, 285 | "htmltools": { 286 | "Package": "htmltools", 287 | "Version": "0.5.1.1", 288 | "Source": "Repository", 289 | "Repository": "CRAN", 290 | "Hash": "af2c2531e55df5cf230c4b5444fc973c" 291 | }, 292 | "httr": { 293 | "Package": "httr", 294 | "Version": "1.4.2", 295 | "Source": "Repository", 296 | "Repository": "CRAN", 297 | "Hash": "a525aba14184fec243f9eaec62fbed43" 298 | }, 299 | "isoband": { 300 | "Package": "isoband", 301 | "Version": "0.2.4", 302 | "Source": "Repository", 303 | "Repository": "CRAN", 304 | "Hash": "b2008df40fb297e3fef135c7e8eeec1a" 305 | }, 306 | "jsonlite": { 307 | "Package": "jsonlite", 308 | "Version": "1.7.2", 309 | "Source": "Repository", 310 | "Repository": "CRAN", 311 | "Hash": "98138e0994d41508c7a6b84a0600cfcb" 312 | }, 313 | "kableExtra": { 314 | "Package": "kableExtra", 315 | "Version": "1.3.1", 316 | "Source": "Repository", 317 | "Repository": "CRAN", 318 | "Hash": "fa7f610e7eadfc9c47d501a333e03f1a" 319 | }, 320 | "knitr": { 321 | "Package": "knitr", 322 | "Version": "1.29", 323 | "Source": "Repository", 324 | "Repository": "RSPM", 325 | "Hash": "e5f4c41c17df8cdf7b0df12117c0d99a" 326 | }, 327 | "labeling": { 328 | "Package": "labeling", 329 | "Version": "0.4.2", 330 | "Source": "Repository", 331 | "Repository": "CRAN", 332 | "Hash": "3d5108641f47470611a32d0bdf357a72" 333 | }, 334 | "lattice": { 335 | "Package": "lattice", 336 | "Version": "0.20-41", 337 | "Source": "Repository", 338 | "Repository": "CRAN", 339 | "Hash": "fbd9285028b0263d76d18c95ae51a53d" 340 | }, 341 | "lifecycle": { 342 | "Package": "lifecycle", 343 | "Version": "1.0.0", 344 | "Source": "Repository", 345 | "Repository": "CRAN", 346 | "Hash": "3471fb65971f1a7b2d4ae7848cf2db8d" 347 | }, 348 | "lubridate": { 349 | "Package": "lubridate", 350 | "Version": "1.7.10", 351 | "Source": "Repository", 352 | "Repository": "CRAN", 353 | "Hash": "1ebfdc8a3cfe8fe19184f5481972b092" 354 | }, 355 | "magrittr": { 356 | "Package": "magrittr", 357 | "Version": "2.0.1", 358 | "Source": "Repository", 359 | "Repository": "CRAN", 360 | "Hash": "41287f1ac7d28a92f0a286ed507928d3" 361 | }, 362 | "markdown": { 363 | "Package": "markdown", 364 | "Version": "1.1", 365 | "Source": "Repository", 366 | "Repository": "CRAN", 367 | "Hash": "61e4a10781dd00d7d81dd06ca9b94e95" 368 | }, 369 | "mgcv": { 370 | "Package": "mgcv", 371 | "Version": "1.8-33", 372 | "Source": "Repository", 373 | "Repository": "CRAN", 374 | "Hash": "eb7b6439bc6d812eed2cddba5edc6be3" 375 | }, 376 | "mime": { 377 | "Package": "mime", 378 | "Version": "0.10", 379 | "Source": "Repository", 380 | "Repository": "CRAN", 381 | "Hash": "26fa77e707223e1ce042b2b5d09993dc" 382 | }, 383 | "modelr": { 384 | "Package": "modelr", 385 | "Version": "0.1.8", 386 | "Source": "Repository", 387 | "Repository": "RSPM", 388 | "Hash": "9fd59716311ee82cba83dc2826fc5577" 389 | }, 390 | "modelsummary": { 391 | "Package": "modelsummary", 392 | "Version": "0.6.3", 393 | "Source": "Repository", 394 | "Repository": "CRAN", 395 | "Hash": "82822df9055c15da449647eb3b5339d3" 396 | }, 397 | "munsell": { 398 | "Package": "munsell", 399 | "Version": "0.5.0", 400 | "Source": "Repository", 401 | "Repository": "CRAN", 402 | "Hash": "6dfe8bf774944bd5595785e3229d8771" 403 | }, 404 | "nlme": { 405 | "Package": "nlme", 406 | "Version": "3.1-151", 407 | "Source": "Repository", 408 | "Repository": "CRAN", 409 | "Hash": "42c8ba2b6a32a6bf0874e93e3bd86a43" 410 | }, 411 | "openssl": { 412 | "Package": "openssl", 413 | "Version": "1.4.3", 414 | "Source": "Repository", 415 | "Repository": "CRAN", 416 | "Hash": "a399e4773075fc2375b71f45fca186c4" 417 | }, 418 | "pillar": { 419 | "Package": "pillar", 420 | "Version": "1.5.1", 421 | "Source": "Repository", 422 | "Repository": "CRAN", 423 | "Hash": "24622aa4a0d3de3463c34513edca99b2" 424 | }, 425 | "pkgconfig": { 426 | "Package": "pkgconfig", 427 | "Version": "2.0.3", 428 | "Source": "Repository", 429 | "Repository": "CRAN", 430 | "Hash": "01f28d4278f15c76cddbea05899c5d6f" 431 | }, 432 | "prettyunits": { 433 | "Package": "prettyunits", 434 | "Version": "1.1.1", 435 | "Source": "Repository", 436 | "Repository": "CRAN", 437 | "Hash": "95ef9167b75dde9d2ccc3c7528393e7e" 438 | }, 439 | "processx": { 440 | "Package": "processx", 441 | "Version": "3.4.5", 442 | "Source": "Repository", 443 | "Repository": "CRAN", 444 | "Hash": "22aab6098cb14edd0a5973a8438b569b" 445 | }, 446 | "progress": { 447 | "Package": "progress", 448 | "Version": "1.2.2", 449 | "Source": "Repository", 450 | "Repository": "CRAN", 451 | "Hash": "14dc9f7a3c91ebb14ec5bb9208a07061" 452 | }, 453 | "ps": { 454 | "Package": "ps", 455 | "Version": "1.5.0", 456 | "Source": "Repository", 457 | "Repository": "CRAN", 458 | "Hash": "ebaed51a03411fd5cfc1e12d9079b353" 459 | }, 460 | "purrr": { 461 | "Package": "purrr", 462 | "Version": "0.3.4", 463 | "Source": "Repository", 464 | "Repository": "RSPM", 465 | "Hash": "97def703420c8ab10d8f0e6c72101e02" 466 | }, 467 | "readr": { 468 | "Package": "readr", 469 | "Version": "1.4.0", 470 | "Source": "Repository", 471 | "Repository": "CRAN", 472 | "Hash": "2639976851f71f330264a9c9c3d43a61" 473 | }, 474 | "readxl": { 475 | "Package": "readxl", 476 | "Version": "1.3.1", 477 | "Source": "Repository", 478 | "Repository": "CRAN", 479 | "Hash": "63537c483c2dbec8d9e3183b3735254a" 480 | }, 481 | "rematch": { 482 | "Package": "rematch", 483 | "Version": "1.0.1", 484 | "Source": "Repository", 485 | "Repository": "CRAN", 486 | "Hash": "c66b930d20bb6d858cd18e1cebcfae5c" 487 | }, 488 | "renv": { 489 | "Package": "renv", 490 | "Version": "0.11.0", 491 | "Source": "Repository", 492 | "Repository": "CRAN", 493 | "Hash": "1c3ef87cbb81c23ac96797781ec7aecc" 494 | }, 495 | "reprex": { 496 | "Package": "reprex", 497 | "Version": "0.3.0", 498 | "Source": "Repository", 499 | "Repository": "CRAN", 500 | "Hash": "b06bfb3504cc8a4579fd5567646f745b" 501 | }, 502 | "rlang": { 503 | "Package": "rlang", 504 | "Version": "0.4.10", 505 | "Source": "Repository", 506 | "Repository": "CRAN", 507 | "Hash": "599df23c40a4fce9c7b4764f28c37857" 508 | }, 509 | "rmarkdown": { 510 | "Package": "rmarkdown", 511 | "Version": "2.7", 512 | "Source": "Repository", 513 | "Repository": "RSPM", 514 | "Hash": "edbf4cb1aefae783fd8d3a008ae51943" 515 | }, 516 | "rprojroot": { 517 | "Package": "rprojroot", 518 | "Version": "2.0.2", 519 | "Source": "Repository", 520 | "Repository": "CRAN", 521 | "Hash": "249d8cd1e74a8f6a26194a91b47f21d1" 522 | }, 523 | "rstudioapi": { 524 | "Package": "rstudioapi", 525 | "Version": "0.13", 526 | "Source": "Repository", 527 | "Repository": "CRAN", 528 | "Hash": "06c85365a03fdaf699966cc1d3cf53ea" 529 | }, 530 | "rvest": { 531 | "Package": "rvest", 532 | "Version": "0.3.6", 533 | "Source": "Repository", 534 | "Repository": "CRAN", 535 | "Hash": "a9795ccb2d608330e841998b67156764" 536 | }, 537 | "scales": { 538 | "Package": "scales", 539 | "Version": "1.1.1", 540 | "Source": "Repository", 541 | "Repository": "CRAN", 542 | "Hash": "6f76f71042411426ec8df6c54f34e6dd" 543 | }, 544 | "selectr": { 545 | "Package": "selectr", 546 | "Version": "0.4-2", 547 | "Source": "Repository", 548 | "Repository": "CRAN", 549 | "Hash": "3838071b66e0c566d55cc26bd6e27bf4" 550 | }, 551 | "stringi": { 552 | "Package": "stringi", 553 | "Version": "1.5.3", 554 | "Source": "Repository", 555 | "Repository": "CRAN", 556 | "Hash": "a063ebea753c92910a4cca7b18bc1f05" 557 | }, 558 | "stringr": { 559 | "Package": "stringr", 560 | "Version": "1.4.0", 561 | "Source": "Repository", 562 | "Repository": "CRAN", 563 | "Hash": "0759e6b6c0957edb1311028a49a35e76" 564 | }, 565 | "sys": { 566 | "Package": "sys", 567 | "Version": "3.4", 568 | "Source": "Repository", 569 | "Repository": "CRAN", 570 | "Hash": "b227d13e29222b4574486cfcbde077fa" 571 | }, 572 | "tables": { 573 | "Package": "tables", 574 | "Version": "0.9.6", 575 | "Source": "Repository", 576 | "Repository": "CRAN", 577 | "Hash": "d2c1e82fc3b3eab94fbecf8febabe145" 578 | }, 579 | "tibble": { 580 | "Package": "tibble", 581 | "Version": "3.1.0", 582 | "Source": "Repository", 583 | "Repository": "CRAN", 584 | "Hash": "4d894a114dbd4ecafeda5074e7c538e6" 585 | }, 586 | "tidyr": { 587 | "Package": "tidyr", 588 | "Version": "1.1.3", 589 | "Source": "Repository", 590 | "Repository": "CRAN", 591 | "Hash": "450d7dfaedde58e28586b854eeece4fa" 592 | }, 593 | "tidyselect": { 594 | "Package": "tidyselect", 595 | "Version": "1.1.0", 596 | "Source": "Repository", 597 | "Repository": "RSPM", 598 | "Hash": "6ea435c354e8448819627cf686f66e0a" 599 | }, 600 | "tidyverse": { 601 | "Package": "tidyverse", 602 | "Version": "1.3.0", 603 | "Source": "Repository", 604 | "Repository": "CRAN", 605 | "Hash": "bd51be662f359fa99021f3d51e911490" 606 | }, 607 | "tinytex": { 608 | "Package": "tinytex", 609 | "Version": "0.25", 610 | "Source": "Repository", 611 | "Repository": "CRAN", 612 | "Hash": "a4b9662282097d1033c60420dcb83350" 613 | }, 614 | "utf8": { 615 | "Package": "utf8", 616 | "Version": "1.2.1", 617 | "Source": "Repository", 618 | "Repository": "CRAN", 619 | "Hash": "c3ad47dc6da0751f18ed53c4613e3ac7" 620 | }, 621 | "vctrs": { 622 | "Package": "vctrs", 623 | "Version": "0.3.6", 624 | "Source": "Repository", 625 | "Repository": "CRAN", 626 | "Hash": "5cf1957f93076c19fdc81d01409d240b" 627 | }, 628 | "viridisLite": { 629 | "Package": "viridisLite", 630 | "Version": "0.3.0", 631 | "Source": "Repository", 632 | "Repository": "CRAN", 633 | "Hash": "ce4f6271baa94776db692f1cb2055bee" 634 | }, 635 | "webshot": { 636 | "Package": "webshot", 637 | "Version": "0.5.2", 638 | "Source": "Repository", 639 | "Repository": "RSPM", 640 | "Hash": "e99d80ad34457a4853674e89d5e806de" 641 | }, 642 | "whisker": { 643 | "Package": "whisker", 644 | "Version": "0.4", 645 | "Source": "Repository", 646 | "Repository": "CRAN", 647 | "Hash": "ca970b96d894e90397ed20637a0c1bbe" 648 | }, 649 | "withr": { 650 | "Package": "withr", 651 | "Version": "2.4.1", 652 | "Source": "Repository", 653 | "Repository": "CRAN", 654 | "Hash": "caf4781c674ffa549a4676d2d77b13cc" 655 | }, 656 | "xfun": { 657 | "Package": "xfun", 658 | "Version": "0.16", 659 | "Source": "Repository", 660 | "Repository": "CRAN", 661 | "Hash": "b4106139b90981a8bfea9c10bab0baf1" 662 | }, 663 | "xml2": { 664 | "Package": "xml2", 665 | "Version": "1.3.2", 666 | "Source": "Repository", 667 | "Repository": "RSPM", 668 | "Hash": "d4d71a75dd3ea9eb5fa28cc21f9585e2" 669 | }, 670 | "yaml": { 671 | "Package": "yaml", 672 | "Version": "2.2.1", 673 | "Source": "Repository", 674 | "Repository": "CRAN", 675 | "Hash": "2826c5d9efb0a88f657c7a679c7106db" 676 | } 677 | } 678 | } 679 | --------------------------------------------------------------------------------