Skip to content

Storm Events annual tables

Available since v0.8 as noaa:storm-events. Anonymous NCEI bulk access returns whole annual tables, compressed with gzip. The default is the details table, one row per event. -p table=fatalities and -p table=locations select its two siblings, which NCEI publishes in the same directory under the same naming and revision scheme (available since v0.22.0). The adapter supports schema v1.0; it does not guess how to interpret a newer schema.

The three tables

table One row per Columns
details (default) event the event record: type, place, times, impacts, narratives
fatalities death FAT_YEARMONTH, FAT_DAY, FAT_TIME, FATALITY_ID, EVENT_ID, FATALITY_TYPE, FATALITY_DATE, FATALITY_AGE, FATALITY_SEX, FATALITY_LOCATION
locations point of an event YEARMONTH, EPISODE_ID, EVENT_ID, LOCATION_INDEX, RANGE, AZIMUTH, LOCATION, LATITUDE, LONGITUDE, LAT2, LON2

EVENT_ID joins both to details; nothing is joined for you. An event has no fatalities row unless someone died, and may have several location rows, numbered by LOCATION_INDEX: the 6 May 2024 Osage County EF4 (event 1184254) has two of each. A location is a named place with the RANGE in miles and AZIMUTH from it, plus the point itself in decimal degrees; LAT2 and LON2 repeat it in a packed degrees-and-minutes form. Those points partly fill the gap between the start and end coordinates on a details row and a surveyed damage path.

Checked on 2026-09-18: all three tables have a file for every year from 1950 to 2026. Fatalities rows exist throughout (11 in 1950). The locations files hold rows only from 1996; each earlier one is a header and nothing else, so an early-year request succeeds and opens as an empty frame rather than failing. The tables of one year are revised together and carry the same creation date.

Both dates are required and interpreted in UTC for annual file selection. Every calendar year touched is returned in full: May 1–31 selects the complete year; December 31–January 1 selects both years. Asset time bounds label those whole file years, not precise coverage of local event timestamps. The files are split by each event's local begin date, so an event early on January 1 UTC can sit in the previous year's file: at UTC−6 it is still December 31 until 06:00 UTC, and at UTC−11 until 11:00 UTC; Guam, at UTC+10, is already in the next year from 14:00 UTC on December 31. A window within those hours of a new year selects only one file; start it a day earlier, or end it a day later, to fetch both. Dates before 1950 and a missing requested year are errors, so a multi-year request cannot silently succeed with only some years. Location/bbox, variables, and text are rejected, and table is the only parameter, so spatial_subset and variable_subset are false: selecting an annual object performs no server-side row subsetting. Only temporal_subset is true, because the requested dates choose which annual objects are returned.

The directory lists filenames such as StormEvents_details-ftp_v1.0_d2024_c20260728.csv.gz: d identifies the data year and c the creation date. Resolution chooses the greatest valid creation date per year. The complete filename is the stable asset ID; URL, original compressed bytes, size, and checksum are preserved. Exact integer directory sizes are used when present; approximate sizes are left unknown. A lockfile restores its pinned URL without listing current revisions, so pull --force is what moves to a newer annual revision.

Upstream revisions

Agencies revise files in place. A locked restore downloads each pinned URL and fails with a checksum mismatch when the bytes have changed; accept the new bytes for chosen entries with pull --update, or re-resolve the whole manifest with pull --force. A lockfile detects changed data; it does not archive it. Keep the cache with the manifest and lockfile. See provenance and drift.

FetchedAsset.open() uses the pandas extra and a local gzip stream. It preserves raw identifier strings and records source provenance in DataFrame attributes; it does not decompress into the cache. EVENT_ID identifies event records, while EPISODE_ID can group several events. CZ_TYPE distinguishes county, forecast-zone, and marine records; CZ_FIPS is not always a county code. Dates/times in rows are local, with CZ_TIMEZONE needed for instant conversion. The executed notebook filters on reported calendar dates and keeps timezone labels visible.

The timezone rule

CZ_TIMEZONE is a local-standard-time label ending in its whole-hour UTC offset, so CST-6 is UTC−6 and Guam's GST10, which omits the sign, is UTC+10. The labels seen in the annual files are CST-6, EST-5, MST-7, PST-8, AST-4, AKST-9, HST-10, SST-11, and GST10, plus a few daylight spellings (CDT-5, EDT-4, PDT-7) whose trailing offset is still the one to use. No Python timezone accepts these strings, and applying a named regional zone would add daylight saving the reports do not use.

