`## Synopsis
This analysis examines severe weather events across the United States using the NOAA Storm Database. It identifies the event types associated with the greatest population health impacts and economic consequences. Fatalities and injuries are used to measure harm to human health. Property damage and crop damage are converted into estimated dollar amounts and aggregated by event type. The ten highest-ranking event types are presented in tables and figures. The results provide an overview of the historical impacts of severe weather events. The analysis starts from the original compressed CSV file and performs all data processing in R. Differences in reporting practices, event classifications, and damage estimates may affect the results.
The NOAA Storm Database contains records of severe weather events, including event types, fatalities, injuries, and estimates of property and crop damage. The original compressed CSV file is downloaded directly and read into R.
# Install ggplot2 if it is not already installed.
if (!requireNamespace("ggplot2", quietly = TRUE)) {
install.packages("ggplot2")
}
library(ggplot2)
# URL of the original NOAA Storm Database file.
file_url <- paste0(
"https://d396qusza40orc.cloudfront.net/",
"repdata%2Fdata%2FStormData.csv.bz2"
)
data_file <- "StormData.csv.bz2"
# Download the raw file if necessary.
if (!file.exists(data_file)) {
download.file(
file_url,
destfile = data_file,
mode = "wb"
)
}
# Read the compressed CSV.
storm <- read.csv(
data_file,
stringsAsFactors = FALSE
)
# Inspect the dimensions and variables.
dim(storm)
## [1] 902297 37
names(storm)
## [1] "STATE__" "BGN_DATE" "BGN_TIME" "TIME_ZONE" "COUNTY"
## [6] "COUNTYNAME" "STATE" "EVTYPE" "BGN_RANGE" "BGN_AZI"
## [11] "BGN_LOCATI" "END_DATE" "END_TIME" "COUNTY_END" "COUNTYENDN"
## [16] "END_RANGE" "END_AZI" "END_LOCATI" "LENGTH" "WIDTH"
## [21] "F" "MAG" "FATALITIES" "INJURIES" "PROPDMG"
## [26] "PROPDMGEXP" "CROPDMG" "CROPDMGEXP" "WFO" "STATEOFFIC"
## [31] "ZONENAMES" "LATITUDE" "LONGITUDE" "LATITUDE_E" "LONGITUDE_"
## [36] "REMARKS" "REFNUM"
head(storm)
## STATE__ BGN_DATE BGN_TIME TIME_ZONE COUNTY COUNTYNAME STATE EVTYPE
## 1 1 4/18/1950 0:00:00 0130 CST 97 MOBILE AL TORNADO
## 2 1 4/18/1950 0:00:00 0145 CST 3 BALDWIN AL TORNADO
## 3 1 2/20/1951 0:00:00 1600 CST 57 FAYETTE AL TORNADO
## 4 1 6/8/1951 0:00:00 0900 CST 89 MADISON AL TORNADO
## 5 1 11/15/1951 0:00:00 1500 CST 43 CULLMAN AL TORNADO
## 6 1 11/15/1951 0:00:00 2000 CST 77 LAUDERDALE AL TORNADO
## BGN_RANGE BGN_AZI BGN_LOCATI END_DATE END_TIME COUNTY_END COUNTYENDN
## 1 0 0 NA
## 2 0 0 NA
## 3 0 0 NA
## 4 0 0 NA
## 5 0 0 NA
## 6 0 0 NA
## END_RANGE END_AZI END_LOCATI LENGTH WIDTH F MAG FATALITIES INJURIES PROPDMG
## 1 0 14.0 100 3 0 0 15 25.0
## 2 0 2.0 150 2 0 0 0 2.5
## 3 0 0.1 123 2 0 0 2 25.0
## 4 0 0.0 100 2 0 0 2 2.5
## 5 0 0.0 150 2 0 0 2 2.5
## 6 0 1.5 177 2 0 0 6 2.5
## PROPDMGEXP CROPDMG CROPDMGEXP WFO STATEOFFIC ZONENAMES LATITUDE LONGITUDE
## 1 K 0 3040 8812
## 2 K 0 3042 8755
## 3 K 0 3340 8742
## 4 K 0 3458 8626
## 5 K 0 3412 8642
## 6 K 0 3450 8748
## LATITUDE_E LONGITUDE_ REMARKS REFNUM
## 1 3051 8806 1
## 2 0 0 2
## 3 0 0 3
## 4 0 0 4
## 5 0 0 5
## 6 0 0 6
The analysis uses EVTYPE, FATALITIES, INJURIES, PROPDMG, PROPDMGEXP, CROPDMG, and CROPDMGEXP. Event names are converted to uppercase and surrounding whitespace is removed. Records without an event type are excluded. Missing health counts are treated as zero.
required_columns <- c(
"EVTYPE",
"FATALITIES",
"INJURIES",
"PROPDMG",
"PROPDMGEXP",
"CROPDMG",
"CROPDMGEXP"
)
stopifnot(all(required_columns %in% names(storm)))
storm <- storm[, required_columns]
# Standardize capitalization and whitespace.
storm$EVTYPE <- toupper(trimws(storm$EVTYPE))
# Remove missing or empty event types.
storm <- storm[
!is.na(storm$EVTYPE) & storm$EVTYPE != "",
]
# Convert health measures to numeric.
storm$FATALITIES <- as.numeric(storm$FATALITIES)
storm$INJURIES <- as.numeric(storm$INJURIES)
# Replace missing health counts with zero.
storm$FATALITIES[is.na(storm$FATALITIES)] <- 0
storm$INJURIES[is.na(storm$INJURIES)] <- 0
summary(storm[, c("FATALITIES", "INJURIES")])
## FATALITIES INJURIES
## Min. : 0.0000 Min. : 0.0000
## 1st Qu.: 0.0000 1st Qu.: 0.0000
## Median : 0.0000 Median : 0.0000
## Mean : 0.0168 Mean : 0.1557
## 3rd Qu.: 0.0000 3rd Qu.: 0.0000
## Max. :583.0000 Max. :1700.0000
Property and crop damage are recorded as numeric amounts with separate magnitude codes. For example, K represents thousands, M represents millions, and B represents billions. Numeric codes from 0 through 8 are interpreted as powers of ten. Blank or unrecognized codes receive a multiplier of one; this is a simplifying assumption that may affect some estimates.
damage_multiplier <- function(x) {
code <- toupper(trimws(as.character(x)))
# Default multiplier.
multiplier <- rep(1, length(code))
# Standard magnitude codes.
multiplier[code == "H"] <- 1e2
multiplier[code == "K"] <- 1e3
multiplier[code == "M"] <- 1e6
multiplier[code == "B"] <- 1e9
# Numeric exponent codes.
numeric_code <- !is.na(code) & grepl("^[0-8]$", code)
multiplier[numeric_code] <-
10 ^ as.numeric(code[numeric_code])
# Treat blanks and conventional placeholder codes as multiplier 1.
multiplier[
is.na(code) |
code == "" |
code %in% c("+", "-", "?")
] <- 1
multiplier
}
# Convert damage amounts to numeric values.
storm$PROPDMG <- as.numeric(storm$PROPDMG)
storm$CROPDMG <- as.numeric(storm$CROPDMG)
storm$PROPDMG[is.na(storm$PROPDMG)] <- 0
storm$CROPDMG[is.na(storm$CROPDMG)] <- 0
# Calculate property and crop damage in dollars.
storm$PROPERTY_DAMAGE <-
storm$PROPDMG * damage_multiplier(storm$PROPDMGEXP)
storm$CROP_DAMAGE <-
storm$CROPDMG * damage_multiplier(storm$CROPDMGEXP)
# Guard against invalid calculations.
storm$PROPERTY_DAMAGE[
!is.finite(storm$PROPERTY_DAMAGE)
] <- 0
storm$CROP_DAMAGE[
!is.finite(storm$CROP_DAMAGE)
] <- 0
# Combined economic damage.
storm$TOTAL_DAMAGE <-
storm$PROPERTY_DAMAGE + storm$CROP_DAMAGE
summary(
storm[, c(
"PROPERTY_DAMAGE",
"CROP_DAMAGE",
"TOTAL_DAMAGE"
)]
)
## PROPERTY_DAMAGE CROP_DAMAGE TOTAL_DAMAGE
## Min. :0.000e+00 Min. :0.000e+00 Min. :0.00e+00
## 1st Qu.:0.000e+00 1st Qu.:0.000e+00 1st Qu.:0.00e+00
## Median :0.000e+00 Median :0.000e+00 Median :0.00e+00
## Mean :4.746e+05 Mean :5.442e+04 Mean :5.29e+05
## 3rd Qu.:5.000e+02 3rd Qu.:0.000e+00 3rd Qu.:1.00e+03
## Max. :1.150e+11 Max. :5.000e+09 Max. :1.15e+11
The health measures are summed separately. Economic damage is also aggregated separately for property, crops, and their combined total. Each ranking is sorted from highest to lowest impact.
# Aggregate fatalities and injuries.
health_summary <- aggregate(
cbind(FATALITIES, INJURIES) ~ EVTYPE,
data = storm,
FUN = sum
)
health_by_fatalities <- health_summary[
order(-health_summary$FATALITIES),
]
health_by_injuries <- health_summary[
order(-health_summary$INJURIES),
]
# Aggregate property, crop, and combined damage.
economic_summary <- aggregate(
cbind(
PROPERTY_DAMAGE,
CROP_DAMAGE,
TOTAL_DAMAGE
) ~ EVTYPE,
data = storm,
FUN = sum
)
economic_by_total <- economic_summary[
order(-economic_summary$TOTAL_DAMAGE),
]
economic_by_property <- economic_summary[
order(-economic_summary$PROPERTY_DAMAGE),
]
economic_by_crops <- economic_summary[
order(-economic_summary$CROP_DAMAGE),
]
Fatalities and injuries provide two different measures of population health impacts. The following tables identify the ten event types with the highest cumulative totals for each measure.
fatality_top10 <- head(
health_by_fatalities[
, c("EVTYPE", "FATALITIES")
],
10
)
knitr::kable(
fatality_top10,
col.names = c("Event type", "Total fatalities"),
caption = "Top 10 event types by reported fatalities",
format.args = list(big.mark = ",")
)
| Event type | Total fatalities | |
|---|---|---|
| 750 | TORNADO | 5,633 |
| 108 | EXCESSIVE HEAT | 1,903 |
| 130 | FLASH FLOOD | 978 |
| 235 | HEAT | 937 |
| 410 | LIGHTNING | 816 |
| 771 | TSTM WIND | 504 |
| 146 | FLOOD | 470 |
| 516 | RIP CURRENT | 368 |
| 312 | HIGH WIND | 248 |
| 11 | AVALANCHE | 224 |
injury_top10 <- head(
health_by_injuries[
, c("EVTYPE", "INJURIES")
],
10
)
knitr::kable(
injury_top10,
col.names = c("Event type", "Total injuries"),
caption = "Top 10 event types by reported injuries",
format.args = list(big.mark = ",")
)
| Event type | Total injuries | |
|---|---|---|
| 750 | TORNADO | 91,346 |
| 771 | TSTM WIND | 6,957 |
| 146 | FLOOD | 6,789 |
| 108 | EXCESSIVE HEAT | 6,525 |
| 410 | LIGHTNING | 5,230 |
| 235 | HEAT | 2,100 |
| 379 | ICE STORM | 1,975 |
| 130 | FLASH FLOOD | 1,777 |
| 677 | THUNDERSTORM WIND | 1,488 |
| 204 | HAIL | 1,361 |
The following figure compares the leading event types for fatalities and injuries. The panels are ranked independently, and their horizontal axes are scaled separately.
fatality_plot_data <- data.frame(
EVTYPE = fatality_top10$EVTYPE,
Impact = "Fatalities",
Count = fatality_top10$FATALITIES
)
injury_plot_data <- data.frame(
EVTYPE = injury_top10$EVTYPE,
Impact = "Injuries",
Count = injury_top10$INJURIES
)
health_plot_data <- rbind(
fatality_plot_data,
injury_plot_data
)
ggplot(
health_plot_data,
aes(x = Count, y = reorder(EVTYPE, Count))
) +
geom_col(fill = "steelblue") +
facet_wrap(
~ Impact,
scales = "free",
ncol = 2
) +
labs(
title = "Weather Events with the Greatest Health Impacts",
x = "Total reported count",
y = "Event type"
) +
theme_minimal(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
strip.text = element_text(face = "bold")
)
The tables and figure show which event types account for the largest reported numbers of fatalities and injuries. The two rankings may differ because some event types contribute more heavily to deaths while others cause more nonfatal injuries. These figures represent cumulative totals across recorded events, not the average severity of an individual event.
Economic impacts are measured by the combined estimates of property and crop damage. The values are expressed in dollars after applying the recorded magnitude codes.
economic_top10 <- head(
economic_by_total[
, c(
"EVTYPE",
"PROPERTY_DAMAGE",
"CROP_DAMAGE",
"TOTAL_DAMAGE"
)
],
10
)
knitr::kable(
economic_top10,
col.names = c(
"Event type",
"Property damage ($)",
"Crop damage ($)",
"Combined damage ($)"
),
caption = "Top 10 event types by combined estimated damage",
format.args = list(big.mark = ",", scientific = FALSE)
)
| Event type | Property damage (\()| Crop damage (\)) | Combined damage ($) | ||
|---|---|---|---|---|
| 146 | FLOOD | 144,657,709,807 | 5,661,968,450 | 150,319,678,257 |
| 364 | HURRICANE/TYPHOON | 69,305,840,000 | 2,607,872,800 | 71,913,712,800 |
| 750 | TORNADO | 56,947,380,677 | 414,953,270 | 57,362,333,947 |
| 591 | STORM SURGE | 43,323,536,000 | 5,000 | 43,323,541,000 |
| 204 | HAIL | 15,735,267,513 | 3,025,954,473 | 18,761,221,986 |
| 130 | FLASH FLOOD | 16,822,723,979 | 1,421,317,100 | 18,244,041,079 |
| 76 | DROUGHT | 1,046,106,000 | 13,972,566,000 | 15,018,672,000 |
| 355 | HURRICANE | 11,868,319,010 | 2,741,910,000 | 14,610,229,010 |
| 521 | RIVER FLOOD | 5,118,945,500 | 5,029,459,000 | 10,148,404,500 |
| 379 | ICE STORM | 3,944,927,860 | 5,022,113,500 | 8,967,041,360 |
The next two tables distinguish losses to property from agricultural losses.
property_top10 <- head(
economic_by_property[
, c("EVTYPE", "PROPERTY_DAMAGE")
],
10
)
crop_top10 <- head(
economic_by_crops[
, c("EVTYPE", "CROP_DAMAGE")
],
10
)
knitr::kable(
property_top10,
col.names = c("Event type", "Property damage ($)"),
caption = "Top 10 event types by property damage",
format.args = list(big.mark = ",", scientific = FALSE)
)
| Event type | Property damage ($) | |
|---|---|---|
| 146 | FLOOD | 144,657,709,807 |
| 364 | HURRICANE/TYPHOON | 69,305,840,000 |
| 750 | TORNADO | 56,947,380,677 |
| 591 | STORM SURGE | 43,323,536,000 |
| 130 | FLASH FLOOD | 16,822,723,979 |
| 204 | HAIL | 15,735,267,513 |
| 355 | HURRICANE | 11,868,319,010 |
| 764 | TROPICAL STORM | 7,703,890,550 |
| 880 | WINTER STORM | 6,688,497,251 |
| 312 | HIGH WIND | 5,270,046,295 |
knitr::kable(
crop_top10,
col.names = c("Event type", "Crop damage ($)"),
caption = "Top 10 event types by crop damage",
format.args = list(big.mark = ",", scientific = FALSE)
)
| Event type | Crop damage ($) | |
|---|---|---|
| 76 | DROUGHT | 13,972,566,000 |
| 146 | FLOOD | 5,661,968,450 |
| 521 | RIVER FLOOD | 5,029,459,000 |
| 379 | ICE STORM | 5,022,113,500 |
| 204 | HAIL | 3,025,954,473 |
| 355 | HURRICANE | 2,741,910,000 |
| 364 | HURRICANE/TYPHOON | 2,607,872,800 |
| 130 | FLASH FLOOD | 1,421,317,100 |
| 117 | EXTREME COLD | 1,312,973,000 |
| 179 | FROST/FREEZE | 1,094,186,000 |
This figure compares the ten leading event types for property damage and crop damage. Damage values are displayed in billions of dollars for readability.
property_plot_data <- data.frame(
EVTYPE = property_top10$EVTYPE,
DamageType = "Property damage",
Damage = property_top10$PROPERTY_DAMAGE
)
crop_plot_data <- data.frame(
EVTYPE = crop_top10$EVTYPE,
DamageType = "Crop damage",
Damage = crop_top10$CROP_DAMAGE
)
economic_plot_data <- rbind(
property_plot_data,
crop_plot_data
)
ggplot(
economic_plot_data,
aes(x = Damage / 1e9, y = reorder(EVTYPE, Damage))
) +
geom_col(fill = "darkorange3") +
facet_wrap(
~ DamageType,
scales = "free",
ncol = 2
) +
labs(
title = "Weather Events with the Greatest Economic Losses",
x = "Estimated damage (billions of dollars)",
y = "Event type"
) +
theme_minimal(base_size = 10) +
theme(
plot.title = element_text(face = "bold"),
strip.text = element_text(face = "bold")
)
The combined economic ranking identifies the event types associated with the largest total reported losses. The separate rankings show whether property or crop damage contributes more heavily to each event type’s overall economic impact. These values are estimates and depend on the quality of the original records and the interpretation of the damage magnitude codes.
The following code prints the top-ranked event type for each impact measure directly from the aggregated data.
top_fatalities <- health_by_fatalities[1, ]
top_injuries <- health_by_injuries[1, ]
top_combined_damage <- economic_by_total[1, ]
top_property_damage <- economic_by_property[1, ]
top_crop_damage <- economic_by_crops[1, ]
cat(
"Most fatalities:",
top_fatalities$EVTYPE,
"\nTotal fatalities:",
format(top_fatalities$FATALITIES, big.mark = ","),
"\n\n"
)
## Most fatalities: TORNADO
## Total fatalities: 5,633
cat(
"Most injuries:",
top_injuries$EVTYPE,
"\nTotal injuries:",
format(top_injuries$INJURIES, big.mark = ","),
"\n\n"
)
## Most injuries: TORNADO
## Total injuries: 91,346
cat(
"Greatest combined economic damage:",
top_combined_damage$EVTYPE,
"\nCombined damage ($):",
format(
round(top_combined_damage$TOTAL_DAMAGE, 0),
big.mark = ",",
scientific = FALSE
),
"\n\n"
)
## Greatest combined economic damage: FLOOD
## Combined damage ($): 150,319,678,257
cat(
"Greatest property damage:",
top_property_damage$EVTYPE,
"\nProperty damage ($):",
format(
round(top_property_damage$PROPERTY_DAMAGE, 0),
big.mark = ",",
scientific = FALSE
),
"\n\n"
)
## Greatest property damage: FLOOD
## Property damage ($): 144,657,709,807
cat(
"Greatest crop damage:",
top_crop_damage$EVTYPE,
"\nCrop damage ($):",
format(
round(top_crop_damage$CROP_DAMAGE, 0),
big.mark = ",",
scientific = FALSE
),
"\n"
)
## Greatest crop damage: DROUGHT
## Crop damage ($): 13,972,566,000
Several limitations should be considered when interpreting these results.
This analysis uses the NOAA Storm Database to compare the health and economic impacts of severe weather events across the United States. Fatality and injury rankings identify the event types associated with the greatest cumulative human harm. Property, crop, and combined damage rankings identify the event types associated with the largest estimated economic losses. The tables and figures provide complementary perspectives on historical weather impacts. The findings can support further analysis of severe weather risks, while the limitations of historical reporting and event classification should be considered when interpreting the rankings.