Activity: Wrangling Your Data
Importing more than one way, reshaping columns, and saving what you make
Worksheet: Wrangling Your Data
π₯ Download the data (.csv) π₯ Download the data (.xlsx) π₯ Download the messy file
pine_data.csv / pine_data.xlsx
Both files hold the same six columns, with exactly these names. Type them exactly as written β R is case-sensitive, and Needle_Length or needle_length will not work:
| Column | What it is |
|---|---|
source_file |
the raw team file the row came from |
team |
field team β one team = one tree |
replicate |
needle number within a tree side (1-8) |
side |
sun exposure β "shady" or "sunny" |
needle_length_mm |
needle length, millimetres |
needle_width_mm |
needle width, millimetres |
After you read the file in, run names(pine_df) and check the names on your screen against this table before you write any other code.
Part 1 Β· Import the same data two ways
π Read more: R4DS Β§7 β Data import and Β§20 β Spreadsheets.
βΆ Run this in your Script:
# ββ Part 1.1 Β· Import the same data two ways ββββββββββββββββββββββββββββ
library(tidyverse)
library(readxl)
pine_csv_df <- read_csv("data/pine_data.csv")
pine_excel_df <- read_excel("data/pine_data.xlsx")π‘ You do not need
glimpse(). Clickpine_csv_dfin the Variables pane in Positron β it lists every column name, its type, and how many rows, right there.
βοΈ Your turn: List the six column names and, for each, its type (<chr>, <dbl>, etc.):
Column: Type:
Column: Type:
Column: Type:
Column: Type:
Column: Type:
Column: Type:
Part 2 Β· filter() β picking rows
π Read more: R4DS Β§3.2 β
filter().
βΆ Run this:
# ββ Part 2.1 Β· `filter()` βββββββββββββββββββββββββββββββββββββββββββββββ
pine_csv_df |> filter(side == "shady") |> head()
pine_csv_df |> filter(needle_length_mm > 17) |> head()βοΈ Your turn: Write a filter() that keeps only sunny (side == "sunny") needles longer than 15 mm.
# ββ Part 2.2 Β· `filter()` βββββββββββββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:β οΈ Watch out!
filter(side = "shady")(one=) is an error. You need==to test equality.
Part 2b Β· filter() β OR, %in%, and missing values
π Read more: R4DS Β§12 β Logical vectors and Β§18 β Missing values.
βΆ Run this:
# ββ Part 2b.1 Β· `filter()` ββββββββββββββββββββββββββββββββββββββββββββββ
# OR: shady side, OR any exceptionally long needle
pine_csv_df |> filter(side == "shady" | needle_length_mm > 18)
# %in%: match one of several named teams
pine_csv_df |> filter(team %in% c("botany", "waterbugs"))βοΈ Your turn: Rewrite the %in% line above as a chain of | conditions instead β same result, longer code.
# ββ Part 2b.2 Β· `filter()` ββββββββββββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:βΆ Run this:
# ββ Part 2b.3 Β· `filter()` ββββββββββββββββββββββββββββββββββββββββββββββ
survey_df <- tibble(
site = c("A", "B", "C", "D"),
count = c(12, NA, 8, NA)
)
survey_df |> filter(is.na(count))
survey_df |> filter(!is.na(count))βοΈ Your turn: Write a filter() on pine_csv_df that keeps sunny (side == "sunny") needles that are also longer than 14mm β think about whether you need ,/& or |.
# ββ Part 2b.4 Β· `filter()` ββββββββββββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:Part 3 Β· select() β picking columns
π Read more: R4DS Β§3.3 β
select().
βΆ Run this:
# ββ Part 3.1 Β· `select()` βββββββββββββββββββββββββββββββββββββββββββββββ
pine_csv_df |> select(side, needle_length_mm) |> head()
pine_csv_df |> select(-source_file) |> head()βοΈ Your turn: Write a select() that keeps every column except replicate.
# ββ Part 3.2 Β· `select()` βββββββββββββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:Part 4 Β· mutate() β new and changed columns
π Read more: R4DS Β§3.3 β
mutate()and Β§13 β Numbers.
βΆ Run this:
# ββ Part 4.1 Β· `mutate()` βββββββββββββββββββββββββββββββββββββββββββββββ
pine_csv_df |>
mutate(needle_length_cm = needle_length_mm / 10) |>
head()
pine_csv_df |>
mutate(log_length = log(needle_length_mm)) |>
head()βοΈ Your turn: Add a column called size_class that is "long" when needle_length_mm > 15 and "short" otherwise. Use if_else().
# ββ Part 4.2 Β· `mutate()` βββββββββββββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:βοΈ Your turn: Does mutate() change how many rows your data frame has? How many columns did each example above add?
________________________
π If you finish early: Add a second transformed column β a square-root transform (sqrt(needle_length_mm)) alongside your log transform. Which one looks like it changes the spread of the values more?
Part 4b Β· case_when() β recoding and binning
π Read more: R4DS Β§12.5 β Conditional transformations.
βΆ Run this:
# ββ Part 4b.1 Β· `case_when()` βββββββββββββββββββββββββββββββββββββββββββ
pine_csv_df |>
mutate(
size_class = case_when(
needle_length_mm < 13 ~ "short",
needle_length_mm < 16 ~ "medium",
TRUE ~ "long"
)
) |>
count(size_class)βοΈ Your turn: Change the two cutoffs (13 and 16) to different values of your choosing. How do the counts in each bin shift?
# ββ Part 4b.2 Β· `case_when()` βββββββββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:βοΈ Your turn: What happens if you delete the TRUE ~ "long" line and rerun? Try it, then explain why thatβs dangerous.
____________________________________________________________________________________
βΆ Run this:
# ββ Part 4b.3 Β· `case_when()` βββββββββββββββββββββββββββββββββββββββββββ
pine_csv_df |>
mutate(
team_code = case_when(
team == "botany" ~ "BOT",
team == "grace_jace" ~ "GRJ",
team == "one" ~ "ONE",
team == "waterbugs" ~ "WBG",
TRUE ~ "UNK"
)
) |>
distinct(team, team_code)βοΈ Your turn: Add a size_class column (as above) to pine_wrangled_dfβs recipe β youβll build the full pipeline with it in Part 6.
π If you finish early: Write a case_when() that uses two conditions per row (e.g., side == "shady" & needle_length_mm > 17 ~ "long shady") to build a combined category.
Part 5 Β· arrange() β sorting rows
π Read more: R4DS Β§3.2 β
arrange().
βΆ Run this:
# ββ Part 5.1 Β· `arrange()` ββββββββββββββββββββββββββββββββββββββββββββββ
pine_csv_df |> arrange(needle_length_mm)
pine_csv_df |> arrange(desc(needle_length_mm))βοΈ Your turn: Sort the data by side, and within each side, by needle_length_mm from longest to shortest.
# ββ Part 5.2 Β· `arrange()` ββββββββββββββββββββββββββββββββββββββββββββββ
# Write your code in the R script:Part 6 Β· The full pipeline
βΆ Run this:
# ββ Part 6.1 Β· The full pipeline ββββββββββββββββββββββββββββββββββββββββ
pine_wrangled_df <- pine_csv_df |>
filter(needle_length_mm > 0) |>
select(team, side, needle_length_mm, needle_width_mm) |>
mutate(
needle_length_cm = needle_length_mm / 10,
log_length = log(needle_length_mm),
size_class = case_when(
needle_length_mm < 13 ~ "short",
needle_length_mm < 16 ~ "medium",
TRUE ~ "long"
)
) |>
arrange(side, desc(needle_length_mm))
head(pine_wrangled_df)βοΈ Your turn: In your own words, read this pipeline out loud, step by step, the way we did in lecture.
Take pine_csv_df, then ______________________, then ______________________,
then ______________________, then ______________________.
Part 7 Β· Finding duplicate rows
π Read more: R4DS Β§14 β Strings for the text cleanup, and Gotelli & Ellison Ch. 8 (Managing and Curating Data) on Canvas for why this matters.
βΆ Run this:
# ββ Part 7.1 Β· Finding duplicate rows βββββββββββββββββββββββββββββββββββ
needle_check_df <- read_csv("data/pine_needles_error.csv")
nrow(needle_check_df)
n_distinct(needle_check_df)βοΈ Your turn: Do nrow() and n_distinct() match?
What does that tell you?
_______________________________________________________________________
βΆ Run this:
# ββ Part 7.2 Β· Finding duplicate rows βββββββββββββββββββββββββββββββββββ
needle_check_df |> filter(duplicated(needle_check_df))
needle_clean_df <- needle_check_df |> distinct()
nrow(needle_clean_df)βοΈ Your turn: How many rows did distinct() remove? Why does this matter for the sample size youβd report?
_________________________________________________________________________
β οΈ Watch out!
distinct()only catches exact duplicate rows β a mistiped name wonβt be caught. Always look at your data too.
Part 8 Β· Saving your work with write_csv()
Everything runs one way: data/ (raw, read-only) β scripts/ (your code) β output/ (results).
π Read more: R4DS Β§7.5 β Writing to a file.
Before you save, trim to the columns worth keeping:
βΆ Run this:
# ββ Part 8.1 Β· Saving your work with `write_csv()` ββββββββββββββββββββββ
pine_out_df <- pine_wrangled_df |>
select(team, side, needle_length_mm, needle_width_mm)
write_csv(pine_out_df, "output/pine_data_wrangled.csv")Check your output/ folder β the new CSV should be there, and data/pine_data.csv should be exactly as it was.
β οΈ Watch out!
write_csv()overwrites without asking. That is fine inoutput/, because your script can rebuild anything in there. It would be a disaster indata/.
βοΈ Your turn: Save your cleaned needle_clean_df from Part 7 to output/pine_needles_cleaned.csv.
# ββ Part 8.2 Β· Saving your work with `write_csv()` ββββββββββββββββββββββ
# Write your code in the R script:β οΈ Watch out! Never give a wrangled file the same name as the raw file. If your scriptβs output can overwrite your raw data, one accidental re-run destroys it permanently.
Part 9 Β· Review and checkpoint
At this point you can:
βοΈ Your turn β before you move on: Run your whole script top to bottom.
Ran cleanly? Y / N β if not, the error was:
____________________________________________________________
π€ What to turn in before next class
Upload both of these to the course management system:
- Your code β the
scripts/folder (or just02_wrangling_data.R) - This worksheet, with your written answers
Readings
Before next class β chapter PDFs are posted on Canvas:
- Gotelli & Ellison, A Primer of Ecological Statistics β Ch. 8, Managing and Curating Data (raw vs. processed data, and the audit trail from field sheet to analysis)
- Whitlock & Schluter, The Analysis of Biological Data β Ch. 1, Statistics and Samples, Β§1.4 on types of variables β the categorical/numerical distinction behind
case_when()and everygroup_by()to come
Neither statistics text teaches dplyr. Read them for why a clean, documented dataset matters; read R4DS for how.
R for Data Science (2nd ed.) β free online at r4ds.hadley.nz:
| Chapter | Covers | Worksheet part |
|---|---|---|
| Β§3 β Data transformation | filter(), select(), mutate(), arrange(), the pipe |
Parts 2-6 |
| Β§5 β Data tidying | one row per observation β the shape we are aiming for | Part 6 |
| Β§7 β Data import | read_csv(), write_csv() |
Parts 1, 8 |
| Β§12 β Logical vectors | &, \|, %in%, is.na(), case_when() |
Parts 2b, 4b |
| Β§13 β Numbers | unit conversions, logs, as.numeric() |
Part 4 |
| Β§14 β Strings | str_to_lower(), str_squish(), fixing typoβd categories |
Part 7 |
| Β§18 β Missing values | NA, na_if(), sentinel codes like -999 |
Parts 2b, 7 |
| Β§20 β Spreadsheets | reading .xlsx with readxl |
Part 1 |