Synopsis

This analysis draws on the U.S. NOAA Storm Database (1950 to November 2011) to determine which types of weather events do the most damage to population health and which cause the greatest economic losses nationwide. Working from the raw compressed CSV file, I consolidated the inconsistent EVTYPE labels into the official event categories defined in the National Weather Service Storm Data documentation (NWSI 10-1605). Harm to health is measured as total fatalities plus injuries. Economic loss is measured as combined property and crop damage in US dollars, calculated after converting the PROPDMGEXP and CROPDMGEXP multiplier codes (K, M, B) into numeric values. Tornadoes are by far the largest source of fatalities and injuries, followed by heat-related events, thunderstorm wind and flooding. The costliest events economically are floods, hurricanes/typhoons, storm surge and tornadoes. Two figures present the ten most damaging event types for each outcome.

Data Processing

Loading the raw data

The analysis begins with the original repdata_data_StormData.csv.bz2 file. If it is not already present, it is downloaded and read directly from the compressed file with no pre-processing outside this document.

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggplot2)
library(tidyr)

url  <- "https://d396qusza40orc.cloudfront.net/repdata%2Fdata%2FStormData.csv.bz2"
file <- "repdata_data_StormData.csv.bz2"
if (!file.exists(file)) download.file(url, file, mode = "wb")

storm <- read.csv(bzfile(file), stringsAsFactors = FALSE)
dim(storm)
## [1] 902297     37

Selecting variables

Only the variables needed to answer the two questions are kept: event type, fatalities, injuries and property/crop damage with their exponent columns. Events with no recorded harm or damage are dropped, since they have no affect the rankings.

sd <- storm %>%
  select(EVTYPE, FATALITIES, INJURIES, PROPDMG, PROPDMGEXP, CROPDMG, CROPDMGEXP) %>%
  filter(FATALITIES > 0 | INJURIES > 0 | PROPDMG > 0 | CROPDMG > 0)
nrow(sd)
## [1] 254633

Cleaning event types

The EVTYPE variable holds nearly 1,000 distinct labels, many of them typos, plurals, inconsistent capitalisation, or combined events such as “TSTM WIND/HAIL”. Because the NWS documentation allows only 48 official event names (Table 1, Section 2.1.1), any whitespace was trimmed, each label was converted to upper case and mapped it to one of those categories using an ordered list of regular expressions. The order is important: specific patterns such as “FLASH FLOOD” and “STORM SURGE” are tested before broader ones like “FLOOD”, so they are not absorbed by the general category. A label that names more than one event is assigned to the first pattern it matches, and any label that matches none is grouped under “Other”.

rules <- c(
  "TROPICAL STORM"                      = "TROPICAL STORM",
  "TROPICAL DEPRESSION"                 = "TROPICAL DEPRESSION",
  "HURRICANE|TYPHOON"                   = "HURRICANE/TYPHOON",
  "STORM SURGE|SURGE|ASTRONOMICAL HIGH" = "STORM SURGE/TIDE",
  "TSUNAMI"                             = "TSUNAMI",
  "SEICHE"                              = "SEICHE",
  "LAKESHORE|LAKE FLOOD"                = "LAKESHORE FLOOD",
  "COASTAL ?FLOOD|COASTAL ?SURGE|BEACH EROSION|TIDAL FLOOD" = "COASTAL FLOOD",
  "FLASH FLOOD|FLOOD/FLASH|DAM (BREAK|FAILURE)" = "FLASH FLOOD",
  "FLOOD|RIVER RISE|URBAN/SML|STREAM FLD"       = "FLOOD",
  "TORNADO|LANDSPOUT|GUSTNADO"          = "TORNADO",
  "WATERSPOUT"                          = "WATERSPOUT",
  "FUNNEL"                              = "FUNNEL CLOUD",
  "DUST DEVIL"                          = "DUST DEVIL",
  "DUST ?STORM|BLOWING DUST"            = "DUST STORM",
  "AVALAN"                              = "AVALANCHE",
  "LANDSLIDE|MUD ?SLIDE|ROCK SLIDE|DEBRIS FLOW" = "DEBRIS FLOW",
  "VOLCANIC"                            = "VOLCANIC ASH",
  "TSTM|THUNDERSTORM|MICROBURST|DOWNBURST" = "THUNDERSTORM WIND",
  "LIGHTNING"                           = "LIGHTNING",
  "HAIL"                                = "HAIL",
  "RIP CURRENT"                         = "RIP CURRENT",
  "HIGH SURF|HEAVY SURF|ROUGH SURF|HAZARDOUS SURF|HEAVY SEAS|HIGH SEAS|HIGH WAVES|ROGUE WAVE" = "HIGH SURF",
  "EXCESSIVE HEAT|EXTREME HEAT|RECORD HEAT|RECORD/EXCESSIVE HEAT" = "EXCESSIVE HEAT",
  "HEAT|WARM|HOT|HYPERTHERMIA"          = "HEAT",
  "EXTREME COLD|EXTREME WIND ?CHILL|RECORD COLD" = "EXTREME COLD/WIND CHILL",
  "COLD|WIND ?CHILL|HYPOTHERMIA|COOL"   = "COLD/WIND CHILL",
  "FREEZING FOG"                        = "FREEZING FOG",
  "FROST|FREEZE|FREEZING TEMP"          = "FROST/FREEZE",
  "BLIZZARD"                            = "BLIZZARD",
  "ICE STORM|GLAZE|FREEZING RAIN|FREEZING DRIZZLE" = "ICE STORM",
  "LAKE.?EFFECT"                        = "LAKE-EFFECT SNOW",
  "WINTER STORM"                        = "WINTER STORM",
  "HEAVY SNOW|EXCESSIVE SNOW|SNOW"      = "HEAVY SNOW",
  "SLEET"                               = "SLEET",
  "WINTER WEATHER|WINTRY|ICE|ICY"       = "WINTER WEATHER",
  "HEAVY RAIN|EXCESSIVE RAIN|TORRENTIAL|RAIN|HEAVY PRECIP|UNSEASONABLY WET" = "HEAVY RAIN",
  "MARINE.*(TSTM|THUNDERSTORM)"         = "MARINE THUNDERSTORM WIND",
  "DENSE FOG|FOG"                       = "DENSE FOG",
  "SMOKE"                               = "DENSE SMOKE",
  "DROUGHT|DRY|DRIEST"                  = "DROUGHT",
  "WILD ?FIRE|FOREST FIRE|BRUSH FIRE|GRASS FIRE|FIRE" = "WILDFIRE",
  "HIGH WIND|GUSTY WIND|WIND STORM|WINDSTORM|^WIND|DAMAGING WIND" = "HIGH WIND",
  "STRONG WIND"                         = "STRONG WIND"
)

