Introduction

Embarking on a Whirlwind Adventure

Welcome to our exploration of tornado paths across the United States. This project aims to provide a detailed visualization of tornado occurrences from 2016 to 2021, using a combination of tornado path and point maps to illustrate the impact of these natural events.

Load Required Libraries

# Toolkit for Exploration

library(here)    # Constructs paths relative to the project root, enhancing reproducibility.
library(tidyverse) # Offers a collection of packages designed for data manipulation and visualization.
library(sf)      # Enables handling, processing, and analyzing of spatial data.
library(ggplot2) # Provides a system for declaratively creating graphics based on The Grammar of Graphics.
library(dplyr)   # A grammar of data manipulation, providing a consistent set of verbs.
library(cowplot) # Facilitates plot arrangement and combining multiple plots into one figure.
library(magrittr) # Supports pipe operators for more readable code pipelines.
library(units)   # Allows for symbolic handling of physical quantities.
library(scales)  # Contains functions for scaling data for visualizations.
library(RColorBrewer)  # Provides color palettes for enhancing plot aesthetics.
library(tidyr)   # Designed for data tidying and replacing missing values.
library(gridExtra) # Offers functions for arranging multiple grid-based plots on a page and drawing tables.
library(ggspatial)  # Enhances ggplot2 with spatial data frameworks.
library(patchwork)  # Facilitates plot composition by combining multiple ggplot objects.

Setting the Stage

To begin, we’ll confirm our project’s directory setup with here(), ensuring that our work is organized and reproducible. Following this, we will locate and load the necessary shapefiles for tornado paths and county boundaries, which are crucial for our spatial analysis.

# Verify the project directory setup using here(), ensuring organized and reproducible work.
here() # Confirms the current project directory, ensuring correct path setup.
## [1] "D:/GEOG_588/Lab_Assignments/Lab_4/Tornadoes_in_Oklahoma"

Constructing Pathways

Before diving into analysis, let’s locate and load the shapefiles containing tornado paths and county boundaries.

# Construct the full paths to the required shapefiles using here(), preparing for data loading.
okcounty_path <- here("data", "shapefiles", "ok_counties.shp")
tpoint_path <- here("data", "shapefiles", "ok_tornado_point.shp")
tpath_path <- here("data", "shapefiles", "ok_tornado_path.shp")

Reading the Data

Now that we know where our data resides, let’s bring it into our environment. We’ll read the shapefiles into spatial objects using st_read() from the sf package.

# Load the shapefiles as spatial objects for analysis.
okcounty <- st_read(okcounty_path, quiet = TRUE) # Reads county shapefiles into sf objects.
tpoint <- st_read(tpoint_path, quiet = TRUE)     # Reads tornado point data into sf objects.
tpath <- st_read(tpath_path, quiet = TRUE)       # Reads tornado path data into sf objects.

Examining the Data

An understanding of the data structure and attributes is essential before progressing. Thus, we will examine the classes and summaries of our spatial objects, providing insights into the contained information.

