From c6edfb977e2afbeb72e2bec6fcfb9c1025d5400e Mon Sep 17 00:00:00 2001 From: Mine Cetinkaya-Rundel Date: Tue, 7 Mar 2023 08:52:43 -0500 Subject: [PATCH] More TR edits (#1343) * Data tidy edits * Style edits * Bit more clarification based on review comments * Import review edits * Scripts edits * Help edits * Insert edits throughout * Update data-import.qmd Co-authored-by: Hadley Wickham * Update data-import.qmd Co-authored-by: Hadley Wickham * Update data-tidy.qmd Co-authored-by: Hadley Wickham * Update data-tidy.qmd Co-authored-by: Hadley Wickham * Update data-tidy.qmd Co-authored-by: Hadley Wickham * Update data-transform.qmd Co-authored-by: Hadley Wickham * Update data-transform.qmd Co-authored-by: Hadley Wickham * Update data-import.qmd Co-authored-by: Hadley Wickham --------- Co-authored-by: Hadley Wickham --- EDA.qmd | 4 +- data-import.qmd | 78 ++++++++++++++++++------- data-tidy.qmd | 113 +++++++++++++++++++++---------------- data-transform.qmd | 47 ++++++++++----- data-visualize.qmd | 2 +- intro.qmd | 10 ++-- layers.qmd | 2 +- numbers.qmd | 3 +- screenshots/rstudio-wd.png | Bin 26080 -> 24801 bytes workflow-basics.qmd | 6 +- workflow-help.qmd | 13 ++--- workflow-scripts.qmd | 66 +++++++++++++--------- workflow-style.qmd | 8 ++- 13 files changed, 218 insertions(+), 134 deletions(-) diff --git a/EDA.qmd b/EDA.qmd index 441c1d1..d9730a9 100644 --- a/EDA.qmd +++ b/EDA.qmd @@ -23,7 +23,7 @@ EDA is not a formal process with a strict set of rules. More than anything, EDA is a state of mind. During the initial phases of EDA you should feel free to investigate every idea that occurs to you. Some of these ideas will pan out, and some will be dead ends. -As your exploration continues, you will home in on a few particularly productive areas that you'll eventually write up and communicate to others. +As your exploration continues, you will home in on a few particularly productive insights that you'll eventually write up and communicate to others. EDA is an important part of any data analysis, even if the questions are handed to you on a platter, because you always need to investigate the quality of your data. Data cleaning is just one application of EDA: you ask questions about whether your data meets your expectations or not. @@ -181,7 +181,7 @@ We'll get to that shortly. ### Unusual values Outliers are observations that are unusual; data points that don't seem to fit the pattern. -Sometimes outliers are data entry errors; other times outliers suggest important new science. +Sometimes outliers are data entry errors; other times outliers suggest important new discoveries. When you have a lot of data, outliers are sometimes difficult to see in a histogram. For example, take the distribution of the `y` variable from the diamonds dataset. The only evidence of outliers is the unusually wide limits on the x-axis. diff --git a/data-import.qmd b/data-import.qmd index 16ffba0..95f83e5 100644 --- a/data-import.qmd +++ b/data-import.qmd @@ -3,6 +3,7 @@ ```{r} #| results: "asis" #| echo: false + source("_common.R") status("complete") ``` @@ -30,13 +31,15 @@ library(tidyverse) ## Reading data from a file -To begin, we'll focus on the most rectangular data file type: CSV, which is short for comma-separated values. +To begin, we'll focus on the most common rectangular data file type: CSV, which is short for comma-separated values. Here is what a simple CSV file looks like. The first row, commonly called the header row, gives the column names, and the following six rows provide the data. +The columns are separated, aka delimited, by commas. ```{r} #| echo: false #| message: false +#| comment: "" read_lines("data/students.csv") |> cat(sep = "\n") ``` @@ -54,7 +57,9 @@ read_csv("data/students.csv") |> ``` We can read this file into R using `read_csv()`. -The first argument is the most important: it's the path to the file. +The first argument is the most important: the path to the file. +You can think about the path as the address of the file. +The following says that the file is called `students.csv` and that it's in the `data` folder. ```{r} #| message: true @@ -62,6 +67,15 @@ The first argument is the most important: it's the path to the file. students <- read_csv("data/students.csv") ``` +The code above will work if you have the `students.csv` file in a `data` folder in your project. +You can download the `students.csv` file from or you can read it directly from that URL with: + +```{r} +#| eval: false + +students <- read_csv("https://pos.it/r4ds-students-csv") +``` + When you run `read_csv()`, it prints out a message telling you the number of rows and columns of data, the delimiter that was used, and the column specifications (names of columns organized by the type of data the column contains). It also prints out some information about retrieving the full column specification and how to quiet this message. This message is an integral part of readr, and we'll return to it in @sec-col-types. @@ -71,8 +85,13 @@ This message is an integral part of readr, and we'll return to it in @sec-col-ty Once you read data in, the first step usually involves transforming it in some way to make it easier to work with in the rest of your analysis. Let's take another look at the `students` data with that in mind. +```{r} +students +``` + In the `favourite.food` column, there are a bunch of food items, and then the character string `N/A`, which should have been a real `NA` that R will recognize as "not available". This is something we can address using the `na` argument. +By default `read_csv()` only recognizes empty strings (`""`) as `NA`s, we want it to also recognize the character string `"N/A` ```{r} #| message: false @@ -82,8 +101,8 @@ students ``` You might also notice that the `Student ID` and `Full Name` columns are surrounded by backticks. -That's because they contain spaces, breaking R's usual rules for variable names. -To refer to them, you need to use those backticks: +That's because they contain spaces, breaking R's usual rules for variable names; they're **non-syntactic** names. +To refer to these variables, you need to surround them with backticks, `` ` ``: ```{r} students |> @@ -117,8 +136,8 @@ students |> Note that the values in the `meal_plan` variable have stayed the same, but the type of variable denoted underneath the variable name has changed from character (``) to factor (``). You'll learn more about factors in @sec-factors. -Before you analyze these data, you'll probably want to fix the `age` column. -Currently, it's a character variable because one of the observations is typed out as `five` instead of a numeric `5`. +Before you analyze these data, you'll probably want to fix the `age` and `id` columns. +Currently, `age` is a character variable because one of the observations is typed out as `five` instead of a numeric `5`. We discuss the details of fixing this issue in @sec-import-spreadsheets. ```{r} @@ -134,7 +153,7 @@ students ### Other arguments -There are a couple of other important arguments that we need to mention, and they'll be easier to demonstrate if we first show you a handy trick: `read_csv()` can read CSV files that you've created in a string: +There are a couple of other important arguments that we need to mention, and they'll be easier to demonstrate if we first show you a handy trick: `read_csv()` can read text strings that you've created and formatted like a CSV file: ```{r} #| message: false @@ -300,7 +319,7 @@ One of the most common causes for this is a missing value, recorded using someth Take this simple 1 column CSV file as an example: ```{r} -csv <- " +simple_csv <- " x 10 . @@ -312,16 +331,19 @@ csv <- " If we read it without any additional arguments, `x` becomes a character column: ```{r} -df <- read_csv(csv) +df <- read_csv(simple_csv) ``` In this very small case, you can easily see the missing value `.`. But what happens if you have thousands of rows with only a few missing values represented by `.`s speckled among them? One approach is to tell readr that `x` is a numeric column, and then see where it fails. -You can do that with the `col_types` argument, which takes a named list: +You can do that with the `col_types` argument, which takes a named list where the names match the column names in the CSV file: ```{r} -df <- read_csv(csv, col_types = list(x = col_double())) +df <- read_csv( + simple_csv, + col_types = list(x = col_double()) +) ``` Now `read_csv()` reports that there was a problem, and tells us we can find out more with `problems()`: @@ -335,7 +357,7 @@ That suggests this dataset uses `.` for missing values. So then we set `na = "."`, the automatic guessing succeeds, giving us the numeric column that we want: ```{r} -df <- read_csv(csv, na = ".") +df <- read_csv(simple_csv, na = ".") ``` ### Column types @@ -344,19 +366,22 @@ readr provides a total of nine column types for you to use: - `col_logical()` and `col_double()` read logicals and real numbers. They're relatively rarely needed (except as above), since readr will usually guess them for you. - `col_integer()` reads integers. We distinguish integers and doubles in this book because they're functionally equivalent, but reading integers explicitly can occasionally be useful because they occupy half the memory of doubles. -- `col_character()` reads strings. This is sometimes useful to specify explicitly when you have a column that is a numeric identifier, i.e. long series of digits that identifies some object, but it doesn't make sense to (e.g.) divide it in half. +- `col_character()` reads strings. This is sometimes useful to specify explicitly when you have a column that is a numeric identifier, i.e. long series of digits that identifies some object, but it doesn't make sense to (e.g.) divide it in half, for example a phone number, social security number, credit card number, etc. - `col_factor()`, `col_date()`, and `col_datetime()` create factors, dates, and date-times respectively; you'll learn more about those when we get to those data types in @sec-factors and @sec-dates-and-times. - `col_number()` is a permissive numeric parser that will ignore non-numeric components, and is particularly useful for currencies. You'll learn more about it in @sec-numbers. -- `col_skip()` skips a column so it's not included in the result. +- `col_skip()` skips a column so it's not included in the result, which can be useful for speeding up reading the data if you have a large CSV file and you only want to use some of the columns. It's also possible to override the default column by switching from `list()` to `cols()`: ```{r} -csv <- " +another_csv <- " x,y,z 1,2,3" -read_csv(csv, col_types = cols(.default = col_character())) +read_csv( + another_csv, + col_types = cols(.default = col_character()) +) ``` Another useful helper is `cols_only()` which will read in only the columns you specify: @@ -380,6 +405,20 @@ sales_files <- c("data/01-sales.csv", "data/02-sales.csv", "data/03-sales.csv") read_csv(sales_files, id = "file") ``` +Once again, the code above will work if you have the CSV files in a `data` folder in your project. +You can download these files from , , and or you can read them directly with: + +```{r} +#| eval: false + +sales_files <- c( + "https://pos.it/r4ds-01-sales", + "https://pos.it/r4ds-02-sales", + "https://pos.it/r4ds-03-sales" +) +read_csv(sales_files, id = "file") +``` + With the additional `id` parameter we have added a new column called `file` to the resulting data frame that identifies the file the data come from. This is especially helpful in circumstances where the files you're reading in do not have an identifying column that can help you trace the observations back to their original sources. @@ -407,7 +446,7 @@ write_csv(students, "students.csv") ``` Now let's read that csv file back in. -Note that the type information is lost when you save to csv: +Note that the variable type information that you just set up is lost when you save to CSV because you're starting over with reading from a plain text file again: ```{r} #| warning: false @@ -422,7 +461,8 @@ This makes CSVs a little unreliable for caching interim results---you need to re There are two main alternative: 1. `write_rds()` and `read_rds()` are uniform wrappers around the base functions `readRDS()` and `saveRDS()`. - These store data in R's custom binary format called RDS: + These store data in R's custom binary format called RDS. + This means that when you reload the object, you are loading the *exact same* R object that you stored. ```{r} write_rds(students, "students.rds") @@ -496,8 +536,6 @@ tribble( ) ``` -We'll use `tibble()` and `tribble()` later in the book to construct small examples to demonstrate how various functions work. - ## Summary In this chapter, you've learned how to load CSV files with `read_csv()` and to do your own data entry with `tibble()` and `tribble()`. diff --git a/data-tidy.qmd b/data-tidy.qmd index abf043e..70d5015 100644 --- a/data-tidy.qmd +++ b/data-tidy.qmd @@ -3,6 +3,7 @@ ```{r} #| results: "asis" #| echo: false + source("_common.R") status("complete") ``` @@ -119,20 +120,21 @@ Here are a few small examples showing how you might work with `table1`. table1 |> mutate(rate = cases / population * 10000) -# Compute cases per year -table1 |> - count(year, wt = cases) +# Compute total cases per year +table1 |> + group_by(year) |> + summarize(total_cases = sum(cases)) # Visualize changes over time ggplot(table1, aes(x = year, y = cases)) + geom_line(aes(group = country), color = "grey50") + geom_point(aes(color = country, shape = country)) + - scale_x_continuous(breaks = c(1999, 2000)) + scale_x_continuous(breaks = c(1999, 2000)) # x-axis breaks at 1999 and 2000 ``` ### Exercises -1. Using words, describe how the variables and observations are organized in each of the sample tables. +1. For each of the sample tables, describe what each observation and each column represents. 2. Sketch out the process you'd use to calculate the `rate` for `table2` and `table3`. You will need to perform four operations: @@ -177,14 +179,7 @@ The first three columns (`artist`, `track` and `date.entered`) are variables tha Then we have 76 columns (`wk1`-`wk76`) that describe the rank of the song in each week. Here, the column names are one variable (the `week`) and the cell values are another (the `rank`). -To tidy this data, we'll use `pivot_longer()`. -After the data, there are three key arguments: - -- `cols` specifies which columns need to be pivoted, i.e. which columns aren't variables. This argument uses the same syntax as `select()` so here we could use `!c(artist, track, date.entered)` or `starts_with("wk")`. -- `names_to` names of the variable stored in the column names, here `"week"`. -- `values_to` names the variable stored in the cell values, here `"rank"`. - -That gives the following call: +To tidy this data, we'll use `pivot_longer()`: ```{r, R.options=list(pillar.print_min = 10)} billboard |> @@ -195,10 +190,19 @@ billboard |> ) ``` +After the data, there are three key arguments: + +- `cols` specifies which columns need to be pivoted, i.e. which columns aren't variables. This argument uses the same syntax as `select()` so here we could use `!c(artist, track, date.entered)` or `starts_with("wk")`. +- `names_to` names of the variable stored in the column names, we named that variable `week`. +- `values_to` names the variable stored in the cell values, we named that variable `rank`. + +Note that in the code `"week"` and `"rank"` are quoted because those are new variables we're creating, they don't yet exist in the data when we run the `pivot_longer()` call. + +Now let's turn our attention to the resulting, longer data frame. What happens if a song is in the top 100 for less than 76 weeks? Take 2 Pac's "Baby Don't Cry", for example. -The above output suggests that it was only the top 100 for 7 weeks, and all the remaining weeks are filled in with missing values. -These `NA`s don't really represent unknown observations; they're forced to exist by the structure of the dataset[^data-tidy-1], so we can ask `pivot_longer()` to get rid of them by setting `values_drop_na = TRUE`: +The above output suggests that it was only in the top 100 for 7 weeks, and all the remaining weeks are filled in with missing values. +These `NA`s don't really represent unknown observations; they were forced to exist by the structure of the dataset[^data-tidy-1], so we can ask `pivot_longer()` to get rid of them by setting `values_drop_na = TRUE`: [^data-tidy-1]: We'll come back to this idea in @sec-missing-values. @@ -212,14 +216,16 @@ billboard |> ) ``` +The number of rows is now much lower, indicating that the rows with `NA`s were dropped. + You might also wonder what happens if a song is in the top 100 for more than 76 weeks? We can't tell from this data, but you might guess that additional columns `wk77`, `wk78`, ... would be added to the dataset. -This data is now tidy, but we could make future computation a bit easier by converting `week` into a number using `mutate()` and `readr::parse_number()`. +This data is now tidy, but we could make future computation a bit easier by converting values of `week` from character strings to numbers using `mutate()` and `readr::parse_number()`. `parse_number()` is a handy function that will extract the first number from a string, ignoring all other text. ```{r} -billboard_tidy <- billboard |> +billboard_longer <- billboard |> pivot_longer( cols = starts_with("wk"), names_to = "week", @@ -229,11 +235,12 @@ billboard_tidy <- billboard |> mutate( week = parse_number(week) ) -billboard_tidy +billboard_longer ``` -Now we're in a good position to look at how song ranks vary over time by drawing a plot. -The code is shown below and the result is @fig-billboard-ranks. +Now that we have all the week numbers in one variable and all the rank values in another, we're in a good position to visualize how song ranks vary over time. +The code is shown below and the result is in @fig-billboard-ranks. +We can see that very few songs stay in the top 100 for more than 20 weeks. ```{r} #| label: fig-billboard-ranks @@ -246,16 +253,17 @@ The code is shown below and the result is @fig-billboard-ranks. #| surprisingly few tracks in the region when week is >20 and rank is #| >50. -billboard_tidy |> +billboard_longer |> ggplot(aes(x = week, y = rank, group = track)) + - geom_line(alpha = 1/3) + + geom_line(alpha = 0.25) + scale_y_reverse() ``` ### How does pivoting work? -Now that you've seen what pivoting can do for you, it's worth taking a little time to gain some intuition about what it does to the data. -Let's start with a very simple dataset to make it easier to see what's happening: +Now that you've seen how we can use pivoting to reshape our data, let's take a little time to gain some intuition about what pivoting does to the data. +Let's start with a very simple dataset to make it easier to see what's happening. +We'll create it with `tribble()`, a handy function for creating small tibbles by hand: ```{r} df <- tribble( @@ -266,8 +274,8 @@ df <- tribble( ) ``` -Here we'll say there are three variables: `var` (already in a variable), `name` (the column names in the column names), and `value` (the cell values). -So we can tidy it with: +We want out new dataset to have three variables: `var` (already exists), `name` (the column names), and `value` (the cell values). +So we can tidy `df` with: ```{r} df |> @@ -278,9 +286,9 @@ df |> ) ``` -How does this transformation take place? -It's easier to see if we take it component by component. -Columns that are already variables need to be repeated, once for each column in `cols`, as shown in @fig-pivot-variables. +How does the reshaping work? +It's easier to see if we think about it column by column. +As shown in @fig-pivot-variables, the values in column that was already a variable in the original dataset (`var`) need to be repeated, once for each column that is pivoted. ```{r} #| label: fig-pivot-variables @@ -297,15 +305,15 @@ Columns that are already variables need to be repeated, once for each column in knitr::include_graphics("diagrams/tidy-data/variables.png", dpi = 270) ``` -The column names become values in a new variable, whose name is given by `names_to`, as shown in @fig-pivot-names. +The column names become values in a new variable, whose name is defined by `names_to`, as shown in @fig-pivot-names. They need to be repeated once for each row in the original dataset. ```{r} #| label: fig-pivot-names #| echo: false #| fig-cap: > -#| The column names of pivoted columns become a new column. The values -#| need to be repeated once for each row of the original dataset. +#| The column names of pivoted columns become values in a new column. The +#| values need to be repeated once for each row of the original dataset. #| fig-alt: > #| A diagram showing how `pivot_longer()` transforms a simple #| data set, using color to highlight how column names ("col1" and @@ -315,7 +323,7 @@ They need to be repeated once for each row in the original dataset. knitr::include_graphics("diagrams/tidy-data/column-names.png", dpi = 270) ``` -The cell values also become values in a new variable, with a name given by `values_to`. +The cell values also become values in a new variable, with a name defined by `values_to`. They are unwound row by row. @fig-pivot-values illustrates the process. @@ -337,22 +345,22 @@ knitr::include_graphics("diagrams/tidy-data/cell-values.png", dpi = 270) ### Many variables in column names -A more challenging situation occurs when you have multiple variables crammed into the column names. +A more challenging situation occurs when you have multiple pieces of information crammed into the column names, and you would like to store these in separate new variables. For example, take the `who2` dataset, the source of `table1` and friends that you saw above: ```{r} who2 ``` -This dataset records information about tuberculosis data collected by the WHO. +This dataset, collected by the World Health Organisation, records information about tuberculosis diagnoses. There are two columns that are already variables and are easy to interpret: `country` and `year`. They are followed by 56 columns like `sp_m_014`, `ep_m_4554`, and `rel_m_3544`. If you stare at these columns for long enough, you'll notice there's a pattern. Each column name is made up of three pieces separated by `_`. -The first piece, `sp`/`rel`/`ep`, describes the method used for the `diagnosis`, the second piece, `m`/`f` is the `gender`, and the third piece, `014`/`1524`/`2535`/`3544`/`4554`/`65` is the `age` range. +The first piece, `sp`/`rel`/`ep`, describes the method used for the diagnosis, the second piece, `m`/`f` is the `gender` (coded as a binary variable in this dataset), and the third piece, `014`/`1524`/`2535`/`3544`/`4554`/`65` is the `age` range (`014` represents 0-14, for example). -So in this case we have six variables: two variables are already columns, three variables are contained in the column name, and one variable is in the cell value. -This requires two changes to our call to `pivot_longer()`: `names_to` gets a vector of column names and `names_sep` describes how to split the variable name up into pieces: +So in this case we have six pieces of information recorded in `who2`: the country and the year (already columns); the method of diagnosis, the gender category, and the age range category (contained in the other column names); and the count of patients in that category (cell values). +To organize these six pieces of information in six separate columns, we use `pivot_longer()` with a vector of column names for `names_to` and instructors for splitting the original variable names into pieces for `names_sep` as well as a column name for `values_to`: ```{r} who2 |> @@ -374,8 +382,9 @@ You can imagine this happening in two steps (first pivoting and then separating) #| label: fig-pivot-multiple-names #| echo: false #| fig-cap: > -#| Pivotting with many variables in the column names means that each -#| column name now fills in values in multiple output columns. +#| Pivoting columns with multiple pieces of information in the names +#| means that each column name now fills in values in multiple output +#| columns. #| fig-alt: > #| A diagram that uses color to illustrate how supplying `names_sep` #| and multiple `names_to` creates multiple variables in the output. @@ -448,7 +457,7 @@ We'll start by looking at `cms_patient_experience`, a dataset from the Centers o cms_patient_experience ``` -An observation is an organization, but each organization is spread across six rows, with one row for each variable, or measure. +The core unit being studied is an organization, but each organization is spread across six rows, with one row for each measurement taken in the survey organization. We can see the complete set of values for `measure_cd` and `measure_title` by using `distinct()`: ```{r} @@ -457,9 +466,9 @@ cms_patient_experience |> ``` Neither of these columns will make particularly great variable names: `measure_cd` doesn't hint at the meaning of the variable and `measure_title` is a long sentence containing spaces. -We'll use `measure_cd` for now, but in a real analysis you might want to create your own variable names that are both short and meaningful. +We'll use `measure_cd` as the source for our new column names for now, but in a real analysis you might want to create your own variable names that are both short and meaningful. -`pivot_wider()` has the opposite interface to `pivot_longer()`: we need to provide the existing columns that define the values (`values_from`) and the column name (`names_from)`: +`pivot_wider()` has the opposite interface to `pivot_longer()`: instead of choosing new column names, we need to provide the existing columns that define the values (`values_from`) and the column name (`names_from)`: ```{r} cms_patient_experience |> @@ -470,8 +479,7 @@ cms_patient_experience |> ``` The output doesn't look quite right; we still seem to have multiple rows for each organization. -That's because, by default, `pivot_wider()` will attempt to preserve all the existing columns including `measure_title` which has six distinct observations for each organizations. -To fix this problem we need to tell `pivot_wider()` which columns identify each row; in this case those are the variables starting with `"org"`: +That's because, we also need to tell `pivot_wider()` which column or columns have values that uniquely identify each row; in this case those are the variables starting with `"org"`: ```{r} cms_patient_experience |> @@ -512,7 +520,7 @@ df |> The connection between the position of the row in the input and the cell in the output is weaker than in `pivot_longer()` because the rows and columns in the output are primarily determined by the values of variables, not their locations. To begin the process `pivot_wider()` needs to first figure out what will go in the rows and columns. -Finding the column names is easy: it's just the unique values of `name`. +Finding the new column names is easy: it's just the unique values of `name`. ```{r} df |> @@ -520,7 +528,7 @@ df |> pull() ``` -By default, the rows in the output are formed by all the variables that aren't going into the names or values. +By default, the rows in the output are determined by all the variables that aren't going into the new names or values. These are called the `id_cols`. Here there is only one column, but in general there can be any number. @@ -580,11 +588,16 @@ It's then up to you to figure out what's gone wrong with your data and either re ## Summary In this chapter you learned about tidy data: data that has variables in columns and observations in rows. -Tidy data makes working in the tidyverse easier, because it's a consistent structure understood by most functions: the main challenge is data from whatever structure you receive it in to a tidy format. +Tidy data makes working in the tidyverse easier, because it's a consistent structure understood by most functions, the main challenge is transforming the data from whatever structure you receive it in to a tidy format. To that end, you learned about `pivot_longer()` and `pivot_wider()` which allow you to tidy up many untidy datasets. -The examples we used here are just a selection of those from `vignette("pivot", package = "tidyr")`, so if you encounter a problem that this chapter doesn't help you with, that vignette is a good place to try next. +The examples we presented here are a selection of those from `vignette("pivot", package = "tidyr")`, so if you encounter a problem that this chapter doesn't help you with, that vignette is a good place to try next. -If you particularly enjoyed this chapter and want to learn more about the underlying theory, you can learn more about the history and theoretical underpinnings in the [Tidy Data](https://www.jstatsoft.org/article/view/v059i10) paper published in the Journal of Statistical Software. +Another challenge is that, for a given dataset, it can be impossible to label the longer or the wider version as the "tidy" one. +This is partly a reflection of our definition of tidy data, where we said tidy data has one variable in each column, but we didn't actually define what a variable is (and it's surprisingly hard to do so). +It's totally fine to be pragmatic and to say a variable is whatever makes your analysis easiest. +So if you're stuck figuring out how to do some computation, consider switching up the organisation of your data; don't be afraid to untidy, transform, and re-tidy as needed! + +If you enjoyed this chapter and want to learn more about the underlying theory, you can learn more about the history and theoretical underpinnings in the [Tidy Data](https://www.jstatsoft.org/article/view/v059i10) paper published in the Journal of Statistical Software. Now that you're writing a substantial amount of R code, it's time to learn more about organizing your code into files and directories. In the next chapter, you'll learn all about the advantages of scripts and projects, and some of the many tools that they provide to make your life easier. diff --git a/data-transform.qmd b/data-transform.qmd index 7096735..983f032 100644 --- a/data-transform.qmd +++ b/data-transform.qmd @@ -10,7 +10,7 @@ status("complete") ## Introduction -Visualization is an important tool for generating insight, but it's rare that you get the data in exactly the right form you need to visualize it. +Visualization is an important tool for generating insight, but it's rare that you get the data in exactly the right form you need to make the graph you want. Often you'll need to create some new variables or summaries to answer your questions with your data, or maybe you just want to rename the variables or reorder the observations to make the data a little easier to work with. You'll learn how to do all that (and more!) in this chapter, which will introduce you to data transformation using the **dplyr** package and a new dataset on flights that departed New York City in 2013. @@ -69,7 +69,7 @@ But before we discuss their individual differences, it's worth stating what they 2. The subsequent arguments typically describe which columns to operate on, using the variable names (without quotes). -3. The result is always a new data frame. +3. The output is always a new data frame. Because each verb does one thing well, solving complex problems will usually require combining multiple verbs, and we'll do so with the pipe, `|>`. We'll discuss the pipe more in @the-pipe, but in brief, the pipe takes the thing on its left and passes it along to the function on its right so that `x |> f(y)` is equivalent to `f(x, y)`, and `x |> f(y) |> g(z)` is equivalent to into `g(f(x, y), z)`. @@ -112,7 +112,7 @@ flights |> ``` As well as `>` (greater than), you can use `>=` (greater than or equal to), `<` (less than), `<=` (less than or equal to), `==` (equal to), and `!=` (not equal to). -You can also use `&` (and) or `|` (or) to combine multiple conditions: +You can also combine conditions with `&` or `,` to indicate "and" (check for both conditions) or with `|` to indicate "or" (check for either condition): ```{r} # Flights that departed on January 1 @@ -181,8 +181,8 @@ flights |> arrange(year, month, day, dep_time) ``` -You can use `desc()` to re-order the data frame based a column, in descending order. -For example, this code shows the most delayed flights first: +You can use `desc()` on a column inside of `arrange()` to re-order the data frame based on that column in descending (big-to-small) order. +For example, this code orders flights from most to least delayed: ```{r} flights |> @@ -197,16 +197,31 @@ Note that the number of rows has not changed -- we're only arranging the data, w Most of the time, however, you'll want the distinct combination of some variables, so you can also optionally supply column names: ```{r} -# This would remove any duplicate rows if there were any +# Remove duplicate rows, if any flights |> distinct() -# This finds all unique origin and destination pairs +# Find all unique origin and destination pairs flights |> distinct(origin, dest) ``` -Note that if you want to find the number of duplicates, or rows that weren't duplicated, you're better off swapping `distinct()` for `count()`, which will give the number of observations per unique level, and then filtering as needed. +Alternatively, if you want to the keep other columns when filtering for unique rows, you can use the `.keep_all = TRUE` option. + +```{r} +flights |> + distinct(origin, dest, .keep_all = TRUE) +``` + +It's not a coincidence that all of these distinct flights are on January 1, `distinct()` will find the find the first occurrence of a unique row in the dataset and discard the rest. + +If you want to find the number of occurrences instead, you're better off swapping `distinct()` for `count()`, and with the `sort = TRUE` argument you can arrange them in descending order of number of occurrences. +You'll learn more about count in @sec-counts. + +```{r} +flights |> + count(origin, dest, sort = TRUE) +``` ### Exercises @@ -282,7 +297,7 @@ flights |> ``` Alternatively, you can control which variables are kept with the `.keep` argument. -A particularly useful argument is `"used"` which allows you to keep only the inputs and outputs from your calculations. +A particularly useful argument is `"used"` which specifies that we only keep the columns that were involved or created in the `mutate()` step. For example, the following output will contain only the variables `dep_delay`, `arr_delay`, `air_time`, `gain`, `hours`, and `gain_per_hour`. ```{r} @@ -335,6 +350,8 @@ In this situation, the first challenge is often just focusing on the variables y select(!year:day) ``` + You can also use `-` instead of `!` (and you're likely to see that in the wild); we recommend `!` because it reads as "not", and combines well with `&` and `|`. + - Select all columns that are characters: ```{r} @@ -419,7 +436,7 @@ ggplot(flights, aes(x = air_time - airtime2)) + geom_histogram() 2. Brainstorm as many ways as possible to select `dep_time`, `dep_delay`, `arr_time`, and `arr_delay` from `flights`. -3. What happens if you specify the name of a variable multiple times in a `select()` call? +3. What happens if you specify the name of the same variable multiple times in a `select()` call? 4. What does the `any_of()` function do? Why might it be helpful in conjunction with this vector? @@ -552,9 +569,9 @@ flights |> group_by(month) ``` -`group_by()` doesn't change the data but, if you look closely at the output, you'll notice that it's now "grouped by" month. +`group_by()` doesn't change the data but, if you look closely at the output, you'll notice that the output indicates that it is "grouped by" month (`Groups: month [12]`). This means subsequent operations will now work "by month". -`group_by()` doesn't do anything by itself; instead it changes the behavior of the subsequent verbs. +`group_by()` adds this grouped feature (referred to as class) to the data frame, which changes the behavior of the subsequent verbs applied to the data. ### `summarize()` {#sec-summarize} @@ -623,7 +640,7 @@ This is similar to computing the max delay with `summarize()`, but you get the w ### Grouping by multiple variables You can create groups using more than one variable. -For example, we could make a group for each day. +For example, we could make a group for each date. ```{r} daily <- flights |> @@ -792,7 +809,7 @@ When we plot the skill of the batter (measured by the batting average, `performa 1. The variation in `performance` is larger among players with fewer at-bats. The shape of this plot is very characteristic: whenever you plot a mean (or other summary statistics) vs. group size, you'll see that the variation decreases as the sample size increases[^data-transform-4]. -2. There's a positive correlation between skill (`perf`) and opportunities to hit the ball (`n`) because teams want to give their best batters the most opportunities to hit the ball. +2. There's a positive correlation between skill (`performance`) and opportunities to hit the ball (`n`) because teams want to give their best batters the most opportunities to hit the ball. [^data-transform-4]: \*cough\* the law of large numbers \*cough\*. @@ -816,7 +833,7 @@ Note the handy pattern for combining ggplot2 and dplyr. You just have to remember to switch from `|>`, for dataset processing, to `+` for adding layers to your plot. This also has important implications for ranking. -If you naively sort on `desc(performance)`, the people with the best batting averages are clearly lucky, not skilled: +If you naively sort on `desc(performance)`, the people with the best batting averages are clearly the ones who tried to put the ball in play very few times and happened to get a hit, they're not necessarily the most skilled players: ```{r} batters |> diff --git a/data-visualize.qmd b/data-visualize.qmd index 08976c2..264fe2a 100644 --- a/data-visualize.qmd +++ b/data-visualize.qmd @@ -155,7 +155,7 @@ ggplot(data = penguins) Next, we need to tell `ggplot()` how the information from our data will be visually represented. The `mapping` argument of the `ggplot()` function defines how variables in your dataset are mapped to visual properties (**aesthetics**) of your plot. -The `mapping` argument is always defined using the `aes()` function, and the `x` and `y` arguments of `aes()` specify which variables to map to the x and y axes. +The `mapping` argument is always defined in the `aes()` function, and the `x` and `y` arguments of `aes()` specify which variables to map to the x and y axes. For now, we will only map flipper length to the `x` aesthetic and body mass to the `y` aesthetic. ggplot2 looks for the mapped variables in the `data` argument, in this case, `penguins`. diff --git a/intro.qmd b/intro.qmd index d51a1d9..a0c19f7 100644 --- a/intro.qmd +++ b/intro.qmd @@ -14,7 +14,7 @@ After reading this book, you'll have the tools to tackle a wide variety of data Data science is a vast field, and there's no way you can master it all by reading a single book. This book aims to give you a solid foundation in the most important tools and enough knowledge to find the resources to learn more when necessary. -Our model of the tools needed in a typical data science project looks something like @fig-ds-diagram. +Our model of the steps of a typical data science project looks something like @fig-ds-diagram. ```{r} #| label: fig-ds-diagram @@ -47,7 +47,7 @@ Transformation includes narrowing in on observations of interest (like all peopl Together, tidying and transforming are called **wrangling** because getting your data in a form that's natural to work with often feels like a fight! Once you have tidy data with the variables you need, there are two main engines of knowledge generation: visualization and modeling. -These have complementary strengths and weaknesses, so any real analysis will iterate between them many times. +These have complementary strengths and weaknesses, so any real data analysis will iterate between them many times. **Visualization** is a fundamentally human activity. A good visualization will show you things you did not expect or raise new questions about the data. @@ -75,12 +75,12 @@ Throughout this book, we'll point you to resources where you can learn more. ## How this book is organized The previous description of the tools of data science is organized roughly according to the order in which you use them in an analysis (although, of course, you'll iterate through them multiple times). -In our experience, however, learning data ingesting and tidying first is sub-optimal because 80% of the time, it's routine and boring, and the other 20% of the time, it's weird and frustrating. +In our experience, however, learning data importing and tidying first is sub-optimal because 80% of the time, it's routine and boring, and the other 20% of the time, it's weird and frustrating. That's a bad place to start learning a new subject! Instead, we'll start with visualization and transformation of data that's already been imported and tidied. That way, when you ingest and tidy your own data, your motivation will stay high because you know the pain is worth the effort. -Within each chapter, we try and adhere to a similar pattern: start with some motivating examples so you can see the bigger picture and then dive into the details. +Within each chapter, we try and adhere to a consistent pattern: start with some motivating examples so you can see the bigger picture and then dive into the details. Each section of the book is paired with exercises to help you practice what you've learned. Although it can be tempting to skip the exercises, there's no better way to learn than practicing on real problems. @@ -197,7 +197,7 @@ In other words, the complement to the tidyverse is not the messyverse but many o As you tackle more data science projects with R, you'll learn new packages and new ways of thinking about data. We'll use many packages from outside the tidyverse in this book. -For example, we use the following four data packages to provide interesting applications: +For example, we use the following packages to that provide interesting data sets: ```{r} #| eval: false diff --git a/layers.qmd b/layers.qmd index 7b4502f..8d12a97 100644 --- a/layers.qmd +++ b/layers.qmd @@ -10,7 +10,7 @@ status("complete") ## Introduction -In the @sec-data-visualization, you learned much more than just how to make scatterplots, bar charts, and boxplots. +In @sec-data-visualization, you learned much more than just how to make scatterplots, bar charts, and boxplots. You learned a foundation that you can use to make *any* type of plot with ggplot2. In this chapter, you'll expand on that foundation as you learn about the layered grammar of graphics. diff --git a/numbers.qmd b/numbers.qmd index 3626b0e..9c6715b 100644 --- a/numbers.qmd +++ b/numbers.qmd @@ -3,6 +3,7 @@ ```{r} #| results: "asis" #| echo: false + source("_common.R") status("complete") ``` @@ -51,7 +52,7 @@ x <- c("$1,234", "USD 3,513", "59%") parse_number(x) ``` -## Counts +## Counts {#sec-counts} It's surprising how much data science you can do with just counts and a little basic arithmetic, so dplyr strives to make counting as easy as possible with `count()`. This function is great for quick exploration and checks during analysis: diff --git a/screenshots/rstudio-wd.png b/screenshots/rstudio-wd.png index 79b0a229079121b139ccd72949813bb41c4ebb23..5401607c136f5fc9048f65c248bae22d5008d94f 100644 GIT binary patch delta 21283 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 S^CsWJ9;!;3iscICLH_~>#v85x diff --git a/workflow-basics.qmd b/workflow-basics.qmd index d7568f7..f053d93 100644 --- a/workflow-basics.qmd +++ b/workflow-basics.qmd @@ -41,7 +41,7 @@ You can **c**ombine multiple elements into a vector with `c()`: primes <- c(2, 3, 5, 7, 11, 13) ``` -And operations applied to the vector are applied to every element of it: +And basic arithmetic on vectors is applied to every element of of the vector: ```{r} primes * 2 @@ -72,7 +72,7 @@ We'll sometimes include comments in examples explaining what's happening with th Comments can be helpful for briefly describing what the following code does. ```{r} -# define primes +# create vector of primes primes <- c(2, 3, 5, 7, 11, 13) # multiply primes by 2 @@ -88,7 +88,7 @@ If you describe every step in the comments, and then change the code, you will h Figuring out *why* something was done is much more difficult, if not impossible. For example, `geom_smooth()` has an argument called `span`, which controls the smoothness of the curve, with larger values yielding a smoother curve. -Suppose you decide to change the value of `span` from its default of 0.75 to 0.3: it's easy for a future reader to understand *what* is happening, but unless you note your thinking in a comment, no one will understand *why* you changed the default. +Suppose you decide to change the value of `span` from its default of 0.75 to 0.9: it's easy for a future reader to understand *what* is happening, but unless you note your thinking in a comment, no one will understand *why* you changed the default. For data analysis code, use comments to explain your overall plan of attack and record important insights as you encounter them. There's no way to re-capture this knowledge from the code itself. diff --git a/workflow-help.qmd b/workflow-help.qmd index 73874b5..a214b94 100644 --- a/workflow-help.qmd +++ b/workflow-help.qmd @@ -59,14 +59,14 @@ y <- 1:4 mean(y) ``` -Then call `reprex()`, where the default target venue is GitHub: +Then call `reprex()`, where the default output is formatted for GitHub: ``` r reprex::reprex() ``` A nicely rendered HTML preview will display in RStudio's Viewer (if you're in RStudio) or your default browser otherwise. -The relevant bit of GitHub-flavored Markdown is ready to be pasted from your clipboard (on RStudio Server or Cloud, you will need to copy this yourself): +The reprex is automatically copied to your clipboard (on RStudio Server or Cloud, you will need to copy this yourself): ``` r y <- 1:4 @@ -74,7 +74,8 @@ The relevant bit of GitHub-flavored Markdown is ready to be pasted from your cli #> [1] 2.5 ``` -Here's what that Markdown would look like rendered in a GitHub issue: +This text is formatted in a special way, called Markdown, which can be pasted to sites like StackOverflow or Github and they will automatically render it to look like code. +Here's what that Markdown would look like rendered on GitHub: ```{r} #| eval: false @@ -99,7 +100,7 @@ There are three things you need to include to make your example reproducible: re 2. Copy the output 3. In reprex, type `mtcars <-`, then paste. - Try and find the smallest subset of your data that still reveals the problem. + Try to use the smallest subset of your data that still reveals the problem. 3. Spend a little bit of time ensuring that your **code** is easy for others to read: @@ -123,10 +124,6 @@ Investing a little time in learning R each day will pay off handsomely in the lo One way is to follow what the tidyverse team is doing on the [tidyverse blog](https://www.tidyverse.org/blog/). To keep up with the R community more broadly, we recommend reading [R Weekly](https://rweekly.org): it's a community effort to aggregate the most interesting news in the R community each week. -If you're an active Twitter user, you might also want to follow Hadley ([\@hadleywickham](https://twitter.com/hadleywickham)), Mine ([\@minebocek](https://twitter.com/minebocek)), Garrett ([\@statgarrett](https://twitter.com/statgarrett)), or follow [\@rstudiotips](https://twitter.com/rstudiotips) to keep up with new features in the IDE. -If you want the full fire hose of new developments, you can also read the ([`#rstats`](https://twitter.com/search?q=%23rstats)) hashtag. -This is one of the key tools that Hadley and Mine use to keep up with new developments in the community. - ## Summary This chapter concludes the Whole Game part of the book. diff --git a/workflow-scripts.qmd b/workflow-scripts.qmd index 638db1c..000a2fb 100644 --- a/workflow-scripts.qmd +++ b/workflow-scripts.qmd @@ -16,8 +16,9 @@ That's a great place to start, but you'll find it gets cramped pretty quickly as To give yourself more room to work, use the script editor. Open it up by clicking the File menu, selecting New File, then R script, or using the keyboard shortcut Cmd/Ctrl + Shift + N. Now you'll see four panes, as in @fig-rstudio-script. -The script editor is a great place to put code you care about. -Keep experimenting in the console, but once you have written code that works and does what you want, put it in the script editor. +The script editor is a great place to experiment with your code. +When you want to change something, you don't have to re-type the whole thing, you can just edit the script and re-run it. +And once you have written code that works and does what you want, you can save it as a script file to easily return to later. ```{r} #| label: fig-rstudio-script @@ -37,9 +38,6 @@ The script editor is an excellent place for building complex ggplot2 plots or lo The key to using the script editor effectively is to memorize one of the most important keyboard shortcuts: Cmd/Ctrl + Enter. This executes the current R expression in the console. For example, take the code below. -If your cursor is at █, pressing Cmd/Ctrl + Enter will run the complete command that generates `not_cancelled`. -It will also move the cursor to the following statement (beginning with `not_cancelled |>`). -That makes it easy to step through your complete script by repeatedly pressing Cmd/Ctrl + Enter. ```{r} #| eval: false @@ -55,13 +53,17 @@ not_cancelled |> summarize(mean = mean(dep_delay)) ``` +If your cursor is at █, pressing Cmd/Ctrl + Enter will run the complete command that generates `not_cancelled`. +It will also move the cursor to the following statement (beginning with `not_cancelled |>`). +That makes it easy to step through your complete script by repeatedly pressing Cmd/Ctrl + Enter. + Instead of running your code expression-by-expression, you can also execute the complete script in one step with Cmd/Ctrl + Shift + S. Doing this regularly is a great way to ensure that you've captured all the important parts of your code in the script. We recommend you always start your script with the packages you need. That way, if you share your code with others, they can easily see which packages they need to install. Note, however, that you should never include `install.packages()` in a script you share. -It's very antisocial to change settings on someone else's computer! +It's inconsiderate to hand off a script that will something on their computer if they're not being careful! When working through future chapters, we highly recommend starting in the script editor and practicing your keyboard shortcuts. Over time, sending code to the console in this way will become so natural that you won't even think about it. @@ -134,7 +136,7 @@ For example, suppose you have the following files in a project folder. There are a variety of problems here: it's hard to find which file to run first, file names contain spaces, there are two files with the same name but different capitalization (`finalreport` vs. `FinalReport`[^workflow-scripts-1]), and some names don't describe their contents (`run-first` and `temp`). -[^workflow-scripts-1]: Not to mention that you're tempting fate by using "final" in the name 😆 The comic piled higher and deeper has a [fun strip on this](https://phdcomics.com/comics/archive.php?comicid=1531). +[^workflow-scripts-1]: Not to mention that you're tempting fate by using "final" in the name 😆 The comic Piled Higher and Deeper has a [fun strip on this](https://phdcomics.com/comics/archive.php?comicid=1531). Here's better way of naming and organizing the same set of files: @@ -150,6 +152,7 @@ Here's better way of naming and organizing the same set of files: Numbering the key scripts make it obvious in which order to run them and a consistent naming scheme makes it easier to see what varies. Additionally, the figures are labelled similarly, the reports are distinguished by dates included in the file names, and `temp` is renamed to `report-draft-notes` to better describe its contents. +If you have a lot of files in a directory, taking organization one step further and placing different types of files (scripts, figures, etc.) in different directories is recommended. ## Projects @@ -166,15 +169,15 @@ To handle these real life situations, you need to make two decisions: ### What is the source of truth? -As a beginning R user, it's OK to consider your environment (i.e. the objects listed in the environment pane) to be your analysis. -However, in the long run, you'll be much better off if you ensure that your R scripts are the source of truth. +As a beginner, it's okay to rely on your current Environment to contain all the objects you have created throughout your analysis. +However, to make it easier to work on larger projects or collaborate with others, your source of truth should be the R scripts. With your R scripts (and your data files), you can recreate the environment. With only your environment, it's much harder to recreate your R scripts: you'll either have to retype a lot of code from memory (inevitably making mistakes along the way) or you'll have to carefully mine your R history. To help keep your R scripts as the source of truth for your analysis, we highly recommend that you instruct RStudio not to preserve your workspace between sessions. -You can do this either by running `usethis::use_blank_slate()`[^workflow-scripts-2] or by mimicking the options shown in @fig-blank-slate. This will cause you some short-term pain, because now when you restart RStudio, it will no longer remember the code that you ran last time. -But this short-term pain saves you long-term agony because it forces you to capture all important interactions in your code. -There's nothing worse than discovering three months after the fact that you've only stored the results of an important calculation in your workspace, not the calculation itself in your code. +You can do this either by running `usethis::use_blank_slate()`[^workflow-scripts-2] or by mimicking the options shown in @fig-blank-slate. This will cause you some short-term pain, because now when you restart RStudio, it will no longer remember the code that you ran last time nor will the objects you created or datasets you read be available to use. +But this short-term pain saves you long-term agony because it forces you to capture all important procedures in your code. +There's nothing worse than discovering three months after the fact that you've only stored the results of an important calculation in your environment, not the calculation itself in your code. [^workflow-scripts-2]: If you don't have usethis installed, you can install it with `install.packages("usethis")`. @@ -185,7 +188,7 @@ There's nothing worse than discovering three months after the fact that you've o #| Copy these options in your RStudio options to always start your #| RStudio session with a clean slate. #| fig-alt: > -#| RStudio preferences window where the option Restore .RData into workspace +#| RStudio Global Options window where the option Restore .RData into workspace #| at startup is not checked. Also, the option Save workspace to .RData #| on exit is set to Never. #| out-width: ~ @@ -200,6 +203,8 @@ There is a great pair of keyboard shortcuts that will work together to make sure We collectively use this pattern hundreds of times a week. +Alternatively, if you don't use keyboard shortcuts, you can go to Session \> Restart R and then highlight and re-run your current script. + ::: callout-note ## RStudio server @@ -219,7 +224,7 @@ RStudio shows your current working directory at the top of the console: #| echo: false #| fig-alt: > #| The Console tab shows the current working directory as -#| ~/Documents/r4ds/r4ds. +#| ~/Documents/r4ds. #| out-width: ~ knitr::include_graphics("screenshots/rstudio-wd.png") ``` @@ -229,11 +234,14 @@ And you can print this out in R code by running `getwd()`: ```{r} #| eval: false getwd() -#> [1] "/Users/hadley/Documents/r4ds/r4ds" +#> [1] "/Users/hadley/Documents/r4ds" ``` +In this R session, the current working directory (think of it as "home") is in hadley's Documents folder, in a subfolder called r4ds. +This code will return a different result when you run it, because your computer has a different directory structure than Hadley's! + As a beginning R user, it's OK to let your working directory be your home directory, documents directory, or any other weird directory on your computer. -But you're nine chapters into this book, and you're no longer a rank beginner. +But you're seven chapters into this book, and you're no longer a beginner. Very soon now you should evolve to organizing your projects into directories and, when working on a project, set R's working directory to the associated directory. You can set the working directory from within R but **we** **do not recommend it**: @@ -285,11 +293,13 @@ Check that the "home" of your project is the current working directory: ```{r} #| eval: false getwd() -#> [1] /Users/hadley/Documents/r4ds/r4ds +#> [1] /Users/hadley/Documents/r4ds ``` Now enter the following commands in the script editor, and save the file, calling it "diamonds.R". -Next, run the complete script which will save a PDF and CSV file into your project directory. +Then, create a new folder called "data". +You can do this by clicking on the "New Folder" button in the Files pane in RStudio. +Finally, run the complete script which will save a PNG and CSV file into your project directory. Don't worry about the details, you'll learn them later in the book. ```{r} @@ -300,9 +310,9 @@ library(tidyverse) ggplot(diamonds, aes(x = carat, y = price)) + geom_hex() -ggsave("diamonds.pdf") +ggsave("diamonds.png") -write_csv(diamonds, "diamonds.csv") +write_csv(diamonds, "data/diamonds.csv") ``` Quit RStudio. @@ -311,7 +321,7 @@ Double-click that file to re-open the project. Notice you get back to where you left off: it's the same working directory and command history, and all the files you were working on are still open. Because you followed our instructions above, you will, however, have a completely fresh environment, guaranteeing that you're starting with a clean slate. -In your favorite OS-specific way, search your computer for `diamonds.pdf` and you will find the PDF (no surprise) but *also the script that created it* (`diamonds.R`). +In your favorite OS-specific way, search your computer for `diamonds.png` and you will find the PNG (no surprise) but *also the script that created it* (`diamonds.R`). This is a huge win! One day, you will want to remake a figure or just understand where it came from. If you rigorously save figures to files **with R code** and never with the mouse or the clipboard, you will be able to reproduce old work with ease! @@ -320,10 +330,10 @@ If you rigorously save figures to files **with R code** and never with the mouse Once you're inside a project, you should only ever use relative paths not absolute paths. What's the difference? -A relative path is **relative** to the working directory, i.e. the project's home. -When Hadley wrote `diamonds.R` above it was a shortcut for `/Users/hadley/Documents/r4ds/r4ds/diamonds.R`. -But importantly, if Mine ran this code on her computer, it would point to `/Users/Mine/Documents/r4ds/r4ds/diamonds.R`. -This is why relative paths are important: they'll work regardless of where the project ends up. +A relative path is relative to the working directory, i.e. the project's home. +When Hadley wrote `data/diamonds.csv` above it was a shortcut for `/Users/hadley/Documents/r4ds/data/diamonds.csv`. +But importantly, if Mine ran this code on her computer, it would point to `/Users/Mine/Documents/r4ds/data/diamonds.csv`. +This is why relative paths are important: they'll work regardless of where the R project folder ends up. Absolute paths point to the same place regardless of your working directory. They look a little different depending on your operating system. @@ -331,7 +341,7 @@ On Windows they start with a drive letter (e.g. `C:`) or two backslashes (e.g. ` You should **never** use absolute paths in your scripts, because they hinder sharing: no one else will have exactly the same directory configuration as you. There's another important difference between operating systems: how you separate the components of the path. -Mac and Linux uses slashes (e.g. `plots/diamonds.pdf`) and Windows uses backslashes (e.g. `plots\diamonds.pdf`). +Mac and Linux uses slashes (e.g. `data/diamonds.csv`) and Windows uses backslashes (e.g. `data\diamonds.csv`). R can work with either type (no matter what platform you're currently using), but unfortunately, backslashes mean something special to R, and to get a single backslash in the path, you need to type two backslashes! That makes life frustrating, so we recommend always using the Linux/Mac style with forward slashes. @@ -345,6 +355,10 @@ That makes life frustrating, so we recommend always using the Linux/Mac style wi ## Summary +In this chapter, you've learned how to organize your R code in scripts (files) and projects (directories). +Much like code style, this may feel like busywork at first. +But as you accumulate more code across multiple projects, you'll learn to appreciate how a little up front organisation can save you a bunch of time down the road. + In summary, scripts and projects give you a solid workflow that will serve you well in the future: - Create one RStudio project for each data analysis project. diff --git a/workflow-style.qmd b/workflow-style.qmd index 0708737..d74ac30 100644 --- a/workflow-style.qmd +++ b/workflow-style.qmd @@ -34,6 +34,8 @@ Open the palette by pressing Cmd/Ctrl + Shift + P, then type "styler" to see all knitr::include_graphics("screenshots/rstudio-palette.png") ``` +We'll use the tidyverse and nycflights13 packages for code examples in this chapter. + ```{r} #| label: setup #| message: false @@ -112,7 +114,7 @@ flights |> ## Pipes {#sec-pipes} `|>` should always have a space before it and should typically be the last thing on a line. -This makes it easier to add new steps, rearrange existing steps, modify elements within a step, and get a 50,000 ft view by skimming the verbs on the left-hand side. +This makes it easier to add new steps, rearrange existing steps, modify elements within a step, and get a 10,000 ft view by skimming the verbs on the left-hand side. ```{r} #| eval: false @@ -149,6 +151,7 @@ flights |> ``` After the first step of the pipeline, indent each line by two spaces. +RStudio will automatically put the spaces in for you after a line break following a `\>` . If you're putting each argument on its own line, indent by an extra two spaces. Make sure `)` is on its own line, and un-indented to match the horizontal position of the function name. @@ -171,6 +174,7 @@ flights|> n = n() ) +# Avoid flights|> group_by(tailnum) |> summarize( @@ -199,7 +203,7 @@ df |> Finally, be wary of writing very long pipes, say longer than 10-15 lines. Try to break them up into smaller sub-tasks, giving each task an informative name. The names will help cue the reader into what's happening and makes it easier to check that intermediate results are as expected. -Whenever you can give something an informative name, you should give it an informative name. +Whenever you can give something an informative name, you should give it an informative name, for example when you fundamentally change the structure of the data, e.g., after pivoting or summarizing. Don't expect to get it right the first time! This means breaking up long pipelines if there are intermediate states that can get good names.