clean_event <- function(x) {
  x <- toupper(trimws(x))
  out <- rep("OTHER", length(x))
  done <- rep(FALSE, length(x))
  # Marine thunderstorm wind must be tested before generic thunderstorm wind
  m <- grepl("MARINE.*(TSTM|THUNDERSTORM)", x)
  out[m] <- "MARINE THUNDERSTORM WIND"; done[m] <- TRUE
  for (p in names(rules)) {
    m <- !done & grepl(p, x)
    out[m]  <- rules[[p]]
    done[m] <- TRUE
  }
  out
}

sd$EVENT <- clean_event(sd$EVTYPE)
c(distinct_before = n_distinct(toupper(trimws(sd$EVTYPE))),
  distinct_after  = n_distinct(sd$EVENT))
## distinct_before  distinct_after 
##             444              45

How much of the harm and damage ends up in the “Other” bucket was checked, to confirm the mapping did not hide anything important.

sd %>% group_by(EVENT) %>%
  summarise(people = sum(FATALITIES + INJURIES), n = n()) %>%
  mutate(share_of_people_harmed = round(100 * people / sum(people), 2)) %>%
  filter(EVENT == "OTHER")
## # A tibble: 1 × 4
##   EVENT people     n share_of_people_harmed
##   <chr>  <dbl> <int>                  <dbl>
## 1 OTHER     90   132                   0.06

Converting damage to dollars

According to the documentation (Section 2.7), each damage value is recorded as a number (PROPDMG, CROPDMG) paired with a letter that gives its magnitude: K for thousands, M for millions and B for billions. The exponent columns also include H/h for hundreds, digits from 0 to 8 representing powers of ten and a handful of undocumented symbols (+, -, ? and blanks). Each digit was interpreted as the corresponding power of ten, and gave the undocumented symbols and blanks a multiplier of 1, since their meaning is unknown. Only a very small number of rows are affected by this choice.

multiplier <- function(e) {
  e <- toupper(trimws(e))
  m <- rep(1, length(e))
  m[e == "H"] <- 1e2
  m[e == "K"] <- 1e3
  m[e == "M"] <- 1e6
  m[e == "B"] <- 1e9
  d <- grepl("^[0-8]$", e)
  m[d] <- 10 ^ as.numeric(e[d])
  m
}

sd <- sd %>%
  mutate(PROP_USD = PROPDMG * multiplier(PROPDMGEXP),
         CROP_USD = CROPDMG * multiplier(CROPDMGEXP),
         TOTAL_USD = PROP_USD + CROP_USD,
         HARM = FATALITIES + INJURIES)

table(toupper(trimws(storm$PROPDMGEXP)))
## 
##             -      ?      +      0      1      2      3      4      5      6 
## 465934      1      8      5    216     25     13      4      4     28      4 
##      7      8      B      H      K      M 
##      5      1     40      7 424665  11337

Summarising by event type

