·9 min read

Storm Damage Costs by Decade: A NOAA Data Deep Dive

Explore 75 years of US storm damage data with Python. Analyze property and crop losses by event type, decade, and state using the NOAA Storm Events Database — 2M+ events from 1950 to present.

NOAAclimatePythonpandasgeospatialtutorialweather

Since 1950, NOAA's National Centers for Environmental Information has catalogued every significant weather event in the United States — tornadoes, floods, hurricanes, hail storms, winter storms, and dozens of other types. Each event record includes the date, location, casualties, property damage, crop damage, and a narrative description written by NWS forecasters. The result is one of the most complete climate impact datasets in existence: 2 million+ events spanning 75 years.

In this tutorial we'll load the ClarityStorm NOAA Storm Events dataset, analyze how storm damage costs have evolved by decade, identify the most destructive event types and states, and plot geospatial damage maps using the event coordinates.

What's in the Dataset

The ClarityStorm NOAA release ships three linked tables as CSV and Parquet: details (one row per storm event), fatalities (one row per death linked to an event), and locations (additional geographic points per event). The details table is the primary analytical surface — it contains the parsed damage amounts, casualty counts, event type, coordinates, and narratives. Damage fields are pre-parsed from NOAA's shorthand strings (e.g. "50.00K", "1.2M") into USD float columns ready for aggregation.

  • 2M+ storm events (details table), 1950–2024
  • DAMAGE_PROPERTY_USD and DAMAGE_CROPS_USD — pre-parsed float columns
  • TOTAL_DAMAGE_USD — property + crops combined
  • 48 event types: Tornado, Flash Flood, Thunderstorm Wind, Hurricane, Ice Storm, Wildfire, and more
  • BEGIN_LAT / BEGIN_LON for geospatial mapping
  • EPISODE_NARRATIVE and EVENT_NARRATIVE free text for NLP
  • Linked fatalities table with age, sex, and location of each death

Loading the Data

python
import pandas as pd

# Load the details table — primary analytical surface
details = pd.read_parquet("noaa_storm_events_details.parquet")

print(f"Events: {len(details):,}")
print(f"Date range: {details['YEAR'].min()} – {details['YEAR'].max()}")
print(f"Total property damage: ${details['DAMAGE_PROPERTY_USD'].sum():,.0f}")
print(f"Total crop damage: ${details['DAMAGE_CROPS_USD'].sum():,.0f}")
print(f"Total deaths: {details['DEATHS_TOTAL'].sum():,.0f}")

Damage Costs by Decade

The most striking pattern in the NOAA data is the dramatic increase in recorded damage costs over time. Some of this reflects genuine increases in extreme weather frequency and intensity — but a large component is also growth in property values, improved damage reporting, and the expansion of the dataset's event-type coverage (NOAA added many event types in 1996). Any decade-over-decade comparison should account for these structural breaks.

python
import matplotlib.pyplot as plt
import numpy as np

