A short analysis on the most harmful weather events in the USA with respect to population health and economic consequences

Synopsis

The goal of this report is to explore the NOAA Storm Database and answer the following questions:

-Across the United States, which types of events are most harmful with respect to population health?

-Across the United States, which types of events have the greatest economic consequences?

The original dataset can be found here It was obtained and downloaded in bz format on 8/04/2024. All data processing and analysis were conducted in R Studio.

Upon importing the file, the data was grouped by event type and the sum of injuries and fatalities were calculated. Subsequently, a graph was generated with two plots illustrating the event types with the highest number of injuries and fatalities along with their corresponding figures.

To address the second question, two new columns were created to incorporate financial damage, encompassing both crop and property damage. The dataset was then grouped by event type and the sum of total damage caused by each event was calculated. Finally, a barplot was generated to visualize which event types incurred the most damage.

Data Processing

The details of the software used are below:

sessionInfo()
## R version 4.2.3 (2023-03-15 ucrt)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19045)
## 
## Matrix products: default
## 
## locale:
## [1] LC_COLLATE=English_United Kingdom.utf8 
## [2] LC_CTYPE=English_United Kingdom.utf8   
## [3] LC_MONETARY=English_United Kingdom.utf8
## [4] LC_NUMERIC=C                           
## [5] LC_TIME=English_United Kingdom.utf8    
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## loaded via a namespace (and not attached):
##  [1] digest_0.6.31   R6_2.5.1        jsonlite_1.8.4  evaluate_0.20  
##  [5] cachem_1.0.7    rlang_1.1.0     cli_3.6.1       rstudioapi_0.14
##  [9] jquerylib_0.1.4 bslib_0.4.2     rmarkdown_2.21  tools_4.2.3    
## [13] xfun_0.38       yaml_2.3.7      fastmap_1.1.1   compiler_4.2.3 
## [17] htmltools_0.5.5 knitr_1.42      sass_0.4.5

The following libraries were loaded

# Load the packages
library(base)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggplot2)
library(tidyr)
library(patchwork)
library(gridExtra)
## 
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
## 
##     combine

A path was specified to the file

After opening the connection bz filed was read and the connection closed.

# Open a connection to the compressed file using bzfile
bzfile_conn <- bzfile(file_path, "r")
# Use read.csv to read the data from the compressed file
data <- read.csv(bzfile_conn)
# Close the connection
close(bzfile_conn)

After reading in the data we check summary of the dataset and the first few rows.

str(data)
## 'data.frame':    902297 obs. of  37 variables:
##  $ STATE__   : num  1 1 1 1 1 1 1 1 1 1 ...
##  $ 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    : num  97 3 57 89 43 77 9 123 125 57 ...
##  $ COUNTYNAME: chr  "MOBILE" "BALDWIN" "FAYETTE" "MADISON" ...
##  $ STATE     : chr  "AL" "AL" "AL" "AL" ...
##  $ EVTYPE    : chr  "TORNADO" "TORNADO" "TORNADO" "TORNADO" ...
##  $ BGN_RANGE : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ BGN_AZI   : chr  "" "" "" "" ...
##  $ BGN_LOCATI: chr  "" "" "" "" ...
##  $ END_DATE  : chr  "" "" "" "" ...
##  $ END_TIME  : chr  "" "" "" "" ...
##  $ COUNTY_END: num  0 0 0 0 0 0 0 0 0 0 ...
##  $ COUNTYENDN: logi  NA NA NA NA NA NA ...
##  $ END_RANGE : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ END_AZI   : chr  "" "" "" "" ...
##  $ END_LOCATI: chr  "" "" "" "" ...
##  $ LENGTH    : num  14 2 0.1 0 0 1.5 1.5 0 3.3 2.3 ...
##  $ WIDTH     : num  100 150 123 100 150 177 33 33 100 100 ...
##  $ F         : int  3 2 2 2 2 2 2 1 3 3 ...
##  $ MAG       : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ 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  "" "" "" "" ...
##  $ 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 ...
head(data)
##   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 columns we are interested in for the first question are EVTYPE, FATALITIES and INJURIES and for the second one EVTYPE, PROPDMG, PROPDMGEXP, CROPDMG and CROPDMGEXP.

columns_of_interest <- c("EVTYPE","FATALITIES","INJURIES","PROPDMG","CROPDMG","PROPDMGEXP","CROPDMGEXP")
missing_values <- colSums(is.na(data[, columns_of_interest]))

According to the NATIONAL WEATHER SERVICE INSTRUCTION 10-1605 the following Alphabetical characters are used to signify magnitude are “K” for thousands, “M” for millions, and “B” for billion for damage.

unique(data$PROPDMGEXP)
##  [1] "K" "M" ""  "B" "m" "+" "0" "5" "6" "?" "4" "2" "3" "h" "7" "H" "-" "1" "8"
unique(data$CROPDMGEXP)
## [1] ""  "M" "K" "m" "B" "?" "0" "k" "2"
not_stndsrd1 <-  sum(data$PROPDMGEXP != "K" & data$PROPDMGEXP != "M" & data$PROPDMGEXP != "B" & data$PROPDMGEXP != "" )
not_stndsrd2 <-  sum(data$CROPDMGEXP != "K" & data$CROPDMGEXP != "M" & data$CROPDMGEXP != "B" & data$CROPDMGEXP != "" )
not_stndsrd1/902297
## [1] 0.0003635167
not_stndsrd2/902297
## [1] 5.430584e-05

