This analysis examines recorded health and economic impacts of severe weather in the NOAA Storm Database from 1950 through November 2011. Population health impacts are assessed using fatalities and injuries separately. Economic impacts are measured as the sum of interpretable property and crop damage estimates in nominal US dollars. Tornadoes have the highest recorded totals, with 5,633 fatalities and 91,346 injuries. Flood has the largest recorded economic total, approximately $150.32 billion. However, this ranking is strongly influenced by one California flood record containing $115 billion in property damage. When that entire record is excluded in a sensitivity analysis, hurricane/typhoon ranks first at approximately $87.12 billion. The results describe historical recorded totals rather than current risk, and are limited by uneven reporting, incomplete event-label harmonization, ambiguous damage codes, and influential records.
All processing starts from the original compressed CSV file. If the file is absent, the code downloads it from the course data source. No manually processed intermediate dataset is required.
knitr::opts_chunk$set(
echo = TRUE,
fig.width = 11,
fig.height = 6,
fig.align = "center"
)
options(timeout = 600)
data_file <- "StormData.csv.bz2"
data_url <- paste0(
"https://d396qusza40orc.cloudfront.net/",
"repdata%2Fdata%2FStormData.csv.bz2"
)
if (!file.exists(data_file)) {
download.file(
data_url,
destfile = data_file,
mode = "wb"
)
}
storm <- read.csv(
data_file,
stringsAsFactors = FALSE
)
dim(storm)
## [1] 902297 37
dat <- storm[, c(
"BGN_DATE", "STATE", "EVTYPE",
"FATALITIES", "INJURIES",
"PROPDMG", "PROPDMGEXP",
"CROPDMG", "CROPDMGEXP"
)]
dat$row_id <- seq_len(nrow(dat))
colSums(is.na(dat[, c(
"FATALITIES", "INJURIES", "PROPDMG", "CROPDMG"
)]))
## FATALITIES INJURIES PROPDMG CROPDMG
## 0 0 0 0
All records in the supplied file are retained for the primary analysis. Earlier years have less complete event coverage, so cumulative totals should not be interpreted as comparisons over equal observation periods. Fatalities and injuries are analyzed separately to avoid assigning them an arbitrary common weight.
Event labels are converted to uppercase, and leading, trailing, and repeated spaces are removed. The explicit mapping below combines a small number of clear naming variants. Other labels remain distinct; this is not a complete reconciliation to the NOAA event classification.
dat$event <- toupper(trimws(dat$EVTYPE))
dat$event <- gsub("[[:space:]]+", " ", dat$event)
aliases <- c(
"TSTM WIND" = "THUNDERSTORM WIND",
"THUNDERSTORM WINDS" = "THUNDERSTORM WIND",
"MARINE TSTM WIND" = "MARINE THUNDERSTORM WIND",
"HURRICANE" = "HURRICANE/TYPHOON",
"TYPHOON" = "HURRICANE/TYPHOON"
)
idx <- match(dat$event, names(aliases))
matched <- !is.na(idx)
dat$event[matched] <- unname(aliases[idx[matched]])
data.frame(
original_labels = length(unique(dat$EVTYPE)),
normalized_labels = length(unique(dat$event))
)
## original_labels normalized_labels
## 1 985 878
The National Weather Service documentation identifies K, M, and B as thousands, millions, and billions, respectively. See the Storm Data Preparation instructions.
Lowercase codes are converted to uppercase. For this analysis, H is assumed to mean hundreds and a blank code is assumed to indicate unscaled dollars. These are explicit assumptions for legacy entries. Other numeric and symbolic codes are treated as uninterpretable. Zero-valued damage components remain zero regardless of their code.
Uninterpretable positive components are excluded from monetary sums, while any interpretable component from the same record is retained. Therefore, the reported sums do not establish that omitted losses were zero, and they may understate recorded damage.
table(dat$PROPDMGEXP, useNA = "ifany")
##
## - ? + 0 1 2 3 4 5 6
## 465934 1 8 5 216 25 13 4 4 28 4
## 7 8 B h H K m M
## 5 1 40 1 6 424665 7 11330
table(dat$CROPDMGEXP, useNA = "ifany")
##
## ? 0 2 B k K m M
## 618413 7 19 1 9 21 281832 1 1994
damage_dollars <- function(amount, exponent) {
exponent <- toupper(trimws(exponent))
multipliers <- c(
H = 100,
K = 1000,
M = 1000000,
B = 1000000000
)
multiplier <- unname(multipliers[exponent])
multiplier[exponent == ""] <- 1
result <- amount * multiplier
result[amount == 0] <- 0
result
}
dat$property_usd <- damage_dollars(
dat$PROPDMG, dat$PROPDMGEXP
)
dat$crop_usd <- damage_dollars(
dat$CROPDMG, dat$CROPDMGEXP
)
unknown_components <- data.frame(
component = c("Property", "Crop"),
excluded_positive_components = c(
sum(is.na(dat$property_usd)),
sum(is.na(dat$crop_usd))
)
)
knitr::kable(unknown_components)
| component | excluded_positive_components |
|---|---|
| Property | 244 |
| Crop | 12 |
Damage estimates are summed in nominal dollars without inflation adjustment. They cover reported property and crop damage, rather than all economic costs such as business interruption or health expenditure.
Health outcomes and interpretable monetary components are summed by normalized event label. Rankings measure total recorded burden, not average severity per event or risk per exposed person.
The largest property-damage entry is inspected below. The primary analysis retains it as recorded. A sensitivity analysis excludes the entire record without claiming that its reported amount has been independently verified or corrected.
health <- aggregate(
cbind(FATALITIES, INJURIES) ~ event,
data = dat,
FUN = sum
)
economic_totals <- function(x) {
result <- aggregate(
x[, c("property_usd", "crop_usd")],
by = list(event = x$event),
FUN = sum,
na.rm = TRUE
)
result$total_usd <- result$property_usd + result$crop_usd
result <- result[
order(result$total_usd, decreasing = TRUE),
]
result
}
economic <- economic_totals(dat)
influential <- which(
dat$STATE == "CA" &
dat$event == "FLOOD" &
dat$BGN_DATE == "1/1/2006 0:00:00" &
dat$PROPDMG == 115 &
toupper(trimws(dat$PROPDMGEXP)) == "B"
)
stopifnot(length(influential) == 1L)
knitr::kable(
dat[influential, c(
"row_id", "BGN_DATE", "STATE", "event",
"property_usd", "crop_usd"
)]
)
| row_id | BGN_DATE | STATE | event | property_usd | crop_usd | |
|---|---|---|---|---|---|---|
| 605953 | 605953 | 1/1/2006 0:00:00 | CA | FLOOD | 1.15e+11 | 32500000 |
economic_sensitivity <- economic_totals(
dat[-influential, ]
)
top_fatalities <- head(
health[order(health$FATALITIES, decreasing = TRUE), ],
10
)
top_injuries <- head(
health[order(health$INJURIES, decreasing = TRUE), ],
10
)
knitr::kable(
top_fatalities,
row.names = FALSE,
caption = "Top ten event categories ranked by fatalities."
)
| event | FATALITIES | INJURIES |
|---|---|---|
| TORNADO | 5633 | 91346 |
| EXCESSIVE HEAT | 1903 | 6525 |
| FLASH FLOOD | 978 | 1777 |
| HEAT | 937 | 2100 |
| LIGHTNING | 816 | 5230 |
| THUNDERSTORM WIND | 701 | 9363 |
| FLOOD | 470 | 6789 |
| RIP CURRENT | 368 | 232 |
| HIGH WIND | 248 | 1137 |
| AVALANCHE | 224 | 170 |
knitr::kable(
top_injuries,
row.names = FALSE,
caption = "Top ten event categories ranked by injuries."
)
| event | FATALITIES | INJURIES |
|---|---|---|
| TORNADO | 5633 | 91346 |
| THUNDERSTORM WIND | 701 | 9363 |
| FLOOD | 470 | 6789 |
| EXCESSIVE HEAT | 1903 | 6525 |
| LIGHTNING | 816 | 5230 |
| HEAT | 937 | 2100 |
| ICE STORM | 89 | 1975 |
| FLASH FLOOD | 978 | 1777 |
| HAIL | 15 | 1361 |
| HURRICANE/TYPHOON | 125 | 1326 |
par(mfrow = c(1, 2), mar = c(5, 11, 3, 1))
barplot(
rev(top_fatalities$FATALITIES),
names.arg = rev(top_fatalities$event),
horiz = TRUE,
las = 1,
col = "firebrick",
border = NA,
cex.names = 0.75,
main = "Recorded Fatalities",
xlab = "Number of fatalities"
)
barplot(
rev(top_injuries$INJURIES),
names.arg = rev(top_injuries$event),
horiz = TRUE,
las = 1,
col = "steelblue",
border = NA,
cex.names = 0.75,
main = "Recorded Injuries",
xlab = "Number of injuries"
)
Figure 1. Ten leading event categories by recorded fatalities and injuries. Each panel uses its own numerical scale.
par(mfrow = c(1, 1))
Tornadoes rank first for both outcomes, with 5,633 fatalities and 91,346 injuries. Excessive heat ranks second for fatalities, whereas thunderstorm wind ranks second for injuries. The ordering therefore depends on the health outcome examined.
top_economic <- head(economic, 10)
economic_display <- data.frame(
Event = top_economic$event,
Property_billion_USD = top_economic$property_usd / 1e9,
Crop_billion_USD = top_economic$crop_usd / 1e9,
Total_billion_USD = top_economic$total_usd / 1e9
)
knitr::kable(
economic_display,
digits = 3,
row.names = FALSE
)
| Event | Property_billion_USD | Crop_billion_USD | Total_billion_USD |
|---|---|---|---|
| FLOOD | 144.658 | 5.662 | 150.320 |
| HURRICANE/TYPHOON | 81.774 | 5.351 | 87.125 |
| TORNADO | 56.937 | 0.415 | 57.352 |
| STORM SURGE | 43.324 | 0.000 | 43.324 |
| HAIL | 15.732 | 3.026 | 18.758 |
| FLASH FLOOD | 16.141 | 1.421 | 17.562 |
| DROUGHT | 1.046 | 13.973 | 15.019 |
| THUNDERSTORM WIND | 9.712 | 1.160 | 10.872 |
| RIVER FLOOD | 5.119 | 5.029 | 10.148 |
| ICE STORM | 3.945 | 5.022 | 8.967 |
par(mar = c(5, 12, 3, 2))
barplot(
rev(top_economic$total_usd / 1e9),
names.arg = rev(top_economic$event),
horiz = TRUE,
las = 1,
col = "darkorange",
border = NA,
cex.names = 0.85,
main = "Recorded Property and Crop Damage",
xlab = "Damage (billion nominal USD)"
)
Figure 2. Ten leading event categories by combined interpretable property and crop damage, retaining all records. Values are billions of nominal US dollars.
With all records retained, flood has the largest estimated total, approximately $150.32 billion. Hurricane/typhoon and tornado rank next. These are totals for the retained event categories, not complete costs of individual named disasters: for example, storm surge remains a separate category.
sensitivity_display <- data.frame(
Event = head(economic_sensitivity$event, 10),
Total_billion_USD = head(
economic_sensitivity$total_usd, 10
) / 1e9
)
knitr::kable(
sensitivity_display,
digits = 3,
row.names = FALSE
)
| Event | Total_billion_USD |
|---|---|
| HURRICANE/TYPHOON | 87.125 |
| TORNADO | 57.352 |
| STORM SURGE | 43.324 |
| FLOOD | 35.287 |
| HAIL | 18.758 |
| FLASH FLOOD | 17.562 |
| DROUGHT | 15.019 |
| THUNDERSTORM WIND | 10.872 |
| RIVER FLOOD | 10.148 |
| ICE STORM | 8.967 |
comparison_events <- unique(c(
head(economic$event, 5),
head(economic_sensitivity$event, 5)
))
comparison_values <- rbind(
economic$total_usd[
match(comparison_events, economic$event)
],
economic_sensitivity$total_usd[
match(comparison_events, economic_sensitivity$event)
]
) / 1e9
colnames(comparison_values) <- comparison_events
par(mar = c(5, 12, 3, 2))
barplot(
comparison_values,
beside = TRUE,
horiz = TRUE,
las = 1,
col = c("steelblue", "darkorange"),
border = NA,
cex.names = 0.85,
main = "Sensitivity of Economic Damage Totals",
xlab = "Damage (billion nominal USD)"
)
legend(
"topright",
legend = c("All records", "Influential record excluded"),
fill = c("steelblue", "darkorange"),
bty = "n",
cex = 0.8
)
Figure 3. Economic totals with all records retained and with the entire influential California flood record excluded. The selected categories are the union of the top five in either analysis.
Excluding the entire influential record reduces the flood total to approximately $35.29 billion. Hurricane/typhoon then ranks first at approximately $87.12 billion. The removed record includes both property and crop damage, so the reduction slightly exceeds $115 billion.
The economic leader is therefore sensitive to this record. The primary result is reported as recorded, with the alternative ranking presented to show its influence.
Reporting practices and coverage changed over the study period. Only selected naming variants were combined, so related hazards may remain split across categories. Damage-code assumptions and excluded uninterpretable components introduce uncertainty. Monetary values are not inflation-adjusted, and the influential flood entry has not been independently corrected. The findings summarize historical recorded burden and do not estimate present-day probabilities or future losses.
sessionInfo()
## R version 4.6.0 (2026-04-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=English_Indonesia.utf8 LC_CTYPE=English_Indonesia.utf8
## [3] LC_MONETARY=English_Indonesia.utf8 LC_NUMERIC=C
## [5] LC_TIME=English_Indonesia.utf8
##
## time zone: Asia/Makassar
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## loaded via a namespace (and not attached):
## [1] digest_0.6.39 R6_2.6.1 fastmap_1.2.0 xfun_0.60
## [5] cachem_1.1.0 knitr_1.51 htmltools_0.5.9 rmarkdown_2.31
## [9] lifecycle_1.0.5 cli_3.6.6 sass_0.4.10 jquerylib_0.1.4
## [13] compiler_4.6.0 rstudioapi_0.18.0 tools_4.6.0 evaluate_1.0.5
## [17] bslib_0.11.0 yaml_2.3.12 otel_0.2.0 jsonlite_2.0.0
## [21] rlang_1.2.0