From d5b79104a52124edcfb9a8d19e37d07dc0c9c37f Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 06:38:04 +0300 Subject: [PATCH 01/28] Use penguins for whole lesson --- episodes/basic-targets.Rmd | 131 ++++++++---- episodes/batch.Rmd | 421 +++++++++++++++---------------------- episodes/cache.Rmd | 36 ++-- episodes/files.Rmd | 55 ++++- episodes/files/functions.R | 339 +++++++++++++++++++++++------ episodes/introduction.Rmd | 17 +- episodes/lifecycle.Rmd | 53 +++-- episodes/organization.Rmd | 126 +++++------ episodes/packages.Rmd | 16 +- episodes/quarto.Rmd | 71 +++---- 10 files changed, 745 insertions(+), 520 deletions(-) diff --git a/episodes/basic-targets.Rmd b/episodes/basic-targets.Rmd index 51ba7096..3bf434fe 100644 --- a/episodes/basic-targets.Rmd +++ b/episodes/basic-targets.Rmd @@ -116,47 +116,110 @@ Note that the name of the target is **unquoted**, that is, it is written without ::::::::::::::::::::::::::::::::::::: callout -## Edit `_targets.R` +## Set up `_targets.R` file to run example analysis -Let's modify the default workflow: rename the names of the targets from `data` to `my_data` and `summary` to `my_summary`. -This is to avoid confusion with base R functions `data()` and `summary()`. -It is generally a good idea to avoid naming objects in R with the names of existing functions. +### Background: non-`targets` version -Make sure to rename `data` to `my_data` **each time it appears** (twice). -The names of the targets are very important: this is how `targets` knows how workflow steps depend on each other. +We will use this template to start building our analysis of bill shape in penguins. +First though, to get familiar with the functions and packages we'll use, let's run the code like you would in a "normal" R script without using `targets`. -::::::::::::::::::::::::::::::::::::: +Recall that we are using the `palmerpenguins` R package to obtain the data. +This package actually includes two variations of the dataset: one is an external CSV file with the raw data, and another is the cleaned data loaded into R. +In real life you are probably have externally stored raw data, so **let's use the raw penguin data** as the starting point for our analysis too. -Your final `_targets.R` file should look like this: +The `path_to_file()` function in `palmerpenguins` provides the path to the raw data CSV file (it is inside the `palmerpenguins` R package source code that you downloaded to your computer when you installed the package) + +```{r} +#| label: normal-r-path +library(palmerpenguins) + +# Get path to CSV file +penguins_csv_file <- path_to_file("penguins_raw.csv") + +penguins_csv_file +``` + +We will use the `tidyverse` set of packages for loading and manipulating the data. We don't have time to cover all the details about using `tidyverse` now, but if you want to learn more about it, please see this lesson. + +Let's load the data with `read_csv()`. + +```{r} +#| label: normal-r-load +library(tidyverse) + +# Read CSV file into R +penguins_data_raw <- read_csv(penguins_csv_file) + +penguins_data_raw +``` + +We see the raw data has some awkward column names with spaces (these are hard to type out and can easily lead to mistakes in the code), and far more columns than we need. +For the purposes of this analysis, we only need species name, bill length, and bill depth. +In the raw data, the rather technical term "Culmen" is used to refer to the bill. + +Let's clean up the data to make it easier to use for downstream analyses. +We will also remove any rows with missing data, because this could cause errors for some functions later. + +```{r} +#| label: normal-r-clean + +# Clean up raw data +penguins_data <- penguins_data_raw %>% + # Rename columns for easier typing and + # subset to only the columns needed for analysis + select( + species = Species, + bill_length_mm = `Culmen Length (mm)`, + bill_depth_mm = `Culmen Depth (mm)` + ) %>% + # Delete rows with missing data + remove_missing(na.rm = TRUE) + +penguins_data +``` + +That's better! + +### `targets` version + +What does this look like using `targets`? + +The biggest difference is that we need to **put each step of the workflow into the list at the end**. + +We also define a custom function for the data cleaning step. +That is because the list of targets at the end **should look like a high-level summary of your analysis**. +You want to avoid lengthy chunks of code when defining the targets; instead, put that code in the custom functions. +The other steps (setting the file path and loading the data) are each just one function call so there's not much point in putting those into their own custom functions. + +Finally, each step in the workflow is defined with the `tar_target()` function. ```{r} #| label: targets-show-workflow #| eval: FALSE library(targets) -# This is an example _targets.R file. Every -# {targets} pipeline needs one. -# Use tar_script() to create _targets.R and tar_edit() -# to open it again for editing. -# Then, run tar_make() to run the pipeline -# and tar_read(summary) to view the results. - -# Define custom functions and other global objects. -# This is where you write source(\"R/functions.R\") -# if you keep your functions in external scripts. -summ <- function(dataset) { - colMeans(dataset) +library(palmerpenguins) +library(tidyverse) + +clean_penguin_data <- function(penguins_data_raw) { + penguins_data_raw %>% + select( + species = Species, + bill_length_mm = `Culmen Length (mm)`, + bill_depth_mm = `Culmen Depth (mm)` + ) %>% + remove_missing(na.rm = TRUE) } -# Set target-specific options such as packages: -# tar_option_set(packages = "utils") # nolint - -# End this file with a list of target objects. list( - tar_target(my_data, data.frame(x = sample.int(100), y = sample.int(100))), - tar_target(my_summary, summ(my_data)) # Call your custom functions as needed. + tar_target(penguins_csv_file, path_to_file("penguins_raw.csv")), + tar_target(penguins_data_raw, read_csv( + penguins_csv_file, show_col_types = FALSE)), + tar_target(penguins_data, clean_penguin_data(penguins_data_raw)) ) ``` +I have set `show_col_types = FALSE` in `read_csv()` because we know from the earlier code that the column types were set correctly by default (character for species and numeric for bill length and depth), so we don't need to see the warning it would otherwise issue. + ## Run the workflow Now that we have a workflow, we can run it with the `tar_make()` function. @@ -172,27 +235,13 @@ tar_make() #| label: targets-run-hide #| echo: FALSE tar_dir({ - write_example_plan(2) + write_example_plan(1) tar_make() }) ``` Congratulations, you've run your first workflow with `targets`! -::::::::::::::::::::::::::::::::::::: challenge - -## Challenge: What happened during the workflow? - -Inspect the list at the end of `_targets.R`. Can you describe the steps of the workflow? - -:::::::::::::::::::::::::::::::::: solution - -The first step of the workflow built a target called `my_data` that includes two variables, `x` and `y`. - -The second step of the workflow built a target called `my_summ` that includes the mean of `x` and `y` calculated with a custom function called `summ()`. - -:::::::::::::::::::::::::::::::::::::::::::: - ::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::: keypoints diff --git a/episodes/batch.Rmd b/episodes/batch.Rmd index bdb16c78..d5e2489c 100644 --- a/episodes/batch.Rmd +++ b/episodes/batch.Rmd @@ -54,31 +54,109 @@ In this workshop, we will only cover dynamic branching since it is generally eas ## Example without branching -To see how this works, let's use an example based on the `palmerpenguins` dataset. -Our hypothesis is that bill depth decreases with bill length. -We want to test this using several alternative models. -The models will either ignore species identity, add a parameter for species, or add an interaction effect between species and bill length. +To see how this works, let's continue our analysis of the `palmerpenguins` dataset. -This is what a workflow for such an analysis might look like **without branching**: +**Our hypothesis is that bill depth decreases with bill length.** +We will test this hypothesis with a linear model. + +For example, this is a model of bill depth dependent on bill length: + +```{r} +#| label: example-lm +#| eval: FALSE +lm(bill_depth_mm ~ bill_length_mm, data = penguin_data) +``` + +We can add this to our pipeline. We will call it the `combined_model` because it combines all the species together without distinction: + +```{r} +#| label: example-lm-pipeline-show +#| eval: FALSE +source("R/packages.R") +source("R/functions.R") + +tar_plan( + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), + # Build model + combined_model = lm( + bill_depth_mm ~ bill_length_mm, data = penguins_data) +) +``` + +```{r} +#| label: example-lm-pipeline-hide +#| show: FALSE +tar_dir({ + # New workflow + write_example_plan(3) + # Run it + tar_make(reporter = "silent") + write_example_plan(4) + tar_make() +}) +``` + +Let's have a look at the model. We will use the `glance()` function from the `broom` package. Unlike base R `summary()`, this function returns output as a tibble (the tidyverse equivalent of a dataframe), which as we will see later is quite useful for downstream analyses. + +```{r} +#| label: example-lm-pipeline-inspect-show +#| eval: FALSE +tar_load(combined_model) +broom::glance(combined_model) +``` + +```{r} +#| label: example-lm-pipeline-inspect-hide +#| show: FALSE +tar_dir({ + # New workflow + write_example_plan(4) + # Run it + tar_make(reporter = "silent") + tar_load(combined_model) + broom::glance(combined_model) +}) +``` + +Notice the small *P*-value. +This seems to indicate that the model is highly significant. + +But wait a moment... is this really an appropriate model? Recall that there are three species of penguins in the dataset. It is possible that the relationship between bill depth and length **varies by species**. + +We should probably test some alternative models. +These could include models that add a parameter for species, or add an interaction effect between species and bill length. + +Now our workflow is getting more complicated. This is what a workflow for such an analysis might look like **without branching** (make sure to add `library(broom)` to `packages.R`): ```{r} #| label: example-model-show-1 #| eval: FALSE -library(targets) -library(tarchetypes) -library(palmerpenguins) -library(broom) +source("R/packages.R") +source("R/functions.R") tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), # Build models combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), + bill_depth_mm ~ bill_length_mm, data = penguins_data), species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data), + bill_depth_mm ~ bill_length_mm * species, data = penguins_data), # Get model summaries combined_summary = glance(combined_model), species_summary = glance(species_model), @@ -88,74 +166,38 @@ tar_plan( ```{r} #| label: example-model-hide-1 -#| echo: FALSE +#| show: FALSE tar_dir({ - tar_script({ - library(targets) - library(tarchetypes) - library(palmerpenguins) - library(broom) - - tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, - # Build models - combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), - species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), - interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data), - # Get model summaries - combined_summary = glance(combined_model), - species_summary = glance(species_model), - interaction_summary = glance(interaction_model) - ) - }) + # New workflow + write_example_plan(4) + # Run it + tar_make(reporter = "silent") + write_example_plan(5) tar_make() }) ``` -It worked. Let's check some of the model output: +Let's look at the summary of one of the models: ```{r} #| label: example-model-show-2 #| eval: FALSE -tar_read(combined_summary) +tar_read(species_summary) ``` ```{r} #| label: example-model-hide-2 #| echo: FALSE tar_dir({ - tar_script({ - library(targets) - library(tarchetypes) - library(palmerpenguins) - library(broom) - - tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, - # Build models - combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), - species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), - interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data), - # Get model summaries - combined_summary = glance(combined_model), - species_summary = glance(species_model), - interaction_summary = glance(interaction_model) - ) - }) + # New workflow + write_example_plan(5) + # Run it tar_make(reporter = "silent") - tar_read(combined_summary) + tar_read(species_summary) }) ``` -This way of writing the pipeline is repetitive: we have to call `glance()` each time we want to obtain summary statistics for each model. +So this way of writing the pipeline works, but is repetitive: we have to call `glance()` each time we want to obtain summary statistics for each model. Furthermore, each summary target (`combined_summary`, etc.) is explicitly named and typed out manually. It would be fairly easy to make a typo and end up with the wrong model being summarized. @@ -168,22 +210,26 @@ Let's see how to write the same plan using **dynamic branching**: ```{r} #| label: example-model-show-3 #| eval: FALSE -library(targets) -library(tarchetypes) -library(palmerpenguins) -library(broom) +source("R/packages.R") +source("R/functions.R") tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), # Build models models = list( combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), + bill_depth_mm ~ bill_length_mm, data = penguins_data), species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) ), # Get model summaries tar_target( @@ -202,36 +248,17 @@ First, let's look at the messages provided by `tar_make()`. #| label: example-model-hide-3 #| echo: FALSE tar_dir({ - tar_script({ - library(targets) - library(tarchetypes) - library(palmerpenguins) - library(broom) - tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, - # Build models - models = list( - combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), - species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), - interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) - ), - # Get model summaries - tar_target( - model_summaries, - glance(models[[1]]), - pattern = map(models) - ) - ) - }) + # New workflow + write_example_plan(5) + # Run it + tar_make(reporter = "silent") + write_example_plan(6) tar_make() + example_branch_name <- tar_branch_names(model_summaries, 1) }) ``` -There is a series of smaller targets (branches) that are each named like `model_summaries_f9795da2`, then one overall `model_summaries` target. +There is a series of smaller targets (branches) that are each named like `r example_branch_name`, then one overall `model_summaries` target. That is the result of specifying targets using branching: each of the smaller targets are the "branches" that comprise the overall target. Since `targets` has no way of knowing ahead of time how many branches there will be or what they represent, it names each one using this series of numbers and letters (the "hash"). `targets` builds each branch one at a time, then combines them into the overall target. @@ -244,11 +271,11 @@ Next, let's look in more detail about how the workflow is set up, starting with # Build models models <- list( combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), + bill_depth_mm ~ bill_length_mm, data = penguins_data), species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) ) ``` @@ -288,31 +315,9 @@ tar_read(model_summaries) #| label: example-model-hide-4 #| echo: FALSE tar_dir({ - tar_script({ - library(targets) - library(tarchetypes) - library(palmerpenguins) - library(broom) - tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, - # Build models - models = list( - combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), - species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), - interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) - ), - # Get model summaries - tar_target( - model_summaries, - glance(models[[1]]), - pattern = map(models) - ) - ) - }) + # New workflow + write_example_plan(6) + # Run it tar_make(reporter = "silent") tar_read(model_summaries) }) @@ -351,24 +356,26 @@ Our new pipeline looks almost the same as before, but this time we use the custo ```{r} #| label: example-model-show-6 #| eval: FALSE -library(targets) -library(tarchetypes) -library(palmerpenguins) -library(tidyverse) - +source("R/packages.R") source("R/functions.R") tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), # Build models models = list( combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), + bill_depth_mm ~ bill_length_mm, data = penguins_data), species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) ), # Get model summaries tar_target( @@ -381,41 +388,13 @@ tar_plan( ```{r} #| label: example-model-hide-6 -#| echo: false -#| warning: false +#| echo: FALSE tar_dir({ - tar_script({ - options(tidyverse.quiet = TRUE) - library(targets) - library(tarchetypes) - library(palmerpenguins) - library(tidyverse) - glance_with_mod_name <- function(model_in_list) { - model_name <- names(model_in_list) - model <- model_in_list[[1]] - broom::glance(model) %>% - mutate(model_name = model_name) - } - tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, - # Build models - models = list( - combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), - species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), - interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) - ), - # Get model summaries - tar_target( - model_summaries, - glance_with_mod_name(models), - pattern = map(models) - ) - ) - }) + # New workflow + write_example_plan(6) + # Run it + tar_make(reporter = "silent") + write_example_plan(7) tar_make() }) ``` @@ -427,38 +406,9 @@ And this time, when we load the `model_summaries`, we can tell which model corre #| echo: false #| warning: false tar_dir({ - tar_script({ - options(tidyverse.quiet = TRUE) - library(targets) - library(tarchetypes) - library(palmerpenguins) - library(tidyverse) - glance_with_mod_name <- function(model_in_list) { - model_name <- names(model_in_list) - model <- model_in_list[[1]] - broom::glance(model) %>% - mutate(model_name = model_name) - } - tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, - # Build models - models = list( - combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), - species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), - interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) - ), - # Get model summaries - tar_target( - model_summaries, - glance_with_mod_name(models), - pattern = map(models) - ) - ) - }) + # New workflow + write_example_plan(7) + # Run it tar_make(reporter = "silent") tar_read(model_summaries) }) @@ -603,33 +553,35 @@ Notice that although the time required to build each individual target is about The unique and powerful thing about targets is that **we did not need to change our custom function to run it in parallel**. We only adjusted *the workflow*. This means it is relatively easy to refactor (modify) a workflow for running sequentially locally or running in parallel in a high-performance context. -We can see this by applying parallel processing to a workflow that we were previously running sequentially, the penguins analysis: +We can see this by applying parallel processing to the penguins analysis. +Add two more packages to `packages.R`, `library(future)` and `library(future.callr)`. +Also, add the line `plan(callr)` to `_targets.R`: ```{r} #| label: example-model-show-9 #| eval: FALSE -library(targets) -library(tarchetypes) -library(palmerpenguins) -library(tidyverse) -library(future) -library(future.callr) - +source("R/packages.R") source("R/functions.R") plan(callr) tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), # Build models models = list( combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), + bill_depth_mm ~ bill_length_mm, data = penguins_data), species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) ), # Get model summaries tar_target( @@ -640,44 +592,15 @@ tar_plan( ) ``` +Finally, run the pipeline with `tar_make_future()` (you may need to run `tar_invalidate(everything())` to reset the pipeline first). + ```{r} #| label: example-model-hide-9 #| echo: FALSE tar_dir({ - tar_script({ - library(targets) - library(tarchetypes) - library(palmerpenguins) - library(tidyverse) - library(future) - library(future.callr) - plan(callr) - glance_with_mod_name <- function(model_in_list) { - model_name <- names(model_in_list) - model <- model_in_list[[1]] - broom::glance(model) %>% - mutate(model_name = model_name) - } - tar_plan( - # Load data from package - penguin_data = palmerpenguins::penguins, - # Build models - models = list( - combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), - species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguin_data), - interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) - ), - # Get model summaries - tar_target( - model_summaries, - glance_with_mod_name(models), - pattern = map(models) - ) - ) - }) + # New workflow + write_example_plan(8) + # Run it tar_make_future(workers = 2) }) ``` diff --git a/episodes/cache.Rmd b/episodes/cache.Rmd index e6b0bd37..e61bc81f 100644 --- a/episodes/cache.Rmd +++ b/episodes/cache.Rmd @@ -37,19 +37,17 @@ Episode summary: Show how to get at the objects that we built So we just finished running our first workflow. Now you probably want to look at its output. -But, if we just call the name of the object (`my_data` or `my_summary`), we get an error. +But, if we just call the name of the object (for example, `penguins_data`), we get an error. ```{r} #| label: error -my_data +penguins_data ``` Where are the results of our workflow? ::::::::::::::::::::::::::::::::::::: instructor -- To reinforce the concept of `targets` running in a separate R session, you may want to pretend trying to run `my_data` or `my_summary`, then feigning surprise when it doesn't work and using it as a teaching moment (errors are pedagogy!). - -- In the previous episode, the default names of targets `data` and `summary` were changed to avoid confusion with the base R functions `data()` and `summary()`. +- To reinforce the concept of `targets` running in a separate R session, you may want to pretend trying to run `penguins_data`, then feigning surprise when it doesn't work and using it as a teaching moment (errors are pedagogy!). :::::::::::::::::::::::::::::::::::::::::::::::: @@ -63,7 +61,7 @@ Let's see how these work. `tar_load()` loads an object built by the workflow into the current session. Its first argument is the name of the object you want to load. -Let's use this to load `my_data` and inspect the first several rows with `head()`. +Let's use this to load `penguins_data` and get an overview of the data with `summary()`. ```{r} #| label: targets-run-hide @@ -71,24 +69,24 @@ Let's use this to load `my_data` and inspect the first several rows with `head() # When building the Rmd, each instance of the workflow is isolated, so need # to re-run tar_dir({ - write_example_plan(2) + write_example_plan(1) tar_make(reporter = "silent") - my_summary_hide <- tar_read(my_summary) - my_data_hide <- tar_read(my_data) + penguins_csv_file_hide <- tar_read(penguins_csv_file) + penguins_data_hide <- tar_read(penguins_data) }) ``` ```{r} #| label: targets-load-show #| eval: FALSE -tar_load(my_data) -head(my_data) +tar_load(penguins_data) +summary(penguins_data) ``` ```{r} #| label: targets-load-hide #| echo: FALSE -head(my_data_hide) +summary(penguins_data_hide) ``` Note that `tar_load()` is used for its **side-effect**---loading the desired object into the current R session. @@ -98,27 +96,27 @@ It doesn't actually return a value. `tar_read()` is similar to `tar_load()` in that it is used to retrieve objects built by the workflow, but unlike `tar_load()`, it returns them directly as output. -Let's try it with `my_summary`. +Let's try it with `penguins_csv_file`. ```{r} #| label: targets-read-show #| eval: FALSE -tar_read(my_summary) +tar_read(penguins_csv_file) ``` ```{r} #| label: targets-read-hide #| echo: FALSE -my_summary_hide +penguins_csv_file_hide ``` -We immediately see the contents of `my_summary`. -But it has not been loaded into the enviroment. -If you try to run `my_summary` now, you will get an error: +We immediately see the contents of `penguins_csv_file`. +But it has not been loaded into the environment. +If you try to run `penguins_csv_file` now, you will get an error: ```{r} #| label: error-2 -my_summary +penguins_csv_file ``` ## When to use which function diff --git a/episodes/files.Rmd b/episodes/files.Rmd index 0be5b9e6..f9ee8dbe 100644 --- a/episodes/files.Rmd +++ b/episodes/files.Rmd @@ -41,6 +41,16 @@ As a simple example, let's create an external data file in RStudio with the "New We will read in the contents of this file and store it as `some_data` in the workflow by writing the following plan and running `tar_make()`: +::::::::::::::::::::::::::::::::::::: {.callout} + +## Save your progress + +You can only have one active `_targets.R` file at a time in a given project. + +We are about to create a new `_targets.R` file, but you probably don't want to lose your progress in the one we have been working on so far (the penguins analysis). You can temporarily rename that one to something like `_targets_old.R` so that you don't overwrite it with the new example `_targets.R` file below. Then, rename them when you are ready to work on it again. + +::::::::::::::::::::::::::::::::::::: + ```{r} #| label: example-file-show-1 #| eval: FALSE @@ -206,6 +216,47 @@ Although we used `readLines()` as an example here, you can use the same pattern This is generally recommended so that your pipeline stays up to date with your input data. +::::::::::::::::::::::::::::::::::::: {.challenge} + +## Challenge: Use `tar_file_read()` with the penguins example + +We didn't know about `tar_file_read()` yet when we started on the penguins bill analysis. + +How can you use `tar_file_read()` to load the CSV file while tracking its contents? + +:::::::::::::::::::::::::::::::::: {.solution} + +```{r} +#| label: tar-file-read-answer +#| eval: FALSE +source("R/packages.R") +source("R/functions.R") + +tar_plan( + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + penguins_data = clean_penguin_data(penguins_data_raw) +) +``` + +```{r} +#| label: tar-file-read-answer +#| show: FALSE +tar_dir({ + # New workflow + write_example_plan(3) + # Run it + tar_make() +}) +``` + +:::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: + ## Writing out data Writing to files is similar to loading in files: we will use the `tar_file()` function. There is one important caveat: in this case, the second argument of `tar_file()` (the command to build the target) **must return the path to the file**. Not all functions that write files do this (some return nothing; these treat the output file is a side-effect of running the function), so you may need to define a custom function that writes out the file and then returns its path. @@ -280,11 +331,11 @@ tar_plan( #| label: example-file-hide-6 #| echo: false tar_dir({ - + fs::dir_create("_targets/user/data") fs::dir_create("_targets/user/results") writeLines("Hello World. How are you?", "_targets/user/data/hello.txt") - + tar_script({ library(targets) library(tarchetypes) diff --git a/episodes/files/functions.R b/episodes/files/functions.R index 878749d5..a9e5a1f4 100644 --- a/episodes/files/functions.R +++ b/episodes/files/functions.R @@ -9,95 +9,308 @@ #' @examples #' library(targets) #' tar_dir({ -#' write_example_plan(2) +#' write_example_plan(1) #' tar_make() #' }) #' write_example_plan <- function(plan_select) { - # default example plan from tar_script() + # functions + glance_with_mod_name_func <- c( + "glance_with_mod_name <- function(model_in_list) {", + "model_name <- names(model_in_list)", + "model <- model_in_list[[1]]", + "broom::glance(model) %>%", + " mutate(model_name = model_name)", + "}" + ) + clean_penguin_data_func <- c( + "clean_penguin_data <- function(penguins_data_raw) {", + " penguins_data_raw %>%", + " select(", + " species = Species,", + " bill_length_mm = `Culmen Length (mm)`,", + " bill_depth_mm = `Culmen Depth (mm)`", + " ) %>%", + " remove_missing(na.rm = TRUE) %>%", + " separate(species, into = 'species', extra = 'drop')", + "}" + ) + # original plan plan_1 <- c( "library(targets)", - "# This is an example _targets.R file. Every", - "# {targets} pipeline needs one.", - "# Use tar_script() to create _targets.R and tar_edit()", - "# to open it again for editing.", - "# Then, run tar_make() to run the pipeline", - "# and tar_read(summary) to view the results.", - "", - "# Define custom functions and other global objects.", - "# This is where you write source(\\\"R/functions.R\\\")", - "# if you keep your functions in external scripts.", - "summ <- function(dataset) {", - " colMeans(dataset)", + "library(palmerpenguins)", + "suppressPackageStartupMessages(library(tidyverse))", + "clean_penguin_data <- function(penguins_data_raw) {", + " penguins_data_raw %>%", + " select(", + " species = Species,", + " bill_length_mm = `Culmen Length (mm)`,", + " bill_depth_mm = `Culmen Depth (mm)`", + " ) %>%", + " remove_missing(na.rm = TRUE)", "}", - "", - "# Set target-specific options such as packages:", - "# tar_option_set(packages = \"utils\") # nolint", - "", - "# End this file with a list of target objects.", "list(", - " tar_target(data, data.frame(x = sample.int(100), y = sample.int(100))),", - " tar_target(summary, summ(data)) # Call your custom functions as needed.", + " tar_target(penguins_csv_file, path_to_file('penguins_raw.csv')),", + " tar_target(penguins_data_raw, read_csv(", + " penguins_csv_file, show_col_types = FALSE)),", + " tar_target(penguins_data, clean_penguin_data(penguins_data_raw))", ")" ) - # change names to "my_data" and "my_summary" + # separate species names plan_2 <- c( "library(targets)", - "# This is an example _targets.R file. Every", - "# {targets} pipeline needs one.", - "# Use tar_script() to create _targets.R and tar_edit()", - "# to open it again for editing.", - "# Then, run tar_make() to run the pipeline", - "# and tar_read(summary) to view the results.", - "", - "# Define custom functions and other global objects.", - "# This is where you write source(\\\"R/functions.R\\\")", - "# if you keep your functions in external scripts.", - "summ <- function(dataset) {", - " colMeans(dataset)", + "library(palmerpenguins)", + "suppressPackageStartupMessages(library(tidyverse))", + "clean_penguin_data <- function(penguins_data_raw) {", + " penguins_data_raw %>%", + " select(", + " species = Species,", + " bill_length_mm = `Culmen Length (mm)`,", + " bill_depth_mm = `Culmen Depth (mm)`", + " ) %>%", + " remove_missing(na.rm = TRUE) %>%", + " separate(species, into = 'species', extra = 'drop')", "}", - "", - "# Set target-specific options such as packages:", - "# tar_option_set(packages = \"utils\") # nolint", - "", - "# End this file with a list of target objects.", "list(", - " tar_target(my_data, data.frame(x = sample.int(100), y = sample.int(100))),", # nolint - " tar_target(my_summary, summ(my_data)) # Call your custom functions as needed.", # nolint + " tar_target(penguins_csv_file, path_to_file('penguins_raw.csv')),", + " tar_target(penguins_data_raw, read_csv(", + " penguins_csv_file, show_col_types = FALSE)),", + " tar_target(penguins_data, clean_penguin_data(penguins_data_raw))", ")" ) - # Return row sums instead of col sums + # tar_file_read plan_3 <- c( "library(targets)", - "# This is an example _targets.R file. Every", - "# {targets} pipeline needs one.", - "# Use tar_script() to create _targets.R and tar_edit()", - "# to open it again for editing.", - "# Then, run tar_make() to run the pipeline", - "# and tar_read(summary) to view the results.", - "", - "# Define custom functions and other global objects.", - "# This is where you write source(\\\"R/functions.R\\\")", - "# if you keep your functions in external scripts.", - "summ <- function(dataset) {", - " rowMeans(dataset)", + "library(palmerpenguins)", + "library(tarchetypes)", + "suppressPackageStartupMessages(library(tidyverse))", + "clean_penguin_data <- function(penguins_data_raw) {", + " penguins_data_raw %>%", + " select(", + " species = Species,", + " bill_length_mm = `Culmen Length (mm)`,", + " bill_depth_mm = `Culmen Depth (mm)`", + " ) %>%", + " remove_missing(na.rm = TRUE) %>%", + " separate(species, into = 'species', extra = 'drop')", "}", - "", - "# Set target-specific options such as packages:", - "# tar_option_set(packages = \"utils\") # nolint", - "", - "# End this file with a list of target objects.", - "list(", - " tar_target(my_data, data.frame(x = sample.int(100), y = sample.int(100))),", # nolint - " tar_target(my_summary, summ(my_data)) # Call your custom functions as needed.", # nolint + "tar_plan(", + " tar_file_read(", + " penguins_data_raw,", + " path_to_file('penguins_raw.csv'),", + " read_csv(!!.x, show_col_types = FALSE)", + " ),", + " penguins_data = clean_penguin_data(penguins_data_raw)", + ")" + ) + # add one model + plan_4 <- c( + "library(targets)", + "library(palmerpenguins)", + "library(tarchetypes)", + "suppressPackageStartupMessages(library(tidyverse))", + "clean_penguin_data <- function(penguins_data_raw) {", + " penguins_data_raw %>%", + " select(", + " species = Species,", + " bill_length_mm = `Culmen Length (mm)`,", + " bill_depth_mm = `Culmen Depth (mm)`", + " ) %>%", + " remove_missing(na.rm = TRUE) %>%", + " separate(species, into = 'species', extra = 'drop')", + "}", + "tar_plan(", + " tar_file_read(", + " penguins_data_raw,", + " path_to_file('penguins_raw.csv'),", + " read_csv(!!.x, show_col_types = FALSE)", + " ),", + " penguins_data = clean_penguin_data(penguins_data_raw),", + " combined_model = lm(", + " bill_depth_mm ~ bill_length_mm, data = penguins_data)", + ")" + ) + # add multiple models + plan_5 <- c( + "library(targets)", + "library(palmerpenguins)", + "library(tarchetypes)", + "library(broom)", + "suppressPackageStartupMessages(library(tidyverse))", + "clean_penguin_data <- function(penguins_data_raw) {", + " penguins_data_raw %>%", + " select(", + " species = Species,", + " bill_length_mm = `Culmen Length (mm)`,", + " bill_depth_mm = `Culmen Depth (mm)`", + " ) %>%", + " remove_missing(na.rm = TRUE) %>%", + " separate(species, into = 'species', extra = 'drop')", + "}", + "tar_plan(", + " tar_file_read(", + " penguins_data_raw,", + " path_to_file('penguins_raw.csv'),", + " read_csv(!!.x, show_col_types = FALSE)", + " ),", + " penguins_data = clean_penguin_data(penguins_data_raw),", + " combined_model = lm(", + " bill_depth_mm ~ bill_length_mm, data = penguins_data),", + " species_model = lm(", + " bill_depth_mm ~ bill_length_mm + species, data = penguins_data),", + " interaction_model = lm(", + " bill_depth_mm ~ bill_length_mm * species, data = penguins_data),", + " combined_summary = glance(combined_model),", + " species_summary = glance(species_model),", + " interaction_summary = glance(interaction_model)", ")" ) + # add multiple models with branching + plan_6 <- c( + "library(targets)", + "library(palmerpenguins)", + "library(tarchetypes)", + "library(broom)", + "suppressPackageStartupMessages(library(tidyverse))", + clean_penguin_data_func, + "tar_plan(", + " tar_file_read(", + " penguins_data_raw,", + " path_to_file('penguins_raw.csv'),", + " read_csv(!!.x, show_col_types = FALSE)", + " ),", + " penguins_data = clean_penguin_data(penguins_data_raw),", + " models = list(", + " combined_model = lm(", + " bill_depth_mm ~ bill_length_mm, data = penguins_data),", + " species_model = lm(", + " bill_depth_mm ~ bill_length_mm + species, data = penguins_data),", + " interaction_model = lm(", + " bill_depth_mm ~ bill_length_mm * species, data = penguins_data)", + " ),", + " tar_target(", + " model_summaries,", + " glance(models[[1]]),", + " pattern = map(models)", + " )", + ")" + ) + # add multiple models with branching, custom glance func + plan_7 <- c( + "library(targets)", + "library(palmerpenguins)", + "library(tarchetypes)", + "library(broom)", + "suppressPackageStartupMessages(library(tidyverse))", + glance_with_mod_name_func, + clean_penguin_data_func, + "tar_plan(", + " tar_file_read(", + " penguins_data_raw,", + " path_to_file('penguins_raw.csv'),", + " read_csv(!!.x, show_col_types = FALSE)", + " ),", + " penguins_data = clean_penguin_data(penguins_data_raw),", + " models = list(", + " combined_model = lm(", + " bill_depth_mm ~ bill_length_mm, data = penguins_data),", + " species_model = lm(", + " bill_depth_mm ~ bill_length_mm + species, data = penguins_data),", + " interaction_model = lm(", + " bill_depth_mm ~ bill_length_mm * species, data = penguins_data)", + " ),", + " tar_target(", + " model_summaries,", + " glance_with_mod_name(models),", + " pattern = map(models)", + " )", + ")" + ) + # adds future + plan_8 <- c( + "library(targets)", + "library(palmerpenguins)", + "library(tarchetypes)", + "library(broom)", + "library(future)", + "library(future.callr)", + "suppressPackageStartupMessages(library(tidyverse))", + glance_with_mod_name_func, + clean_penguin_data_func, + "plan(callr)", + "tar_plan(", + " tar_file_read(", + " penguins_data_raw,", + " path_to_file('penguins_raw.csv'),", + " read_csv(!!.x, show_col_types = FALSE)", + " ),", + " penguins_data = clean_penguin_data(penguins_data_raw),", + " models = list(", + " combined_model = lm(", + " bill_depth_mm ~ bill_length_mm, data = penguins_data),", + " species_model = lm(", + " bill_depth_mm ~ bill_length_mm + species, data = penguins_data),", + " interaction_model = lm(", + " bill_depth_mm ~ bill_length_mm * species, data = penguins_data)", + " ),", + " tar_target(", + " model_summaries,", + " glance_with_mod_name(models),", + " pattern = map(models)", + " )", + ")" + ) + # adds report + plan_9 <- c( + "library(targets)", + "library(palmerpenguins)", + "library(tarchetypes)", + "library(broom)", + "suppressPackageStartupMessages(library(tidyverse))", + glance_with_mod_name_func, + clean_penguin_data_func, + "plan(callr)", + "tar_plan(", + " tar_file_read(", + " penguins_data_raw,", + " path_to_file('penguins_raw.csv'),", + " read_csv(!!.x, show_col_types = FALSE)", + " ),", + " penguins_data = clean_penguin_data(penguins_data_raw),", + " models = list(", + " combined_model = lm(", + " bill_depth_mm ~ bill_length_mm, data = penguins_data),", + " species_model = lm(", + " bill_depth_mm ~ bill_length_mm + species, data = penguins_data),", + " interaction_model = lm(", + " bill_depth_mm ~ bill_length_mm * species, data = penguins_data)", + " ),", + " tar_target(", + " model_summaries,", + " glance_with_mod_name(models),", + " pattern = map(models)", + " ),", + " # Generate report", + " tar_quarto(", + " penguin_report,", + " path = 'penguin_report.qmd',", + " quiet = FALSE,", + " packages = c('targets', 'tidyverse')", + " )", + ")" + ) switch( as.character(plan_select), "1" = readr::write_lines(plan_1, "_targets.R"), "2" = readr::write_lines(plan_2, "_targets.R"), "3" = readr::write_lines(plan_3, "_targets.R"), - stop("plan_select must be 1, 2, or 3") + "4" = readr::write_lines(plan_4, "_targets.R"), + "5" = readr::write_lines(plan_5, "_targets.R"), + "6" = readr::write_lines(plan_6, "_targets.R"), + "7" = readr::write_lines(plan_7, "_targets.R"), + "8" = readr::write_lines(plan_8, "_targets.R"), + "9" = readr::write_lines(plan_9, "_targets.R"), + stop("plan_select must be 1, 2, 3, 4, 5, 6, 7, 8, or 9") ) } diff --git a/episodes/introduction.Rmd b/episodes/introduction.Rmd index 3f5d8aec..ffb4a304 100644 --- a/episodes/introduction.Rmd +++ b/episodes/introduction.Rmd @@ -65,11 +65,24 @@ If you mostly code with other tools, you may want to consider an alternative. The **goal** of this workshop is to **learn how to use `targets` to reproducible data analysis in R**. +## About the example dataset + +For this workshop, we will analyze an example dataset of measurements taken on adult foraging Adélie, Chinstrap, and Gentoo penguins observed on islands in the Palmer Archipelago, Antarctica. + +**Our goal is to determine the relationship between bill length and depth by using linear models.** + +The data are available from the `palmerpenguins` R package. You can get more information about the data by running `?palmerpenguins`. + +```{r} +library(palmerpenguins) +?palmerpenguins +``` + ::::::::::::::::::::::::::::::::::::: keypoints -- We can only have confidence in the results of scientific analyses if they can be reproduced by others -- "Others" includes your future self +- We can only have confidence in the results of scientific analyses if they can be reproduced by others (including your future self) - `targets` helps achieve reproducibility by automating workflow - `targests` is designed for use with the R programming language +- The example dataset for this workshop includes measurements taken on penguins in Antarctica :::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/episodes/lifecycle.Rmd b/episodes/lifecycle.Rmd index 6d127ca3..b1f71cd0 100644 --- a/episodes/lifecycle.Rmd +++ b/episodes/lifecycle.Rmd @@ -52,7 +52,7 @@ tar_make() #| echo: FALSE # Each tar_script is fresh, so need to run once to catch up to learners tar_dir({ - write_example_plan(2) + write_example_plan(1) tar_make(reporter = "silent") tar_make() }) @@ -68,15 +68,24 @@ Remember, the fastest code is the code you don't have to run! What happens when we change one part of the workflow then run it again? -Let's modify the workflow to calculate the **sum** of the columns instead of the mean. +Say that we decide the species names should be shorter. +Right now they include the common name and the scientific name, but we really only need the first part of the common name to distinguish them. -Edit `_targets.R` so that the `summ()` function looks like this: +Edit `_targets.R` so that the `clean_penguin_data()` function looks like this: ```{r} #| label: new-func #| eval: FALSE -summ <- function(dataset) { - colSums(dataset) +clean_penguin_data <- function(penguins_data_raw) { + penguins_data_raw %>% + select( + species = Species, + bill_length_mm = `Culmen Length (mm)`, + bill_depth_mm = `Culmen Depth (mm)` + ) %>% + remove_missing(na.rm = TRUE) %>% + # Split "species" apart on spaces, and only keep the first word + separate(species, into = "species", extra = "drop") } ``` @@ -93,11 +102,11 @@ tar_make() #| echo: FALSE tar_dir({ # Original workflow - write_example_plan(2) + write_example_plan(1) # Run it silently tar_make(reporter = "silent") # New workflow - write_example_plan(3) + write_example_plan(2) # Run it again tar_make() }) @@ -105,7 +114,7 @@ tar_dir({ What happened? -This time, it skipped `my_data` and only ran `my_summary`. +This time, it skipped `penguins_csv_file` and `penguins_data_raw` and only ran `penguins_data`. Of course, since our example workflow is so short we don't even notice the amount of time saved. But imagine using this in a series of computationally intensive analysis steps. @@ -115,11 +124,11 @@ The ability to automatically skip steps results in a massive increase in efficie ## Challenge 1: Inspect the output -How can you inspect the contents of `my_summary`? +How can you inspect the contents of `penguins_data`? :::::::::::::::::::::::::::::::::: solution -With `tar_read(my_summary`) or by running `tar_load(my_summary)` followed by `my_summary`. +With `tar_read(penguins_data`) or by running `tar_load(penguins_data)` followed by `penguins_data`. :::::::::::::::::::::::::::::::::::::::::::: @@ -171,7 +180,7 @@ tar_visnetwork() tar_dir({ # New workflow - write_example_plan(3) + write_example_plan(2) # Run it tar_make(reporter = "silent") # Run it again @@ -180,7 +189,7 @@ tar_dir({ }) ``` -![](fig/lifecycle-visnetwork.png){alt="Visualization of the targets worklow, showing 'my_summary' connected by lines to 'my_data' and 'summ'"} +![](fig/lifecycle-visnetwork.png){alt="Visualization of the targets worklow, showing 'penguins_data' connected by lines to 'penguins_data_raw', 'penguins_csv_file' and 'clean_penguin_data'"} You should see the network show up in the plot area of RStudio. @@ -188,7 +197,7 @@ It is an HTML widget, so you can zoom in and out (this isn't important for the c Here, we see that all of the targets are dark green, indicating that they are up-to-date and would be skipped if we were to run the workflow again. -::::::::::::::::::::::::::::::::::::: callout +::::::::::::::::::::::::::::::::::::: prereq ## Installing visNetwork @@ -255,7 +264,7 @@ tar_outdated() #| echo: FALSE tar_dir({ # New workflow - write_example_plan(3) + write_example_plan(2) # Run it tar_make(reporter = "silent") # Run it again @@ -275,7 +284,7 @@ tar_progress() #| echo: FALSE tar_dir({ # New workflow - write_example_plan(3) + write_example_plan(2) # Run it tar_make(reporter = "silent") # Run it again @@ -289,7 +298,7 @@ tar_dir({ It is possible to only make a particular target instead of running the entire workflow. To do this, type the name of the target you wish to build after `tar_make()` (note that any targets required by the one you specify will also be built). -For example, `tar_make(my_data)` would **only** build `my_data`, not `my_summary`. +For example, `tar_make(penguins_data_raw)` would **only** build `penguins_data_raw`, not `penguins_data`. Furthermore, if you want to manually "reset" a target and make it appear out-of-date, you can do so with `tar_invalidate()`. This means that target (and any that depend on it) will be re-run next time. @@ -306,7 +315,7 @@ tar_make() #| echo: FALSE tar_dir({ # New workflow - write_example_plan(3) + write_example_plan(2) # Run it tar_make(reporter = "silent") # Run it again @@ -314,12 +323,12 @@ tar_dir({ }) ``` -Let's invalidate `my_summary` and run it again: +Let's invalidate `penguins_data` and run it again: ```{r} #| label: targets-progress-show-3 #| eval: FALSE -tar_invalidate(my_summary) +tar_invalidate(penguins_data) tar_make() ``` @@ -328,15 +337,17 @@ tar_make() #| echo: FALSE tar_dir({ # New workflow - write_example_plan(3) + write_example_plan(2) # Run it tar_make(reporter = "silent") - tar_invalidate(my_summary) + tar_invalidate(penguins_data) # Run it again tar_make() }) ``` +If you want to reset **everything** and start fresh, you can use `tar_invalidate(everything())` (`tar_invalidate()` accepts tidy select functions to specify target names). + **Caution should be exercised** when using granular methods like this, though, since you may end up with your workflow in an unexpected state. The surest way to maintain an up-to-date workflow is to run `tar_make()` frequently. ## How this all works in practice diff --git a/episodes/organization.Rmd b/episodes/organization.Rmd index 7db07e45..482048e1 100644 --- a/episodes/organization.Rmd +++ b/episodes/organization.Rmd @@ -40,100 +40,95 @@ The default way to specify targets in the plan is with the `tar_target()` functi But this way of writing plans can be a bit verbose. There is an alternative provided by the `tarchetypes` package, also written by the creator of `targets`, Will Landau. -If you haven't already, install it with `install.packages("tarchetypes")`. + +::::::::::::::::::::::::::::::::::::: prereq + +## Install `tarchetypes` + +If you haven't done so yet, install `tarchetypes` with `install.packages("tarchetypes")`. + +::::::::::::::::::::::::::::::::::::: The purpose of the `tarchetypes` is to provide various shortcuts that make writing `targets` pipelines easier. We will introduce just one for now, `tar_plan()`. This is used in place of `list()` at the end of the `_targets.R` script. By using `tar_plan()`, instead of specifying targets with `tar_target()`, we can use a syntax like this: `target_name = target_command`. -For example, try this short targets script: +Let's edit the penguins workflow to use the `tar_plan()` syntax: ```{r} -#| label: example-tarchetypes-show +#| label: tar-plan-show #| eval: FALSE library(targets) -library(tarchetypes) +library(palmerpenguins) +library(tidyverse) + +clean_penguin_data <- function(penguins_data_raw) { + penguins_data_raw %>% + select( + species = Species, + bill_length_mm = `Culmen Length (mm)`, + bill_depth_mm = `Culmen Depth (mm)` + ) %>% + remove_missing(na.rm = TRUE) +} tar_plan( - height_cm = 160, - height_in = height_cm / 2.54 + penguins_csv_file = path_to_file("penguins_raw.csv"), + penguins_data_raw = read_csv(penguins_csv_file, show_col_types = FALSE), + penguins_data = clean_penguin_data(penguins_data_raw) ) ``` -```{r} -#| label: example-tarchetypes-hide -#| echo: FALSE -tar_dir({ - tar_script({ - library(targets) - library(tarchetypes) - tar_plan( - height_cm = 160, - height_in = height_cm / 2.54 - ) - }) - tar_make() -}) -``` +I think it is easier to read, do you? Notice that `tar_plan()` does not mean you have to write *all* targets this way; you can still use the `tar_target()` format within `tar_plan()`. That is because `=`, while short and easy to read, does not provide all of the customization that `targets` is capable of. This doesn't matter so much for now, but it will become important when you start to create more advanced `targets` workflows. -::::::::::::::::::::::::::::::::::::: {.challenge} +## Organizing files and folders -## Challenge 1: Try `tar_plan()` +So far, we have been doing everything with a single `_targets.R` file. +This is OK for a small workflow, but does not work very well when the workflow gets bigger. +There are better ways to organize your code. -How can we use `tar_plan()` to rewrite `_targets.R` with simpler syntax? +First, let's create a directory called `R` to store R code *other than* `_targets.R` (remember, `_targets.R` must be placed in the overall project directory, not in a subdirectory). +Create a new R file in `R/` called `functions.R`. +This is where we will put our custom functions. +Let's go ahead and put `clean_penguin_data()` in there now and save it. + +Similarly, let's put the `library()` calls in their own script in `R/` called `packages.R` (this isn't the only way to do it though; see the "Managing Packages" episode for alternative approaches). -:::::::::::::::::::::::::::::::::: {.solution} +We will also need to modify our `_targets.R` script to call these scripts with `source`: ```{r} -#| label: solution-1 +#| label: tar-plan-show #| eval: FALSE -library(targets) -library(tarchetypes) - -summarize_data <- function(dataset) { - rowMeans(dataset) -} +source("R/packages.R") +source("R/functions.R") tar_plan( - my_data = data.frame(x = sample.int(100), y = sample.int(100)), - my_summary = summarize_data(data) + penguins_csv_file = path_to_file("penguins_raw.csv"), + penguins_data_raw = read_csv(penguins_csv_file, show_col_types = FALSE), + penguins_data = clean_penguin_data(penguins_data_raw) ) ``` -:::::::::::::::::::::::::::::::::: +Now `_targets.R` is much more streamlined: it is focused just on the workflow and immediately tells us what happens in each step. -::::::::::::::::::::::::::::::::::::: - -## Organizing files and folders - -So far, we have been doing everything with a single `_targets.R` file. -This is OK for a small workflow, but does not work very well when the workflow gets bigger. -There are better ways to organize your code. - -First, let's create a directory called `R` to store R code *other than* `_targets.R` (remember, `_targets.R` must be placed in the overall project directory, not in a subdirectory). -Create a new R file in `R/` called `functions.R`. -This is where we will put our custom functions. -Let's go ahead and put `summ()` in there now and save it. - -We will also need to modify our `_targets.R` script. - -Next, let's make some directories for storing data and output---files that are not code. +Finally, let's make some directories for storing data and output---files that are not code. Create a new directory inside the targets cache called `user`: `_targets/user`. Within `user`, create two more directories, `data` and `results`. (If you use version control, you will probably want to ignore the `_targets` directory). ## A word about functions +We mentioned custom functions earlier in the lesson, but this is an important topic that deserves further clarification. If you are used to analyzing data in R with a series of scripts instead of a single workflow like `targets`, you may not write many functions (using the `function()` function). This is a major difference from `targets`. -It would be quite difficult to write an efficient `targets` pipeline without the use of custom functions, because each target you build has to be the output of a single function. +It would be quite difficult to write an efficient `targets` pipeline without the use of custom functions, because each target you build has to be the output of a single command. -We don't have time in this curriculum to cover how to write functions in R, but the Software Carpentry lesson is recommended for reviewing this topic. +We don't have time in this curriculum to cover how to write functions in R, but the Software Carpentry lesson is recommended for reviewing this topic. Another major difference is that **each target must have a unique name**. You may be used to writing code that looks like this: @@ -182,36 +177,13 @@ On the other hand, they should not be so big that each has large numbers of inpu Striking this balance is more of art than science, and only comes with practice. I find a good rule of thumb is no more than three inputs per target. -## A more realistic workflow - -So far, we have only used a very simple targets workflow to demonstrate basic usage of `targets`. - -There is a demonstration of a more realistic workflow based on Palmer's Penguins, a dataset of measurements taken on penguins in Antarctica, available at . - -Please browse [the repo](https://github.com/joelnitta/penguins-targets) and verify that it is set up using the folder organization described above. - -::::::::::::::::::::::::::::::::::::: {.challenge} - -## Challenge 2: Set up a new project - -For this challenge, we will emulate the "Palmer's Penguins" [demonstration project](https://github.com/joelnitta/penguins-targets). - -Set up a new R project called "penguins_analysis" using the recommended file and folder structure for `targets`. It is recommended to create the project on the desktop so you can find it easily. - -:::::::::::::::::::::::::::::::::: {.solution} -You should have created a folder called "penguins_analysis" on your desktop containing a file called "_targets.R" in the main directory and two files, "functions.R" and "packages.R" in the "R" subdirectory. - -You could have also created a folder called "_targets" containing subfolders "user/data" and "user/results". -:::::::::::::::::::::::::::::::::: - -::::::::::::::::::::::::::::::::::::: - ::::::::::::::::::::::::::::::::::::: keypoints - Put code in the `R/` folder - Put functions in `R/functions.R` - Specify packages in `R/packages.R` - Put other miscellaneous files in `_targets/user` +- Writing functions is a key skill for `targets` pipelines :::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/episodes/packages.Rmd b/episodes/packages.Rmd index a3758aea..3dd2e821 100644 --- a/episodes/packages.Rmd +++ b/episodes/packages.Rmd @@ -42,7 +42,7 @@ There are three main ways to load packages in `targets` workflows. This is the method you are almost certainly more familiar with, and is the method we have been using by default so far. -Like any other R script, include `library()` calls near the top of the `_targets.R` script. Alternatively (and as the recommended best practice for project organization), you can put all of the `library()` calls in a separate script---this is typically called `packages.R` and stored in the `R/` directory of your project. +Like any other R script, include `library()` calls near the top of the `_targets.R` script. Alternatively (and as the recommended best practice for project organization), you can put all of the `library()` calls in a separate script---this is typically called `packages.R` and stored in the `R/` directory of your project. The potential downside to this approach is that if you have a long list of packages to load, certain functions like `tar_visnetwork()`, `tar_outdated()`, etc., may take an unnecessarily long time to run because they have to load all the packages, even though they don't necessarily use them. @@ -50,7 +50,19 @@ The potential downside to this approach is that if you have a long list of packa In this method, use the `tar_option_set()` function in `_targets.R` to specify the packages to load when running the workflow. -For example, let's filter some data from the `palmerpenguins` package: +This will be demonstrated using the pre-cleaned dataset from the `palmerpenguins` package. Let's say we want to filter it down to just data for the Adelie penguin. + +::::::::::::::::::::::::::::::::::::: {.callout} + +## Save your progress + +You can only have one active `_targets.R` file at a time in a given project. + +We are about to create a new `_targets.R` file, but you probably don't want to lose your progress in the one we have been working on so far (the penguins analysis). You can temporarily rename that one to something like `_targets_old.R` so that you don't overwrite it with the new example `_targets.R` file below. Then, rename them when you are ready to work on it again. + +::::::::::::::::::::::::::::::::::::: + +This is what using the `tar_option_set()` method looks like: ```{r} #| eval: FALSE diff --git a/episodes/quarto.Rmd b/episodes/quarto.Rmd index 1002f0c6..9134cdde 100644 --- a/episodes/quarto.Rmd +++ b/episodes/quarto.Rmd @@ -80,53 +80,44 @@ But that is not recommended because it doesn't scale well and lacks the sophisti Our suggested approach is to conduct the vast majority of data analysis (in other words, the "heavy lifting") in the `targets` pipeline, then use the Quarto document to **summarize** and **plot** the results. -## An example: Bill size in penguins +## Report on bill size in penguins -For example, the [demo workflow on bill size in penguins](https://github.com/joelnitta/penguins-targets) constructs several models of bill size that differ in how they account for species, then generates a report: +Continuing our penguin bill size analysis, let's write a report evaluating each model. -::::::::::::::::::::::::::::::::::::: {.instructor} +To save time, the report is already available at . -You will probably want to clone and run the [penguins-targets](https://github.com/joelnitta/penguins-targets) demo since this is a lot to type out by hand. - -::::::::::::::::::::::::::::::::::::: + +Download the `penguin_report.qmd` file to your project folder, and add one more target to the pipeline using the `tar_quarto()` function like this: ```{r} #| label: example-penguins-show-1 #| eval: FALSE -library(targets) -library(tarchetypes) -library(palmerpenguins) -library(tidyverse) - +source("R/packages.R") source("R/functions.R") tar_plan( # Load raw data tar_file_read( - penguin_data_raw, + penguins_data_raw, path_to_file("penguins_raw.csv"), - read_csv(!!.x) + read_csv(!!.x, show_col_types = FALSE) ), # Clean data - penguin_data = clean_penguin_data(penguin_data_raw), - # Make models - penguin_models = list( - combined = lm( - bill_depth_mm ~ bill_length_mm, data = penguin_data), - separate = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguin_data) - ), - # Predict points from models - tar_target( - penguin_models_augmented, - augment_penguins(penguin_models), - pattern = map(penguin_models) + penguins_data = clean_penguin_data(penguins_data_raw), + # Build models + models = list( + combined_model = lm( + bill_depth_mm ~ bill_length_mm, data = penguins_data), + species_model = lm( + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), + interaction_model = lm( + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) ), # Get model summaries tar_target( - penguin_models_summary, - glance_penguins(penguin_models), - pattern = map(penguin_models) + model_summaries, + glance_with_mod_name(models), + pattern = map(models) ), # Generate report tar_quarto( @@ -141,24 +132,16 @@ tar_plan( ```{r} #| label: example-penguins-hide-1 #| echo: FALSE -#| warning: FALSE -#| eval: FALSE - -# This code could be run to execute the penguins-targets workflow -# unfortunately I can't get the tidyverse load warnings to go away options(tidyverse.quiet = TRUE) tar_dir({ - options(tidyverse.quiet = TRUE) - fs::dir_create("R") - readr::read_lines("https://raw.githubusercontent.com/joelnitta/penguins-targets/main/_targets.R") |> - readr::write_lines("_targets.R") - readr::read_lines("https://raw.githubusercontent.com/joelnitta/penguins-targets/main/penguin_report.qmd") |> - readr::write_lines("penguin_report.qmd") - readr::read_lines("https://raw.githubusercontent.com/joelnitta/penguins-targets/main/R/functions.R") |> - readr::write_lines("R/functions.R") - readr::read_lines("https://raw.githubusercontent.com/joelnitta/penguins-targets/main/R/packages.R") |> - readr::write_lines("R/packages.R") + write_example_plan(9) + readr::read_lines("https://raw.githubusercontent.com/joelnitta/penguins-targets/main/penguin_report.qmd") |> + readr::write_lines("penguin_report.qmd") + # Run it + write_example_plan(7) + tar_make(reporter = "silent") + write_example_plan(9) tar_make() }) ``` From 37e0c70eafecca197f97b08758d31487ae93d1ff Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 07:01:10 +0300 Subject: [PATCH 02/28] Update lesson --- episodes/basic-targets.Rmd | 4 ---- episodes/batch.Rmd | 8 ++++---- episodes/files.Rmd | 8 ++++---- episodes/introduction.Rmd | 2 +- episodes/organization.Rmd | 4 ++-- episodes/packages.Rmd | 2 +- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/episodes/basic-targets.Rmd b/episodes/basic-targets.Rmd index 3bf434fe..85c8f159 100644 --- a/episodes/basic-targets.Rmd +++ b/episodes/basic-targets.Rmd @@ -114,8 +114,6 @@ Furthermore, each item in the list is a call of the `tar_target()` function. The first argument of `tar_target()` is name of the target to build, and the second argument is the command used to build it. Note that the name of the target is **unquoted**, that is, it is written without any surrounding quotation marks. -::::::::::::::::::::::::::::::::::::: callout - ## Set up `_targets.R` file to run example analysis ### Background: non-`targets` version @@ -242,8 +240,6 @@ tar_dir({ Congratulations, you've run your first workflow with `targets`! -::::::::::::::::::::::::::::::::::::::::::::::: - ::::::::::::::::::::::::::::::::::::: keypoints - Projects help keep our analyses organized so we can easily re-run them later diff --git a/episodes/batch.Rmd b/episodes/batch.Rmd index d5e2489c..0b7e3be9 100644 --- a/episodes/batch.Rmd +++ b/episodes/batch.Rmd @@ -92,7 +92,7 @@ tar_plan( ```{r} #| label: example-lm-pipeline-hide -#| show: FALSE +#| echo: FALSE tar_dir({ # New workflow write_example_plan(3) @@ -114,7 +114,7 @@ broom::glance(combined_model) ```{r} #| label: example-lm-pipeline-inspect-hide -#| show: FALSE +#| echo: FALSE tar_dir({ # New workflow write_example_plan(4) @@ -166,7 +166,7 @@ tar_plan( ```{r} #| label: example-model-hide-1 -#| show: FALSE +#| echo: FALSE tar_dir({ # New workflow write_example_plan(4) @@ -280,7 +280,7 @@ models <- list( ``` Unlike the non-branching version, we defined the models **in a list** (instead of one target per model). -This is because dynamic branching is similar to the `apply()` or [`purrrr::map()`](https://purrr.tidyverse.org/reference/map.html) method of looping: it applies a function to each element of a list. +This is because dynamic branching is similar to the `base::apply()` or [`purrrr::map()`](https://purrr.tidyverse.org/reference/map.html) method of looping: it applies a function to each element of a list. So we need to prepare the input for looping as a list. Next, take a look at the command to build the target `model_summaries`. diff --git a/episodes/files.Rmd b/episodes/files.Rmd index f9ee8dbe..61efaf83 100644 --- a/episodes/files.Rmd +++ b/episodes/files.Rmd @@ -47,7 +47,7 @@ We will read in the contents of this file and store it as `some_data` in the wor You can only have one active `_targets.R` file at a time in a given project. -We are about to create a new `_targets.R` file, but you probably don't want to lose your progress in the one we have been working on so far (the penguins analysis). You can temporarily rename that one to something like `_targets_old.R` so that you don't overwrite it with the new example `_targets.R` file below. Then, rename them when you are ready to work on it again. +We are about to create a new `_targets.R` file, but you probably don't want to lose your progress in the one we have been working on so far (the penguins bill analysis). You can temporarily rename that one to something like `_targets_old.R` so that you don't overwrite it with the new example `_targets.R` file below. Then, rename them when you are ready to work on it again. ::::::::::::::::::::::::::::::::::::: @@ -227,7 +227,7 @@ How can you use `tar_file_read()` to load the CSV file while tracking its conten :::::::::::::::::::::::::::::::::: {.solution} ```{r} -#| label: tar-file-read-answer +#| label: tar-file-read-answer-show #| eval: FALSE source("R/packages.R") source("R/functions.R") @@ -243,8 +243,8 @@ tar_plan( ``` ```{r} -#| label: tar-file-read-answer -#| show: FALSE +#| label: tar-file-read-answer-hide +#| echo: FALSE tar_dir({ # New workflow write_example_plan(3) diff --git a/episodes/introduction.Rmd b/episodes/introduction.Rmd index ffb4a304..acb8692a 100644 --- a/episodes/introduction.Rmd +++ b/episodes/introduction.Rmd @@ -69,7 +69,7 @@ The **goal** of this workshop is to **learn how to use `targets` to reproducible For this workshop, we will analyze an example dataset of measurements taken on adult foraging Adélie, Chinstrap, and Gentoo penguins observed on islands in the Palmer Archipelago, Antarctica. -**Our goal is to determine the relationship between bill length and depth by using linear models.** +The goal of the analysis is to determine the relationship between bill length and depth by using linear models. The data are available from the `palmerpenguins` R package. You can get more information about the data by running `?palmerpenguins`. diff --git a/episodes/organization.Rmd b/episodes/organization.Rmd index 482048e1..a9f93afb 100644 --- a/episodes/organization.Rmd +++ b/episodes/organization.Rmd @@ -56,7 +56,7 @@ By using `tar_plan()`, instead of specifying targets with `tar_target()`, we can Let's edit the penguins workflow to use the `tar_plan()` syntax: ```{r} -#| label: tar-plan-show +#| label: tar-plan-show-1 #| eval: FALSE library(targets) library(palmerpenguins) @@ -101,7 +101,7 @@ Similarly, let's put the `library()` calls in their own script in `R/` called `p We will also need to modify our `_targets.R` script to call these scripts with `source`: ```{r} -#| label: tar-plan-show +#| label: tar-plan-show-2 #| eval: FALSE source("R/packages.R") source("R/functions.R") diff --git a/episodes/packages.Rmd b/episodes/packages.Rmd index 3dd2e821..0d6451b8 100644 --- a/episodes/packages.Rmd +++ b/episodes/packages.Rmd @@ -58,7 +58,7 @@ This will be demonstrated using the pre-cleaned dataset from the `palmerpenguins You can only have one active `_targets.R` file at a time in a given project. -We are about to create a new `_targets.R` file, but you probably don't want to lose your progress in the one we have been working on so far (the penguins analysis). You can temporarily rename that one to something like `_targets_old.R` so that you don't overwrite it with the new example `_targets.R` file below. Then, rename them when you are ready to work on it again. +We are about to create a new `_targets.R` file, but you probably don't want to lose your progress in the one we have been working on so far (the penguins bill analysis). You can temporarily rename that one to something like `_targets_old.R` so that you don't overwrite it with the new example `_targets.R` file below. Then, rename them when you are ready to work on it again. ::::::::::::::::::::::::::::::::::::: From 6e7464d01c439957dc6f80ac6c0faaf3aadab186 Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 17:51:48 +0200 Subject: [PATCH 03/28] Update functions --- episodes/files/functions.R | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/episodes/files/functions.R b/episodes/files/functions.R index a9e5a1f4..5da66817 100644 --- a/episodes/files/functions.R +++ b/episodes/files/functions.R @@ -23,6 +23,14 @@ write_example_plan <- function(plan_select) { " mutate(model_name = model_name)", "}" ) + augment_with_mod_name_func <- c( + "augment_with_mod_name <- function(model_in_list) {", + "model_name <- names(model_in_list)", + "model <- model_in_list[[1]]", + "broom::augment(model) %>%", + " mutate(model_name = model_name)", + "}" + ) clean_penguin_data_func <- c( "clean_penguin_data <- function(penguins_data_raw) {", " penguins_data_raw %>%", @@ -226,7 +234,7 @@ write_example_plan <- function(plan_select) { " )", ")" ) - # adds future + # adds future and predictions plan_8 <- c( "library(targets)", "library(palmerpenguins)", @@ -236,6 +244,7 @@ write_example_plan <- function(plan_select) { "library(future.callr)", "suppressPackageStartupMessages(library(tidyverse))", glance_with_mod_name_func, + augment_with_mod_name_func, clean_penguin_data_func, "plan(callr)", "tar_plan(", @@ -257,7 +266,12 @@ write_example_plan <- function(plan_select) { " model_summaries,", " glance_with_mod_name(models),", " pattern = map(models)", - " )", + " ),", + " tar_target(", + " model_predictions,", + " augment_with_mod_name(models),", + " pattern = map(models)", + " ),", ")" ) # adds report @@ -268,8 +282,8 @@ write_example_plan <- function(plan_select) { "library(broom)", "suppressPackageStartupMessages(library(tidyverse))", glance_with_mod_name_func, + augment_with_mod_name_func, clean_penguin_data_func, - "plan(callr)", "tar_plan(", " tar_file_read(", " penguins_data_raw,", @@ -290,7 +304,11 @@ write_example_plan <- function(plan_select) { " glance_with_mod_name(models),", " pattern = map(models)", " ),", - " # Generate report", + " tar_target(", + " model_predictions,", + " augment_with_mod_name(models),", + " pattern = map(models)", + " ),", " tar_quarto(", " penguin_report,", " path = 'penguin_report.qmd',", From 3875a3083a3edd998d5bea5e01100179fb49475e Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 18:13:57 +0200 Subject: [PATCH 04/28] Fix repo address --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec44704c..bf39b04f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -101,7 +101,7 @@ community listed at including via social media, slack, newsletters, and email lists. You can also [reach us by email][contact]. -[repo]: https://example.com/FIXME +[repo]: https://github.com/joelnitta/targets-workshop [contact]: mailto:team@carpentries.org [cp-site]: https://carpentries.org/ [dc-issues]: https://github.com/issues?q=user%3Adatacarpentry From 1f405f359f5fc593606c063558d4a45b65104cfa Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 18:15:28 +0200 Subject: [PATCH 05/28] Make sure to source most recent raw code --- episodes/basic-targets.Rmd | 2 +- episodes/batch.Rmd | 3 ++- episodes/cache.Rmd | 2 +- episodes/lifecycle.Rmd | 2 +- episodes/organization.Rmd | 2 +- episodes/packages.Rmd | 2 +- episodes/quarto.Rmd | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/episodes/basic-targets.Rmd b/episodes/basic-targets.Rmd index 85c8f159..cebb7e33 100644 --- a/episodes/basic-targets.Rmd +++ b/episodes/basic-targets.Rmd @@ -34,7 +34,7 @@ Episode summary: First chance to get hands dirty by writing a very simple workfl #| message: FALSE #| warning: FALSE library(targets) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R") # nolint +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint ``` ## Create a project diff --git a/episodes/batch.Rmd b/episodes/batch.Rmd index 0b7e3be9..041581e9 100644 --- a/episodes/batch.Rmd +++ b/episodes/batch.Rmd @@ -31,7 +31,8 @@ Episode summary: Show how to use branching and parallel processing (technically #| warning: FALSE library(targets) library(tarchetypes) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R") # nolint +library(broom) +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint # Increase width for printing tibbles options(width = 140) diff --git a/episodes/cache.Rmd b/episodes/cache.Rmd index e61bc81f..de84ad23 100644 --- a/episodes/cache.Rmd +++ b/episodes/cache.Rmd @@ -10,7 +10,7 @@ exercises: 2 #| message: FALSE #| warning: FALSE library(targets) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R") # nolint +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint ``` :::::::::::::::::::::::::::::::::::::: questions diff --git a/episodes/lifecycle.Rmd b/episodes/lifecycle.Rmd index b1f71cd0..f8f17328 100644 --- a/episodes/lifecycle.Rmd +++ b/episodes/lifecycle.Rmd @@ -32,7 +32,7 @@ Episode summary: Demonstrate typical cycle of running `targets`: make, inspect, #| warning: FALSE library(targets) library(visNetwork) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R") # nolint +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint ``` ## Re-running the workflow diff --git a/episodes/organization.Rmd b/episodes/organization.Rmd index a9f93afb..b285d3bd 100644 --- a/episodes/organization.Rmd +++ b/episodes/organization.Rmd @@ -31,7 +31,7 @@ Episode summary: Demonstrate best-practices for project organization #| warning: FALSE library(targets) library(tarchetypes) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R") # nolint +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint ``` ## A simpler way to write workflow plans diff --git a/episodes/packages.Rmd b/episodes/packages.Rmd index 0d6451b8..7e01b39b 100644 --- a/episodes/packages.Rmd +++ b/episodes/packages.Rmd @@ -29,7 +29,7 @@ Episode summary: Show how to load packages and maintain package versions #| warning: FALSE library(targets) library(tarchetypes) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R") # nolint +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint ``` ## Loading packages diff --git a/episodes/quarto.Rmd b/episodes/quarto.Rmd index 9134cdde..eae38460 100644 --- a/episodes/quarto.Rmd +++ b/episodes/quarto.Rmd @@ -29,7 +29,7 @@ Episode summary: Show how to write reports with Quarto #| warning: FALSE library(targets) library(tarchetypes) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R") # nolint +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint # Increase width for printing tibbles options(width = 140) From 6d9d4a2cd03fd8f5578ed071c063702cae10ab30 Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 18:15:56 +0200 Subject: [PATCH 06/28] Other updates to use penguins --- episodes/basic-targets.Rmd | 22 +++++- episodes/batch.Rmd | 110 ++++++++++++++++++++++++-- episodes/fig/lifecycle-visnetwork.png | Bin 65159 -> 70903 bytes episodes/files.Rmd | 2 +- episodes/introduction.Rmd | 13 ++- episodes/lifecycle.Rmd | 2 +- episodes/organization.Rmd | 4 +- episodes/quarto.Rmd | 12 ++- learners/setup.md | 19 ++++- site/README.md | 2 +- 10 files changed, 160 insertions(+), 26 deletions(-) diff --git a/episodes/basic-targets.Rmd b/episodes/basic-targets.Rmd index cebb7e33..f1bdc769 100644 --- a/episodes/basic-targets.Rmd +++ b/episodes/basic-targets.Rmd @@ -125,7 +125,7 @@ Recall that we are using the `palmerpenguins` R package to obtain the data. This package actually includes two variations of the dataset: one is an external CSV file with the raw data, and another is the cleaned data loaded into R. In real life you are probably have externally stored raw data, so **let's use the raw penguin data** as the starting point for our analysis too. -The `path_to_file()` function in `palmerpenguins` provides the path to the raw data CSV file (it is inside the `palmerpenguins` R package source code that you downloaded to your computer when you installed the package) +The `path_to_file()` function in `palmerpenguins` provides the path to the raw data CSV file (it is inside the `palmerpenguins` R package source code that you downloaded to your computer when you installed the package). ```{r} #| label: normal-r-path @@ -137,12 +137,13 @@ penguins_csv_file <- path_to_file("penguins_raw.csv") penguins_csv_file ``` -We will use the `tidyverse` set of packages for loading and manipulating the data. We don't have time to cover all the details about using `tidyverse` now, but if you want to learn more about it, please see this lesson. +We will use the `tidyverse` set of packages for loading and manipulating the data. We don't have time to cover all the details about using `tidyverse` now, but if you want to learn more about it, please see the ["Manipulating, analyzing and exporting data with tidyverse" lesson](https://datacarpentry.org/R-ecology-lesson/03-dplyr.html). Let's load the data with `read_csv()`. ```{r} -#| label: normal-r-load +#| label: normal-r-load-show +#| eval: false library(tidyverse) # Read CSV file into R @@ -151,9 +152,22 @@ penguins_data_raw <- read_csv(penguins_csv_file) penguins_data_raw ``` +```{r} +#| label: normal-r-load-hide +#| echo: false +suppressPackageStartupMessages(library(tidyverse)) + +# Read CSV file into R +penguins_data_raw <- read_csv(penguins_csv_file) + +penguins_data_raw +``` + We see the raw data has some awkward column names with spaces (these are hard to type out and can easily lead to mistakes in the code), and far more columns than we need. For the purposes of this analysis, we only need species name, bill length, and bill depth. -In the raw data, the rather technical term "Culmen" is used to refer to the bill. +In the raw data, the rather technical term "culmen" is used to refer to the bill. + +![Illustration of bill (culmen) length and depth. Artwork by @allison_horst.](https://allisonhorst.github.io/palmerpenguins/reference/figures/culmen_depth.png) Let's clean up the data to make it easier to use for downstream analyses. We will also remove any rows with missing data, because this could cause errors for some functions later. diff --git a/episodes/batch.Rmd b/episodes/batch.Rmd index 041581e9..2b4bbbf5 100644 --- a/episodes/batch.Rmd +++ b/episodes/batch.Rmd @@ -65,7 +65,7 @@ For example, this is a model of bill depth dependent on bill length: ```{r} #| label: example-lm #| eval: FALSE -lm(bill_depth_mm ~ bill_length_mm, data = penguin_data) +lm(bill_depth_mm ~ bill_length_mm, data = penguins_data) ``` We can add this to our pipeline. We will call it the `combined_model` because it combines all the species together without distinction: @@ -109,8 +109,11 @@ Let's have a look at the model. We will use the `glance()` function from the `br ```{r} #| label: example-lm-pipeline-inspect-show #| eval: FALSE +library(broom) + tar_load(combined_model) -broom::glance(combined_model) + +glance(combined_model) ``` ```{r} @@ -122,7 +125,7 @@ tar_dir({ # Run it tar_make(reporter = "silent") tar_load(combined_model) - broom::glance(combined_model) + glance(combined_model) }) ``` @@ -347,12 +350,12 @@ Here is the function. Save this in `R/functions.R`: glance_with_mod_name <- function(model_in_list) { model_name <- names(model_in_list) model <- model_in_list[[1]] - broom::glance(model) %>% + glance(model) %>% mutate(model_name = model_name) } ``` -Our new pipeline looks almost the same as before, but this time we use the custom function instead of `broom::glance()`. +Our new pipeline looks almost the same as before, but this time we use the custom function instead of `glance()`. ```{r} #| label: example-model-show-6 @@ -415,6 +418,97 @@ tar_dir({ }) ``` +Next we will add one more target, a prediction of bill depth based on each model. These will be needed for plotting the models in the report. +Such a prediction can be obtained with the `augment()` function of the `broom` package. + +```{r} +#| label: example-augment-show +#| echo: true +#| eval: false +tar_load(models) +augment(models[[1]]) +``` + +```{r} +#| label: example-augment-hide +#| echo: false +#| warning: false +tar_dir({ + # New workflow + write_example_plan(7) + # Run it + tar_make(reporter = "silent") + tar_load(models) +}) +augment(models[[1]]) +``` + +::::::::::::::::::::::::::::::::::::: {.challenge} + +## Challenge: Add model predictions to the workflow + +Can you add the model predictions using `augment()`? You will need to define a custom function just like we did for `glance()`. + +:::::::::::::::::::::::::::::::::: {.solution} + +Define the new function as `augment_with_mod_name()`. It is the same as `glance_with_mod_name()`, but use `augment()` instead of `glance()`: + +```{r} +#| label: example-model-augment-func +#| eval: FALSE +augment_with_mod_name <- function(model_in_list) { + model_name <- names(model_in_list) + model <- model_in_list[[1]] + augment(model) %>% + mutate(model_name = model_name) +} +``` + +Add the step to the workflow: + +```{r} +#| label: example-model-augment-show +#| eval: FALSE +source("R/packages.R") +source("R/functions.R") + +tar_plan( + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), + # Build models + models = list( + combined_model = lm( + bill_depth_mm ~ bill_length_mm, data = penguins_data), + species_model = lm( + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), + interaction_model = lm( + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) + ), + # Get model summaries + tar_target( + model_summaries, + glance_with_mod_name(models), + pattern = map(models) + ), + # Get model predictions + tar_target( + model_predictions, + augment_with_mod_name(models), + pattern = map(models) + ) +) +``` + +:::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: + ::::::::::::::::::::::::::::::::::::: {.callout} ## Best practices for branching @@ -589,6 +683,12 @@ tar_plan( model_summaries, glance_with_mod_name(models), pattern = map(models) + ), + # Get model predictions + tar_target( + model_predictions, + augment_with_mod_name(models), + pattern = map(models) ) ) ``` diff --git a/episodes/fig/lifecycle-visnetwork.png b/episodes/fig/lifecycle-visnetwork.png index c9c3d00cd1a853ea6c4cbfea1e6dbf577062b351..5187a62fb7caf780f777836fa327df5d53ac1e6f 100644 GIT binary patch literal 70903 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

