Classwork 1

Build and Audit a Greenhouse-Gas Change Pipeline

Guided dplyr practice using a greenhouse-gas inventory table.
Published

August 26, 2026

🎯 Research studio goal

This classwork applies Lecture 2: Data Transformation with dplyr.

Your final artifact is:

  • one six-row table comparing U.S. sector values in 2005 and 2017;
  • one defensible sentence describing the strongest pattern; and
  • a six-part audit covering unit, scope, order, missingness, duplicates, and claim.

Work with a partner. One person drives the code while the other predicts the output grain and audits each decision; switch roles halfway through.

By the end, you should be able to:

  1. choose dplyr verbs from a research question;
  2. track what one row represents through a pipeline;
  3. distinguish missing values from zeros;
  4. defend a denominator rule for percentage change; and
  5. write a claim that stays within the evidence.

📥 Data and provenance

We will use the course-hosted GHG_emissions_by.csv.

Variable Meaning available from the file
Party Reporting party or aggregate
Year Inventory year
Sector Environmental inventory sector
GHG_emissions Reported numeric inventory value
Warning

The file does not include a unit label or source documentation. Throughout this activity, say reported inventory value rather than silently assigning a unit or interpreting every value as a direct emission.

Setup

Run this code once.

library(tidyverse)

ghg_url <- "https://bcdanl.github.io/data/GHG_emissions_by.csv"

ghg <- read_csv(
  ghg_url,
  show_col_types = FALSE
)

Before looking at any summary, complete the sentence:

One intended row represents one ________________________________ record.

🧪 Part 1 · Audit before answering

1A. Predict the output grain

The pipeline below has no group_by(). How many rows should it return? Write your prediction, then replace the blanks and run it.

ghg |>
  summarise(
    rows = ___,
    parties = n_distinct(___),
    first_year = min(___),
    last_year = max(___),
    sectors = n_distinct(___),
    missing_values = sum(is.na(___))
  )

1B. Audit the intended key

If one row is intended to be one party × year × sector record, those three variables form the candidate key. Complete a pipeline that returns only duplicated keys.

ghg |>
  count(___, ___, ___) |>
  filter(___ > 1)

Write: What is the difference between checking a key and checking the truth of a value?

🕳️ Part 2 · Locate missingness

Count missing values by sector, retain the group size, compute the missing share, and place the most incomplete sector first.

missing_by_sector <- ghg |>
  group_by(___) |>
  summarise(
    rows = ___,
    missing = sum(is.na(___)),
    missing_share = mean(is.na(___)),
    .groups = "drop"
  ) |>
  arrange(desc(___))

missing_by_sector

Discuss with your partner:

  1. Why would sum(GHG_emissions, na.rm = TRUE) be different from claiming that every missing value equals zero?
  2. Which additional variable would you group by to learn whether missingness is concentrated in particular years or parties?

🗂️ Part 3 · Make the working table legible

Create lowercase names while preserving all 7,224 rows. Use select(), rename(), and relocate() once each.

ghg_working <- ghg |>
  select(___, ___, ___, ___) |>
  rename(
    party = ___,
    year = ___,
    sector = ___,
    emissions = ___
  ) |>
  relocate(___, ___, ___)

glimpse(ghg_working)

🇺🇸 Part 4 · Focus the environmental question

Our question is:

Between 2005 and 2017, which U.S. sector values changed the most, and which changes can be expressed as percentages without using a nonpositive denominator?

Create us_endpoints with only the United States, the two endpoint years, and all six sectors. Sort before using first() and last() later.

us_endpoints <- ghg_working |>
  filter(
    party == ___,
    year %in% c(___, ___)
  ) |>
  arrange(___, ___)

us_endpoints

Before running it, predict:

  • number of rows: ________
  • one row now represents: ________________________________

🧮 Part 5 · Change the grain and calculate change

Build one output row per sector. Keep an endpoint-completeness count, compute absolute change, and compute percentage change only when the 2005 value is positive.

us_change <- us_endpoints |>
  group_by(___) |>
  summarise(
    start = first(___),
    end = last(___),
    endpoint_values = sum(!is.na(___)),
    .groups = "drop"
  ) |>
  mutate(
    change = ___ - ___,
    pct_change = if_else(
      ___ > 0,
      100 * ___ / ___,
      NA_real_
    )
  ) |>
  arrange(___)

us_change

Answer in words:

  1. What does one row in us_change represent?
  2. Why did order matter before first() and last()?
  3. Why is pct_change missing for LULUCF even though both endpoints exist?
  4. Why should the positive change for a negative LULUCF value not be interpreted without sector documentation?

📊 Part 6 · Produce the analytical artifact

Create a presentation-ready six-row table. Keep the missing and nonpositive-baseline cases visible rather than silently dropping them.

final_table <- us_change |>
  transmute(
    sector,
    value_2005 = round(start),
    value_2017 = round(end),
    absolute_change = round(change),
    percent_change = round(pct_change, 1),
    audit_status = case_when(
      endpoint_values < 2 ~ "missing endpoint",
      start <= 0 ~ "nonpositive baseline",
      TRUE ~ "percent reported"
    )
  )

final_table

Write one defensible sentence

Use a structure like this, then improve it:

In this inventory file, the reported U.S. ________ value [rose/fell] by ________ between 2005 and 2017 (________% using a positive 2005 baseline); the file does not document ________, so the result should be interpreted as ________________________________.

🛡️ Part 7 · Peer audit the claim

Exchange artifacts with another pair. The reviewing pair should check each item and identify the exact line of code or sentence that supplies the evidence.

Revise one line of code or one phrase after the audit. Record what changed and why.

🐦 Optional extension · Transfer the pipeline to climate tweets

If your pair finishes early, use the four 2017 climate-tweet files from the lecture. The CSVs contain only content; sentiment and strength must be recovered from the filenames.

tweet_urls <- c(
  positive_weak =
    "https://bcdanl.github.io/data/UStweets_positive_weak_2017.csv",
  positive_strong =
    "https://bcdanl.github.io/data/UStweets_positive_strong_2017.csv",
  negative_weak =
    "https://bcdanl.github.io/data/UStweets_negative_weak_2017.csv",
  negative_strong =
    "https://bcdanl.github.io/data/UStweets_negative_strong_2017.csv"
)

tweets <- bind_rows(
  positive_weak = read_csv(tweet_urls[["positive_weak"]]),
  positive_strong = read_csv(tweet_urls[["positive_strong"]]),
  negative_weak = read_csv(tweet_urls[["negative_weak"]]),
  negative_strong = read_csv(tweet_urls[["negative_strong"]]),
  .id = "sample"
) |>
  mutate(
    sentiment = if_else(
      str_starts(sample, "positive"), "positive", "negative"
    ),
    strength = if_else(
      str_ends(sample, "strong"), "strong", "weak"
    )
  )

Choose one topic—hurricane, wildfire, flood, or drought—and produce one table with matching rows and within-topic shares by sentiment and strength. Then audit either repeated text or suspicious encoding.

Your claim must begin:

Within these four labeled tweet files, …

It may not claim to represent all U.S. tweets or public opinion because the files have no sampling documentation, tweet IDs, dates, users, or locations.

✅ Exit ticket

Submit or save:

  1. final_table;
  2. your revised one-sentence claim; and
  3. this completion:

The most consequential decision in our pipeline was __________________ because __________________.

Then annotate your pipeline with the row grain after filter(), after group_by() + summarise(), and in the final artifact.

Back to top