Files through 2006 write the label bare, with no offset: CST, not CST-6. Eight bare labels name one offset wherever they appear in the archive and are converted with it: CST −6, EST −5, MST −7, PST −8, and HST −10, and the daylight labels CDT −5, EDT −4, and MDT −6, a few rows a year, read at their word as CDT-5 is. The rest are left unconverted rather than guessed (ADR 0033):

  • AST labels both Alaska (UTC−9) and Puerto Rico and the Virgin Islands (UTC−4).
  • SST labels both American Samoa (UTC−11) and Guam (UTC+10).
  • UNK says nothing.

STATE tells the first two apart, so a caller who needs those rows can convert them by hand. The local timestamps carry two-digit years; 50 to 99 are read as 1950 to 1999, since the archive begins in 1950, where the usual pivot would put its first nineteen years a century late.

When the frame keeps CZ_TIMEZONE, open() and open_csv() add BEGIN_UTC for a kept BEGIN_DATE_TIME and END_UTC for a kept END_DATE_TIME, as timezone-aware UTC timestamps, leaving the original columns alone. A frame that keeps a local column without CZ_TIMEZONE gets no UTC column and a UserWarning naming what to add to usecols. A row whose label yields no offset, or whose timestamp does not parse, gets NaT. frame.attrs["usdata"]["derived"] lists, per derived column, the rule used, the count of such rows as unparsed, and under labels_without_offset each label that gave no offset with how many rows carry it.

frame = item.open_csv(usecols=["EVENT_ID", "BEGIN_DATE_TIME", "END_DATE_TIME", "CZ_TIMEZONE"])
print(frame[["BEGIN_DATE_TIME", "CZ_TIMEZONE", "BEGIN_UTC", "END_UTC"]].head())
print(frame.attrs["usdata"]["derived"])  # column, source, rule, unparsed, labels_without_offset
hours = pd.to_numeric(frame.CZ_TIMEZONE.str.extract(r"^[A-Za-z]+([+-]?\d{1,2})$", expand=False))
local = pd.to_datetime(frame.BEGIN_DATE_TIME, format="%d-%b-%y %H:%M:%S", errors="coerce")

The last two lines are the manual form for files from 2007 on: subtract pd.to_timedelta(hours, unit="h") from local and call .dt.tz_localize("UTC") to get the same instants without the reader. Earlier files also need the bare labels mapped and, before 1969, the century corrected, which %y gets wrong.

Reported events, impacts, and damage ratings require care when aggregating: physical storms can span several records, historical reporting varies, and missing reports do not establish an absence of hazards. Source field meanings are documented in the NCEI bulk format reference. The archive README describes filenames and revisions; dataset metadata describes historical coverage.

See the service research notes for dated upstream probes.

Metadata sources

Every value in the catalog entry's resolution, cadence, citation, terms, variables, and limits comes from one of these pages. A field the agency does not publish is left empty rather than estimated.

  • Updates: the archive README, which says the current yearly file is updated each month.
  • Resolution and variables: the NCEI bulk format reference for the record granularity and the identifying columns this guide names. The full column set is larger.
  • Terms and citation: the NCEI dataset record, whose use constraint is only "cite dataset when used as a source", so the entry uses the agency, product, and access form.
  • Latency is empty: neither the README nor the dataset record states how far behind the present the files run.

All NOAA datasets.

Reference

noaa:storm-events · Released · Included since usdata 0.8. Storm Events Database.

At a glance

Parameters

Pass these as --param name=value to the CLI, as params: entries in a manifest, or as keyword arguments to build_query.

Parameter Meaning
table Annual table: details (default, one row per event), fatalities (one row per death), or locations (points per event, from 1996); EVENT_ID joins them.

Variables

Variable Units Meaning
EVENT_ID — Identifier of one event record
EPISODE_ID — Identifier grouping the events of one storm episode
CZ_TYPE — County (C), forecast zone (Z), or marine (M) record
CZ_FIPS — County or zone code; not always a county FIPS code
CZ_TIMEZONE — Time zone of the record's local timestamps
TOR_F_SCALE — Tornado rating as text, such as EF1

Catalog facts

  • Availability: since 0.8
  • Domain: Severe weather
  • Spatial resolution: One record per county, forecast zone, or marine zone touched by an event
  • Temporal resolution: One record per reported event, timestamped in the reporting office's local time
  • Updates: The current year's file is updated each month
  • Terms of use: https://www.ncei.noaa.gov/metadata/geoportal/rest/metadata/item/gov.noaa.ncdc:C00510/html
  • Citation: NOAA National Centers for Environmental Information, Storm Events Database, accessed via usdata
  • Catalog date range: 1950-01-01 to open-ended
  • Coverage varies by station, product, and date; the range above does not guarantee observations.
  • Upstream documentation
  • License: US Government Work (public domain)
  • Transport: http
  • Adapter: usdata.providers.noaa.storm_events:StormEvents