Activity: Wrangling Your Data

Importing more than one way, reshaping columns, and saving what you make

Hands-on worksheet: reading CSV and Excel files, filter/select/mutate/arrange, the full boolean-operator toolkit (%in%, is.na(), AND vs. OR), recoding with case_when(), log and unit transforms, catching duplicate rows, and write_csv().
Author

Bill Perry

Worksheet: Wrangling Your Data

How to use this worksheet

Work through each part in order, at your own pace. Type every line of code yourself into a plain R script — do not copy-paste. Blocks marked ▶ Run this are code you should type and execute. Blocks marked ✏️ Your turn ask you to write, modify, or answer something. Boxes marked 🚀 If you finish early are optional bonus material.


Part 1 · Import the same data two ways

▶ Run this in your Script:

library(tidyverse)
library(readxl)

pine_csv_df   <- read_csv("data/pine_needles.csv")
pine_excel_df <- read_excel("data/pine_needles.xlsx")

▶ Run this:

glimpse(pine_csv_df)
dim(pine_csv_df)

✏️ 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

▶ Run this:

pine_csv_df |> filter(n_s == "n") |> head()
pine_csv_df |> filter(length_mm > 20) |> head()

✏️ Your turn: Write a filter() that keeps only sunny (sun == "sunny") needles longer than 15 mm.

# Write your code here:

⚠️ Watch out! filter(n_s = "n") (one =) is an error. You need == to test equality.


Part 2b · filter() — OR, %in%, and missing values

▶ Run this:

# OR: shady side, OR any exceptionally long needle
pine_csv_df |> filter(n_s == "n" | length_mm > 22)

# %in%: match one of several named teams
pine_csv_df |> filter(group %in% c("cephalopods", "salmon"))

✏️ Your turn: Rewrite the %in% line above as a chain of | conditions instead — same result, longer code.

# Write your code here:

▶ Run this:

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 (sun == "sunny") needles that are also longer than 14mm — think about whether you need ,/& or |.

# Write your code here:


Part 3 · select() — picking columns

▶ Run this:

pine_csv_df |> select(n_s, length_mm) |> head()
pine_csv_df |> select(-date) |> head()

✏️ Your turn: Write a select() that keeps every column except tree_no.

# Write your code here:


Part 4 · mutate() — new and changed columns

▶ Run this:

pine_csv_df |>
  mutate(length_cm = length_mm / 10) |>
  head()

pine_csv_df |>
  mutate(log_length = log(length_mm)) |>
  head()

✏️ Your turn: Add a column called size_class that is "long" when length_mm > 18 and "short" otherwise. Use if_else().

# Write your code here:

✏️ Your turn: Does mutate() change how many rows your data frame has? How many columns did each example above add?
________________________

Tip

🚀 If you finish early: Add a second transformed column — a square-root transform (sqrt(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

▶ Run this:

pine_csv_df |>
  mutate(
    size_class = case_when(
      length_mm < 16 ~ "short",
      length_mm < 20 ~ "medium",
      TRUE           ~ "long"
    )
  ) |>
  count(size_class)

✏️ Your turn: Change the two cutoffs (16 and 20) to different values of your choosing. How do the counts in each bin shift?

# Write your code here:

✏️ Your turn: What happens if you delete the TRUE ~ "long" line and rerun? Try it, then explain why that’s dangerous.



____________________________________________________________________________________

▶ Run this:

pine_csv_df |>
  mutate(
    team_code = case_when(
      group == "cephalopods" ~ "CEPH",
      group == "salmon"      ~ "SALM",
      group == "crayfish"    ~ "CRAY",
      group == "snail"       ~ "SNAI",
      TRUE                   ~ "UNK"
    )
  ) |>
  distinct(group, 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.

Tip

🚀 If you finish early: Write a case_when() that uses two conditions per row (e.g., n_s == "n" & length_mm > 20 ~ "long shady") to build a combined category.


Part 5 · arrange() — sorting rows

▶ Run this:

pine_csv_df |> arrange(length_mm)
pine_csv_df |> arrange(desc(length_mm))

✏️ Your turn: Sort the data by n_s, and within each side, by length_mm from longest to shortest.

# Write your code here:


Part 6 · The full pipeline

▶ Run this:

pine_wrangled_df <- pine_csv_df |>
  filter(length_mm > 0) |>
  select(group, n_s, sun, length_mm) |>
  mutate(
    length_cm  = length_mm / 10,
    log_length = log(length_mm),
    size_class = case_when(
      length_mm < 16 ~ "short",
      length_mm < 20 ~ "medium",
      TRUE           ~ "long"
    )
  ) |>
  arrange(n_s, desc(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

▶ Run this:

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:

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()

▶ Run this:

write_csv(pine_wrangled_df, "data/pine_needles_wrangled.csv")

Check your data/ folder — the new CSV should be there, alongside (not instead of) the raw pine_needles.csv.

✏️ Your turn: Save your cleaned needle_clean_df from Part 7 to data/pine_needles_error_clean.csv.

# Write your code here:

⚠️ 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:

____________________________________________________________

Note

📤 What to turn in before next class

Upload both of these to the course management system:

  1. Your code — the scripts/ folder (or just 02_wrangling_data.R)
  2. This worksheet, with your written answers

Getting unstuck

When code breaks — and it will, that is normal:

  1. Read the error message out loud. R usually names the line and the problem.
  2. Check the usual suspects: did you run library(tidyverse) and library(readxl)?
    • Spelling?
    • A missing ) or a |> at the start of a line?
  3. ?function_name opens the built-in help page.
  4. Bring the exact error (copy-paste it) to class or office hours.

💡 Key idea: Every working scientist googles error messages daily. Getting stuck is not failing — it is the job.