# Investigate the structure and summary of the spatial objects to understand the contained data.
class(okcounty) # Displays the class of the okcounty object.
## [1] "sf"         "data.frame"
glimpse(okcounty) # Provides a detailed overview of the okcounty data.
## Rows: 77
## Columns: 8
## $ STATEFP  <chr> "40", "40", "40", "40", "40", "40", "40", "40", "40", "40", "…
## $ COUNTYFP <chr> "077", "025", "011", "107", "105", "153", "001", "053", "059"…
## $ COUNTYNS <chr> "01101826", "01101800", "01101793", "01101841", "01101840", "…
## $ AFFGEOID <chr> "0500000US40077", "0500000US40025", "0500000US40011", "050000…
## $ GEOID    <chr> "40077", "40025", "40011", "40107", "40105", "40153", "40001"…
## $ NAME     <chr> "Latimer", "Cimarron", "Blaine", "Okfuskee", "Nowata", "Woodw…
## $ LSAD     <chr> "06", "06", "06", "06", "06", "06", "06", "06", "06", "06", "…
## $ geometry <POLYGON [°]> POLYGON ((-95.50766 35.0292..., POLYGON ((-103.0025 3…
class(tpoint) # Displays the class of the tpoint object.
## [1] "sf"         "data.frame"
glimpse(tpoint) # Provides a detailed overview of the tpoint data.
## Rows: 4,092
## Columns: 23
## $ om       <dbl> 192, 27, 38, 57, 60, 61, 50, 52, 96, 108, 113, 117, 119, 76, …
## $ yr       <dbl> 1950, 1950, 1950, 1950, 1950, 1950, 1950, 1950, 1950, 1950, 1…
## $ mo       <dbl> 10, 2, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, …
## $ dy       <dbl> 1, 27, 27, 28, 28, 28, 2, 3, 11, 16, 22, 24, 29, 4, 4, 4, 7, …
## $ date     <chr> "1950-10-01", "1950-02-27", "1950-03-27", "1950-04-28", "1950…
## $ time     <chr> "21:00:00", "10:20:00", "03:00:00", "14:17:00", "19:05:00", "…
## $ tz       <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3…
## $ st       <chr> "OK", "OK", "OK", "OK", "OK", "OK", "OK", "OK", "OK", "OK", "…
## $ stf      <dbl> 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 4…
## $ stn      <dbl> 23, 1, 2, 5, 6, 7, 3, 4, 15, 16, 17, 18, 19, 8, 9, 10, 11, 12…
## $ mag      <dbl> 1, 2, 2, 3, 4, 2, 2, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1…
## $ inj      <dbl> 0, 0, 0, 1, 32, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, …
## $ fat      <dbl> 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ loss     <dbl> 4, 4, 3, 5, 5, 4, 4, 3, 2, 3, 0, 4, 2, 4, 3, 5, 0, 4, 3, 4, 3…
## $ closs    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ slat     <dbl> 36.73, 35.55, 34.85, 34.88, 35.08, 34.55, 35.82, 36.13, 36.82…
## $ slon     <dbl> -102.52, -97.60, -95.75, -99.28, -96.40, -96.20, -97.02, -95.…
## $ elat     <dbl> 36.8800, 35.5501, 34.8501, 35.1700, 35.1300, 34.5501, 35.8201…
## $ elon     <dbl> -102.3000, -97.5999, -95.7499, -99.2000, -96.3500, -96.1999, …
## $ len      <dbl> 15.8, 2.0, 0.1, 20.8, 4.5, 0.8, 1.0, 1.0, 0.5, 7.3, 1.5, 1.0,…
## $ wid      <dbl> 10, 50, 77, 400, 200, 100, 100, 33, 77, 100, 100, 33, 33, 293…
## $ fc       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ geometry <POINT [°]> POINT (-102.52 36.73), POINT (-97.6 35.55), POINT (-95.…
class(tpath) # Displays the class of the tpath object.
## [1] "sf"         "data.frame"
glimpse(tpath) # Provides a detailed overview of the tpath data.
## Rows: 4,092
## Columns: 23
## $ om       <dbl> 192, 27, 38, 57, 60, 61, 50, 52, 96, 108, 113, 117, 119, 76, …
## $ yr       <dbl> 1950, 1950, 1950, 1950, 1950, 1950, 1950, 1950, 1950, 1950, 1…
## $ mo       <dbl> 10, 2, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, …
## $ dy       <dbl> 1, 27, 27, 28, 28, 28, 2, 3, 11, 16, 22, 24, 29, 4, 4, 4, 7, …
## $ date     <chr> "1950-10-01", "1950-02-27", "1950-03-27", "1950-04-28", "1950…
## $ time     <chr> "21:00:00", "10:20:00", "03:00:00", "14:17:00", "19:05:00", "…
## $ tz       <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3…
## $ st       <chr> "OK", "OK", "OK", "OK", "OK", "OK", "OK", "OK", "OK", "OK", "…
## $ stf      <dbl> 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 4…
## $ stn      <dbl> 23, 1, 2, 5, 6, 7, 3, 4, 15, 16, 17, 18, 19, 8, 9, 10, 11, 12…
## $ mag      <dbl> 1, 2, 2, 3, 4, 2, 2, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 1, 1…
## $ inj      <dbl> 0, 0, 0, 1, 32, 0, 0, 0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 3, 0, 0, …
## $ fat      <dbl> 0, 0, 0, 1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ loss     <dbl> 4, 4, 3, 5, 5, 4, 4, 3, 2, 3, 0, 4, 2, 4, 3, 5, 0, 4, 3, 4, 3…
## $ closs    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ slat     <dbl> 36.73, 35.55, 34.85, 34.88, 35.08, 34.55, 35.82, 36.13, 36.82…
## $ slon     <dbl> -102.52, -97.60, -95.75, -99.28, -96.40, -96.20, -97.02, -95.…
## $ elat     <dbl> 36.8800, 35.5501, 34.8501, 35.1700, 35.1300, 34.5501, 35.8201…
## $ elon     <dbl> -102.3000, -97.5999, -95.7499, -99.2000, -96.3500, -96.1999, …
## $ len      <dbl> 15.8, 2.0, 0.1, 20.8, 4.5, 0.8, 1.0, 1.0, 0.5, 7.3, 1.5, 1.0,…
## $ wid      <dbl> 10, 50, 77, 400, 200, 100, 100, 33, 77, 100, 100, 33, 33, 293…
## $ fc       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0…
## $ geometry <LINESTRING [°]> LINESTRING (-102.52 36.73, ..., LINESTRING (-97.6 …

