knitr::opts_chunk$set(fig.path = "figure/")

Synopsis

This report uses the U.S. National Oceanic and Atmospheric Administration’s (NOAA) Storm Database, which records characteristics of major storms and weather events across the United States from 1950 to November 2011, to identify which types of severe weather events are most harmful to population health and which have the greatest economic consequences. Health impact is measured as the sum of fatalities and injuries reported per event type, and economic impact is measured as the sum of property and crop damage (in dollars), after converting the database’s coded damage-magnitude exponents (K/M/B) into numeric multipliers. Event type labels in the raw data are lightly standardized (trimmed and case-normalized, with a small number of clear abbreviation duplicates merged) before aggregation. The analysis finds that tornadoes are, by a wide margin, the event type responsible for the most fatalities and injuries combined, while floods account for the largest total economic damage, with hurricanes/typhoons and tornadoes also among the most economically costly event types. These results are intended to help a government or municipal manager prioritize resources across event types when preparing for severe weather.

Data Processing

The raw data is provided as a bzip2-compressed CSV file. It is read directly from the compressed file with read.csv(), which handles the bzip2 decompression automatically, so no separate unzip step is needed. The full file has 37 columns, but this analysis only needs 7 of them (the event type and the four fields that measure human and economic impact). Loading all 37 columns - including free-text fields like REMARKS - uses substantially more memory than the analysis needs, so colClasses is used to tell read.csv() to skip every column we don’t need while it reads, rather than loading everything and subsetting afterward. This chunk is cached since reading the full file is still the slowest step.

col_names <- c("STATE__", "BGN_DATE", "BGN_TIME", "TIME_ZONE", "COUNTY",
               "COUNTYNAME", "STATE", "EVTYPE", "BGN_RANGE", "BGN_AZI",
               "BGN_LOCATI", "END_DATE", "END_TIME", "COUNTY_END",
               "COUNTYENDN", "END_RANGE", "END_AZI", "END_LOCATI", "LENGTH",
               "WIDTH", "F", "MAG", "FATALITIES", "INJURIES", "PROPDMG",
               "PROPDMGEXP", "CROPDMG", "CROPDMGEXP", "WFO", "STATEOFFIC",
               "ZONENAMES", "LATITUDE", "LONGITUDE", "LATITUDE_E",
               "LONGITUDE_", "REMARKS", "REFNUM")
keep_cols <- c("EVTYPE", "FATALITIES", "INJURIES",
               "PROPDMG", "PROPDMGEXP", "CROPDMG", "CROPDMGEXP")
numeric_cols <- c("FATALITIES", "INJURIES", "PROPDMG", "CROPDMG")

col_classes <- ifelse(col_names %in% keep_cols,
                       ifelse(col_names %in% numeric_cols, "numeric", "character"),
                       "NULL")

storm <- read.csv("repdata-data-StormData.csv.bz2", stringsAsFactors = FALSE,
                   colClasses = col_classes)
dim(storm)
## [1] 902297      7
str(storm)
## 'data.frame':    902297 obs. of  7 variables:
##  $ EVTYPE    : chr  "TORNADO" "TORNADO" "TORNADO" "TORNADO" ...
##  $ FATALITIES: num  0 0 0 0 0 0 0 0 1 0 ...
##  $ INJURIES  : num  15 0 2 2 2 6 1 0 14 0 ...
##  $ PROPDMG   : num  25 2.5 25 2.5 2.5 2.5 2.5 2.5 25 25 ...
##  $ PROPDMGEXP: chr  "K" "K" "K" "K" ...
##  $ CROPDMG   : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ CROPDMGEXP: chr  "" "" "" "" ...

This reads only the 7 columns needed for the analysis below, which is far lighter on memory than loading the full 37-column, 902,297-row dataset and subsetting afterward.

Cleaning the event type (EVTYPE) field

