Assignment 5A: Airline Delays, Tidying and Transforming Data
Author
Noelle
Published
September 25, 2026
Introduction
This report tidies and analyzes a small table of arrival delays for two airlines, Alaska and AM West, across five destinations (Los Angeles, Phoenix, San Diego, San Francisco, Seattle). The table comes from Numbersense by Kaiser Fung (McGraw-Hill, 2013), as given in the DATA 607 assignment. We recreate it as a CSV in its original “wide” layout (blank cells included), clean and reshape it with tidyr and dplyr, and compare the airlines’ delay percentages, first overall and then city by city. The two comparisons disagree, and we explain why.
The data file airline_delays.csv was typed in by hand from the source table and lives in this GitHub repository so the report is reproducible. We checked it against an independent total: 11,000 flights (3,775 Alaska + 7,225 AM West).
Approach
I started with the airline delays table and kept its original layout in a CSV file. I then cleaned the blank rows and airline names and changed the data into a format with one row for each airline, city, and delay category. From there, I calculated the percentage of delayed flights for each airline overall and for each city. Finally, I compared the results and explained why the overall comparison gives a different impression from the city-by-city comparison.
Step 1: Read the data
The CSV keeps the source layout on purpose: the airline name appears only on the first row of each pair, the first two header cells are blank, and a blank row separates the airlines.
read_csv() names the two blank headers ...1 and ...2 and keeps the spacer row as a row of NA values.
Step 2: Clean the table and fill in the missing airline names
We name the two columns, drop the spacer row, and copy each airline name down into the blank cells below it. The order matters: if we filled first, the spacer row would inherit “ALASKA” and would no longer be entirely NA, so it could not be dropped.
clean <- raw |>rename(airline =1, status =2) |>filter(!if_all(everything(), is.na)) |># drop the all-NA spacer row firstfill(airline, .direction ="down") # then fill the airline name downclean
# A tibble: 4 × 7
airline status `Los Angeles` Phoenix `San Diego` `San Francisco` Seattle
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ALASKA on time 497 221 212 503 1841
2 ALASKA delayed 62 12 20 102 305
3 AM WEST on time 694 4840 383 320 201
4 AM WEST delayed 117 415 65 129 61
# A tibble: 20 × 4
airline status city flights
<chr> <chr> <chr> <dbl>
1 ALASKA on_time Los Angeles 497
2 ALASKA on_time Phoenix 221
3 ALASKA on_time San Diego 212
4 ALASKA on_time San Francisco 503
5 ALASKA on_time Seattle 1841
6 ALASKA delayed Los Angeles 62
7 ALASKA delayed Phoenix 12
8 ALASKA delayed San Diego 20
9 ALASKA delayed San Francisco 102
10 ALASKA delayed Seattle 305
11 AM WEST on_time Los Angeles 694
12 AM WEST on_time Phoenix 4840
13 AM WEST on_time San Diego 383
14 AM WEST on_time San Francisco 320
15 AM WEST on_time Seattle 201
16 AM WEST delayed Los Angeles 117
17 AM WEST delayed Phoenix 415
18 AM WEST delayed San Diego 65
19 AM WEST delayed San Francisco 129
20 AM WEST delayed Seattle 61
stopifnot(nrow(flights_long) ==20) # 2 airlines x 2 statuses x 5 citiesstopifnot(sum(flights_long$flights) ==11000)
Step 4: Compute delay percentages
We compare percentages, not counts, because AM West operates almost twice as many flights as Alaska. For each airline and city we put on_time and delayed side by side, then divide delayed flights by all flights. The overall rate sums the counts first and then divides; it is not the average of the five city percentages.
ggplot(overall, aes(airline, pct_delayed, fill = airline)) +geom_col(width =0.55) +geom_text(aes(label = scales::percent(pct_delayed, accuracy =0.1)), vjust =-0.4) +scale_y_continuous(labels = scales::percent, limits =c(0, 0.16)) +scale_fill_manual(values =c("ALASKA"="#1F4E79", "AM WEST"="#D98E04")) +labs(title ="Overall share of flights delayed", x =NULL, y ="Delayed flights") +theme_minimal() +theme(legend.position ="none")
Across all five cities, Alaska delayed 501 of 3,775 flights (13.3%) and AM West delayed 787 of 7,225 (10.9%). By this measure AM West looks better by about 2.4 percentage points.
# A tibble: 5 × 5
city ALASKA `AM WEST` gap_pp better
<chr> <dbl> <dbl> <dbl> <chr>
1 Los Angeles 0.111 0.144 -3.3 ALASKA
2 Phoenix 0.0515 0.0790 -2.7 ALASKA
3 San Diego 0.0862 0.145 -5.9 ALASKA
4 San Francisco 0.169 0.287 -11.9 ALASKA
5 Seattle 0.142 0.233 -9.1 ALASKA
stopifnot(nrow(city_compare) ==5)stopifnot(all(city_compare$better =="ALASKA"))stopifnot(all(abs(city_compare$gap_pp -c(-3.3, -2.7, -5.9, -11.9, -9.1)) <1e-9))ggplot(by_city, aes(city, pct_delayed, fill = airline)) +geom_col(position ="dodge") +geom_text(aes(label = scales::percent(pct_delayed, accuracy =0.1)),position =position_dodge(width =0.9), vjust =-0.3, size =3) +scale_y_continuous(labels = scales::percent, limits =c(0, 0.33)) +scale_fill_manual(values =c("ALASKA"="#1F4E79", "AM WEST"="#D98E04")) +labs(title ="Share of flights delayed, by city", x =NULL, y ="Delayed flights", fill =NULL) +theme_minimal() +theme(legend.position ="bottom")
City by city, the conclusion reverses. Alaska has a lower delay rate in all five cities, by 2.7 to 11.9 percentage points. The gap is largest in San Francisco and Seattle.
Why the two comparisons disagree (Simpson’s paradox)
An airline’s overall delay rate is a weighted average of its city rates, and the two airlines use very different weights. The table shows how each airline’s flights are spread across the cities.
# The weighted sum of city rates reproduces the overall rate exactlycheck <- mix |>summarise(x =sum(share_of_airline * pct_delayed), .by = airline) |>pull(x)stopifnot(isTRUE(all.equal(check, overall$pct_delayed)))
Phoenix is the easiest city for both airlines, and 73% of AM West’s flights go there. Seattle and San Francisco are hard for both, and 57% of Alaska’s flights go to Seattle. To compare the airlines fairly, we give both the same city mix (each city’s share of all 11,000 flights) and re-weight their own city rates:
The overall comparison and the city-by-city comparison disagree, which is an example of Simpson’s paradox. Overall, AM West appears to have fewer delays (10.9% vs 13.3%), but Alaska has a lower delay rate in each of the five cities. The reason is the mix of destinations: 73% of AM West’s flights go to Phoenix, the city with the fewest delays, while 57% of Alaska’s flights go to Seattle, which has many. Once both airlines are given the same city mix, Alaska’s delay rate (9.2%) is well below AM West’s (14.5%).
Conclusions and Findings
Which airline performs better? TODO: state your answer and which of the two views (overall or city by city) you trust more, and why.
What is the lesson about overall percentages? TODO: what should we check before trusting an overall rate?
What are the limits of this data? TODO: think about only five cities, no dates or flight distances, and what “delayed” means.
How would you extend or verify this? TODO: e.g. use the full flight data, add time of day or season, or test whether the gaps are statistically significant.
AI Use
Anthropic. (2026). Claude Sonnet 5 [Large language model]. https://claude.ai. Accessed September 24, 2026.
I used Claude to help me understand the Elo formula and regular expressions, work through parts of the R code, and proofread my explanations. I reviewed the code, checked the results against calculations, and revised the writing before submitting the report.