# Assign decade
details["decade"] = (details["YEAR"] // 10) * 10

decade_damage = (
    details.groupby("decade")
    .agg(
        property_damage=("DAMAGE_PROPERTY_USD", "sum"),
        crop_damage=("DAMAGE_CROPS_USD", "sum"),
        events=("EVENT_ID", "count"),
        deaths=("DEATHS_TOTAL", "sum"),
    )
    .reset_index()
)
decade_damage["total_damage_B"] = (
    decade_damage["property_damage"] + decade_damage["crop_damage"]
) / 1e9

fig, ax = plt.subplots(figsize=(12, 6))
ax.bar(decade_damage["decade"], decade_damage["total_damage_B"],
       width=8, color="#0ea5e9", alpha=0.85)
ax.set_xlabel("Decade")
ax.set_ylabel("Total Damage ($ billions)")
ax.set_title("US Storm Damage Costs by Decade — NOAA Storm Events 1950–2024")
plt.tight_layout()
plt.savefig("noaa_damage_by_decade.png", dpi=150)

Most Destructive Event Types

Hurricanes and tropical storms dominate total property damage despite relatively low event counts — single landfalling hurricanes routinely account for tens of billions of dollars. Flash floods are the leading cause of weather-related fatalities. Tornadoes punch above their weight on a per-event basis, particularly in the EF3–EF5 range.

python
# Top event types by total damage
event_damage = (
    details.groupby("EVENT_TYPE")
    .agg(
        total_damage=("TOTAL_DAMAGE_USD", "sum"),
        events=("EVENT_ID", "count"),
        deaths=("DEATHS_TOTAL", "sum"),
    )
    .sort_values("total_damage", ascending=False)
    .head(15)
)

event_damage["damage_per_event_M"] = (
    event_damage["total_damage"] / event_damage["events"] / 1e6
)

print(event_damage[["total_damage","events","deaths","damage_per_event_M"]]
      .rename(columns={
          "total_damage": "Total Damage ($)",
          "events": "Events",
          "deaths": "Deaths",
          "damage_per_event_M": "Avg Damage/Event ($M)"
      })
      .to_string(float_format="{:,.1f}".format))

State-Level Damage Rankings

Texas, Florida, and Louisiana lead total storm damage — driven by hurricane exposure and sheer geographic scale. But normalising by land area or GDP reshuffles the ranking considerably. North Carolina and South Carolina show high damage-to-size ratios from hurricane tracks. The Great Plains states dominate tornado event counts but with lower average damage per event than coastal hurricane states.

python
state_damage = (
    details.groupby("STATE")
    .agg(
        total_damage=("TOTAL_DAMAGE_USD", "sum"),
        deaths=("DEATHS_TOTAL", "sum"),
        events=("EVENT_ID", "count"),
    )
    .sort_values("total_damage", ascending=False)
    .head(20)
)

fig, ax = plt.subplots(figsize=(10, 8))
(state_damage["total_damage"] / 1e9).sort_values().plot(
    kind="barh", ax=ax, color="#0ea5e9", alpha=0.85
)
ax.set_xlabel("Total Property + Crop Damage ($ billions)")
ax.set_title("Top 20 States by Storm Damage — NOAA 1950–2024")
plt.tight_layout()
plt.savefig("noaa_damage_by_state.png", dpi=150)

Geospatial Damage Map

Events with coordinates (available for post-2007 records and most tornado tracks) can be plotted directly. Filtering to events above a damage threshold reveals the geography of billion-dollar disasters: the Gulf Coast hurricane corridor, the Mississippi River flood plain, Tornado Alley, and California wildfire footprints all appear as dense clusters.

python
# Map high-damage events with coordinates
mapped = details[
    (details["BEGIN_LAT"].notna()) &
    (details["BEGIN_LON"].notna()) &
    (details["TOTAL_DAMAGE_USD"] > 1_000_000)  # $1M+ events only
].copy()

fig, ax = plt.subplots(figsize=(16, 9))
scatter = ax.scatter(
    mapped["BEGIN_LON"],
    mapped["BEGIN_LAT"],
    c=np.log1p(mapped["TOTAL_DAMAGE_USD"]),
    cmap="YlOrRd",
    s=4,
    alpha=0.5,
)
ax.set_xlim(-130, -60)
ax.set_ylim(22, 52)
ax.set_title("US Storm Events with $1M+ Damage — NOAA (coordinates available)")
plt.colorbar(scatter, ax=ax, label="log(damage USD)")
plt.tight_layout()
plt.savefig("noaa_damage_map.png", dpi=150)

Tornado Deep Dive

The dataset includes Fujita and Enhanced Fujita scale ratings for tornadoes, along with path length and width in miles and yards respectively. EF3–EF5 tornadoes account for under 2% of all tornado events but over 70% of tornado fatalities. The dataset's TOR_LENGTH field enables path-length analysis — useful for modelling the probability of a given structure being in a tornado's damage path.

python
tornadoes = details[details["EVENT_TYPE"] == "Tornado"].copy()

# Fatalities by EF scale
ef_stats = (
    tornadoes.groupby("TOR_F_SCALE")
    .agg(events=("EVENT_ID","count"), deaths=("DEATHS_TOTAL","sum"),
         avg_path_miles=("TOR_LENGTH","mean"))
    .reset_index()
    .sort_values("TOR_F_SCALE")
)
print(ef_stats.to_string(index=False))

Use Cases

  • Climate risk modelling: long-horizon damage trends for insurance underwriting and catastrophe modelling
  • Agricultural loss estimation: 75 years of crop damage by state and event type for crop insurance pricing
  • Infrastructure resilience: identify counties with repeated high-damage events for public investment prioritisation
  • NLP training data: EPISODE_NARRATIVE and EVENT_NARRATIVE fields for weather event classification models
  • Fatality analysis: age, sex, and location-of-death fields support public health research on storm mortality

The free sample contains 1,000 rows from the details table. The complete NOAA Storm Events dataset covers 2M+ events across all three tables (1950–2024), available as CSV and Parquet with a commercial license for $79.

Get the Full Dataset

NOAA Storm Events Database 1950–Present

From $79