health <- sd %>%
  group_by(EVENT) %>%
  summarise(Fatalities = sum(FATALITIES), Injuries = sum(INJURIES),
            Total = sum(HARM), .groups = "drop") %>%
  arrange(desc(Total))

econ <- sd %>%
  group_by(EVENT) %>%
  summarise(Property = sum(PROP_USD) / 1e9, Crop = sum(CROP_USD) / 1e9,
            Total = sum(TOTAL_USD) / 1e9, .groups = "drop") %>%
  arrange(desc(Total))

Results

Which events are most harmful to population health?

Figure 1 shows the ten event types with the highest total number of people harmed (fatalities plus injuries), split into fatalities and injuries. Totals are summed over the whole record, 1950 to November 2011.

top_health <- health %>% slice_head(n = 10)

top_health %>%
  select(EVENT, Fatalities, Injuries) %>%
  pivot_longer(c(Fatalities, Injuries), names_to = "Type", values_to = "People") %>%
  mutate(EVENT = factor(EVENT, levels = rev(top_health$EVENT))) %>%
  ggplot(aes(EVENT, People, fill = Type)) +
  geom_col() +
  coord_flip() +
  scale_y_continuous(labels = scales::comma) +
  scale_fill_manual(values = c(Fatalities = "red3", Injuries = "royalblue3")) +
  labs(title = "Most harmful event types for population health",
       x = NULL, y = "Total people harmed (1950-2011)", fill = NULL) +
  theme_minimal()
Figure 1: Top 10 event types by total fatalities and injuries in the United States, 1950-2011. Bars are stacked so that their full length is the total number of people harmed; the colour shows how much of that is fatalities versus injuries.

Figure 1: Top 10 event types by total fatalities and injuries in the United States, 1950-2011. Bars are stacked so that their full length is the total number of people harmed; the colour shows how much of that is fatalities versus injuries.

knitr::kable(top_health, format.args = list(big.mark = ","),
             caption = "Top 10 event types by fatalities plus injuries")
Top 10 event types by fatalities plus injuries
EVENT Fatalities Injuries Total
TORNADO 5,661 91,407 97,068
THUNDERSTORM WIND 713 9,538 10,251
EXCESSIVE HEAT 2,020 6,730 8,750
FLOOD 512 6,873 7,385
LIGHTNING 817 5,231 6,048
HEAT 1,159 2,513 3,672
FLASH FLOOD 1,035 1,802 2,837
ICE STORM 106 2,244 2,350
HIGH WIND 321 1,560 1,881
WILDFIRE 90 1,608 1,698

Which events have the greatest economic consequences?

Figure 2 shows the ten event types with the largest combined property and crop damage, in billions of US dollars (not adjusted for inflation).

top_econ <- econ %>% slice_head(n = 10)

top_econ %>%
  select(EVENT, Property, Crop) %>%
  pivot_longer(c(Property, Crop), names_to = "Type", values_to = "Billions") %>%
  mutate(EVENT = factor(EVENT, levels = rev(top_econ$EVENT))) %>%
  ggplot(aes(EVENT, Billions, fill = Type)) +
  geom_col() +
  coord_flip() +
  scale_fill_manual(values = c(Property = "tomato1", Crop = "darkolivegreen")) +
  labs(title = "Event types with the greatest economic consequences",
       x = NULL, y = "Total damage, billions of US dollars (1950-2011)", fill = NULL) +
  theme_minimal()
Figure 2: Top 10 event types by total economic damage (property plus crop) in the United States, 1950-2011, in billions of US dollars. Bar colour shows the split between property and crop damage. Dollar values are nominal, not inflation-adjusted.

Figure 2: Top 10 event types by total economic damage (property plus crop) in the United States, 1950-2011, in billions of US dollars. Bar colour shows the split between property and crop damage. Dollar values are nominal, not inflation-adjusted.

knitr::kable(top_econ, digits = 1,
             caption = "Top 10 event types by property plus crop damage (billions of USD)")
Top 10 event types by property plus crop damage (billions of USD)
EVENT Property Crop Total
FLOOD 150.3 10.9 161.1
HURRICANE/TYPHOON 85.4 5.5 90.9
TORNADO 58.6 0.4 59.0
STORM SURGE/TIDE 48.0 0.0 48.0
FLASH FLOOD 17.6 1.5 19.1
HAIL 16.0 3.0 19.0
DROUGHT 1.0 14.0 15.0
THUNDERSTORM WIND 11.2 1.3 12.5
ICE STORM 4.0 5.0 9.0
WILDFIRE 8.5 0.4 8.9

Summary

Tornadoes are by far the greatest threat to population health, with heat-related events, thunderstorm wind and flooding also ranking high. Economically, property damage is dominated by floods, hurricanes/typhoons, storm surge and tornadoes, while drought and flooding are among the biggest sources of crop damage. Because each total reflects both how severe and how frequent an event type is, and because records from earlier decades are less complete than recent ones, these results are best read as broad rankings rather than precise estimates.