Data Preparation

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(readr)
library(ggplot2)

# Set folder path
folder_path <- "C:/Users/kmohd/Downloads/storm_project"

# Define file paths
details_file <- file.path(folder_path, "StormEvents_details-ftp_v1.0_d2024_c20250401.csv")
fatalities_file <- file.path(folder_path, "StormEvents_fatalities-ftp_v1.0_d2024_c20250401.csv")
locations_file <- file.path(folder_path, "StormEvents_locations-ftp_v1.0_d2024_c20250401.csv")

# Load CSVs
details <- read_csv(details_file)
## Warning: One or more parsing issues, call `problems()` on your data frame for details,
## e.g.:
##   dat <- vroom(...)
##   problems(dat)
## Rows: 70196 Columns: 51
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (26): STATE, MONTH_NAME, EVENT_TYPE, CZ_TYPE, CZ_NAME, WFO, BEGIN_DATE_T...
## dbl (24): BEGIN_YEARMONTH, BEGIN_DAY, BEGIN_TIME, END_YEARMONTH, END_DAY, EN...
## lgl  (1): CATEGORY
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
fatalities <- read_csv(fatalities_file)
## Rows: 1047 Columns: 11
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (4): FATALITY_TYPE, FATALITY_DATE, FATALITY_SEX, FATALITY_LOCATION
## dbl (7): FAT_YEARMONTH, FAT_DAY, FAT_TIME, FATALITY_ID, EVENT_ID, FATALITY_A...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
locations <- read_csv(locations_file)
## Rows: 48112 Columns: 11
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): AZIMUTH, LOCATION
## dbl (9): YEARMONTH, EPISODE_ID, EVENT_ID, LOCATION_INDEX, RANGE, LATITUDE, L...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Join datasets by EVENT_ID
joined_data <- details %>%
  left_join(locations, by = "EVENT_ID") %>%
  left_join(fatalities, by = "EVENT_ID")
## Warning in left_join(., fatalities, by = "EVENT_ID"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 830 of `x` matches multiple rows in `y`.
## ℹ Row 441 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
# Save joined data
output_file <- file.path(folder_path, "StormEvents_joined_data.csv")
write_csv(joined_data, output_file)

# Preview
message("✅ Joined data saved to: ", output_file)
## ✅ Joined data saved to: C:/Users/kmohd/Downloads/storm_project/StormEvents_joined_data.csv
head(joined_data)
## # A tibble: 6 × 71
##   BEGIN_YEARMONTH BEGIN_DAY BEGIN_TIME END_YEARMONTH END_DAY END_TIME
##             <dbl>     <dbl>      <dbl>         <dbl>   <dbl>    <dbl>
## 1          202405        23       1947        202405      23     1947
## 2          202411        16        230        202411      18     1421
## 3          202405        19       1839        202405      19     1902
## 4          202405        23       2155        202405      23     2155
## 5          202405        24       1405        202405      24     1410
## 6          202411         1          0        202411       1     1600
## # ℹ 65 more variables: EPISODE_ID.x <dbl>, EVENT_ID <dbl>, STATE <chr>,
## #   STATE_FIPS <dbl>, YEAR <dbl>, MONTH_NAME <chr>, EVENT_TYPE <chr>,
## #   CZ_TYPE <chr>, CZ_FIPS <dbl>, CZ_NAME <chr>, WFO <chr>,
## #   BEGIN_DATE_TIME <chr>, CZ_TIMEZONE <chr>, END_DATE_TIME <chr>,
## #   INJURIES_DIRECT <dbl>, INJURIES_INDIRECT <dbl>, DEATHS_DIRECT <dbl>,
## #   DEATHS_INDIRECT <dbl>, DAMAGE_PROPERTY <chr>, DAMAGE_CROPS <chr>,
## #   SOURCE <chr>, MAGNITUDE <dbl>, MAGNITUDE_TYPE <chr>, FLOOD_CAUSE <chr>, …

Q1: Most Harmful Events to Population Health

# Summarize total injuries and deaths (direct + indirect)
health_impact <- joined_data %>%
  group_by(EVENT_TYPE) %>%
  summarise(
    total_injuries = sum(INJURIES_DIRECT, na.rm = TRUE) + sum(INJURIES_INDIRECT, na.rm = TRUE),
    total_deaths = sum(DEATHS_DIRECT, na.rm = TRUE) + sum(DEATHS_INDIRECT, na.rm = TRUE),
    total_harm = total_injuries + total_deaths,
    .groups = "drop"
  ) %>%
  arrange(desc(total_harm))

head(health_impact, 10)
## # A tibble: 10 × 4
##    EVENT_TYPE        total_injuries total_deaths total_harm
##    <chr>                      <dbl>        <dbl>      <dbl>
##  1 Excessive Heat               100         4042       4142
##  2 Tornado                     1646          235       1881
##  3 Flash Flood                   68         1422       1490
##  4 Heat                         173          674        847
##  5 Tropical Storm                14          603        617
##  6 Thunderstorm Wind            172          119        291
##  7 Winter Storm                 170          101        271
##  8 Debris Flow                   24          193        217
##  9 Hurricane                    162           37        199
## 10 Flood                          0          192        192
top10_health <- health_impact %>% slice_max(total_harm, n = 10)

