This analysis examines severe weather events recorded in the NOAA Storm Database from 1950 through November 2011. It identifies event types associated with the greatest reported harm to population health and the greatest economic consequences. Fatalities and injuries are used to measure health impacts, while property and crop damage measure economic impacts. The original compressed CSV file is loaded and processed in R. Damage magnitude codes are converted into dollar multipliers, and impacts are aggregated by event type. Two figures present the principal rankings. The results describe historical reported impacts and do not predict future weather events.
The NOAA Storm Database is downloaded if it is not already available in the working directory.
file_url <- "https://d396qusza40orc.cloudfront.net/repdata%2Fdata%2FStormData.csv.bz2"
data_file <- "repdata_data_StormData.csv.bz2"
if (!file.exists(data_file)) {
download.file(
file_url,
destfile = data_file,
mode = "wb",
method = "libcurl"
)
}
storm <- read.csv(
bzfile(data_file),
stringsAsFactors = FALSE
)
cat(
"Dataset dimensions:",
nrow(storm), "rows and",
ncol(storm), "columns\n"
)
## Dataset dimensions: 902297 rows and 37 columns
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
Only variables required for the research questions are retained. Event names are converted to uppercase and trimmed to combine labels that differ only by capitalization or surrounding spaces. Missing event names are labeled UNKNOWN. Missing numeric values are replaced with zero for this analysis.
required_columns <- c(
"EVTYPE",
"FATALITIES",
"INJURIES",
"PROPDMG",
"PROPDMGEXP",
"CROPDMG",
"CROPDMGEXP"
)
missing_columns <- setdiff(required_columns, names(storm))
if (length(missing_columns) > 0) {
stop(
"Required columns missing: ",
paste(missing_columns, collapse = ", ")
)
}
storm <- storm[, required_columns]
storm$EVTYPE <- toupper(trimws(as.character(storm$EVTYPE)))
storm$EVTYPE[
is.na(storm$EVTYPE) | storm$EVTYPE == ""
] <- "UNKNOWN"
numeric_columns <- c(
"FATALITIES",
"INJURIES",
"PROPDMG",
"CROPDMG"
)
for (column in numeric_columns) {
storm[[column]] <- as.numeric(as.character(storm[[column]]))
storm[[column]][is.na(storm[[column]])] <- 0
}
summary(storm[, numeric_columns])
## FATALITIES INJURIES PROPDMG CROPDMG
## Min. : 0.0000 Min. : 0.0000 Min. : 0.00 Min. : 0.000
## 1st Qu.: 0.0000 1st Qu.: 0.0000 1st Qu.: 0.00 1st Qu.: 0.000
## Median : 0.0000 Median : 0.0000 Median : 0.00 Median : 0.000
## Mean : 0.0168 Mean : 0.1557 Mean : 12.06 Mean : 1.527
## 3rd Qu.: 0.0000 3rd Qu.: 0.0000 3rd Qu.: 0.50 3rd Qu.: 0.000
## Max. :583.0000 Max. :1700.0000 Max. :5000.00 Max. :990.000
Fatalities and injuries are aggregated separately by event type. Their sum is used to rank combined reported health impacts, while the separate counts allow deaths and nonfatal injuries to be examined independently.
health <- aggregate(
cbind(FATALITIES, INJURIES) ~ EVTYPE,
data = storm,
FUN = sum
)
health$TOTAL_IMPACTS <-
health$FATALITIES + health$INJURIES
health <- health[
order(
-health$TOTAL_IMPACTS,
-health$FATALITIES,
health$EVTYPE
),
]
health_top <- head(health, 10)
knitr::kable(
health_top,
row.names = FALSE,
caption = paste(
"Ten event types with the highest combined",
"reported fatalities and injuries"
),
digits = 0
)
| EVTYPE | FATALITIES | INJURIES | TOTAL_IMPACTS |
|---|---|---|---|
| TORNADO | 5633 | 91346 | 96979 |
| EXCESSIVE HEAT | 1903 | 6525 | 8428 |
| TSTM WIND | 504 | 6957 | 7461 |
| FLOOD | 470 | 6789 | 7259 |
| LIGHTNING | 816 | 5230 | 6046 |
| HEAT | 937 | 2100 | 3037 |
| FLASH FLOOD | 978 | 1777 | 2755 |
| ICE STORM | 89 | 1975 | 2064 |
| THUNDERSTORM WIND | 133 | 1488 | 1621 |
| WINTER STORM | 206 | 1321 | 1527 |
Damage amounts are stored separately from magnitude codes. Common codes include H for hundreds, K for thousands, M for millions, and B for billions. Numeric codes from 0 to 9 are interpreted as powers of ten. Blank codes are assigned a multiplier of one. Unrecognized codes are treated as unknown, and positive-damage records with unknown codes are reported separately.
damage_multiplier <- function(code) {
code <- toupper(trimws(as.character(code)))
multiplier <- rep(NA_real_, length(code))
# Blank and zero codes
multiplier[
!is.na(code) & code %in% c("", "0")
] <- 1
# Common letter codes
multiplier[!is.na(code) & code == "H"] <- 1e2
multiplier[!is.na(code) & code == "K"] <- 1e3
multiplier[!is.na(code) & code == "M"] <- 1e6
multiplier[!is.na(code) & code == "B"] <- 1e9
# Numeric exponent codes
numeric_code <- !is.na(code) & grepl("^[0-9]+$", code)
multiplier[numeric_code] <-
10^as.numeric(code[numeric_code])
multiplier
}
prop_multiplier <- damage_multiplier(storm$PROPDMGEXP)
crop_multiplier <- damage_multiplier(storm$CROPDMGEXP)
# Initialize damage values in dollars
storm$PROPDMG_USD <- 0
storm$CROPDMG_USD <- 0
# Convert property damage
positive_property <- storm$PROPDMG > 0
valid_property <- positive_property & !is.na(prop_multiplier)
storm$PROPDMG_USD[valid_property] <-
storm$PROPDMG[valid_property] *
prop_multiplier[valid_property]
# Convert crop damage
positive_crop <- storm$CROPDMG > 0
valid_crop <- positive_crop & !is.na(crop_multiplier)
storm$CROPDMG_USD[valid_crop] <-
storm$CROPDMG[valid_crop] *
crop_multiplier[valid_crop]
# Count positive-damage records with unknown codes
unknown_property_codes <- sum(
positive_property & is.na(prop_multiplier)
)
unknown_crop_codes <- sum(
positive_crop & is.na(crop_multiplier)
)
code_summary <- data.frame(
Damage_Type = c("Property", "Crop"),
Positive_Damage_Records_With_Unknown_Codes = c(
unknown_property_codes,
unknown_crop_codes
)
)
knitr::kable(
code_summary,
row.names = FALSE,
caption = "Positive-damage records with unrecognized magnitude codes"
)
| Damage_Type | Positive_Damage_Records_With_Unknown_Codes |
|---|---|
| Property | 6 |
| Crop | 0 |
Property damage and crop damage are summed separately. Their sum gives the combined reported economic damage used for ranking. Positive-damage records with unrecognized magnitude codes are excluded from the corresponding monetary totals because their values cannot be reliably calculated.
economic <- aggregate(
cbind(PROPDMG_USD, CROPDMG_USD) ~ EVTYPE,
data = storm,
FUN = sum
)
economic$TOTAL_DAMAGE_USD <-
economic$PROPDMG_USD + economic$CROPDMG_USD
economic <- economic[
order(
-economic$TOTAL_DAMAGE_USD,
-economic$PROPDMG_USD,
economic$EVTYPE
),
]
economic_top <- head(economic, 10)
economic_display <- data.frame(
Event = economic_top$EVTYPE,
Property_Damage_Billion_USD =
economic_top$PROPDMG_USD / 1e9,
Crop_Damage_Billion_USD =
economic_top$CROPDMG_USD / 1e9,
Total_Damage_Billion_USD =
economic_top$TOTAL_DAMAGE_USD / 1e9
)
knitr::kable(
economic_display,
row.names = FALSE,
digits = 3,
caption = paste(
"Ten event types with the greatest combined",
"reported property and crop damage"
)
)
| Event | Property_Damage_Billion_USD | Crop_Damage_Billion_USD | Total_Damage_Billion_USD |
|---|---|---|---|
| FLOOD | 144.658 | 5.662 | 150.320 |
| HURRICANE/TYPHOON | 69.306 | 2.608 | 71.914 |
| TORNADO | 56.947 | 0.415 | 57.362 |
| STORM SURGE | 43.324 | 0.000 | 43.324 |
| HAIL | 15.735 | 3.026 | 18.761 |
| FLASH FLOOD | 16.823 | 1.421 | 18.244 |
| DROUGHT | 1.046 | 13.973 | 15.019 |
| HURRICANE | 11.868 | 2.742 | 14.610 |
| RIVER FLOOD | 5.119 | 5.029 | 10.148 |
| ICE STORM | 3.945 | 5.022 | 8.967 |
The table above ranks event types by their combined reported fatalities and injuries. The following figure displays the two measures separately to distinguish deaths from nonfatal injuries.
library(ggplot2)
library(scales)
health_long <- rbind(
data.frame(
EVTYPE = health_top$EVTYPE,
Impact = "Fatalities",
Count = health_top$FATALITIES
),
data.frame(
EVTYPE = health_top$EVTYPE,
Impact = "Injuries",
Count = health_top$INJURIES
)
)
health_long$EVTYPE <- factor(
health_long$EVTYPE,
levels = rev(health_top$EVTYPE)
)
ggplot(
health_long,
aes(x = EVTYPE, y = Count, fill = Impact)
) +
geom_col() +
coord_flip() +
scale_y_continuous(labels = scales::comma) +
labs(
title = "Weather Events with the Greatest Health Impacts",
x = "Event type",
y = "Reported count",
fill = "Impact"
) +
theme_minimal()
Figure 1. Ten event types with the highest combined reported fatalities and injuries in the NOAA Storm Database.
The leading event types account for the largest combined reported numbers of fatalities and injuries among the event labels in the database. The separate values should also be considered because fatalities and nonfatal injuries represent different outcomes.
The following figure compares reported property damage and crop damage for the ten event types with the highest combined totals. Monetary values are expressed in billions of U.S. dollars.
economic_long <- rbind(
data.frame(
EVTYPE = economic_top$EVTYPE,
Damage_Type = "Property damage",
Damage_Billion = economic_top$PROPDMG_USD / 1e9
),
data.frame(
EVTYPE = economic_top$EVTYPE,
Damage_Type = "Crop damage",
Damage_Billion = economic_top$CROPDMG_USD / 1e9
)
)
economic_long$EVTYPE <- factor(
economic_long$EVTYPE,
levels = rev(economic_top$EVTYPE)
)
ggplot(
economic_long,
aes(
x = EVTYPE,
y = Damage_Billion,
fill = Damage_Type
)
) +
geom_col() +
coord_flip() +
scale_y_continuous(labels = scales::label_number()) +
labs(
title = "Weather Events with the Greatest Economic Impacts",
x = "Event type",
y = "Reported damage (billions of U.S. dollars)",
fill = "Damage type"
) +
theme_minimal()
Figure 2. Ten event types with the greatest combined reported property and crop damage, expressed in billions of U.S. dollars.
The economic results identify event types with the largest combined reported property and crop damage totals under the stated magnitude-code assumptions. Displaying the two damage categories separately shows their contribution to the overall total.
This analysis examines two important dimensions of severe weather impacts in the United States. Population health impacts are measured through reported fatalities and injuries, while economic impacts are measured through reported property and crop damage.
The tables and figures identify the event types with the largest totals in each category. These results summarize historical database records rather than predict future events. Their interpretation depends on reporting completeness, consistency in event labels, and the treatment of damage magnitude codes. In particular, records with positive damage but unrecognized magnitude codes are excluded from the corresponding monetary totals, which may cause some economic impacts to be underestimated.