Data Transformation

To ensure compatibility with older packages, I’ll convert our sf objects to sp objects. Later, I’ll revert them back to sf objects for compatibility with the latest version of RStudio.

The transformation of data from sf to sp objects isn’t just technical; it’s a significant step. It allows our data to transcend boundaries, facilitating communication between the old and new custodians of geoinformation knowledge. Our journey through data formats demonstrates our adaptability and commitment to embracing the intricacies of geographic exploration.

Our journey completes a full circle as we embrace the latest spatial operation and visualization capabilities. Re-transforming data from sp to sf objects unleashes countless analytical possibilities, propelling us to the forefront of geospatial exploration.

# Convert spatial data between sf and sp objects to ensure compatibility with various R packages.
# The conversion facilitates the integration of modern and legacy spatial data handling approaches.

# Convert sf objects to sp objects for compatibility with older spatial packages.

okcounty_sp <- as(okcounty, 'Spatial') # Converts okcounty from sf to sp object.
tpoint_sp <- as(tpoint, 'Spatial')     # Converts tpoint from sf to sp object.
tpath_sp <- as(tpath, 'Spatial')       # Converts tpath from sf to sp object.

# Convert sp objects back to sf objects for compatibility with newer RStudio versions.

okcounty_sf <- st_as_sf(okcounty_sp) # Converts okcounty_sp back to sf object.
tpoint_sf <- st_as_sf(tpoint_sp)     # Converts tpoint_sp back to sf object.
tpath_sf <- st_as_sf(tpath_sp)       # Converts tpath_sp back to sf object.

Exercise 1: Deciphering the Tempest: A Cartographic Analysis of Tornado Activity

In this exercise, I explore tornado paths in Tornado Alley, focusing on the period from 2016 to 2021. My main goals are to create a map clearly showing tornado paths, with each year’s events color-coded for clarity. Then, I plan to present my findings in a comprehensive figure combining tornado paths and starting points into one cohesive image using the plot_grid() function. This exercise enhances my analytical skills and demonstrates how to turn complex data into a compelling narrative. It highlights when and where tornado events occur in Oklahoma, bringing their story to life.

Tornado Path Visualization

n my investigation, I’m tracing the history of tornado activity in Oklahoma. I start by creating maps that clearly show the paths of tornadoes. Each year’s paths are color-coded for better understanding. Then, I combine these paths with the starting locations of the tornadoes into one detailed figure. This unified visualization helps us see the whole picture of tornado activity in Oklahoma over the years.

Composite Visualization of Tornado Paths and Points

Utilizing ggplot and the plot_grid function, I merged the narratives of tornado paths and their starting points into a coherent visual story. Each year’s paths and points were assigned a distinct color, bringing them to life and emphasizing the temporal aspect of tornado activity. Our goal was to craft a comprehensive visualization that not only tracks the historical journey of these storms but also provides a holistic view of their activity in Oklahoma. This endeavor goes beyond simple mapping; it aims to unite tornado paths and their origins into a cohesive representation, offering detailed insights into their interaction.

Mapping Tornado Paths and Initiation Points

Let’s explore the intriguing process of mapping tornado paths and their starting points, taking a closer look at each year individually.

Filtering Tornado Paths Data

To focus my analysis on the tornado paths between 2016 and 2021, I applied a filter to my dataset and limited the analysis to this specific time frame. This selective method allows me to go in depth and gain meaningful insights from the data, and ensures that my investigation remains both focused and relevant to the years of interest.

# Step 1: Filter the tornado paths dataset for the period from 2016 to 2021
# This step narrows down the dataset to focus on tornado occurrences within the specified time frame, 
# enabling a focused analysis on recent events.

tpath_16_21 <- tpath %>%
  filter(yr >= 2016 & yr <= 2021)
tpoint_16_21 <- tpoint %>%
  filter(yr >= 2016 & yr <= 2021)

Plotting Tornado Paths 2016-2021

With my dataset ready, I proceed to visualize the tornado paths, gaining insights into their geographic distribution and movement patterns within our chosen timeframe. This visualization not only enhances our understanding of spatial dynamics but also highlights the complexities of tornado trajectories.

# Step 2: Ensure datasets are in WGS 84 CRS and load shapefiles
# This is crucial for accurate spatial analysis and mapping. 