xa)t2+dsLU^+ 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 P;3!cvZbo|M*gSrDV&PvvLY-bJ*rXk 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 diff --git a/episodes/files.Rmd b/episodes/files.Rmd index 61efaf83..a5b13208 100644 --- a/episodes/files.Rmd +++ b/episodes/files.Rmd @@ -30,7 +30,7 @@ Episode summary: Show how to read and write external files #| warning: FALSE library(targets) library(tarchetypes) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R") # nolint +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint ``` ## Treating external files as a dependency diff --git a/episodes/introduction.Rmd b/episodes/introduction.Rmd index acb8692a..f46adb0a 100644 --- a/episodes/introduction.Rmd +++ b/episodes/introduction.Rmd @@ -59,7 +59,7 @@ This allows you to do the following: `targets` is by no means the only workflow management software. There is a large number of similar tools, each with varying features and use-cases. -For example, snakemake is a popular workflow tool for python, and `make` is a tool that has been around for a very long time for automating bash scripts. +For example, [snakemake](https://snakemake.readthedocs.io/en/stable/) is a popular workflow tool for python, and [`make`](https://www.gnu.org/software/make/) is a tool that has been around for a very long time for automating bash scripts. `targets` is designed to work specifically with R, so it makes the most sense to use it if you primarily use R, or intend to. If you mostly code with other tools, you may want to consider an alternative. @@ -69,14 +69,13 @@ The **goal** of this workshop is to **learn how to use `targets` to reproducible For this workshop, we will analyze an example dataset of measurements taken on adult foraging Adélie, Chinstrap, and Gentoo penguins observed on islands in the Palmer Archipelago, Antarctica. -The goal of the analysis is to determine the relationship between bill length and depth by using linear models. - The data are available from the `palmerpenguins` R package. You can get more information about the data by running `?palmerpenguins`. -```{r} -library(palmerpenguins) -?palmerpenguins -``` +![The three species of penguins in the `palmerpenguins` dataset. Artwork by @allison_horst.](https://allisonhorst.github.io/palmerpenguins/reference/figures/lter_penguins.png) + +The goal of the analysis is to determine the relationship between bill length and depth by using linear models. + +We will gradually build up the analysis through this lesson, but you can see the final version at . ::::::::::::::::::::::::::::::::::::: keypoints diff --git a/episodes/lifecycle.Rmd b/episodes/lifecycle.Rmd index f8f17328..f5864925 100644 --- a/episodes/lifecycle.Rmd +++ b/episodes/lifecycle.Rmd @@ -346,7 +346,7 @@ tar_dir({ }) ``` -If you want to reset **everything** and start fresh, you can use `tar_invalidate(everything())` (`tar_invalidate()` accepts tidy select functions to specify target names). +If you want to reset **everything** and start fresh, you can use `tar_invalidate(everything())` (`tar_invalidate()` [accepts `tidyselect` expressions](https://docs.ropensci.org/targets/reference/tar_invalidate.html) to specify target names). **Caution should be exercised** when using granular methods like this, though, since you may end up with your workflow in an unexpected state. The surest way to maintain an up-to-date workflow is to run `tar_make()` frequently. diff --git a/episodes/organization.Rmd b/episodes/organization.Rmd index b285d3bd..eca807e8 100644 --- a/episodes/organization.Rmd +++ b/episodes/organization.Rmd @@ -96,7 +96,7 @@ Create a new R file in `R/` called `functions.R`. This is where we will put our custom functions. Let's go ahead and put `clean_penguin_data()` in there now and save it. -Similarly, let's put the `library()` calls in their own script in `R/` called `packages.R` (this isn't the only way to do it though; see the "Managing Packages" episode for alternative approaches). +Similarly, let's put the `library()` calls in their own script in `R/` called `packages.R` (this isn't the only way to do it though; see the ["Managing Packages" episode](https://joelnitta.github.io/targets-workshop/packages.html) for alternative approaches). We will also need to modify our `_targets.R` script to call these scripts with `source`: @@ -128,7 +128,7 @@ If you are used to analyzing data in R with a series of scripts instead of a sin This is a major difference from `targets`. It would be quite difficult to write an efficient `targets` pipeline without the use of custom functions, because each target you build has to be the output of a single command. -We don't have time in this curriculum to cover how to write functions in R, but the Software Carpentry lesson is recommended for reviewing this topic. +We don't have time in this curriculum to cover how to write functions in R, but the [Software Carpentry lesson](https://swcarpentry.github.io/r-novice-gapminder/10-functions) is recommended for reviewing this topic. Another major difference is that **each target must have a unique name**. You may be used to writing code that looks like this: diff --git a/episodes/quarto.Rmd b/episodes/quarto.Rmd index eae38460..b4a9acfb 100644 --- a/episodes/quarto.Rmd +++ b/episodes/quarto.Rmd @@ -86,8 +86,9 @@ Continuing our penguin bill size analysis, let's write a report evaluating each To save time, the report is already available at . - -Download the `penguin_report.qmd` file to your project folder, and add one more target to the pipeline using the `tar_quarto()` function like this: +Copy the [raw code from here](https://raw.githubusercontent.com/joelnitta/penguins-targets/main/penguin_report.qmd) and save it as a new file `penguin_report.qmd` in your project folder (you may also be able to right click in your browser and select "Save As"). + +Then, add one more target to the pipeline using the `tar_quarto()` function like this: ```{r} #| label: example-penguins-show-1 @@ -119,6 +120,12 @@ tar_plan( glance_with_mod_name(models), pattern = map(models) ), + # Get model predictions + tar_target( + model_predictions, + augment_with_mod_name(models), + pattern = map(models) + ), # Generate report tar_quarto( penguin_report, @@ -133,7 +140,6 @@ tar_plan( #| label: example-penguins-hide-1 #| echo: FALSE -options(tidyverse.quiet = TRUE) tar_dir({ write_example_plan(9) readr::read_lines("https://raw.githubusercontent.com/joelnitta/penguins-targets/main/penguin_report.qmd") |> diff --git a/learners/setup.md b/learners/setup.md index 5c23ec89..8dafcd5f 100644 --- a/learners/setup.md +++ b/learners/setup.md @@ -2,8 +2,23 @@ title: Setup --- -This lesson assumes you have R, RStudio, and the `targets` R package installed on your computer. +Follow these instructions to install the required software on your computer. - [Download and install the latest version of R](https://www.r-project.org/). - [Download and install RStudio](https://www.rstudio.com/products/rstudio/download/#download). RStudio is an application (an integrated development environment or IDE) that facilitates the use of R and offers a number of nice additional features. You will need the free Desktop version for your computer. -- Install the `targets` package by running the command `install.packages("targets")` in R. +- Install the necessary R packages with the following command: + +```r +install.packages( + c( + "conflicted", + "future.callr", + "future", + "palmerpenguins", + "tarchetypes", + "targets", + "tidyverse", + "visNetwork" + ) +) +``` diff --git a/site/README.md b/site/README.md index 42997e3d..0a00291c 100644 --- a/site/README.md +++ b/site/README.md @@ -1,2 +1,2 @@ This directory contains rendered lesson materials. Please do not edit files -here. +here. From e55ae1bc0ad82af0714b396a470ac0028652f731 Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 19:04:04 +0200 Subject: [PATCH 07/28] Fix required packages --- episodes/batch.Rmd | 1 - episodes/quarto.Rmd | 1 + learners/setup.md | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/episodes/batch.Rmd b/episodes/batch.Rmd index 2b4bbbf5..148024c1 100644 --- a/episodes/batch.Rmd +++ b/episodes/batch.Rmd @@ -572,7 +572,6 @@ You will need to install several packages to use the `future` backend: #| label: install-future #| eval: false install.packages("future") -install.packages("future.batchtools") install.packages("future.callr") ``` diff --git a/episodes/quarto.Rmd b/episodes/quarto.Rmd index b4a9acfb..b441b93a 100644 --- a/episodes/quarto.Rmd +++ b/episodes/quarto.Rmd @@ -29,6 +29,7 @@ Episode summary: Show how to write reports with Quarto #| warning: FALSE library(targets) library(tarchetypes) +library(quarto) # don't actually need to load, but put here so renv catches it source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint # Increase width for printing tibbles diff --git a/learners/setup.md b/learners/setup.md index 8dafcd5f..97740e0d 100644 --- a/learners/setup.md +++ b/learners/setup.md @@ -15,6 +15,7 @@ install.packages( "future.callr", "future", "palmerpenguins", + "quarto", "tarchetypes", "targets", "tidyverse", From d1950d2d00823ecde3950f5381e5704ded7539fe Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 19:12:21 +0200 Subject: [PATCH 08/28] Try to get quarto to stick --- episodes/quarto.Rmd | 1 + 1 file changed, 1 insertion(+) diff --git a/episodes/quarto.Rmd b/episodes/quarto.Rmd index b441b93a..a087b848 100644 --- a/episodes/quarto.Rmd +++ b/episodes/quarto.Rmd @@ -142,6 +142,7 @@ tar_plan( #| echo: FALSE tar_dir({ + library(quarto) write_example_plan(9) readr::read_lines("https://raw.githubusercontent.com/joelnitta/penguins-targets/main/penguin_report.qmd") |> readr::write_lines("penguin_report.qmd") From e13e2efde56ab0317451887231ac65ad3aef86d7 Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 19:53:35 +0200 Subject: [PATCH 09/28] Skip compiling quarto report --- episodes/quarto.Rmd | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/episodes/quarto.Rmd b/episodes/quarto.Rmd index a087b848..b32b719a 100644 --- a/episodes/quarto.Rmd +++ b/episodes/quarto.Rmd @@ -140,6 +140,11 @@ tar_plan( ```{r} #| label: example-penguins-hide-1 #| echo: FALSE +#| eval: FALSE + +# FIXME +# Skip eval until can figure out how to install quarto CLI in whatever is +# compiling the lesson tar_dir({ library(quarto) From 3998e94a8896701d1f414a77bce77a965794a6c7 Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 19:54:16 +0200 Subject: [PATCH 10/28] Add description of binder to setup --- learners/setup.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/learners/setup.md b/learners/setup.md index 97740e0d..2136e475 100644 --- a/learners/setup.md +++ b/learners/setup.md @@ -2,6 +2,8 @@ title: Setup --- +## Local setup + Follow these instructions to install the required software on your computer. - [Download and install the latest version of R](https://www.r-project.org/). @@ -23,3 +25,11 @@ install.packages( ) ) ``` + +## Alternative: In the cloud + +There is a [Binder](https://mybinder.org/) instance with RStudio and all necessary packages pre-installed available, so you don't need to install anything on your own computer. + +Click the "Launch Binder" button below: + +[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/joelnitta/penguins-targets/HEAD?urlpath=rstudio) From aa3903b19b5add948cba8357d3c778b70f4e6a5c Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 20:23:08 +0200 Subject: [PATCH 11/28] Update renv.lock --- renv/profiles/lesson-requirements/renv.lock | 69 +++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/renv/profiles/lesson-requirements/renv.lock b/renv/profiles/lesson-requirements/renv.lock index cef30b9d..50782226 100644 --- a/renv/profiles/lesson-requirements/renv.lock +++ b/renv/profiles/lesson-requirements/renv.lock @@ -79,6 +79,17 @@ ], "Hash": "45f0398006e83a5b10b72a90663d8d8c" }, + "Rcpp": { + "Package": "Rcpp", + "Version": "1.0.10", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "methods", + "utils" + ], + "Hash": "e749cae40fa9ef469b6050959517453c" + }, "askpass": { "Package": "askpass", "Version": "1.1", @@ -871,6 +882,17 @@ ], "Hash": "3d5108641f47470611a32d0bdf357a72" }, + "later": { + "Package": "later", + "Version": "1.3.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "Rcpp", + "rlang" + ], + "Hash": "40401c9cf2bc2259dfe83311c9384710" + }, "lattice": { "Package": "lattice", "Version": "0.21-8", @@ -1023,6 +1045,18 @@ ], "Hash": "0f7cd2962e3044bb940cca4f4b5cecbe" }, + "packrat": { + "Package": "packrat", + "Version": "0.9.1", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "tools", + "utils" + ], + "Hash": "481428983c19a7c443f7ea1beff0a2de" + }, "palmerpenguins": { "Package": "palmerpenguins", "Version": "0.1.1", @@ -1131,6 +1165,23 @@ ], "Hash": "d71c815267c640f17ddbf7f16144b4bb" }, + "quarto": { + "Package": "quarto", + "Version": "1.2", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "jsonlite", + "later", + "processx", + "rmarkdown", + "rsconnect", + "rstudioapi", + "utils", + "yaml" + ], + "Hash": "298a252816cabed120391c955aced484" + }, "ragg": { "Package": "ragg", "Version": "1.2.5", @@ -1274,6 +1325,24 @@ ], "Hash": "493df4ae51e2e984952ea4d5c75786a3" }, + "rsconnect": { + "Package": "rsconnect", + "Version": "0.8.29", + "Source": "Repository", + "Repository": "CRAN", + "Requirements": [ + "R", + "curl", + "digest", + "jsonlite", + "openssl", + "packrat", + "rstudioapi", + "tools", + "yaml" + ], + "Hash": "fe178fc15af80952f546aafedf655b36" + }, "rstudioapi": { "Package": "rstudioapi", "Version": "0.14", From 23207956d4e5bd68f06f0da5e80c7d80beac35bc Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 20:23:18 +0200 Subject: [PATCH 12/28] Update setup --- learners/setup.md | 1 + 1 file changed, 1 insertion(+) diff --git a/learners/setup.md b/learners/setup.md index 2136e475..4f682784 100644 --- a/learners/setup.md +++ b/learners/setup.md @@ -8,6 +8,7 @@ Follow these instructions to install the required software on your computer. - [Download and install the latest version of R](https://www.r-project.org/). - [Download and install RStudio](https://www.rstudio.com/products/rstudio/download/#download). RStudio is an application (an integrated development environment or IDE) that facilitates the use of R and offers a number of nice additional features. You will need the free Desktop version for your computer. +- [Download and install Quarto](https://quarto.org/docs/download/). Quarto is a program for authoring documents in a variety of formats using code. It is used in this workshop to generate a dynamic report. - Install the necessary R packages with the following command: ```r From 553274b9bca351c5239470f2f0d515b1520fe2f3 Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 20:24:02 +0200 Subject: [PATCH 13/28] Add section on where to get more info, closes #1 --- episodes/introduction.Rmd | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/episodes/introduction.Rmd b/episodes/introduction.Rmd index f46adb0a..fe60c050 100644 --- a/episodes/introduction.Rmd +++ b/episodes/introduction.Rmd @@ -65,6 +65,19 @@ If you mostly code with other tools, you may want to consider an alternative. The **goal** of this workshop is to **learn how to use `targets` to reproducible data analysis in R**. +## Where to get more information + +`targets` is a sophisticated package and there is a lot more to learn that we can cover in this workshop. + +Here are some recommended resources for continuing on your `targets` journey: + +- [The `targets` R package user manual](https://books.ropensci.org/targets/) by the author of `targets`, Will Landau, should be considered required reading for anyone seriously interested in `targets`. +- [The `targets` discussion board](https://github.com/ropensci/targets/discussions) is a great place for asking questions and getting help. Before you ask a question though, be sure to [read the policy on asking for help](https://books.ropensci.org/targets/help.html). +- [The `targets` package webpage](https://docs.ropensci.org/targets/) includes documentation of all `targets` functions. +- [The `tarchetypes` package webpage](https://docs.ropensci.org/tarchetypes/) includes documentation of all `tarchetypes` functions. You will almost certainly use `tarchetypes` along with `targets`, so it's good to consult both. +- [Reproducible computation at scale in R with `targets`](https://github.com/wlandau/targets-tutorial) is a tutorial by Will Landau analyzing customer churn with Keras. +- [Recorded talks](https://github.com/ropensci/targets#recorded-talks) and [example projects](https://github.com/ropensci/targets#example-projects) listed on the `targets` README. + ## About the example dataset For this workshop, we will analyze an example dataset of measurements taken on adult foraging Adélie, Chinstrap, and Gentoo penguins observed on islands in the Palmer Archipelago, Antarctica. From 945ca7486dbf4e6927de8a04102d34cd039a4a83 Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 20:29:02 +0200 Subject: [PATCH 14/28] Add explanation about deleting cache --- episodes/cache.Rmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/episodes/cache.Rmd b/episodes/cache.Rmd index de84ad23..bb6b635c 100644 --- a/episodes/cache.Rmd +++ b/episodes/cache.Rmd @@ -143,6 +143,8 @@ You can create it if you want, and put whatever you want inside. Generally, `_targets/user` is a good place to store files that are not code, like data and output. +Note that if you don't have anything in `_targets/user` that you need to keep around, it is possible to "reset" your workflow by simply deleting the entire `_targets` folder. Of course, this means you will need to run everything over again, so don't do this lightly! + ::::::::::::::::::::::::::::::::::::: keypoints - `targets` workflows are run in a separate, non-interactive R session From 22aa245bd53f4e1c0e663be42fa2abb740bf8ef3 Mon Sep 17 00:00:00 2001 From: carpenter Date: Sat, 3 Jun 2023 20:38:34 +0200 Subject: [PATCH 15/28] Update instructor notes --- instructors/instructor-notes.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/instructors/instructor-notes.md b/instructors/instructor-notes.md index cabe8d52..697f3a03 100644 --- a/instructors/instructor-notes.md +++ b/instructors/instructor-notes.md @@ -4,8 +4,4 @@ title: 'Instructor Notes' ## General notes -Most of the examples are very short so that the participants can easily type them and don't get overwhelmed. However, there are a few downsides to this approach: - -- First, this makes it difficult to demonstrate a realistic (longer) workflow. There is a demo available at of a more typical workflow based on the [Palmer Penguins dataset](https://allisonhorst.github.io/palmerpenguins/). The demo first shows up in "Best Practices for targets Project Organization", but could be shown earlier if needed. The instructor could even clone and run the demo. - -- Second, since a given `targets` project can only have one `_targets.R` file, this means the participants may have to frequently delete their existing `_targets.R` file and write a new one to follow along with the examples. This may cause frustration if they can't keep a record of what they have done so far. One solution would be to save old `_targets.R` files as `_targets_1.R`, `_targets_2.R`, etc. instead of overwriting them. +The examples gradually build up to a [full analysis](https://github.com/joelnitta/penguins-targets) of the [Palmer Penguins dataset](https://allisonhorst.github.io/palmerpenguins/). However, there are a few places where completely different code is demonstrated to explain certain concepts. Since a given `targets` project can only have one `_targets.R` file, this means the participants may have to delete their existing `_targets.R` file and write a new one to follow along with the examples. This may cause frustration if they can't keep a record of what they have done so far. One solution would be to save the old `_targets.R` file as `_targets_old.R` or similar, then rename it when it should be run again. From 0f68c50dba7edd16b313b47122099fdde86a3642 Mon Sep 17 00:00:00 2001 From: carpenter Date: Tue, 6 Jun 2023 16:21:17 +0200 Subject: [PATCH 16/28] Change from binder to posit --- learners/setup.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/learners/setup.md b/learners/setup.md index 4f682784..bde2472a 100644 --- a/learners/setup.md +++ b/learners/setup.md @@ -29,8 +29,6 @@ install.packages( ## Alternative: In the cloud -There is a [Binder](https://mybinder.org/) instance with RStudio and all necessary packages pre-installed available, so you don't need to install anything on your own computer. +There is a [Posit Cloud](https://posit.cloud/) instance with RStudio and all necessary packages pre-installed available, so you don't need to install anything on your own computer. You may need to create an account (free). -Click the "Launch Binder" button below: - -[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/joelnitta/penguins-targets/HEAD?urlpath=rstudio) +Click this link to open: \ No newline at end of file From 602c18a2bbb8c2038678ec5d6fc6a36c1ceeac3c Mon Sep 17 00:00:00 2001 From: Athanasia Monika Mowinckel Date: Wed, 14 Jun 2023 18:31:53 +0200 Subject: [PATCH 17/28] fix conflicts --- config.yaml | 3 +- episodes/basic-targets.Rmd | 9 +- episodes/{batch.Rmd => branch.Rmd} | 183 +---------------------- episodes/files/functions.R | 68 +++++---- episodes/lifecycle.Rmd | 6 +- episodes/organization.Rmd | 4 +- episodes/parallel.Rmd | 224 +++++++++++++++++++++++++++++ 7 files changed, 284 insertions(+), 213 deletions(-) rename episodes/{batch.Rmd => branch.Rmd} (73%) create mode 100644 episodes/parallel.Rmd diff --git a/config.yaml b/config.yaml index d3199d92..d7951c6c 100644 --- a/config.yaml +++ b/config.yaml @@ -66,7 +66,8 @@ episodes: - organization.Rmd - packages.Rmd - files.Rmd -- batch.Rmd +- branch.Rmd +- parallel.Rmd - quarto.Rmd # Information for Learners diff --git a/episodes/basic-targets.Rmd b/episodes/basic-targets.Rmd index f1bdc769..69dd2084 100644 --- a/episodes/basic-targets.Rmd +++ b/episodes/basic-targets.Rmd @@ -176,14 +176,14 @@ We will also remove any rows with missing data, because this could cause errors #| label: normal-r-clean # Clean up raw data -penguins_data <- penguins_data_raw %>% +penguins_data <- penguins_data_raw |> # Rename columns for easier typing and # subset to only the columns needed for analysis select( species = Species, bill_length_mm = `Culmen Length (mm)`, bill_depth_mm = `Culmen Depth (mm)` - ) %>% + ) |> # Delete rows with missing data remove_missing(na.rm = TRUE) @@ -213,12 +213,11 @@ library(palmerpenguins) library(tidyverse) clean_penguin_data <- function(penguins_data_raw) { - penguins_data_raw %>% + penguins_data_raw |> select( species = Species, bill_length_mm = `Culmen Length (mm)`, - bill_depth_mm = `Culmen Depth (mm)` - ) %>% + bill_depth_mm = `Culmen Depth (mm)` ) |> remove_missing(na.rm = TRUE) } diff --git a/episodes/batch.Rmd b/episodes/branch.Rmd similarity index 73% rename from episodes/batch.Rmd rename to episodes/branch.Rmd index 148024c1..c7a9e764 100644 --- a/episodes/batch.Rmd +++ b/episodes/branch.Rmd @@ -1,5 +1,5 @@ --- -title: 'Batch and Parallel Processing' +title: 'Branching' teaching: 10 exercises: 2 --- @@ -7,20 +7,18 @@ exercises: 2 :::::::::::::::::::::::::::::::::::::: questions - How can we specify many targets without typing everything out? -- How can we build targets in parallel? :::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::: objectives - Be able to specify targets using branching -- Be able to build targets in parallel :::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::: instructor -Episode summary: Show how to use branching and parallel processing (technically separate topics, but they go well together) +Episode summary: Show how to use branching ::::::::::::::::::::::::::::::::::::: @@ -38,15 +36,13 @@ source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episod options(width = 140) ``` -## Why batch and parallel processing? +## Why branching? -One of the major strengths of `targets` is the ability to define many targets from a single line of code (batch processing). +One of the major strengths of `targets` is the ability to define many targets from a single line of code ("branching"). This not only saves you typing, it also **reduces the risk of errors** since there is less chance of making a typo. -Furthermore, it is related to another powerful feature: `targets` can run multiple analyses in parallel (at the same time), thereby **making your analysis finish sooner**. ## Types of branching -Batching in `targets` is called "branching." There are two types of branching, **dynamic branching** and **static branching**. "Branching" refers to the idea that you can provide a single specification for how to make targets (the "pattern"), and `targets` generates multiple targets from it ("branches"). "Dynamic" means that the branches that result from the pattern do not have to be defined ahead of time---they are a dynamic result of the code. @@ -350,7 +346,7 @@ Here is the function. Save this in `R/functions.R`: glance_with_mod_name <- function(model_in_list) { model_name <- names(model_in_list) model <- model_in_list[[1]] - glance(model) %>% + glance(model) |> mutate(model_name = model_name) } ``` @@ -459,7 +455,7 @@ Define the new function as `augment_with_mod_name()`. It is the same as `glance_ augment_with_mod_name <- function(model_in_list) { model_name <- names(model_in_list) model <- model_in_list[[1]] - augment(model) %>% + augment(model) |> mutate(model_name = model_name) } ``` @@ -541,176 +537,9 @@ You can [find out more about different branching patterns in the `targets` manua ::::::::::::::::::::::::::::::::::::: -## Parallel processing - -Once a pipeline starts to include many targets, you may want to think about parallel processing. -This takes advantage of multiple processors in your computer to build multiple targets at the same time. - -::::::::::::::::::::::::::::::::::::: {.callout} - -## When to use parallel computing - -Parallel computing should only be used if your workflow has a structure such that it makes sense---if your workflow only consists of a linear sequence of targets, then there is nothing to parallelize. - -::::::::::::::::::::::::::::::::::::: - -`targets` includes support for high-performance computing, cloud computing, and various parallel backends. -Here, we assume you are running this analysis on a laptop and so will use a relatively simple backend. -If you are interested in high-performance computing, [see the `targets` manual](https://books.ropensci.org/targets/hpc.html). - -### Install R packages for parallel computing - -For this demo, we will use the [`future` backend](https://github.com/HenrikBengtsson/future). - -::::::::::::::::::::::::::::::::::::: {.prereq} - -### Install required packages - -You will need to install several packages to use the `future` backend: - -```{r} -#| label: install-future -#| eval: false -install.packages("future") -install.packages("future.callr") -``` - -::::::::::::::::::::::::::::::::::::: - -### Set up workflow - -There are a few things you need to change to enable parallel processing with `future`: - -- Load the `future` and `future.callr` packages -- Add a line with `plan(callr)` -- When you run the pipeline, use `tar_make_future(workers = 2)` instead of `tar_make()` - -Here, `workers = 2` is the number of processes to run in parallel. You may increase this up to the number of cores available on your machine. - -To show how this works we will simulate a long(ish) running analysis with the `Sys.sleep()` function, which just tells the computer to wait some number of seconds. - -```{r} -#| label: example-model-show-8 -#| eval: FALSE -library(targets) -library(tarchetypes) -library(future) -library(future.callr) - -plan(callr) - -long_square <- function(data) { - Sys.sleep(3) - data^2 -} - -tar_plan( - some_data = c(1, 2, 3, 4), - tar_target( - data_squared, - long_square(some_data), - pattern = map(some_data) - ) -) -``` - -Here is the output when running with `tar_make_future(workers = 2)`: - -```{r} -#| label: example-model-hide-8 -#| echo: FALSE -tar_dir({ - tar_script({ - library(targets) - library(tarchetypes) - library(future) - library(future.callr) - plan(callr) - long_square <- function(data) { - Sys.sleep(3) - data^2 - } - tar_plan( - some_data = c(1, 2, 3, 4), - tar_target( - data_squared, - long_square(some_data), - pattern = map(some_data) - ) - ) - }) - tar_make_future(workers = 2) -}) -``` - -Notice that although the time required to build each individual target is about 3 seconds, the total time to run the entire workflow is less than the sum of the individual target times! That is proof that processes are running in parallel **and saving you time**. - -The unique and powerful thing about targets is that **we did not need to change our custom function to run it in parallel**. We only adjusted *the workflow*. This means it is relatively easy to refactor (modify) a workflow for running sequentially locally or running in parallel in a high-performance context. - -We can see this by applying parallel processing to the penguins analysis. -Add two more packages to `packages.R`, `library(future)` and `library(future.callr)`. -Also, add the line `plan(callr)` to `_targets.R`: - -```{r} -#| label: example-model-show-9 -#| eval: FALSE -source("R/packages.R") -source("R/functions.R") - -plan(callr) - -tar_plan( - # Load raw data - tar_file_read( - penguins_data_raw, - path_to_file("penguins_raw.csv"), - read_csv(!!.x, show_col_types = FALSE) - ), - # Clean data - penguins_data = clean_penguin_data(penguins_data_raw), - # Build models - models = list( - combined_model = lm( - bill_depth_mm ~ bill_length_mm, data = penguins_data), - species_model = lm( - bill_depth_mm ~ bill_length_mm + species, data = penguins_data), - interaction_model = lm( - bill_depth_mm ~ bill_length_mm * species, data = penguins_data) - ), - # Get model summaries - tar_target( - model_summaries, - glance_with_mod_name(models), - pattern = map(models) - ), - # Get model predictions - tar_target( - model_predictions, - augment_with_mod_name(models), - pattern = map(models) - ) -) -``` - -Finally, run the pipeline with `tar_make_future()` (you may need to run `tar_invalidate(everything())` to reset the pipeline first). - -```{r} -#| label: example-model-hide-9 -#| echo: FALSE -tar_dir({ - # New workflow - write_example_plan(8) - # Run it - tar_make_future(workers = 2) -}) -``` - -You won't notice much difference since these computations run so quickly, but this demonstrates how easy it is to make massive gains in efficiency with your own real analysis by using parallel computing. - ::::::::::::::::::::::::::::::::::::: keypoints - Dynamic branching creates multiple targets with a single command - You usually need to write custom functions so that the output of the branches includes necessary metadata -- Parallel computing works at the level of the workflow, not the function :::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/episodes/files/functions.R b/episodes/files/functions.R index 5da66817..b323f8ec 100644 --- a/episodes/files/functions.R +++ b/episodes/files/functions.R @@ -19,7 +19,7 @@ write_example_plan <- function(plan_select) { "glance_with_mod_name <- function(model_in_list) {", "model_name <- names(model_in_list)", "model <- model_in_list[[1]]", - "broom::glance(model) %>%", + "broom::glance(model) |>", " mutate(model_name = model_name)", "}" ) @@ -27,19 +27,37 @@ write_example_plan <- function(plan_select) { "augment_with_mod_name <- function(model_in_list) {", "model_name <- names(model_in_list)", "model <- model_in_list[[1]]", - "broom::augment(model) %>%", + "broom::augment(model) |>", + " mutate(model_name = model_name)", + "}" + ) + glance_slow_func <- c( + "glance_with_mod_name_slow <- function(model_in_list) {", + "Sys.sleep(4)", + "model_name <- names(model_in_list)", + "model <- model_in_list[[1]]", + "broom::glance(model) |>", + " mutate(model_name = model_name)", + "}" + ) + augment_slow_func <- c( + "augment_with_mod_name_slow <- function(model_in_list) {", + "Sys.sleep(4)", + "model_name <- names(model_in_list)", + "model <- model_in_list[[1]]", + "broom::augment(model) |>", " mutate(model_name = model_name)", "}" ) clean_penguin_data_func <- c( "clean_penguin_data <- function(penguins_data_raw) {", - " penguins_data_raw %>%", + " penguins_data_raw |>", " select(", " species = Species,", " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", - " ) %>%", - " remove_missing(na.rm = TRUE) %>%", + " ) |>", + " remove_missing(na.rm = TRUE) |>", " separate(species, into = 'species', extra = 'drop')", "}" ) @@ -49,12 +67,12 @@ write_example_plan <- function(plan_select) { "library(palmerpenguins)", "suppressPackageStartupMessages(library(tidyverse))", "clean_penguin_data <- function(penguins_data_raw) {", - " penguins_data_raw %>%", + " penguins_data_raw |>", " select(", " species = Species,", " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", - " ) %>%", + " ) |>", " remove_missing(na.rm = TRUE)", "}", "list(", @@ -70,13 +88,13 @@ write_example_plan <- function(plan_select) { "library(palmerpenguins)", "suppressPackageStartupMessages(library(tidyverse))", "clean_penguin_data <- function(penguins_data_raw) {", - " penguins_data_raw %>%", + " penguins_data_raw |>", " select(", " species = Species,", " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", - " ) %>%", - " remove_missing(na.rm = TRUE) %>%", + " ) |>", + " remove_missing(na.rm = TRUE) |>", " separate(species, into = 'species', extra = 'drop')", "}", "list(", @@ -93,13 +111,13 @@ write_example_plan <- function(plan_select) { "library(tarchetypes)", "suppressPackageStartupMessages(library(tidyverse))", "clean_penguin_data <- function(penguins_data_raw) {", - " penguins_data_raw %>%", + " penguins_data_raw |>", " select(", " species = Species,", " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", - " ) %>%", - " remove_missing(na.rm = TRUE) %>%", + " ) |>", + " remove_missing(na.rm = TRUE) |>", " separate(species, into = 'species', extra = 'drop')", "}", "tar_plan(", @@ -118,13 +136,13 @@ write_example_plan <- function(plan_select) { "library(tarchetypes)", "suppressPackageStartupMessages(library(tidyverse))", "clean_penguin_data <- function(penguins_data_raw) {", - " penguins_data_raw %>%", + " penguins_data_raw |>", " select(", " species = Species,", " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", - " ) %>%", - " remove_missing(na.rm = TRUE) %>%", + " ) |>", + " remove_missing(na.rm = TRUE) |>", " separate(species, into = 'species', extra = 'drop')", "}", "tar_plan(", @@ -146,13 +164,13 @@ write_example_plan <- function(plan_select) { "library(broom)", "suppressPackageStartupMessages(library(tidyverse))", "clean_penguin_data <- function(penguins_data_raw) {", - " penguins_data_raw %>%", + " penguins_data_raw |>", " select(", " species = Species,", " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", - " ) %>%", - " remove_missing(na.rm = TRUE) %>%", + " ) |>", + " remove_missing(na.rm = TRUE) |>", " separate(species, into = 'species', extra = 'drop')", "}", "tar_plan(", @@ -243,8 +261,8 @@ write_example_plan <- function(plan_select) { "library(future)", "library(future.callr)", "suppressPackageStartupMessages(library(tidyverse))", - glance_with_mod_name_func, - augment_with_mod_name_func, + glance_slow_func, + augment_slow_func, clean_penguin_data_func, "plan(callr)", "tar_plan(", @@ -264,12 +282,12 @@ write_example_plan <- function(plan_select) { " ),", " tar_target(", " model_summaries,", - " glance_with_mod_name(models),", + " glance_with_mod_name_slow(models),", " pattern = map(models)", " ),", " tar_target(", " model_predictions,", - " augment_with_mod_name(models),", + " augment_with_mod_name_slow(models),", " pattern = map(models)", " ),", ")" @@ -335,6 +353,6 @@ write_example_plan <- function(plan_select) { glance_with_mod_name <- function(model_in_list) { model_name <- names(model_in_list) model <- model_in_list[[1]] - broom::glance(model) %>% + broom::glance(model) |> mutate(model_name = model_name) -} \ No newline at end of file +} diff --git a/episodes/lifecycle.Rmd b/episodes/lifecycle.Rmd index f5864925..1976ed18 100644 --- a/episodes/lifecycle.Rmd +++ b/episodes/lifecycle.Rmd @@ -77,13 +77,13 @@ Edit `_targets.R` so that the `clean_penguin_data()` function looks like this: #| label: new-func #| eval: FALSE clean_penguin_data <- function(penguins_data_raw) { - penguins_data_raw %>% + penguins_data_raw |> select( species = Species, bill_length_mm = `Culmen Length (mm)`, bill_depth_mm = `Culmen Depth (mm)` - ) %>% - remove_missing(na.rm = TRUE) %>% + ) |> + remove_missing(na.rm = TRUE) |> # Split "species" apart on spaces, and only keep the first word separate(species, into = "species", extra = "drop") } diff --git a/episodes/organization.Rmd b/episodes/organization.Rmd index eca807e8..2a3718a2 100644 --- a/episodes/organization.Rmd +++ b/episodes/organization.Rmd @@ -63,12 +63,12 @@ library(palmerpenguins) library(tidyverse) clean_penguin_data <- function(penguins_data_raw) { - penguins_data_raw %>% + penguins_data_raw |> select( species = Species, bill_length_mm = `Culmen Length (mm)`, bill_depth_mm = `Culmen Depth (mm)` - ) %>% + ) |> remove_missing(na.rm = TRUE) } diff --git a/episodes/parallel.Rmd b/episodes/parallel.Rmd new file mode 100644 index 00000000..d3746ea9 --- /dev/null +++ b/episodes/parallel.Rmd @@ -0,0 +1,224 @@ +--- +title: 'Parallel Processing' +teaching: 10 +exercises: 2 +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- How can we build targets in parallel? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Be able to build targets in parallel + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: instructor + +Episode summary: Show how to use parallel processing + +::::::::::::::::::::::::::::::::::::: + +```{r} +#| label: setup +#| echo: FALSE +#| message: FALSE +#| warning: FALSE +library(targets) +library(tarchetypes) +library(broom) +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint + +# Increase width for printing tibbles +options(width = 140) +``` + +Once a pipeline starts to include many targets, you may want to think about parallel processing. +This takes advantage of multiple processors in your computer to build multiple targets at the same time. + +::::::::::::::::::::::::::::::::::::: {.callout} + +## When to use parallel processing + +Parallel processing should only be used if your workflow has a structure such that it makes sense---if your workflow only consists of a linear sequence of targets, then there is nothing to parallelize. + +::::::::::::::::::::::::::::::::::::: + +`targets` includes support for high-performance computing, cloud computing, and various parallel backends. +Here, we assume you are running this analysis on a laptop and so will use a relatively simple backend. +If you are interested in high-performance computing, [see the `targets` manual](https://books.ropensci.org/targets/hpc.html). + +### Install R packages for parallel computing + +For this demo, we will use the [`future` backend](https://github.com/HenrikBengtsson/future). + +::::::::::::::::::::::::::::::::::::: {.prereq} + +### Install required packages + +You will need to install several packages to use the `future` backend: + +```{r} +#| label: install-future +#| eval: false +install.packages("future") +install.packages("future.callr") +``` + +::::::::::::::::::::::::::::::::::::: + +### Set up workflow + +There are a few things you need to change to enable parallel processing with `future`: + +- Load the `future` and `future.callr` packages +- Add a line with `plan(callr)` +- When you run the pipeline, use `tar_make_future(workers = 2)` instead of `tar_make()` + +Here, `workers = 2` is the number of processes to run in parallel. You may increase this up to the number of cores available on your machine. + +Make these changes to the penguins analysis. It should now look like this: + +```{r} +#| label: example-model-show-setup +#| eval: FALSE +source("R/packages.R") +source("R/functions.R") + +plan(callr) + +tar_plan( + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), + # Build models + models = list( + combined_model = lm( + bill_depth_mm ~ bill_length_mm, data = penguins_data), + species_model = lm( + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), + interaction_model = lm( + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) + ), + # Get model summaries + tar_target( + model_summaries, + glance_with_mod_name(models), + pattern = map(models) + ), + # Get model predictions + tar_target( + model_predictions, + augment_with_mod_name(models), + pattern = map(models) + ) +) +``` + +There is still one more thing we need to modify only for the purposes of this demo: if we ran the analysis in parallel now, you wouldn't notice any difference in compute time because the functions are so fast. + +So let's make "slow" versions of `glance_with_mod_name()` and `augment_with_mod_name()` using the `Sys.sleep()` function, which just tells the computer to wait some number of seconds. +This will simulate a long-running computation and enable us to see the difference between running sequentially and in parallel. + +Add these functions to `functions.R` (you can copy-paste the original ones, then modify them): + +```{r} +#| label: slow-funcs +#| eval: false + +glance_with_mod_name_slow <- function(model_in_list) { + Sys.sleep(4) + model_name <- names(model_in_list) + model <- model_in_list[[1]] + broom::glance(model) |> + mutate(model_name = model_name) +} + +augment_with_mod_name_slow <- function(model_in_list) { + Sys.sleep(4) + model_name <- names(model_in_list) + model <- model_in_list[[1]] + broom::augment(model) |> + mutate(model_name = model_name) +} +``` + +Then, change the plan to use the "slow" version of the functions: + +```{r} +#| label: example-model-show-9 +#| eval: FALSE +source("R/packages.R") +source("R/functions.R") + +plan(callr) + +tar_plan( + # Load raw data + tar_file_read( + penguins_data_raw, + path_to_file("penguins_raw.csv"), + read_csv(!!.x, show_col_types = FALSE) + ), + # Clean data + penguins_data = clean_penguin_data(penguins_data_raw), + # Build models + models = list( + combined_model = lm( + bill_depth_mm ~ bill_length_mm, data = penguins_data), + species_model = lm( + bill_depth_mm ~ bill_length_mm + species, data = penguins_data), + interaction_model = lm( + bill_depth_mm ~ bill_length_mm * species, data = penguins_data) + ), + # Get model summaries + tar_target( + model_summaries, + glance_with_mod_name_slow(models), + pattern = map(models) + ), + # Get model predictions + tar_target( + model_predictions, + augment_with_mod_name_slow(models), + pattern = map(models) + ) +) +``` + +Finally, run the pipeline with `tar_make_future()`. + +```{r} +#| label: example-model-hide-9 +#| echo: FALSE +tar_dir({ + # Non-slow functions + write_example_plan(7) + tar_make(reporter = "silent") + # Slow functions + write_example_plan(8) + tar_make_future(workers = 2) +}) +``` + +Notice that although the time required to build each individual target is about 4 seconds, the total time to run the entire workflow is less than the sum of the individual target times! That is proof that processes are running in parallel **and saving you time**. + +The unique and powerful thing about targets is that **we did not need to change our custom function to run it in parallel**. We only adjusted *the workflow*. This means it is relatively easy to refactor (modify) a workflow for running sequentially locally or running in parallel in a high-performance context. + +Now that we have demonstrated how this works, you can change your analysis plan back to the original versions of the functions you wrote. + +::::::::::::::::::::::::::::::::::::: keypoints + +- Dynamic branching creates multiple targets with a single command +- You usually need to write custom functions so that the output of the branches includes necessary metadata +- Parallel computing works at the level of the workflow, not the function + +:::::::::::::::::::::::::::::::::::::::::::::::: From ff941495e3e2613a39edb9435f9905539d5acea4 Mon Sep 17 00:00:00 2001 From: Athanasia Monika Mowinckel Date: Wed, 14 Jun 2023 19:21:22 +0200 Subject: [PATCH 18/28] draft functions --- episodes/functions.Rmd | 193 ++++++++++++++++++++++++++++++++ instructors/instructor-notes.md | 7 ++ 2 files changed, 200 insertions(+) create mode 100644 episodes/functions.Rmd diff --git a/episodes/functions.Rmd b/episodes/functions.Rmd new file mode 100644 index 00000000..8289f86b --- /dev/null +++ b/episodes/functions.Rmd @@ -0,0 +1,193 @@ + + --- +title: 'A brief introduction to functions' +teaching: 10 +exercises: 1git +--- + +:::::::::::::::::::::::::::::::::::::: questions + +- What are functions? +- Why should we know how to write them? +- What are the main components of a function? + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: objectives + +- Understand the usefulness of custom functions +- Know how to use the `Extract Function` button in RStudio +- Understand the basic concepts around writing functions + +:::::::::::::::::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: {.instructor} + +Episode summary: First chance to get hands dirty by writing a very simple workflow + +::::::::::::::::::::::::::::::::::::: + +```{r} +#| label: setup +#| echo: FALSE +#| message: FALSE +#| warning: FALSE +library(targets) +source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint +``` + +## Create a function + +### About functions + +Functions in R are something we are used to thinking of as something that comes from a package. You find, install and use specialised functions from packages to get your work done. + +But you can, and arguable should, be writing your own functions too! +Functions are a great way of making it easy to repeat the same operation but with different scopes. +How many times have you copy + pasted the exact same code in your script, only to change a couple of things (a variable, an input etc) before running it again? +Only to then discover that there was an error in the code, and when you fix it, you need to remember to do so in all the placed you copied that code. + +Through writing functions you can reduce this back and fort, and create a more efficient workflow for your self. +When you find the bug, you fix it in a single place, the function you made, and each subsequent call of that function will now be fixed. + +### Writing a function +There is not much difference between writing your own function and writing other code in R, you are still coding with R! +Let's imagine we want to convert the millimeter measurements in the Penguins data to centimeters. + +```{r} +#| label: targets-functions-problem +library(palmerpenguins) +library(tidyverse) + +penguins |> + mutate( + bill_length_cm = bill_length_mm/10, + bill_depth_cm = bill_depth_mm/10 + ) + +``` + +This is not a complicated operation, but we might want to anyway make our selves a convenient custom function that can do this conversion for us. + +To write a function, you need to use the `function()` function. +With this function we provide what will be the input arguments of the function inside its parentheses, and what the function will subsequently do with those input arguments in curly braces `{}` after the function parentheses. +The object name we assign this to, will become the functions name. + +```{r} +#| label: targets-functions-skeleton +#| eval: false +my_function <- function(argument1, argument2){ + # the things the function will do +} +# call the function +my_function(1, "something") +``` + +For our mm to cm conversion the function would look like so: + +```{r} +#| label: targets-functions-cm +mm2cm <- function(x){ + x/10 +} +# use it +penguins |> + mutate( + bill_length_cm = mm2cm(bill_length_mm), + bill_depth_cm = mm2cm(bill_depth_mm) + ) +``` +Our custom function will now transform any numerical input by dividing it by 10. + + +### Make a function from existing code + +Many times, we might already have a piece of code that we'd like to use to create a function. +For instance, we've copy + pasted a section of code several times and realise that this piece of code is repeating, so a function is in order. +Or, you are converting your workflow to targets, and need to change your script into a series of functions that targets will call. + +Recall the code snippet we had to clean our Penguins data: + +```{r} +penguins_data_raw |> + select( + species = Species, + bill_length_mm = `Culmen Length (mm)`, + bill_depth_mm = `Culmen Depth (mm)` ) |> + remove_missing(na.rm = TRUE) +``` + +We could with down and make the effort of writing the function our selves, this one is not so complicated that we could not do that, it really only needs a single argument, and that is the incoming dataset. + +RStudio also has a handy helper to extract a function from a piece of code. +It's not perfect, but if you are new to functions, it's a convenient tool to help you get started. + +To use it, highligh the piece of code you want to make into a function. +In our case that is the entire pipeline from `penguins_data_raw` to the `remove_missing` statement. +Once you have done this, in RStudio go to the "Code" section in the top bar, and select "Extract function" from the list. +A prompt will open asking you to name your function, call it "clean_penguin_data". +Hit enter, and you should have the following code in your script where the cursor was. + +```{r} +clean_penguin_data <- function(penguins_data_raw, Species, Culmen Length (mm), Culmen Depth (mm)) { + penguins_data_raw |> + # Rename columns for easier typing and + # subset to only the columns needed for analysis + select( + species = Species, + bill_length_mm = `Culmen Length (mm)`, + bill_depth_mm = `Culmen Depth (mm)` + ) |> + # Delete rows with missing data + remove_missing(na.rm = TRUE) +} +``` +This function will not work, because it contains more stuff than is needed as an argument. +This is because tidyverse uses non-standard evaluation, and we can write unquoted column names insite the `select()`. +The function extractor thinks that all unquoted text (or back-ticked) in the code is a reference to an object. +We will need to remove the unecessary parts manually, the function input should be a single argument for this particular function, which is the input of the raw penguins data. + +It should look like this: +```{r} +clean_penguin_data <- function(penguins_data_raw) { + penguins_data_raw |> + # Rename columns for easier typing and + # subset to only the columns needed for analysis + select( + species = Species, + bill_length_mm = `Culmen Length (mm)`, + bill_depth_mm = `Culmen Depth (mm)` + ) |> + # Delete rows with missing data + remove_missing(na.rm = TRUE) +} +``` + +::::::::::::::::::::::::::::::::::::: {.challenge} +## Challenge: Write a function that takes a numerical vector and returns its mean divided by 10. + + +:::::::::::::::::::::::::::::::::: {.solution} + +```{r} +vecmean <- function(x){ + mean(x)/10 +} +``` + +:::::::::::::::::::::::::::::::::: + +::::::::::::::::::::::::::::::::::::: + + +Congratulations, you've started a whole new journey into functions! +This was a very brief introduction to functions, and you will likely need to get more help in learning about them. +There is an episode in the R Novice lesson from Carpentries that is [all about functions](https://swcarpentry.github.io/r-novice-gapminder/10-functions.html) which you might want to have a read through. + +::::::::::::::::::::::::::::::::::::: keypoints + +- Functions are crucial when repeating the same code many times with minor differences +- RStudio's "Extract function" tool can help you get started with converting code into functions +- Functions are an essential part of how targets work. + +:::::::::::::::::::::::::::::::::::::::::::::::: diff --git a/instructors/instructor-notes.md b/instructors/instructor-notes.md index 697f3a03..dff5fe44 100644 --- a/instructors/instructor-notes.md +++ b/instructors/instructor-notes.md @@ -5,3 +5,10 @@ title: 'Instructor Notes' ## General notes The examples gradually build up to a [full analysis](https://github.com/joelnitta/penguins-targets) of the [Palmer Penguins dataset](https://allisonhorst.github.io/palmerpenguins/). However, there are a few places where completely different code is demonstrated to explain certain concepts. Since a given `targets` project can only have one `_targets.R` file, this means the participants may have to delete their existing `_targets.R` file and write a new one to follow along with the examples. This may cause frustration if they can't keep a record of what they have done so far. One solution would be to save the old `_targets.R` file as `_targets_old.R` or similar, then rename it when it should be run again. + + +## Optional episodes: +The "Function" episode is an optional episode and will depend on the learners coming to your workshop. +We would recommend having a show of hands (or stickies) who has experience with functions, and if you have learners who do not, run this episode. + +targets relies so much on functions we believe it is worth spending a little time on if you have learners inexperienced with them, they will quickly fall behind and not be empowered to use targets at the end of the workshop if they don't get a short introduction. \ No newline at end of file From 4714b3142f9d7efc686dfe79328716c5f2705c86 Mon Sep 17 00:00:00 2001 From: Athanasia Monika Mowinckel Date: Wed, 14 Jun 2023 19:21:39 +0200 Subject: [PATCH 19/28] draft functions --- config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/config.yaml b/config.yaml index d7951c6c..7c19de15 100644 --- a/config.yaml +++ b/config.yaml @@ -61,6 +61,7 @@ contact: 'joelnitta@gmail.com' episodes: - introduction.Rmd - basic-targets.Rmd +- functions.Rmd - cache.Rmd - lifecycle.Rmd - organization.Rmd From f628dcec7f36245a989c64002cf5838ec33dda59 Mon Sep 17 00:00:00 2001 From: Athanasia Monika Mowinckel Date: Wed, 14 Jun 2023 19:25:57 +0200 Subject: [PATCH 20/28] change rproj filename --- targets-workshop.Rproj | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 targets-workshop.Rproj diff --git a/targets-workshop.Rproj b/targets-workshop.Rproj new file mode 100644 index 00000000..718d2734 --- /dev/null +++ b/targets-workshop.Rproj @@ -0,0 +1,19 @@ +Version: 1.0 + +RestoreWorkspace: No +SaveWorkspace: No +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX + +AutoAppendNewline: Yes +StripTrailingWhitespace: Yes +LineEndingConversion: Posix + +BuildType: Website From 84be40f13f67e56db7aac609dcf4dfb1677489fa Mon Sep 17 00:00:00 2001 From: Athanasia Monika Mowinckel Date: Wed, 14 Jun 2023 19:27:54 +0200 Subject: [PATCH 21/28] change rproj filename --- FIXME.Rproj | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 FIXME.Rproj diff --git a/FIXME.Rproj b/FIXME.Rproj deleted file mode 100644 index 718d2734..00000000 --- a/FIXME.Rproj +++ /dev/null @@ -1,19 +0,0 @@ -Version: 1.0 - -RestoreWorkspace: No -SaveWorkspace: No -AlwaysSaveHistory: Default - -EnableCodeIndexing: Yes -UseSpacesForTab: Yes -NumSpacesForTab: 2 -Encoding: UTF-8 - -RnwWeave: Sweave -LaTeX: pdfLaTeX - -AutoAppendNewline: Yes -StripTrailingWhitespace: Yes -LineEndingConversion: Posix - -BuildType: Website From 9090f63e2e65877f363de038b708f624c476268d Mon Sep 17 00:00:00 2001 From: Athanasia Monika Mowinckel Date: Wed, 14 Jun 2023 19:28:17 +0200 Subject: [PATCH 22/28] add tidyverse incubator lesson link --- episodes/basic-targets.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/basic-targets.Rmd b/episodes/basic-targets.Rmd index 69dd2084..08945b60 100644 --- a/episodes/basic-targets.Rmd +++ b/episodes/basic-targets.Rmd @@ -137,7 +137,7 @@ penguins_csv_file <- path_to_file("penguins_raw.csv") penguins_csv_file ``` -We will use the `tidyverse` set of packages for loading and manipulating the data. We don't have time to cover all the details about using `tidyverse` now, but if you want to learn more about it, please see the ["Manipulating, analyzing and exporting data with tidyverse" lesson](https://datacarpentry.org/R-ecology-lesson/03-dplyr.html). +We will use the `tidyverse` set of packages for loading and manipulating the data. We don't have time to cover all the details about using `tidyverse` now, but if you want to learn more about it, please see the ["Manipulating, analyzing and exporting data with tidyverse" lesson](https://datacarpentry.org/R-ecology-lesson/03-dplyr.html), or the Carpentry incubator lesson [R and the tidyverse for working with datasets](https://carpentries-incubator.github.io/r-tidyverse-4-datasets/). Let's load the data with `read_csv()`. From 70cb64f5ff667cc60967b7157d3389f30d2e2e87 Mon Sep 17 00:00:00 2001 From: Athanasia Monika Mowinckel Date: Wed, 14 Jun 2023 19:31:55 +0200 Subject: [PATCH 23/28] fix #6 - switch to tidyr::drop_na over ggplot2:remove_missing --- episodes/basic-targets.Rmd | 4 ++-- episodes/files/functions.R | 12 ++++++------ episodes/functions.Rmd | 8 ++++---- episodes/lifecycle.Rmd | 2 +- episodes/organization.Rmd | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/episodes/basic-targets.Rmd b/episodes/basic-targets.Rmd index 08945b60..6ad1baa2 100644 --- a/episodes/basic-targets.Rmd +++ b/episodes/basic-targets.Rmd @@ -185,7 +185,7 @@ penguins_data <- penguins_data_raw |> bill_depth_mm = `Culmen Depth (mm)` ) |> # Delete rows with missing data - remove_missing(na.rm = TRUE) + drop_na() penguins_data ``` @@ -218,7 +218,7 @@ clean_penguin_data <- function(penguins_data_raw) { species = Species, bill_length_mm = `Culmen Length (mm)`, bill_depth_mm = `Culmen Depth (mm)` ) |> - remove_missing(na.rm = TRUE) + drop_na() } list( diff --git a/episodes/files/functions.R b/episodes/files/functions.R index b323f8ec..9b822e77 100644 --- a/episodes/files/functions.R +++ b/episodes/files/functions.R @@ -57,7 +57,7 @@ write_example_plan <- function(plan_select) { " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", " ) |>", - " remove_missing(na.rm = TRUE) |>", + " drop_na() |>", " separate(species, into = 'species', extra = 'drop')", "}" ) @@ -73,7 +73,7 @@ write_example_plan <- function(plan_select) { " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", " ) |>", - " remove_missing(na.rm = TRUE)", + " drop_na()", "}", "list(", " tar_target(penguins_csv_file, path_to_file('penguins_raw.csv')),", @@ -94,7 +94,7 @@ write_example_plan <- function(plan_select) { " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", " ) |>", - " remove_missing(na.rm = TRUE) |>", + " drop_na() |>", " separate(species, into = 'species', extra = 'drop')", "}", "list(", @@ -117,7 +117,7 @@ write_example_plan <- function(plan_select) { " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", " ) |>", - " remove_missing(na.rm = TRUE) |>", + " drop_na() |>", " separate(species, into = 'species', extra = 'drop')", "}", "tar_plan(", @@ -142,7 +142,7 @@ write_example_plan <- function(plan_select) { " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", " ) |>", - " remove_missing(na.rm = TRUE) |>", + " drop_na() |>", " separate(species, into = 'species', extra = 'drop')", "}", "tar_plan(", @@ -170,7 +170,7 @@ write_example_plan <- function(plan_select) { " bill_length_mm = `Culmen Length (mm)`,", " bill_depth_mm = `Culmen Depth (mm)`", " ) |>", - " remove_missing(na.rm = TRUE) |>", + " drop_na() |>", " separate(species, into = 'species', extra = 'drop')", "}", "tar_plan(", diff --git a/episodes/functions.Rmd b/episodes/functions.Rmd index 8289f86b..1e1df500 100644 --- a/episodes/functions.Rmd +++ b/episodes/functions.Rmd @@ -114,7 +114,7 @@ penguins_data_raw |> species = Species, bill_length_mm = `Culmen Length (mm)`, bill_depth_mm = `Culmen Depth (mm)` ) |> - remove_missing(na.rm = TRUE) + drop_na() ``` We could with down and make the effort of writing the function our selves, this one is not so complicated that we could not do that, it really only needs a single argument, and that is the incoming dataset. @@ -123,7 +123,7 @@ RStudio also has a handy helper to extract a function from a piece of code. It's not perfect, but if you are new to functions, it's a convenient tool to help you get started. To use it, highligh the piece of code you want to make into a function. -In our case that is the entire pipeline from `penguins_data_raw` to the `remove_missing` statement. +In our case that is the entire pipeline from `penguins_data_raw` to the `drop_na` statement. Once you have done this, in RStudio go to the "Code" section in the top bar, and select "Extract function" from the list. A prompt will open asking you to name your function, call it "clean_penguin_data". Hit enter, and you should have the following code in your script where the cursor was. @@ -139,7 +139,7 @@ clean_penguin_data <- function(penguins_data_raw, Species, Culmen Length (mm), C bill_depth_mm = `Culmen Depth (mm)` ) |> # Delete rows with missing data - remove_missing(na.rm = TRUE) + drop_na() } ``` This function will not work, because it contains more stuff than is needed as an argument. @@ -159,7 +159,7 @@ clean_penguin_data <- function(penguins_data_raw) { bill_depth_mm = `Culmen Depth (mm)` ) |> # Delete rows with missing data - remove_missing(na.rm = TRUE) + drop_na() } ``` diff --git a/episodes/lifecycle.Rmd b/episodes/lifecycle.Rmd index 1976ed18..172c39b4 100644 --- a/episodes/lifecycle.Rmd +++ b/episodes/lifecycle.Rmd @@ -83,7 +83,7 @@ clean_penguin_data <- function(penguins_data_raw) { bill_length_mm = `Culmen Length (mm)`, bill_depth_mm = `Culmen Depth (mm)` ) |> - remove_missing(na.rm = TRUE) |> + drop_na() |> # Split "species" apart on spaces, and only keep the first word separate(species, into = "species", extra = "drop") } diff --git a/episodes/organization.Rmd b/episodes/organization.Rmd index 2a3718a2..c2c87d4c 100644 --- a/episodes/organization.Rmd +++ b/episodes/organization.Rmd @@ -69,7 +69,7 @@ clean_penguin_data <- function(penguins_data_raw) { bill_length_mm = `Culmen Length (mm)`, bill_depth_mm = `Culmen Depth (mm)` ) |> - remove_missing(na.rm = TRUE) + drop_na() } tar_plan( From 45e187ad9c40f8123bde78c0ce3d7b4683f3d814 Mon Sep 17 00:00:00 2001 From: Athanasia Monika Mowinckel Date: Mon, 19 Jun 2023 12:43:30 +0200 Subject: [PATCH 24/28] Rstudio extraction to callout --- episodes/functions.Rmd | 70 ++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/episodes/functions.Rmd b/episodes/functions.Rmd index 1e1df500..d988b4ee 100644 --- a/episodes/functions.Rmd +++ b/episodes/functions.Rmd @@ -1,8 +1,7 @@ - - --- +--- title: 'A brief introduction to functions' -teaching: 10 -exercises: 1git +teaching: 5 +exercises: 1 --- :::::::::::::::::::::::::::::::::::::: questions @@ -16,16 +15,15 @@ exercises: 1git ::::::::::::::::::::::::::::::::::::: objectives - Understand the usefulness of custom functions -- Know how to use the `Extract Function` button in RStudio - Understand the basic concepts around writing functions :::::::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::::::::::::::::::::::::: {.instructor} -Episode summary: First chance to get hands dirty by writing a very simple workflow +Episode summary: A very brief introduction to functions, when you have learners who have no experience with them. -::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::: ```{r} #| label: setup @@ -97,6 +95,7 @@ penguins |> bill_depth_cm = mm2cm(bill_depth_mm) ) ``` + Our custom function will now transform any numerical input by dividing it by 10. @@ -117,19 +116,11 @@ penguins_data_raw |> drop_na() ``` -We could with down and make the effort of writing the function our selves, this one is not so complicated that we could not do that, it really only needs a single argument, and that is the incoming dataset. - -RStudio also has a handy helper to extract a function from a piece of code. -It's not perfect, but if you are new to functions, it's a convenient tool to help you get started. - -To use it, highligh the piece of code you want to make into a function. -In our case that is the entire pipeline from `penguins_data_raw` to the `drop_na` statement. -Once you have done this, in RStudio go to the "Code" section in the top bar, and select "Extract function" from the list. -A prompt will open asking you to name your function, call it "clean_penguin_data". -Hit enter, and you should have the following code in your script where the cursor was. +We need to adapt this code to become a function, and this function needs a single argument, which is the dataset it should clean. +It should look like this: ```{r} -clean_penguin_data <- function(penguins_data_raw, Species, Culmen Length (mm), Culmen Depth (mm)) { +clean_penguin_data <- function(penguins_data_raw) { penguins_data_raw |> # Rename columns for easier typing and # subset to only the columns needed for analysis @@ -142,30 +133,37 @@ clean_penguin_data <- function(penguins_data_raw, Species, Culmen Length (mm), C drop_na() } ``` + + +::::::::::::::::: callout + +# RStudio function extraction + +RStudio also has a handy helper to extract a function from a piece oyou have basic familiarity with functions, may help you figure out the main necessary input when turning code into a function. + +:::::::::::::: solution + +## Using the tool + +To use it, highlight the piece of code you want to make into a function. +In our case that is the entire pipeline from `penguins_data_raw` to the `drop_na` statement. +Once you have done this, in RStudio go to the "Code" section in the top bar, and select "Extract function" from the list. +A prompt will open asking you to n +Hit enter, and you should have the following code in your script where the cursor was. + This function will not work, because it contains more stuff than is needed as an argument. -This is because tidyverse uses non-standard evaluation, and we can write unquoted column names insite the `select()`. +This is because tidyverse uses non-standard evaluation, and we can write unquoted column names inside the `select()`. The function extractor thinks that all unquoted text (or back-ticked) in the code is a reference to an object. -We will need to remove the unecessary parts manually, the function input should be a single argument for this particular function, which is the input of the raw penguins data. +You will need to do some manual cleaning to get the function working, which is why its more convenient if you have a little experience with functions already. + +::::::::::::::::::: + +:::::::::::::::::: -It should look like this: -```{r} -clean_penguin_data <- function(penguins_data_raw) { - penguins_data_raw |> - # Rename columns for easier typing and - # subset to only the columns needed for analysis - select( - species = Species, - bill_length_mm = `Culmen Length (mm)`, - bill_depth_mm = `Culmen Depth (mm)` - ) |> - # Delete rows with missing data - drop_na() -} -``` ::::::::::::::::::::::::::::::::::::: {.challenge} -## Challenge: Write a function that takes a numerical vector and returns its mean divided by 10. +## Challenge: Write a function that takes a numerical vector and returns its mean divided by 10. :::::::::::::::::::::::::::::::::: {.solution} From 4addea008dcb1f3faceb9807b4e3186cfefb00b9 Mon Sep 17 00:00:00 2001 From: joelnitta Date: Tue, 24 Dec 2024 17:33:31 +0900 Subject: [PATCH 25/28] Fix function loading --- episodes/functions.Rmd | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/episodes/functions.Rmd b/episodes/functions.Rmd index d988b4ee..56f9bfce 100644 --- a/episodes/functions.Rmd +++ b/episodes/functions.Rmd @@ -31,7 +31,12 @@ Episode summary: A very brief introduction to functions, when you have learners #| message: FALSE #| warning: FALSE library(targets) -source("https://raw.githubusercontent.com/joelnitta/targets-workshop/main/episodes/files/functions.R?token=$(date%20+%s)") # nolint + +if (interactive()) { + setwd("episodes") +} + +source("files/lesson_functions.R") ``` ## Create a function From 1c7ec978b329675411ea55d41cf1150cf5a4e38f Mon Sep 17 00:00:00 2001 From: joelnitta Date: Tue, 24 Dec 2024 17:33:44 +0900 Subject: [PATCH 26/28] Adjust teaching time --- episodes/functions.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/episodes/functions.Rmd b/episodes/functions.Rmd index 56f9bfce..c5c44d96 100644 --- a/episodes/functions.Rmd +++ b/episodes/functions.Rmd @@ -1,6 +1,6 @@ --- title: 'A brief introduction to functions' -teaching: 5 +teaching: 20 exercises: 1 --- From 9d37d6d2c085b7d59cedf124be6efc87217155bd Mon Sep 17 00:00:00 2001 From: joelnitta Date: Tue, 24 Dec 2024 17:35:15 +0900 Subject: [PATCH 27/28] Fix grammar and code style --- episodes/functions.Rmd | 75 +++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/episodes/functions.Rmd b/episodes/functions.Rmd index c5c44d96..59a0c2b3 100644 --- a/episodes/functions.Rmd +++ b/episodes/functions.Rmd @@ -43,17 +43,18 @@ source("files/lesson_functions.R") ### About functions -Functions in R are something we are used to thinking of as something that comes from a package. You find, install and use specialised functions from packages to get your work done. +Functions in R are something we are used to thinking of as something that comes from a package. You find, install and use specialized functions from packages to get your work done. -But you can, and arguable should, be writing your own functions too! -Functions are a great way of making it easy to repeat the same operation but with different scopes. -How many times have you copy + pasted the exact same code in your script, only to change a couple of things (a variable, an input etc) before running it again? -Only to then discover that there was an error in the code, and when you fix it, you need to remember to do so in all the placed you copied that code. +But you can, and arguably should, be writing your own functions too! +Functions are a great way of making it easy to repeat the same operation but with different settings. +How many times have you copy-pasted the exact same code in your script, only to change a couple of things (a variable, an input etc.) before running it again? +Only to then discover that there was an error in the code, and when you fix it, you need to remember to do so in all the places where you copied that code. -Through writing functions you can reduce this back and fort, and create a more efficient workflow for your self. +Through writing functions you can reduce this back and forth, and create a more efficient workflow for yourself. When you find the bug, you fix it in a single place, the function you made, and each subsequent call of that function will now be fixed. ### Writing a function + There is not much difference between writing your own function and writing other code in R, you are still coding with R! Let's imagine we want to convert the millimeter measurements in the Penguins data to centimeters. @@ -64,22 +65,22 @@ library(tidyverse) penguins |> mutate( - bill_length_cm = bill_length_mm/10, - bill_depth_cm = bill_depth_mm/10 - ) + bill_length_cm = bill_length_mm / 10, + bill_depth_cm = bill_depth_mm / 10 + ) ``` -This is not a complicated operation, but we might want to anyway make our selves a convenient custom function that can do this conversion for us. +This is not a complicated operation, but we might want to make a convenient custom function that can do this conversion for us anyways. To write a function, you need to use the `function()` function. With this function we provide what will be the input arguments of the function inside its parentheses, and what the function will subsequently do with those input arguments in curly braces `{}` after the function parentheses. -The object name we assign this to, will become the functions name. +The object name we assign this to, will become the function's name. ```{r} #| label: targets-functions-skeleton #| eval: false -my_function <- function(argument1, argument2){ +my_function <- function(argument1, argument2) { # the things the function will do } # call the function @@ -90,34 +91,36 @@ For our mm to cm conversion the function would look like so: ```{r} #| label: targets-functions-cm -mm2cm <- function(x){ - x/10 +mm2cm <- function(x) { + x / 10 } # use it penguins |> mutate( bill_length_cm = mm2cm(bill_length_mm), bill_depth_cm = mm2cm(bill_depth_mm) - ) + ) ``` Our custom function will now transform any numerical input by dividing it by 10. - ### Make a function from existing code Many times, we might already have a piece of code that we'd like to use to create a function. -For instance, we've copy + pasted a section of code several times and realise that this piece of code is repeating, so a function is in order. -Or, you are converting your workflow to targets, and need to change your script into a series of functions that targets will call. +For instance, we've copy-pasted a section of code several times and realize that this piece of code is repetitive, so a function is in order. +Or, you are converting your workflow to `targets`, and need to change your script into a series of functions that `targets` will call. Recall the code snippet we had to clean our Penguins data: ```{r} +#| label: code-to-convert-to-function +#| eval: false penguins_data_raw |> select( species = Species, bill_length_mm = `Culmen Length (mm)`, - bill_depth_mm = `Culmen Depth (mm)` ) |> + bill_depth_mm = `Culmen Depth (mm)` + ) |> drop_na() ``` @@ -125,47 +128,37 @@ We need to adapt this code to become a function, and this function needs a singl It should look like this: ```{r} +#| label: clean-data-function clean_penguin_data <- function(penguins_data_raw) { penguins_data_raw |> - # Rename columns for easier typing and - # subset to only the columns needed for analysis select( species = Species, bill_length_mm = `Culmen Length (mm)`, bill_depth_mm = `Culmen Depth (mm)` ) |> - # Delete rows with missing data drop_na() } ``` - ::::::::::::::::: callout # RStudio function extraction -RStudio also has a handy helper to extract a function from a piece oyou have basic familiarity with functions, may help you figure out the main necessary input when turning code into a function. - -:::::::::::::: solution - -## Using the tool +RStudio also has a handy helper to extract a function from a piece of code. +Once you have basic familiarity with functions, it may help you figure out the necessary input when turning code into a function. To use it, highlight the piece of code you want to make into a function. -In our case that is the entire pipeline from `penguins_data_raw` to the `drop_na` statement. +In our case that is the entire pipeline from `penguins_data_raw` to the `drop_na()` statement. Once you have done this, in RStudio go to the "Code" section in the top bar, and select "Extract function" from the list. -A prompt will open asking you to n -Hit enter, and you should have the following code in your script where the cursor was. +A prompt will open asking you to hit enter, and you should have the following code in your script where the cursor was. -This function will not work, because it contains more stuff than is needed as an argument. +This function will not work however, because it contains more stuff than is needed as an argument. This is because tidyverse uses non-standard evaluation, and we can write unquoted column names inside the `select()`. -The function extractor thinks that all unquoted text (or back-ticked) in the code is a reference to an object. +The function extractor thinks that all unquoted (or back-ticked) text in the code is a reference to an object. You will need to do some manual cleaning to get the function working, which is why its more convenient if you have a little experience with functions already. -::::::::::::::::::: - :::::::::::::::::: - ::::::::::::::::::::::::::::::::::::: {.challenge} ## Challenge: Write a function that takes a numerical vector and returns its mean divided by 10. @@ -173,8 +166,9 @@ You will need to do some manual cleaning to get the function working, which is w :::::::::::::::::::::::::::::::::: {.solution} ```{r} -vecmean <- function(x){ - mean(x)/10 +#| label: write-function-answer +vecmean <- function(x) { + mean(x) / 10 } ``` @@ -182,15 +176,14 @@ vecmean <- function(x){ ::::::::::::::::::::::::::::::::::::: - Congratulations, you've started a whole new journey into functions! This was a very brief introduction to functions, and you will likely need to get more help in learning about them. -There is an episode in the R Novice lesson from Carpentries that is [all about functions](https://swcarpentry.github.io/r-novice-gapminder/10-functions.html) which you might want to have a read through. +There is an episode in the R Novice lesson from Carpentries that is [all about functions](https://swcarpentry.github.io/r-novice-gapminder/10-functions.html) which you might want to read. ::::::::::::::::::::::::::::::::::::: keypoints - Functions are crucial when repeating the same code many times with minor differences - RStudio's "Extract function" tool can help you get started with converting code into functions -- Functions are an essential part of how targets work. +- Functions are an essential part of how `targets` works. :::::::::::::::::::::::::::::::::::::::::::::::: From 29a3840512c4dcfd2b882077d45f657a8c07d922 Mon Sep 17 00:00:00 2001 From: joelnitta Date: Tue, 24 Dec 2024 17:35:39 +0900 Subject: [PATCH 28/28] Add sentence to intro about use of custom fns in `targets` --- episodes/functions.Rmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/episodes/functions.Rmd b/episodes/functions.Rmd index 59a0c2b3..c2e4e316 100644 --- a/episodes/functions.Rmd +++ b/episodes/functions.Rmd @@ -53,6 +53,8 @@ Only to then discover that there was an error in the code, and when you fix it, Through writing functions you can reduce this back and forth, and create a more efficient workflow for yourself. When you find the bug, you fix it in a single place, the function you made, and each subsequent call of that function will now be fixed. +Furthermore, `targets` makes extensive use of custom functions, so a basic understanding of how they work is very important to successfully using it. + ### Writing a function There is not much difference between writing your own function and writing other code in R, you are still coding with R!