The raw EVTYPE field has 985 distinct values, far more than the ~48 official event categories in the NOAA documentation, because of inconsistent capitalization, leading/trailing whitespace, and a handful of common abbreviations. A full remapping to the official 48 categories is beyond the scope of this assignment, but three simple, justifiable standardizations are applied so that obviously identical event types are not split into separate categories in the results below:

  • Trim leading/trailing whitespace and convert to uppercase.
  • Merge the very common abbreviation TSTM with THUNDERSTORM (e.g. TSTM WIND and THUNDERSTORM WIND are the same event type).
  • Merge the singular/plural variants THUNDERSTORM WIND and THUNDERSTORM WINDS.
storm$EVTYPE <- trimws(toupper(storm$EVTYPE))
storm$EVTYPE <- gsub("TSTM", "THUNDERSTORM", storm$EVTYPE)
storm$EVTYPE <- gsub("THUNDERSTORM WINDS", "THUNDERSTORM WIND", storm$EVTYPE)
length(unique(storm$EVTYPE))
## [1] 874

Converting damage exponent codes to dollar amounts

PROPDMG/CROPDMG give a damage amount, and PROPDMGEXP/CROPDMGEXP give a magnitude code for that amount. Following the commonly used interpretation of these codes (consistent with the National Weather Service instructions for this field): K/k = thousands, M/m = millions, B/b = billions, a digit 0-9 is treated as a power-of-ten exponent, and any other symbol (blank, -, +, ?) is treated as no multiplier (i.e. a multiplier of 1 for +/blank and 0 for -/?, since those do not correspond to a usable magnitude). This lets us convert the coded amounts into actual dollar figures.

exp_to_multiplier <- function(e) {
  e <- toupper(trimws(e))
  ifelse(e == "K", 1e3,
  ifelse(e == "M", 1e6,
  ifelse(e == "B", 1e9,
  ifelse(e %in% as.character(0:9), 10^as.numeric(e),
  ifelse(e == "+", 1,
  0)))))
}

storm$PROP_MULT <- exp_to_multiplier(storm$PROPDMGEXP)
## Warning in ifelse(e %in% as.character(0:9), 10^as.numeric(e), ifelse(e == : NAs
## introduced by coercion
storm$CROP_MULT <- exp_to_multiplier(storm$CROPDMGEXP)
## Warning in ifelse(e %in% as.character(0:9), 10^as.numeric(e), ifelse(e == : NAs
## introduced by coercion
storm$PROP_DAMAGE <- storm$PROPDMG * storm$PROP_MULT
storm$CROP_DAMAGE <- storm$CROPDMG * storm$CROP_MULT
storm$TOTAL_DAMAGE <- storm$PROP_DAMAGE + storm$CROP_DAMAGE

The dataset is now ready for aggregation by event type to answer the two questions posed in this assignment.

Results

Which types of events are most harmful to population health?

For each event type we sum FATALITIES and INJURIES across all recorded events, then look at the 10 event types with the highest totals for each.

fatalities_by_type <- aggregate(FATALITIES ~ EVTYPE, data = storm, FUN = sum)
injuries_by_type   <- aggregate(INJURIES ~ EVTYPE, data = storm, FUN = sum)

top_fatalities <- head(fatalities_by_type[order(-fatalities_by_type$FATALITIES), ], 10)
top_injuries   <- head(injuries_by_type[order(-injuries_by_type$INJURIES), ], 10)

top_fatalities
##                EVTYPE FATALITIES
## 757           TORNADO       5633
## 108    EXCESSIVE HEAT       1903
## 130       FLASH FLOOD        978
## 234              HEAT        937
## 409         LIGHTNING        816
## 676 THUNDERSTORM WIND        701
## 146             FLOOD        470
## 514       RIP CURRENT        368
## 311         HIGH WIND        248
## 11          AVALANCHE        224
top_injuries
##                EVTYPE INJURIES
## 757           TORNADO    91346
## 676 THUNDERSTORM WIND     9353
## 146             FLOOD     6789
## 108    EXCESSIVE HEAT     6525
## 409         LIGHTNING     5230
## 234              HEAT     2100
## 378         ICE STORM     1975
## 130       FLASH FLOOD     1777
## 203              HAIL     1361
## 864      WINTER STORM     1321
par(mfrow = c(1, 2), mar = c(8, 4, 3, 1))