# Transforming the coordinate reference system to WGS 84

okcounty <- st_transform(okcounty, crs = 4326)
tpath <- st_transform(tpath, crs = 4326)
tpoint <- st_transform(tpoint, crs = 4326)

Tornado Paths and Initiation Points Visualization

Overview

Our next step is to visually explore the tornado paths and initiation points from 2016 to 2021, using the tpoint and tpath datasets to understand the spatial dynamics of tornado activity during these years.

Filtering Data and Plotting Tornado Initiation Points and Paths

We begin by filtering the tpoint dataset to focus on our study period. Then, we plot both the initiation points and the paths of tornadoes, using a range of colors to differentiate the years. This approach provides a comprehensive view of tornado activity, highlighting spatial and temporal patterns.

# Step 3: Combined visualization of tornado initiation points and paths
# Defines a consistent base theme for the plots to maintain visual consistency and enhance readability.

# Defining the caption

detailed_caption <- paste(
  "Figure: Tornado initiation points and paths from 2016 to 2021.",
  "Methods: Data from NOAA, projected in WGS 84 CRS, visualized using ggplot2.",
  "Tornado paths are shown in relation to the initiation points, with each year differentiated by color.",
  "Statistics: Data represent verified tornado occurrences without statistical testing.",
  sep = "\n"
)

# Define the base theme
base_theme <- theme_minimal() +
  theme(
    panel.grid.major = element_line(color = "grey80", size = 0.5),
    panel.grid.minor = element_blank(),
    axis.text.x = element_text(angle = 45, hjust = 1),
    axis.text.y = element_text(),
    axis.title.x = element_text(size = 12, margin = margin(t = 20)),
    axis.title.y = element_text(size = 12, margin = margin(r = 20)),
    legend.margin = margin(t = 0, r = 20, b = 0, l = 20),
    plot.title = element_text(margin = margin(b = 50))
  )

# Step 4: # Next, we construct two separate plots for tornado initiation points and paths, each differentiated by year.
# Colors are assigned based on the year to visually distinguish data from different years.

# Plot tornado points
tornado_points_plot <- ggplot(data = tpoint_16_21) +
  geom_sf(aes(color = as.factor(yr))) +
  scale_color_brewer(palette = "Spectral", name = "Year") +
  base_theme +
  labs(
    title = "Figure 1: Tornado Initiation Points by Year",
    x = NULL, # Remove X axis title
    y = NULL  # Remove Y axis title
  ) +
  coord_sf()

# Plot tornado paths
tornado_paths_plot <- ggplot(data = tpath_16_21) +
  geom_sf(aes(color = as.factor(yr))) +
  scale_color_brewer(palette = "Spectral", name = "Year") +
  base_theme +
  labs(
    title = "Figure 2: Tornado Paths by Year",
    x = NULL, # Remove X axis title
    y = NULL  # Remove Y axis title
  ) +
  coord_sf()

# Step 5: # The plots are then combined into a single figure for a comprehensive view.
# This combined figure integrates both the initiation points and paths of tornadoes,
# offering a holistic understanding of tornado activity over the specified years.

# Combining plots
combined_plot <- plot_grid(tornado_points_plot, tornado_paths_plot, labels = NULL)

#Step 6: # Finally, a caption is prepared to describe the figure comprehensively, including the data source,
# methods, and a brief analysis. This caption is designed to provide context and enhance understanding of the visualized data.

# Define the main title for the combined plots
main_title_text <- "Comparative Analysis of Tornado Initiation Points and Paths"
main_title_plot <- ggdraw() + draw_label(main_title_text, size = 16, fontface = "bold", hjust = 0.5)

# Adding an invisible layer to create space between the plot and the caption
tornado_points_plot <- tornado_points_plot +
  annotate("text", x = Inf, y = -Inf, label = " ", vjust = -10, color = NA)

tornado_paths_plot <- tornado_paths_plot +
  annotate("text", x = Inf, y = -Inf, label = " ", vjust = -10, color = NA)

# Combine the plots without labels
combined_plot <- plot_grid(tornado_points_plot, tornado_paths_plot, labels = NULL)

# Define the caption for the combined plots
combined_caption <- detailed_caption # This is already defined in your code snippet

# Create a caption plot
caption_plot <- ggdraw() + draw_label(combined_caption, size = 12, hjust = 0.5)

# Combine the main title, the plots, and the caption into one final plot
final_combined_plot <- plot_grid(
  main_title_plot,    # Main title
  combined_plot,      # Combined plots of points and paths
  caption_plot,       # Combined caption
  ncol = 1,
  rel_heights = c(0.1, 1, 0.1) # Adjust relative heights if needed
)

