This project involves exploring the U.S. National Oceanic and Atmospheric Administration’s (NOAA) storm database. We will answer several questions related to a subset of the data in this database.
The data for this project comes from NOAA; it is downloadable as a bzip2 compressed file.
Information on how the variables are constructed can be found from the following links :-
The events in the database start in the year 1950 and end in November 2011. Less data is available in the earlier years of the database.
The data analysis must address the following qwuestions:-
Across the United States, which types of events (as indicated in the EVTYPE variable) are most harmful with respect to population health?
Across the United States, which types of events have the greatest economic consequences?
sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: aarch64-apple-darwin23
## Running under: macOS Sequoia 15.7.9
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.6/Resources/lib/libRlapack.dylib; LAPACK version 3.12.1
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Pacific/Auckland
## 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.61
## [5] cachem_1.1.0 knitr_1.52 htmltools_0.5.9 rmarkdown_2.32
## [9] lifecycle_1.0.5 cli_3.6.6 sass_0.4.10 jquerylib_0.1.4
## [13] compiler_4.6.1 rstudioapi_0.19.0 tools_4.6.1 evaluate_1.0.5
## [17] bslib_0.12.0 yaml_2.3.12 otel_0.2.0 jsonlite_2.0.0
## [21] rlang_1.3.0
Set to show R code
library(data.table)
library(ggplot2)
library(knitr)
library(dplyr)
library(tidyr)
opts_chunk$set(echo=TRUE)
We will download and read the data into a data table
stormdata
# Windows
#setwd("C:/Users/mchang/Projects/ProgrammingAssignment7")
# Mac
setwd("/Users/mchang/Projects/ProgrammingAssignment7")
path <- getwd()
datasetpath <- file.path(path,"dataset")
datafilename <- "dataFiles.bz2"
# Download file
url <- "https://d396qusza40orc.cloudfront.net/repdata%2Fdata%2FStormData.csv.bz2"
#download.file(url, file.path(path, datafilename))
#unzip(zipfile = datafilename, exdir = datasetpath)
stormdata <- data.table::fread("repdata_data_StormData.csv", fill=TRUE,header=TRUE)
We will take a look at the data
head(stormdata)
## STATE__ BGN_DATE BGN_TIME TIME_ZONE COUNTY COUNTYNAME STATE
## <char> <char> <char> <char> <char> <char> <char>
## 1: 1.00 4/18/1950 0:00:00 0130 CST 97.00 MOBILE AL
## 2: 1.00 4/18/1950 0:00:00 0145 CST 3.00 BALDWIN AL
## 3: 1.00 2/20/1951 0:00:00 1600 CST 57.00 FAYETTE AL
## 4: 1.00 6/8/1951 0:00:00 0900 CST 89.00 MADISON AL
## 5: 1.00 11/15/1951 0:00:00 1500 CST 43.00 CULLMAN AL
## 6: 1.00 11/15/1951 0:00:00 2000 CST 77.00 LAUDERDALE AL
## EVTYPE BGN_RANGE BGN_AZI BGN_LOCATI END_DATE END_TIME COUNTY_END COUNTYENDN
## <char> <char> <char> <char> <char> <char> <char> <char>
## 1: TORNADO 0.00 0.00
## 2: TORNADO 0.00 0.00
## 3: TORNADO 0.00 0.00
## 4: TORNADO 0.00 0.00
## 5: TORNADO 0.00 0.00
## 6: TORNADO 0.00 0.00
## END_RANGE END_AZI END_LOCATI LENGTH WIDTH F MAG FATALITIES INJURIES
## <char> <char> <char> <char> <char> <char> <char> <char> <char>
## 1: 0.00 14.00 100.00 3 0.00 0.00 15.00
## 2: 0.00 2.00 150.00 2 0.00 0.00 0.00
## 3: 0.00 0.10 123.00 2 0.00 0.00 2.00
## 4: 0.00 0.00 100.00 2 0.00 0.00 2.00
## 5: 0.00 0.00 150.00 2 0.00 0.00 2.00
## 6: 0.00 1.50 177.00 2 0.00 0.00 6.00
## PROPDMG PROPDMGEXP CROPDMG CROPDMGEXP WFO STATEOFFIC ZONENAMES LATITUDE
## <num> <char> <num> <char> <char> <char> <char> <num>
## 1: 25.0 K 0 3040
## 2: 2.5 K 0 3042
## 3: 25.0 K 0 3340
## 4: 2.5 K 0 3458
## 5: 2.5 K 0 3412
## 6: 2.5 K 0 3450
## LONGITUDE LATITUDE_E LONGITUDE_ REMARKS REFNUM
## <num> <num> <num> <char> <num>
## 1: 8812 3051 8806 1
## 2: 8755 0 0 2
## 3: 8742 0 0 3
## 4: 8626 0 0 4
## 5: 8642 0 0 5
## 6: 8748 0 0 6
str(stormdata)
## Classes 'data.table' and 'data.frame': 902297 obs. of 37 variables:
## $ STATE__ : chr "1.00" "1.00" "1.00" "1.00" ...
## $ BGN_DATE : chr "4/18/1950 0:00:00" "4/18/1950 0:00:00" "2/20/1951 0:00:00" "6/8/1951 0:00:00" ...
## $ BGN_TIME : chr "0130" "0145" "1600" "0900" ...
## $ TIME_ZONE : chr "CST" "CST" "CST" "CST" ...
## $ COUNTY : chr "97.00" "3.00" "57.00" "89.00" ...
## $ COUNTYNAME: chr "MOBILE" "BALDWIN" "FAYETTE" "MADISON" ...
## $ STATE : chr "AL" "AL" "AL" "AL" ...
## $ EVTYPE : chr "TORNADO" "TORNADO" "TORNADO" "TORNADO" ...
## $ BGN_RANGE : chr "0.00" "0.00" "0.00" "0.00" ...
## $ BGN_AZI : chr "" "" "" "" ...
## $ BGN_LOCATI: chr "" "" "" "" ...
## $ END_DATE : chr "" "" "" "" ...
## $ END_TIME : chr "" "" "" "" ...
## $ COUNTY_END: chr "0.00" "0.00" "0.00" "0.00" ...
## $ COUNTYENDN: chr "" "" "" "" ...
## $ END_RANGE : chr "0.00" "0.00" "0.00" "0.00" ...
## $ END_AZI : chr "" "" "" "" ...
## $ END_LOCATI: chr "" "" "" "" ...
## $ LENGTH : chr "14.00" "2.00" "0.10" "0.00" ...
## $ WIDTH : chr "100.00" "150.00" "123.00" "100.00" ...
## $ F : chr "3" "2" "2" "2" ...
## $ MAG : chr "0.00" "0.00" "0.00" "0.00" ...
## $ FATALITIES: chr "0.00" "0.00" "0.00" "0.00" ...
## $ INJURIES : chr "15.00" "0.00" "2.00" "2.00" ...
## $ 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 "" "" "" "" ...
## $ WFO : chr "" "" "" "" ...
## $ STATEOFFIC: chr "" "" "" "" ...
## $ ZONENAMES : chr "" "" "" "" ...
## $ LATITUDE : num 3040 3042 3340 3458 3412 ...
## $ LONGITUDE : num 8812 8755 8742 8626 8642 ...
## $ LATITUDE_E: num 3051 0 0 0 0 ...
## $ LONGITUDE_: num 8806 0 0 0 0 ...
## $ REMARKS : chr "" "" "" "" ...
## $ REFNUM : num 1 2 3 4 5 6 7 8 9 10 ...
## - attr(*, ".internal.selfref")=<pointer: 0x12a0114e0>
Based on the information above, we have 902,297 rows of observations and 37 columns of variables names. Since the questions are only interested in harmful events with respect to population health and economic consequences, we will focus on only the relevant variables.
names(stormdata)
## [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"
From the links regarding the variable construction and naming, we choose only the following variables.
Only these variables are chosen; everything else is discarded to make
analysis simpler. For readability, all variables are transformed to
lowercase and the fatalities and injuries columns made numeric. We will
read this into a new data table stormdata2
stormdata2 <- stormdata %>% select(c("EVTYPE","FATALITIES","INJURIES","PROPDMG","PROPDMGEXP","CROPDMG","CROPDMGEXP")) %>% rename_all(tolower)
stormdata2$fatalities <- as.numeric(stormdata2$fatalities)
stormdata2$injuries <- as.numeric(stormdata2$injuries)
str(stormdata2)
## Classes 'data.table' and '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 "" "" "" "" ...
## - attr(*, ".internal.selfref")=<pointer: 0x12a0114e0>
stormevents <- length(unique(stormdata2$evtype))
We have 985 unique types of storm events.
We can compute the total number of fatalities and injuries and group
them by storm event type into a new data frame
stormhealth1. We transform the data by using the
gather function to create a single column called type
(which contains the type “property” or “crop”) and a single value column
called value which contains the counts. This format is required for
ggplot2 later on.
stormhealth1 <- stormdata2 %>%
select("evtype","fatalities","injuries") %>%
group_by(evtype) %>%
summarise(totalfatalities = sum(fatalities), totalinjuries = sum(injuries), .groups='drop') %>%
arrange(desc(totalfatalities),desc(totalinjuries)) %>%
slice(1:10) %>%
gather(key=type,value=value,totalfatalities,totalinjuries)
propexp <- unique(stormdata2$propdmgexp)
cropexp <- unique(stormdata2$cropdmgexp)
Looking at the *exp fields, these seem to carry either the exponents
or the place value indicators. propexp carries these values
K, M, , B, m, +, 0, 5, 6, ?, 4, 2, 3, h, 7, H, -, 1, 8 and
cropexp carries these values , M, K, m, B, ?, 0, k, 2.
We will transform them into values that can be used for actual calculations. We set all NA values to 0.
dmgexpkey <- c("-" = 10^0,
"+" = 10^0,
"0" = 10^0,
"1" = 10^1,
"2" = 10^2,
"3" = 10^3,
"4" = 10^4,
"5" = 10^5,
"6" = 10^6,
"7" = 10^7,
"8" = 10^8,
"9" = 10^9,
"H" = 10^2,
"h" = 10^2,
"K" = 10^3,
"k" = 10^3,
"M" = 10^6,
"m" = 10^6,
"B" = 10^9,
"b" = 10^9
)
stormdata2[, propdmgexp := dmgexpkey[as.character(stormdata2[,propdmgexp])]]
stormdata2[, cropdmgexp := dmgexpkey[as.character(stormdata2[,cropdmgexp])]]
stormdata2[is.na(propdmgexp), propdmgexp := 10^0]
stormdata2[is.na(cropdmgexp), cropdmgexp := 10^0]
We can now compute the actual property damage and crop damage costs
by adding calculated columns propdmgvalue and
cropdmgvalue into a new data frame
stormdamage1. We then proceed to group all similar evtypes
together into stormdamage2. We transform the data by using
the gather function to create a single column called type
(which contains the type evtype) and a single value column called value
which contains the dollar values. This format is required for ggplot2
later on.
stormdamage1 <- stormdata2 %>%
select("evtype", "propdmg", "propdmgexp", "cropdmg", "cropdmgexp") %>%
mutate(propdmgvalue = propdmg * propdmgexp) %>%
mutate(cropdmgvalue = cropdmg * cropdmgexp)
stormdamage2 <- stormdamage1 %>%
group_by(evtype) %>%
summarise(totalprop = sum(propdmgvalue), totalcrop = sum(cropdmgvalue), .groups='drop') %>%
arrange(desc(totalprop),desc(totalcrop)) %>%
slice(1:10) %>%
gather(key=type,value=value,totalprop,totalcrop)
We are now ready to answer the questions posed in the assignment.
Question 1 : Across the United States, which types of events (as indicated in the EVTYPE variable) are most harmful with respect to population health?
ggplot(stormhealth1,aes(reorder(evtype,-value), value, fill=type)) + geom_bar(stat="identity", position="dodge") + labs(x="Event Type", y="Count",title="Storm Damage") + theme_bw() + theme(axis.text.x = element_text(angle=20,vjust=1,hjust=1))
knitr::kable(stormhealth1)
| evtype | type | value |
|---|---|---|
| TORNADO | totalfatalities | 5633 |
| EXCESSIVE HEAT | totalfatalities | 1903 |
| FLASH FLOOD | totalfatalities | 978 |
| HEAT | totalfatalities | 937 |
| LIGHTNING | totalfatalities | 816 |
| TSTM WIND | totalfatalities | 504 |
| FLOOD | totalfatalities | 470 |
| RIP CURRENT | totalfatalities | 368 |
| HIGH WIND | totalfatalities | 248 |
| AVALANCHE | totalfatalities | 224 |
| TORNADO | totalinjuries | 91346 |
| EXCESSIVE HEAT | totalinjuries | 6525 |
| FLASH FLOOD | totalinjuries | 1777 |
| HEAT | totalinjuries | 2100 |
| LIGHTNING | totalinjuries | 5230 |
| TSTM WIND | totalinjuries | 6957 |
| FLOOD | totalinjuries | 6789 |
| RIP CURRENT | totalinjuries | 232 |
| HIGH WIND | totalinjuries | 1137 |
| AVALANCHE | totalinjuries | 170 |
Answer : Based on the bar plot, tornadoes have the highest impact on population health since it causes the most fatalities and injuries. It is followed by excessive heat and floods.
ggplot(stormdamage2,aes(reorder(evtype,-value), value / 10^6, fill=type)) + geom_bar(stat="identity", position="dodge") + labs(x="Event Type", y="Damage (in millions)",title="Storm Damage") + theme_bw() + theme(axis.text.x = element_text(angle=20,vjust=1,hjust=1))
knitr::kable(stormdamage2)
| evtype | type | value |
|---|---|---|
| FLOOD | totalprop | 144657709807 |
| HURRICANE/TYPHOON | totalprop | 69305840000 |
| TORNADO | totalprop | 56947380676 |
| STORM SURGE | totalprop | 43323536000 |
| FLASH FLOOD | totalprop | 16822673978 |
| HAIL | totalprop | 15735267513 |
| HURRICANE | totalprop | 11868319010 |
| TROPICAL STORM | totalprop | 7703890550 |
| WINTER STORM | totalprop | 6688497251 |
| HIGH WIND | totalprop | 5270046295 |
| FLOOD | totalcrop | 5661968450 |
| HURRICANE/TYPHOON | totalcrop | 2607872800 |
| TORNADO | totalcrop | 414953270 |
| STORM SURGE | totalcrop | 5000 |
| FLASH FLOOD | totalcrop | 1421317100 |
| HAIL | totalcrop | 3025954473 |
| HURRICANE | totalcrop | 2741910000 |
| TROPICAL STORM | totalcrop | 678346000 |
| WINTER STORM | totalcrop | 26944000 |
| HIGH WIND | totalcrop | 638571300 |
Answwer : Based on the bar plot, floods have the highest economic impact on both property and crop damages. It is followed by hurricanes/typhoons and tornadoes.