ggplot(top10_health, aes(x = reorder(EVENT_TYPE, total_harm), y = total_harm)) +
  geom_bar(stat = "identity", fill = "tomato") +
  coord_flip() +
  labs(
    title = "Top 10 Most Harmful Event Types (Health Impact)",
    x = "Event Type",
    y = "Total Harm (Injuries + Deaths)"
  )


Q2: Most Frequent Events by State

# Count number of events by state and type
event_counts <- joined_data %>%
  group_by(STATE, EVENT_TYPE) %>%
  summarise(total_events = n(), .groups = "drop") %>%
  arrange(desc(total_events))

head(event_counts, 10)
## # A tibble: 10 × 3
##    STATE          EVENT_TYPE        total_events
##    <chr>          <chr>                    <int>
##  1 TEXAS          Flash Flood               1654
##  2 TEXAS          Hail                      1613
##  3 CALIFORNIA     Flood                     1268
##  4 NORTH CAROLINA Flash Flood               1175
##  5 KANSAS         Thunderstorm Wind         1081
##  6 PENNSYLVANIA   Thunderstorm Wind         1039
##  7 GEORGIA        Flash Flood               1037
##  8 NEW YORK       Thunderstorm Wind         1036
##  9 TEXAS          Thunderstorm Wind         1020
## 10 GEORGIA        Thunderstorm Wind         1017
# Top 5 states with most events
top_states <- joined_data %>%
  count(STATE) %>%
  arrange(desc(n)) %>%
  slice_max(n, n = 5) %>%
  pull(STATE)

# Filter event counts
top_state_events <- event_counts %>%
  filter(STATE %in% top_states)

# Plot
ggplot(top_state_events, aes(x = reorder(EVENT_TYPE, total_events), y = total_events, fill = STATE)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ STATE, scales = "free_y") +
  coord_flip() +
  labs(
    title = "Most Frequent Event Types in Top 5 States",
    x = "Event Type",
    y = "Number of Events"
  )


Q3: Most Common Events by Month

monthly_counts <- joined_data %>%
  group_by(MONTH_NAME, EVENT_TYPE) %>%
  summarise(total_events = n(), .groups = "drop") %>%
  arrange(match(MONTH_NAME, month.name))  # To sort Jan–Dec

head(monthly_counts, 10)
## # A tibble: 10 × 3
##    MONTH_NAME EVENT_TYPE              total_events
##    <chr>      <chr>                          <int>
##  1 January    Astronomical Low Tide              8
##  2 January    Avalanche                          9
##  3 January    Blizzard                         192
##  4 January    Coastal Flood                    124
##  5 January    Cold/Wind Chill                  727
##  6 January    Dense Fog                        244
##  7 January    Drought                          447
##  8 January    Dust Storm                         2
##  9 January    Extreme Cold/Wind Chill          703
## 10 January    Flash Flood                      695
# Plot top event types for each month
ggplot(monthly_counts, aes(x = reorder(EVENT_TYPE, total_events), y = total_events, fill = MONTH_NAME)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ MONTH_NAME, scales = "free_y") +
  coord_flip() +
  labs(
    title = "Most Common Event Types by Month",
    x = "Event Type",
    y = "Number of Events"
  )


Q4: Which Event Types Caused the Most Property Damage?

In this final section, we explore which types of weather events led to the highest economic losses in terms of property damage. This is based on the DAMAGE_PROPERTY column in the NOAA data, which includes values like “1.5K”, “2M”, etc. We’ll clean and convert these values before summarizing.

# Function to convert damage values like "1.5K", "2M", "3B" to numeric
convert_damage <- function(x) {
  x <- toupper(x)
  as.numeric(gsub("[KMB]", "", x)) *
    ifelse(grepl("K", x), 1e3,
    ifelse(grepl("M", x), 1e6,
    ifelse(grepl("B", x), 1e9, 1)))
}

# Convert DAMAGE_PROPERTY to numeric
joined_data$damage_clean <- convert_damage(joined_data$DAMAGE_PROPERTY)

# Summarize total property damage per event type
damage_summary <- joined_data %>%
  group_by(EVENT_TYPE) %>%
  summarise(total_damage = sum(damage_clean, na.rm = TRUE), .groups = "drop") %>%
  arrange(desc(total_damage))

head(damage_summary, 10)
## # A tibble: 10 × 2
##    EVENT_TYPE        total_damage
##    <chr>                    <dbl>
##  1 Flash Flood        22059904250
##  2 Tornado             8251050590
##  3 Hurricane           7852560000
##  4 Tropical Storm      7276582000
##  5 Flood               1928549600
##  6 Storm Surge/Tide     870042000
##  7 Wildfire             818268530
##  8 Thunderstorm Wind    476985100
##  9 Hail                 258287630
## 10 Coastal Flood        155358710
top10_damage <- damage_summary %>% slice_max(total_damage, n = 10)

ggplot(top10_damage, aes(x = reorder(EVENT_TYPE, total_damage), y = total_damage)) +
  geom_bar(stat = "identity", fill = "darkblue") +
  coord_flip() +
  labs(
    title = "Top 10 Event Types by Property Damage",
    x = "Event Type",
    y = "Total Property Damage (USD)"
  )