# Print the final combined plot with the main title and caption
print(final_combined_plot)




Summary

In examining the tornado data and maps for Oklahoma, I have made insightful observations about the behavior of tornado activity. Below are the key findings from my analysis:

  • Temporal trends: I have observed distinct patterns in tornado events over time, with a marked increase in certain years. This variability prompted me to further investigate the meteorological and climatic influences that lead to the formation of tornadoes.

  • Spatial distribution: Mapping revealed concentrated areas of tornado activity, particularly in the central and eastern portions of Oklahoma. This finding underscores the critical role that local atmospheric conditions and geographic landscapes play in the formation and progression of tornadoes.

  • Density analysis: Through my examination of tornado density across counties and years, I was able to identify compelling trends and pinpoint regions of increased activity, or “hotspots”

Exercise 2: Analysis of Tornado Occurrences

Introduction

In this analysis, I explore the complex patterns of tornado occurrence in Oklahoma. Using various data analysis and visualization methods, I aim to uncover the underlying dynamics of tornado activity and its impact on the region’s landscape.

Faceted Maps: Comparative Analysis

Crafting the Chronicle of Change

To make a comparative analysis of tornado events over several years, I turn to facet maps. These maps serve as a visual tool to depict the variations in tornado activity across Oklahoma counties and provide a detailed perspective on the changes over time.

# Step 1: Ensure Coordinate Reference Systems (CRS) Match
# This step checks and matches the CRS of the tornado points and county data to ensure spatial
# compatibility for accurate mapping.

if(st_crs(tpoint_16_21) != st_crs(okcounty)) {
  tpoint_16_21 <- st_transform(tpoint_16_21, crs = st_crs(okcounty))
}

# Step 2: Perform a Spatial Join
# Maps tornado points to their corresponding counties by spatially joining the datasets. 
# This step is crucial for subsequent county-based analysis.

countypnt <- st_join(tpoint_16_21, okcounty)

After combining the datasets, I need to simplify our visual representation by removing unnecessary graphics and focusing only on the data. This will help us prepare for summarizing tornado occurrences in each county.

# Step 3: Refine Data by Removing Spatial Geometry
# Focuses analysis on the data attributes by stripping away the spatial geometry component. 
# This simplification aids in the summarization process.

countypnt <- st_drop_geometry(countypnt)

With our data prepared, I proceed to the summarization stage, grouping our findings by county and counting the occurrences of tornadoes. This allows me to uncover the number of tornadoes in each county, providing valuable insights into their spatial distribution.

# Step 4: Summarize Tornado Occurrences by County
# Groups data by county and counts the number of tornado occurrences, offering insight into spatial distribution.

# Summarize Tornado Occurrences by County

countysum <- countypnt %>%
  group_by(GEOID) %>%
  summarize(tcnt = n())

# Further Summarization by County and Year

tornado_summary <- countypnt %>%
  group_by(GEOID, yr) %>%
  summarize(tcnt = n(), .groups = 'drop')

# Quickly inspect the summarized data structure

glimpse(countysum)
## Rows: 75
## Columns: 2
## $ GEOID <chr> "40001", "40005", "40007", "40009", "40011", "40013", "40015", "…
## $ tcnt  <int> 6, 3, 4, 8, 1, 4, 10, 5, 7, 5, 3, 12, 10, 5, 5, 1, 7, 9, 7, 8, 2…
# Expected output structure: 75 Rows, 2 Columns: GEOID, tcnt
# Step 5: Further Summarization by County and Year
# Provides a more granular understanding of tornado occurrences, facilitating year-over-year comparison.

tornado_summary <- countypnt %>%
  group_by(GEOID, yr) %>%
  summarize(tcnt = n(), .groups = 'drop')

# Step 6: Merge Tornado Data with County Polygons
# Joins summarized tornado data with county polygons to prepare for spatial visualization, 
# ensuring each county has a tornado count (default to 0 if no data).

okcounty_unique <- okcounty %>% distinct(GEOID, .keep_all = TRUE)

# Ensure tornado_summary is a regular dataframe

tornado_summary <- as.data.frame(tornado_summary)

# Perform the join
county_tornado_density <- okcounty_unique %>%
  left_join(tornado_summary, by = "GEOID") %>%
  replace_na(list(tcnt = 0))

# Adding an invisible annotation for spacing
spacing_annotation <- annotation_custom(
  grob = grid::rectGrob(gp = grid::gpar(col = NA, fill = NA)), 
  ymin = -Inf, 
  ymax = -Inf
)

