Updated September 19, 2026 for the rebuilt snapshot. Event dates span January 1, 2008 through August 27, 2026. The old 1982–present claim was incorrect. The package contains flight crew, not an occurrences table; 2026 is partial.
The pinned September 1 NTSB source contains 31,124 events: 28,790 accidents and 2,334 incidents. All rows and fields from six selected tables are preserved, with unique keys and verified parent relationships. Source fields remain text. Separately named fields provide typed dates, injury counts and decimal coordinates.
Join at the correct grain
Aircraft join to events on ev_id. Engines, crew, findings and narratives join to aircraft on both ev_id and aircraft_key. Multiple child tables can multiply rows if joined directly; aggregate each before combining them.
import pandas as pd
events = pd.read_parquet("ntsb_aviation_events.parquet")
aircraft = pd.read_parquet("ntsb_aviation_aircraft.parquet")
findings = pd.read_parquet("ntsb_aviation_findings.parquet")
assert events.ev_id.is_unique
assert not aircraft.duplicated(["ev_id", "aircraft_key"]).any()
linked = aircraft.merge(
events[["ev_id", "event_date", "event_year", "fatal_injuries"]],
on="ev_id", how="left", validate="many_to_one")
assert len(linked) == len(aircraft)
assert linked.event_date.notna().all()
finding_counts = findings.groupby(["ev_id", "aircraft_key"]).size().rename("findings")
linked = linked.join(finding_counts, on=["ev_id", "aircraft_key"])
print(f"Events: {len(events):,}; aircraft: {len(linked):,}")Count recorded accidents by year
Use ev_type=ACC to select accidents and exclude the partial 2026 year for a historical annual comparison. Fatal-event share is a fraction of recorded accidents, not a risk per flight/hour. No exposure denominators are provided, and coverage differences or preliminary cases may affect comparisons.
accidents = events.loc[events.ev_type.eq("ACC") & events.event_year.lt(2026)].copy()
accidents["fatal_event"] = accidents.fatal_injuries.gt(0)
annual = accidents.groupby("event_year").agg(
recorded_accidents=("ev_id", "size"),
fatal_accidents=("fatal_event", "sum"))
annual["fatal_event_share"] = annual.fatal_accidents / annual.recorded_accidents
print(annual)Use coordinate quality explicitly
27,731 event coordinate pairs pass numeric bounds; 3,374 are missing and 19 are invalid. Use the separate decimal fields with coordinate_status=bounds_valid. Raw latitude/longitude are legacy text, and even a pair within bounds is not independently verified geographically.
points = events.loc[events.coordinate_status.eq("bounds_valid"),
["ev_id", "event_date", "latitude_decimal", "longitude_decimal"]]
assert points.latitude_decimal.between(-90, 90).all()
assert points.longitude_decimal.between(-180, 180).all()
print(events.coordinate_status.value_counts())Read findings within their limits
There are 3,132 events without narrative rows and 6,775 without findings rows. Absence does not establish no cause. Agency release notes state that cm_inpc supersedes the deprecated cause_factor field for newer findings. The six-table snapshot does not include administrative case status or certify that investigations are final.
The public sample contains the first 1,000 events ordered by ev_id and is not representative of the full period. The full $79 snapshot includes events, aircraft, engines, flight crew, findings and narratives, plus schemas, source hashes and validation documentation. Future updates are not included.