Business analytics
Flight Delay Analysis
- Company
American Airlines- Job positions
- Data AnalystAnalytics EngineerBusiness Analyst
- Topics
- Root cause analysisWindow functionsTime zonesData cleaningSQL
The scenario
American Airlines: Separate the delay a flight inherits from the delay it creates, and trace the worst delays back to the flights that caused them.
One American Airlines aircraft flies four to six flights a day, one after another, and the schedule leaves it just enough time on the ground between them. So a flight that lands late makes the next one late, and the delay follows that aircraft until a long gap lets it catch up. You're an analyst on the network operations team, which can list the late flights but not say what started them.
Your task
Split every departure delay into what the aircraft inherited and what the airport added, then rank the flights that cost the most minutes to the ones behind them.
Instructions
- 1Load and clean the flights. The table arrives unordered, so put them in order by tail number (the aircraft's registration) and then scheduled departure, treating each aircraft's first flight of the day as a fresh start.
- 2Join each airport and aircraft type to its minimum turn time, which is the least time that aircraft needs on the ground there before it can fly again.
- 3The earliest a flight could leave is the previous flight's actual arrival plus that minimum turn. Its inherited delay is how much later that is than its scheduled departure, and zero when the aircraft was back in time.
- 4The created delay is the departure delay minus the inherited delay. Keep its sign, so a flight turned faster than the minimum shows as negative rather than zero.
- 5Decide what to do with flights that have no actual times, where the aircraft never went, and explain your choice. Save leg_delays.csv with the columns leg_id, departure_delay_minutes, inherited_delay_minutes and created_delay_minutes.
- 6A delay starts at a flight whose created delay is 15 minutes or more and whose inherited delay is under 15. Count the unbroken run of later flights on the same aircraft inheriting 15 or more, and save cascade_roots.csv with the columns leg_id, flight_number, tail_number, origin, scheduled_departure, created_delay_minutes, downstream_legs_affected and downstream_delay_minutes, worst first.
- 7Report the airports and local times of day where delays start most often, and say how often the recorded reason agrees with your arithmetic.
Datasets
All files come in one download.
flight_legs.csv
About 11,000 flights from November 3 to November 16, 2025, one row per leg flown, in no particular order. Every timestamp is in UTC.
11,190 rows · 12 columns · 1.3 MB
| Column | Type | Description |
|---|---|---|
| leg_id | integer | Unique ID of the flight. |
| flight_number | text | The flight number it was flown as. |
| tail_number | text | The aircraft that flew it. This is what links one flight to the next. |
| aircraft_type | text | Aircraft type, which decides the minimum turn. |
| origin | text | Airport it departed from. |
| destination | text | Airport it arrived at. |
| scheduled_departure | datetime | Scheduled departure, in UTC. |
| scheduled_arrival | datetime | Scheduled arrival, in UTC. |
| actual_departure | datetime | Actual departure, in UTC. Empty where the flight never went. |
| actual_arrival | datetime | Actual arrival, in UTC. Empty where the flight never went. |
| scheduled_block_minutes | integer | Scheduled time from departure to arrival. |
| delay_reason_code | text | Reason recorded by the airport: weather, atc, crew, maintenance, ground_ops or late_inbound_aircraft. Empty on flights that left on time, and not always filled in on those that did not. |
Preview the first 5 rowsHide preview
| leg_id | flight_number | tail_number | aircraft_type | origin | destination | scheduled_departure | scheduled_arrival | actual_departure | actual_arrival | scheduled_block_minutes | delay_reason_code |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 3110087 | AA2286 | N449AA | A320 | RDU | PHX | 2025-11-04 17:41:00 | 2025-11-04 22:32:00 | 2025-11-04 18:04:00 | 2025-11-04 23:11:00 | 291 | ground_ops |
| 3104564 | AA2163 | N367AA | A320 | CMH | MIA | 2025-11-16 05:40:00 | 2025-11-16 08:28:00 | 2025-11-16 05:51:00 | 2025-11-16 08:45:00 | 168 | empty |
| 3103484 | AA2883 | N351AA | B738 | MIA | OKC | 2025-11-17 01:52:00 | 2025-11-17 05:11:00 | 2025-11-17 01:46:00 | 2025-11-17 05:02:00 | 199 | empty |
| 3105697 | AA1496 | N384AA | E175 | DCA | MCI | 2025-11-11 19:02:00 | 2025-11-11 21:44:00 | 2025-11-11 18:58:00 | 2025-11-11 21:39:00 | 162 | empty |
| 3107957 | AA1956 | N417AA | A320 | BNA | DFW | 2025-11-12 21:21:00 | 2025-11-12 23:19:00 | 2025-11-12 21:29:00 | 2025-11-12 23:27:00 | 118 | empty |
airports.csv
The 24 airports in the two weeks of flying, with the time zone each one keeps.
24 rows · 5 columns · 2 KB
| Column | Type | Description |
|---|---|---|
| airport_code | text | Three letter airport code. |
| airport_name | text | Full name of the airport. |
| city | text | City the airport serves. |
| timezone | text | The airport's time zone, for converting UTC to local time. |
| is_hub | text | Whether the airport is a hub: yes or no. |
Preview the first 5 rowsHide preview
| airport_code | airport_name | city | timezone | is_hub |
|---|---|---|---|---|
| DFW | Dallas Fort Worth International | Dallas | America/Chicago | yes |
| CLT | Charlotte Douglas International | Charlotte | America/New_York | yes |
| ORD | Chicago OHare International | Chicago | America/Chicago | yes |
| MIA | Miami International | Miami | America/New_York | yes |
| PHX | Phoenix Sky Harbor International | Phoenix | America/Phoenix | yes |
minimum_turn_times.csv
The least ground time allowed for each aircraft type at each airport.
120 rows · 3 columns · 1 KB
| Column | Type | Description |
|---|---|---|
| airport_code | text | The airport. |
| aircraft_type | text | The aircraft type. |
| minimum_turn_minutes | integer | Minutes the aircraft needs on the ground before it can fly again. |
Preview the first 5 rowsHide preview
| airport_code | aircraft_type | minimum_turn_minutes |
|---|---|---|
| DFW | A319 | 45 |
| DFW | A320 | 45 |
| DFW | A321 | 50 |
| DFW | B738 | 45 |
| DFW | E175 | 35 |
Hints
Once the flights are in order by aircraft and scheduled departure, the previous flight's actual arrival is the row before it within that aircraft.
legs = legs.sort_values(["tail_number", "scheduled_departure"])
legs["prev_arrival"] = legs.groupby("tail_number")["actual_arrival"].shift(1)Every timestamp is in UTC. Saying delays start at 13:00 tells an airport nothing, so convert to its own time zone before reporting an hour of the day.
legs["local"] = legs["scheduled_departure"].dt.tz_localize("UTC").dt.tz_convert("America/Chicago")Deliverable
A public GitHub repo with your analysis (a notebook, scripts or SQL), leg_delays.csv and cascade_roots.csv, and a README with how you built the sequence, your handling of flights that never went, and what you found.
When you're done, post your repo in the Solutions tab to share it with other learners.
What grading checks
Use this checklist to review your own work before you post and share it.
- Submitted GitHub repo is public and reachable.
- Repo contains at least one notebook, script or SQL file.
- leg_delays.csv has one row per flight, with flights that never went left empty rather than counted as on time.
- The inherited and created split matches the expected values for at least 98 percent of flights that flew, with created delay keeping its sign.
- The ten worst starting flights in cascade_roots.csv match the expected ten.
- Flights that never went do not pass a stale arrival to the next flight, and the first flight of each aircraft's day inherits nothing.
- Times of day are reported in the airport's local time, and the recorded reason is checked against the arithmetic rather than trusted instead of it.