# Step 7: Craft Faceted Maps for Comparative Analysis
plot <- ggplot(data = county_tornado_density) +
  geom_sf(aes(fill = tcnt), color = NA) +
  facet_wrap(~ yr, ncol = 3) + # Adjust the number of columns to fit your data
  scale_fill_gradient(low = "#FFFF00", high = "red", name = "Tornado Count") +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    strip.background = element_blank(),
    panel.border = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.spacing = unit(1, "lines"),
    strip.text = element_text(size = 12),
    plot.caption = element_text(hjust = 0.5) # Center the caption text
  ) +
  labs(
    title = NULL, # Remove the title here as we will add a main title for all facets
    caption = NULL # Remove the caption here as we will add a unified caption
  )

# Step 8: 
# Define the main title for the combined plots
main_title_text <- "County-Level Tornado Density from 2016-2021"
main_title_plot <- ggdraw() + draw_label(main_title_text, size = 18, fontface = "bold", hjust = 0.5)

# Define the unified caption for the combined plots
combined_caption <- 
"Figure 3: County-Level Tornado Density.\nMethods: Data from NOAA showing tornado occurrences by county, visualized using ggplot2.\nStatistics: Each panel represents a year from 2016 to 2021 with counties color-coded by tornado count."
caption_plot <- ggdraw() + draw_label(combined_caption, size = 12, hjust = 0.5)

# Combine the main title, the faceted plot, and the caption into one final plot
final_combined_plot <- plot_grid(
  main_title_plot,  # Main title
  plot,             # Faceted plot
  caption_plot,     # Unified caption
  ncol = 1,
  rel_heights = c(0.1, 1, 0.1) # Adjust the relative heights to allocate space for title and caption
)

# Print the final combined plot with the main title and unified caption
print(final_combined_plot)




Summary

Analysis of Tornado Density

The creation of the facet chart to show tornado density at the county level from 2016 to 2021 has led to several important observations regarding the distribution of tornado events across different counties and years:

  • Temporal Trends: the faceted plots showed significant fluctuations in tornado density over time, indicating variations likely due to meteorological and climatic conditions. These fluctuations call for a closer examination of the causes of tornado occurrence.

  • Spatial Distribution: My review of the faceted maps has brought to light certain areas in Oklahoma that have a higher concentration of tornado activity and thus represent “hotspots” This discovery underscores the critical role of local atmospheric dynamics and geographic landscape in influencing tornado activity.

  • Density Analysis: Detailed examination of tornado density across counties and over the years has revealed both spatial and temporal trends and identified regions with strikingly frequent tornado events. These areas of increased activity require a targeted focus on improving tornado preparedness and implementing effective mitigation strategies.

Exercise 3: The Choropleth Quartet: A Cartographic Journey Through Tornado Density

Now, for choropleth maps, where color intensity and analysis depth are key factors. I’ll make a series of maps using different color divisions based on tornado density. This will show how different perspectives on tornado density influence our understanding.

Objective: Crafting a Narrative with Data

I start with a big goal: to create four choropleth maps. Each map will display tornado events in different regions, divided into 3, 4, 5, and 6 groups based on tornado frequency. These maps aren’t just about visuals; they tell stories through data, offering unique views on tornado density.

Setting the Stage: Preparing the Data

To create a strong foundation, I first connect tornado event data with geographic information, blending tornado occurrences with county boundaries in Oklahoma. This means cleaning up the dataset carefully, removing unnecessary details, and fixing any mistakes to ensure accuracy. This step is crucial—it sets the stage for making reliable maps later on.

Merging Tornado Data with Geographical Context

In earnest, I begin by initiating a spatial join between tornado data points and Oklahoma county boundaries. This crucial step connects the precise locations of tornado events with county jurisdictions, enabling a geographically informed analysis.

# Step 1: Spatially Join Tornado Data with County Boundaries
# Merging tornado event locations with geographical county data.

countypnt <- st_join(tpoint_16_21, okcounty)

Refining the Dataset: Clarity and Precision

With the data merged, my focus shifts to refining the dataset. The aim here is to distill the essence of the data by stripping away the spatial geometry, simplifying the dataset to its core elements and preparing it for a more profound analysis.

# Step 2: Refine the Merged Dataset
# Removing spatial geometry to focus on core data elements.

countypnt <- st_drop_geometry(countypnt)

Uncovering Patterns: Tornado Occurrences by County

Aggregating Tornado Occurrences by County

With a cleaner dataset, I tally the tornado occurrences by county. This step turns raw data into useful insights, revealing how many tornado events happened in each county. This information is essential for understanding where tornadoes are located in Oklahoma.

# Step 3: Aggregate Tornado Occurrences by County