barplot(top_fatalities$FATALITIES,
        names.arg = top_fatalities$EVTYPE,
        las = 2, cex.names = 0.7, col = "firebrick",
        main = "Top 10 Event Types by Total Fatalities",
        ylab = "Total fatalities (1950-2011)")

barplot(top_injuries$INJURIES,
        names.arg = top_injuries$EVTYPE,
        las = 2, cex.names = 0.7, col = "darkorange",
        main = "Top 10 Event Types by Total Injuries",
        ylab = "Total injuries (1950-2011)")

Figure 1. Total fatalities (left) and total injuries (right) summed across all recorded events for each of the ten most harmful event types, 1950-2011. Tornadoes cause by far the largest number of both fatalities and injuries of any event type in the database, followed by excessive heat for fatalities and thunderstorm wind/flood-related events for injuries.

Which types of events have the greatest economic consequences?

For each event type we sum the converted property damage, crop damage, and their total, then look at the 10 event types with the highest total economic damage.

damage_by_type <- aggregate(cbind(PROP_DAMAGE, CROP_DAMAGE, TOTAL_DAMAGE) ~ EVTYPE,
                             data = storm, FUN = sum)

top_damage <- head(damage_by_type[order(-damage_by_type$TOTAL_DAMAGE), ], 10)
# Express in billions of dollars for readability
top_damage$PROP_DAMAGE_B  <- top_damage$PROP_DAMAGE / 1e9
top_damage$CROP_DAMAGE_B  <- top_damage$CROP_DAMAGE / 1e9
top_damage$TOTAL_DAMAGE_B <- top_damage$TOTAL_DAMAGE / 1e9

top_damage[, c("EVTYPE", "PROP_DAMAGE_B", "CROP_DAMAGE_B", "TOTAL_DAMAGE_B")]
##                EVTYPE PROP_DAMAGE_B CROP_DAMAGE_B TOTAL_DAMAGE_B
## 146             FLOOD    144.657710     5.6619684      150.31968
## 363 HURRICANE/TYPHOON     69.305840     2.6078728       71.91371
## 757           TORNADO     56.947381     0.4149533       57.36233
## 589       STORM SURGE     43.323536     0.0000050       43.32354
## 203              HAIL     15.735267     3.0259545       18.76122
## 130       FLASH FLOOD     16.822724     1.4213171       18.24404
## 76            DROUGHT      1.046106    13.9725660       15.01867
## 354         HURRICANE     11.868319     2.7419100       14.61023
## 676 THUNDERSTORM WIND      9.920829     1.1595052       11.08033
## 519       RIVER FLOOD      5.118945     5.0294590       10.14840
plot_matrix <- t(as.matrix(top_damage[, c("PROP_DAMAGE_B", "CROP_DAMAGE_B")]))
colnames(plot_matrix) <- top_damage$EVTYPE

par(mar = c(8, 4, 3, 1))
barplot(plot_matrix,
        names.arg = top_damage$EVTYPE,
        las = 2, cex.names = 0.7,
        col = c("steelblue", "seagreen"),
        legend.text = c("Property damage", "Crop damage"),
        args.legend = list(x = "topright", bty = "n"),
        main = "Top 10 Event Types by Total Economic Damage",
        ylab = "Damage (billions of USD)")

Figure 2. Total property damage and crop damage (stacked, in billions of USD) summed across all recorded events for each of the ten costliest event types, 1950-2011. Floods cause the greatest total economic damage of any event type, driven overwhelmingly by property damage, followed by hurricanes/typhoons and tornadoes. Crop damage is comparatively small for most event types except drought, which is a much larger share of crop losses than of property losses.

Conclusion

Across the United States from 1950 to 2011, tornadoes are the single most harmful event type to population health, causing the largest number of both fatalities and injuries, while floods have caused the greatest total economic damage, with hurricanes/typhoons and tornadoes also ranking among the most economically destructive event types. A government or municipal manager prioritizing resources for severe weather preparedness should treat tornado response and flood mitigation as the two highest priorities under this analysis.