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>, …
# 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)"
)
# 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"
)
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"
)
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)"
)