# Counting tornado events in each county for analysis.
countysum <- countypnt %>%
  group_by(GEOID) %>%
  summarize(tcnt = n())
# Inspecting the summarized data structure.
glimpse(countysum)
## Rows: 75
## Columns: 2
## $ GEOID <chr> "40001", "40005", "40007", "40009", "40011", "40013", "40015", "…
## $ tcnt  <int> 6, 3, 4, 8, 1, 4, 10, 5, 7, 5, 3, 12, 10, 5, 5, 1, 7, 9, 7, 8, 2…

By merging, refining, and summarizing the data, we make analyzing tornado occurrences in Oklahoma easier and gain better insights into how they relate to the state’s geography.

Visualizing Tornado Density Across Oklahoma

Enriching the geographic context with tornado data

Next, I enhance the geographic context by incorporating the aggregated tornado occurrence data. By merging this information with county polygons, I visually highlight areas with higher tornado activity, improving our strategic understanding of disaster preparedness.

# Step 4: Integrate Summarized Tornado Data with County Polygons

# Merging tornado counts back into the county polygons for visualization.
countysum <- as.data.frame(countysum)
countymap <- okcounty %>%
  left_join(countysum, by = "GEOID") %>%
  replace_na(list(tcnt = 0)) %>%
  mutate(area = st_area(.), tdens = tcnt / area * 10^6 * 10^3) %>%
  drop_units()

Enhancing county maps with detailed tornado data turns abstract numbers into a clear story of risk and resilience. It helps us understand how tornadoes interact with Oklahoma’s geography and communities, guiding efforts to reduce their impact.

# Step 5: Define Function for Creating Choropleth Maps

# Generating choropleth maps based on quantile classes for tornado density.
create_map <- function(n_classes) {
  quantile_breaks <- quantile(countymap$tdens, probs = seq(0, 1, length.out = n_classes + 1), na.rm = TRUE)
  countymap$tdens_factor <- cut(countymap$tdens, breaks = quantile_breaks, include.lowest = TRUE,
                                labels = paste("Class", 1:n_classes))
  
  ggplot(data = countymap) +
    geom_sf(aes(fill = tdens_factor), color = "white", size = 0.2) +
    scale_fill_brewer(palette = "Dark2", name = "Tornado Density Class") +
    labs(title = paste(n_classes, "Tornado Density Classes")) +
    theme_minimal() +
    theme(legend.position = "right",
          plot.title = element_text(size = 10, hjust = 0.5, face = "bold"),
          axis.title.x = element_blank(),
          axis.title.y = element_blank(),
          axis.text.x = element_blank(),
          axis.text.y = element_blank(),
          axis.ticks = element_blank(),
          panel.grid = element_blank(),
          plot.margin = margin(t = 0, r = 0, b = 0, l = 0, unit = "pt"))
}

Uniting the Quartet: A Composite of Insights

With each map created, we combine them into one big picture—a visual masterpiece. This brings together the different perspectives from each classification scheme, creating a rich tapestry of insights.

# Step 6: Generating and Combining the Choropleth Maps
# Combining the Choropleth Maps
# Creates maps for 3, 4, 5, and 6 quantile classes and combines them for comparative analysis.

# Generate the maps with titles included in the plot creation
plot1 <- create_map(3) + ggtitle("Figure 4: 3 Tornado Density Classes")
plot2 <- create_map(4) + ggtitle("Figure 5: 4 Tornado Density Classes")
plot3 <- create_map(5) + ggtitle("Figure 6: 5 Tornado Density Classes")
plot4 <- create_map(6) + ggtitle("Figure 7: 6 Tornado Density Classes")

# Combine the plots into one without automatic labels
combined_choropleth <- plot_grid(
  plot1, plot2, plot3, plot4, 
  ncol = 2, 
  labels = "", # We already added the titles to the plots themselves
  label_size = 10,
  hjust = -0.5,
  vjust = 1.5
)

# combined_caption <- "Figure 4-7: Choropleth Maps of Tornado Density.\nMethods: Tornado density is classified into quantile classes based on the frequency of tornado occurrences per county. Quantile breaks are computed to distribute the tornado density evenly across the classes.\nStatistics: Each county is categorized into a specific class representing a range of tornado densities."

combined_choropleth <- plot_grid(
  combined_choropleth, 
  ggdraw() + draw_label(combined_caption, hjust = 0.5, vjust = 1, size = 12, fontface = "plain"),
  ncol = 1,
  rel_heights = c(1, 0.15) # Increase space for the caption; adjust as needed
)

# Define the main title for the combined choropleth maps
main_title <- "Comparative Analysis of Tornado Density Across Quantile Classes"