Looking at the unique values we can see that the proportion of invalid characters (“K”,“M”,“B”, ““) is low so we choose to ignore these.

Results

The weather events with the greatest population health consequences

In order to show the weather events with the greatest population health consequences, we can group the data by event type and calculate the total number of injuries and fatalities for each type.

data_gb <- data %>% group_by(EVTYPE) %>% summarise(sum_fatalities =sum(FATALITIES),sum_injuries =sum(INJURIES))
summary(data_gb)
##     EVTYPE          sum_fatalities     sum_injuries    
##  Length:985         Min.   :   0.00   Min.   :    0.0  
##  Class :character   1st Qu.:   0.00   1st Qu.:    0.0  
##  Mode  :character   Median :   0.00   Median :    0.0  
##                     Mean   :  15.38   Mean   :  142.7  
##                     3rd Qu.:   0.00   3rd Qu.:    0.0  
##                     Max.   :5633.00   Max.   :91346.0

Here (above) we can see that there are 985 types of event categories listed and the mean and maximum number of fatalities are 15.38 and 5633 respectively and the mean and maximum number of injuries are 142.7 and 91346 respectively.

Subsequently, we generate a graph with two plots illustrating the top 15 event types with the highest number of injuries and fatalities.

plot_fatalities <- ggplot(data_gb %>% top_n(15, sum_fatalities), aes(x = sum_fatalities, y = reorder(EVTYPE, sum_fatalities))) +
        geom_bar(stat = "identity", color = "black", fill='pink', width = 0.7) +
        labs(x = "Number of fatalities", y = NULL ) +
        theme(plot.title = element_text(hjust = 0.5)) +
        theme_minimal()

plot_injuries <- ggplot(data_gb %>% top_n(15, sum_injuries), aes(x = sum_injuries, y = reorder(EVTYPE, sum_injuries))) +
        geom_bar(stat = "identity", color = "black", fill='#D02090', width = 0.7) +
        labs(x = "Number of injuries", y = NULL) +
        ggtitle("The most harmful types of events with respect to population health") +
        theme(plot.title = element_text(hjust = 0.5)) +
        theme_minimal()

plot_injuries + plot_fatalities + plot_layout(ncol = 2, widths = c(5,2)) 

From the plot above we can see that there are substantially more injuries than fatalities which occurred due to weather events. Tornado causes both the highest number of fatalities and injuries and they are significantly higher than any of the other weather events. Most event categories are present on both graphs. Interestingly there are a few categories that cause a high number of deaths but not many injuries for example avalanche, heat wave and rip current. On the other hand wildfire and heavy snow are categories that were listed among the weather events with the 15 highest number of injuries but not listed with the highest number of fatalities.

The weather events with the greatest economic consequences

So far, we have looked at the health damage caused. The second question is related to the economic consequences of weather events. We have added 2 new columns to have the actual value of damage recorded. The original data set records both property and crop damage as numeric values rounded to three significant digits. Both of these columns are followed by an alphabetical character signifying the magnitude of the number including “K” for thousands, “M” for millions, and “B” for billions. We have added 2 new columns to have the actual value of damage recorded. These two column values were copied from the property and crop value columns and were multiplied by the correct number based on the alphabetical character recorded. We created a third column that adds the property and crop damage to have a total economic damage recorded.

data$p_dam <- data$PROPDMG
data$c_dam <- data$CROPDMG

result_data <- data %>% mutate(p_dam = case_when(PROPDMGEXP == "K" ~ PROPDMG * 1000, 
                                                 PROPDMGEXP == "M" ~ PROPDMG * 1000000,
                                                 PROPDMGEXP == "B" ~ PROPDMG * 1000000000))
result_data <- data %>% mutate(c_dam = case_when(CROPDMGEXP == "K" ~ CROPDMG * 1000, 
                                                 CROPDMGEXP == "M" ~ CROPDMG * 1000000,
                                                 CROPDMGEXP == "B" ~ CROPDMG * 1000000000))
result_data$c_p_dam <- result_data$p_dam+ result_data$c_dam

To show the weather events with the greatest economic consequences, we can group the data by event type and calculate the total economic damage caused.

fin_gb <- result_data %>% group_by(EVTYPE) %>% summarise(sum_dam =sum(c_p_dam))

Subsequently, we create a graph to show the combined crop and property damage by event type sorted in descending order displaying the top 15.

plot_c_p_dam <- ggplot(fin_gb %>% top_n(15, sum_dam), aes(x = sum_dam / 1e6, y = reorder(EVTYPE, sum_dam))) +
        geom_bar(stat = "identity", color = "black", fill = '#008B45' ,width = 0.7) +
        labs(x = "Crop and Property Damage (in million US Dollar)", y = NULL) +
        ggtitle("Events with the greatest economic consequences") +
        theme(plot.title = element_text(hjust = 0.5)) +
        theme_minimal()

plot_c_p_dam

Looking at the graph we can see that categories with related to “wet” and “cold” conditions are responsible for most of the financial damage caused followed by “freeze” and “frost” followed by other “wet” and “wind” categories. The categories that caused the most financial damage do not coincide with the categories causing the most public health harm.