# Use ggdraw and draw_label to add a main title
main_title_plot <- ggdraw() + draw_label(main_title, size = 16, fontface = "bold", hjust = 0.5)

# Combine the main title plot with the combined choropleth maps
# Adjust the relative height of the title to maintain the balance after changing the caption space
combined_choropleth_with_title <- plot_grid(
  main_title_plot,
  combined_choropleth,
  ncol = 1,
  rel_heights = c(0.1, 1) # The first element corresponds to the title space
)

# Display the Combined Choropleth Maps with the Main Title and more space for the caption
print(combined_choropleth_with_title)




Summary

The set of four choropleth maps displaying tornado density in Oklahoma, each with different divisions into groups, gives a thorough look at tornado activity statewide. Each map, with its own way of categorizing, offers unique insights into where tornadoes occur. The number of categories in each map affects what we see: fewer categories give a broader view, while more categories reveal smaller differences in local areas. This shows how important it is to choose the right categorization for the analysis’s goals. Putting these maps together reveals different aspects of tornado activity. It helps stakeholders find high-risk areas, allocate resources better, and plan ways to make communities more resilient. The insights from these maps are crucial for decision-making and making policies about preparing for disasters and reducing risks. They highlight the need for more research to improve how we categorize data and plan for managing disasters effectively.

Epilogue: The Path Forges Onward

To summarize, this project was an exploration of tornado events in Oklahoma through mapping and analysis. It involved merging tornado event data with geographic information, refining and summarizing the data to understand tornado density by county, and creating choropleth maps with different classification schemes to visualize tornado density across the state. The insights gained from these maps are critical to understanding the spatial distribution of tornadoes, identifying high-risk areas, and informing disaster preparedness and mitigation strategies. Overall, this study has deepened our understanding of the interaction between tornado paths and the geographic landscape of Oklahoma and is the beginning of further investigation and discovery in this area.

Preserving Our Insights Across Formats

As we explore maps, it’s important to save what we find. We’ll store our data in digital formats like shapefiles, GeoPackages, or GeoJSON. This makes it easy for future researchers to use. We’re making sure the data lasts long and stays helpful. This process helps us continue studying tornado patterns and how to stay safe from them.

# Preserving Insights in Digital Formats
# Archives the enriched geographic data in Shapefile, GeoPackage, and GeoJSON formats for future accessibility.

st_write(countymap, "oktornadosum.shp", delete_layer = TRUE)
## Deleting layer `oktornadosum' using driver `ESRI Shapefile'
## Writing layer `oktornadosum' to data source 
##   `oktornadosum.shp' using driver `ESRI Shapefile'
## Writing 77 features with 10 fields and geometry type Polygon.
st_write(countymap, dsn = "oktornado.gpkg", layer = "countysum", delete_dsn = TRUE)
## Deleting source `oktornado.gpkg' using driver `GPKG'
## Writing layer `countysum' to data source `oktornado.gpkg' using driver `GPKG'
## Writing 77 features with 10 fields and geometry type Polygon.
st_write(countymap, "oktornado.geojson", layer_options = "RFC7946=YES", delete_layer = TRUE)
## Deleting layer not supported by driver `GeoJSON'
## Deleting layer `oktornado' failed
## Writing layer `oktornado' to data source `oktornado.geojson' using driver `GeoJSON'
## options:        RFC7946=YES 
## Updating existing layer oktornado
## Writing 77 features with 10 fields and geometry type Polygon.

Citation:

Books

Packages and Libraries

Websites and Online Resources

  • Pebesma, E., & Bivand, R. (2018). Spatial Data Science with Applications in R. Chapman & Hall/CRC.

  • Pebesma, E., & Bivand, R. (2018). Start in the Part 2 Section Entitled ‘R for Spatial Data Science’ (Chapters 7 to 9). In Spatial Data Science With Applications in R (1st ed., pp. 115-314). Chapman & Hall.

  • R Core Team. (2023). R: A Language and Environment for Statistical Computing. R Foundation for Statistical Computing, Vienna, Austria. Retrieved from https://www.R-project.org/

  • Wickham, H. (2016). ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York. Retrieved from https://ggplot2.tidyverse.org

  • Wickham, H., François, R., Henry, L., & Müller, K. (2020). dplyr: A Grammar of Data Manipulation. R package version 1.0.2. Retrieved from https://dplyr.tidyverse.org

  • Wickham, H., Averick, M., Bryan, J., Chang, W., McGowan, L. D., François, R., … & Yutani, H. (2019). Welcome to the tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686

Books, Chapters, and Tutorials