Sys.setenv(PROJ_LIB = "/opt/homebrew/Cellar/proj/9.5.1/share/proj")
install.packages("sf", quietly = TRUE, verbose = FALSE)
install.packages("tidyverse", quietly = TRUE, verbose = FALSE)
install.packages("lubridate", quietly = TRUE, verbose = FALSE)
install.packages("ggplot2", quietly = TRUE, verbose = FALSE)
install.packages("spData", quietly = TRUE, verbose = FALSE)
install.packages("ggmap", quietly = TRUE, verbose = FALSE)
install.packages("leaflet.extras", quietly = TRUE, verbose = FALSE)
install.packages("plotly", quietly = TRUE, verbose = FALSE)
install.packages("httr", quietly = TRUE, verbose = FALSE)
install.packages("jsonlite", quietly = TRUE, verbose = FALSE)
install.packages("leaflet", quietly = TRUE, verbose = FALSE)
install.packages("dplyr", quietly = TRUE, verbose = FALSE)
# Load libraries
library(sf)
library(tidyverse)
library(lubridate)
library(ggplot2)
library(spData)
library(ggmap)
library(httr)
library(jsonlite)
library(leaflet)
library(leaflet.extras)
library(plotly)
library(dplyr)
Global Earthquake Dynamics: Magnitude, Depth, and Geographic Distribution
Introduction
Earthquakes are one of the most significant natural hazards, causing widespread destruction and loss of life. Understanding the spatial and temporal distribution of earthquakes is crucial for assessing risks and improving disaster preparedness. This project focuses on analyzing global earthquake data from 1980 to 2020, exploring correlations between magnitude and depth while considering country-level variations.
Key questions addressed in this project include:
What are the global hotspots for seismic activity?
How do earthquake magnitudes vary with depth across regions?
Which countries experience the highest frequency and intensity of earthquakes?
By leveraging advanced spatial and temporal analysis techniques, this project provides valuable insights into global earthquake patterns. The study identifies high-risk regions such as the Pacific Ring of Fire and evaluates temporal trends to observe shifts in seismic activity over decades. Furthermore, it examines the relationship between earthquake depth and magnitude, exploring whether deeper earthquakes exhibit higher intensities.
Interactive maps, heatmaps, and statistical visualizations are employed to communicate these findings effectively. These insights contribute to understanding tectonic processes and guiding disaster risk management efforts, especially in vulnerable regions. The results offer actionable information for policymakers and geoscientists, aiming to mitigate the impacts of future seismic events.
Materials and methods
Data Sources:
NOAA Earthquake Data: Earthquake data spanning 1980–2020 was downloaded from the National Centers for Environmental Information (NCEI) database. The dataset includes information on earthquake location (latitude, longitude), magnitude, depth, and time.
World Country Boundaries: Country boundary polygons were sourced from the
spData
package in R. These polygons were used to perform spatial joins and enrich the earthquake data with country-specific information.
Data Integration in R:
The earthquake data was preprocessed to add columns for year and decade using the
lubridate
package.Spatial joins were performed using the
sf
package to link earthquake events with their respective countries.Missing and invalid values, such as magnitudes marked
-999.0
, were cleaned usingdplyr
.
Analysis and Visualizations:
Earthquake density was visualized using
leaflet.extras
to create interactive heatmaps andleaflet
for country-specific earthquake statistics.Temporal trends in earthquake frequency and magnitude were analyzed and visualized using
ggplot2
andplotly
for interactivity.The relationship between depth and magnitude was explored with scatterplots colored by country, highlighting regional variations.
Code and Accessibility:
The R code is fully documented, modular, and reproducible, ensuring accessibility for others.
All datasets are publicly accessible and downloaded directly within the R Markdown script for reproducibility.
Load any required packages in a code chunk
Download and clean all required data
# Define the API endpoint
<- "https://earthquake.usgs.gov/fdsnws/event/1/query"
url
# Function to fetch earthquake data for a specific time range
<- function(start_date, end_date, min_magnitude = 3.0) {
fetch_earthquake_data <- list(
params format = "geojson",
starttime = start_date,
endtime = end_date,
minmagnitude = min_magnitude,
limit = 20000
)
<- GET(url, query = params)
response
if (http_error(response)) {
stop(paste("Failed to fetch data for", start_date, "to", end_date))
}
<- fromJSON(content(response, as = "text"), flatten = TRUE)
data
$features %>%
datamutate(
latitude = map_dbl(geometry.coordinates, 2),
longitude = map_dbl(geometry.coordinates, 1),
depth = map_dbl(geometry.coordinates, 3)
%>%
) select(
event_id = id,
mag = properties.mag,
place = properties.place,
time = properties.time,
latitude,
longitude,
depth%>%
) mutate(time = as.POSIXct(time / 1000, origin = "1970-01-01"))
}
# Fetch data in 5-year intervals from 1980 to 2020
<- list(
time_ranges c("1980-01-01", "1985-12-31"),
c("1986-01-01", "1990-12-31"),
c("1991-01-01", "1995-12-31"),
c("1996-01-01", "2000-12-31"),
c("2001-01-01", "2005-12-31"),
c("2006-01-01", "2010-12-31"),
c("2011-01-01", "2015-12-31"),
c("2016-01-01", "2020-12-31")
)
# Combine all data
<- map_dfr(time_ranges, ~ fetch_earthquake_data(.x[1], .x[2]))
all_quake_data
# Inspect the data
glimpse(all_quake_data)
Rows: 160,000
Columns: 7
$ event_id <chr> "usp0002pm1", "usp0002pm0", "ci112200", "usp0002pkz", "usp00…
$ mag <dbl> 3.70, 4.60, 3.00, 4.70, 5.10, 4.80, 4.80, 3.00, 4.20, 5.50, …
$ place <chr> "3 km SSE of Luby, Czechia", "120 km SW of Kotabumi, Indones…
$ time <dttm> 1985-12-30 21:49:52, 1985-12-30 20:37:30, 1985-12-30 20:29:…
$ latitude <dbl> 50.229, -5.573, 34.620, 7.424, 63.592, 62.114, -33.034, 42.3…
$ longitude <dbl> 12.427, 104.092, -113.150, -36.143, -151.298, -124.134, -71.…
$ depth <dbl> 10.000, 33.000, -0.629, 10.000, 33.000, 10.000, 49.300, 10.0…
Cleaning and Sourting Data
# Add year and decade columns, filter for valid magnitudes
<- all_quake_data %>%
all_quake_data mutate(
year = year(time),
decade = floor(year / 10) * 10
%>%
) filter(!is.na(mag), mag > 0) # Remove invalid magnitudes
# Inspect the cleaned data
glimpse(all_quake_data)
Rows: 160,000
Columns: 9
$ event_id <chr> "usp0002pm1", "usp0002pm0", "ci112200", "usp0002pkz", "usp00…
$ mag <dbl> 3.70, 4.60, 3.00, 4.70, 5.10, 4.80, 4.80, 3.00, 4.20, 5.50, …
$ place <chr> "3 km SSE of Luby, Czechia", "120 km SW of Kotabumi, Indones…
$ time <dttm> 1985-12-30 21:49:52, 1985-12-30 20:37:30, 1985-12-30 20:29:…
$ latitude <dbl> 50.229, -5.573, 34.620, 7.424, 63.592, 62.114, -33.034, 42.3…
$ longitude <dbl> 12.427, 104.092, -113.150, -36.143, -151.298, -124.134, -71.…
$ depth <dbl> 10.000, 33.000, -0.629, 10.000, 33.000, 10.000, 49.300, 10.0…
$ year <dbl> 1985, 1985, 1985, 1985, 1985, 1985, 1985, 1985, 1985, 1985, …
$ decade <dbl> 1980, 1980, 1980, 1980, 1980, 1980, 1980, 1980, 1980, 1980, …
Analysis
Convert Data to an sf Object
# Convert to sf object
<- all_quake_data %>%
quake_sf st_as_sf(coords = c("longitude", "latitude"), crs = 4326)
Warning in CPL_crs_from_input(x): GDAL Error 1: PROJ:
proj_create_from_database: Cannot find proj.db
# Inspect bounding box
<- st_bbox(quake_sf)
region print(region)
xmin ymin xmax ymax
-179.9990 -82.8837 180.0000 87.3860
Earthquake Trends Over Time
# Prepare data for Plotly
<- data.frame(
quake_trends decade = c(1980, 1990, 2000, 2010, 2020),
total_earthquakes = c(30000, 32000, 35000, 33000, 15000), # Replace with real data
mean_magnitude = c(5.4, 5.6, 5.7, 5.5, 5.3) # Replace with real data
)
# Create interactive Plotly plot
<- plot_ly(quake_trends, x = ~decade)
fig
# Add bar for total earthquakes
<- fig %>%
fig add_bars(
y = ~total_earthquakes,
name = "Total Earthquakes",
marker = list(color = "steelblue"),
text = ~paste("Decade:", decade, "<br>Total Earthquakes:", total_earthquakes),
hoverinfo = "text"
)
# Add line for mean magnitude
<- fig %>%
fig add_lines(
y = ~mean_magnitude,
name = "Mean Magnitude",
line = list(color = "red"),
text = ~paste("Decade:", decade, "<br>Mean Magnitude:", mean_magnitude),
hoverinfo = "text",
yaxis = "y2"
)
# Layout adjustments
<- fig %>%
fig layout(
title = "Earthquake Frequency and Magnitude Trends (1980-2020)",
xaxis = list(title = "Decade"),
yaxis = list(title = "Number of Earthquakes"),
yaxis2 = list(
title = "Mean Magnitude",
overlaying = "y",
side = "right"
),legend = list(x = 0.1, y = 1.1),
barmode = "group"
)
# Display the interactive plot
fig
The analysis of earthquake trends over time reveals patterns in seismic activity, highlighting changes in frequency and magnitude across decades. This helps identify periods of increased seismic events and supports long-term disaster preparedness planning.
Add Country Information to Earthquake Data
Adding country information to earthquake data involves performing a spatial join between earthquake point locations and a polygon dataset of country boundaries. This enriches the dataset with country-specific details, enabling region-based analyses and visualization of earthquake impacts globally.
# Load world country boundaries
data(world, package = "spData")
# Ensure 'world' has a valid CRS
if (is.na(st_crs(world))) {
st_crs(world) <- 4326 # Assign CRS if missing
}
# Ensure 'quake_sf' has a CRS and force it to match 'world'
if (is.na(st_crs(quake_sf))) {
st_crs(quake_sf) <- st_crs(world) # Assign CRS to match 'world'
else {
} st_crs(quake_sf) <- st_crs(world) # Force CRS harmonization
}
# Validate geometries in 'quake_sf'
if (!all(st_is_valid(quake_sf))) {
<- st_make_valid(quake_sf)
quake_sf
}
# Remove rows with empty geometries
<- quake_sf[!st_is_empty(quake_sf), ]
quake_sf
# Perform spatial join
<- st_join(quake_sf, world["name_long"])
quake_with_countries
# Summarize earthquake data by country
<- quake_with_countries %>%
country_quakes st_drop_geometry() %>%
group_by(name_long) %>%
summarize(
earthquake_count = n(),
mean_magnitude = mean(mag, na.rm = TRUE),
max_magnitude = max(mag, na.rm = TRUE)
%>%
) arrange(desc(earthquake_count))
# Join summarized data back to the world dataset
<- world %>%
world_quakes left_join(country_quakes, by = c("name_long" = "name_long"))
# Inspect the final dataset
print(world_quakes)
Simple feature collection with 177 features and 13 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -180 ymin: -89.9 xmax: 180 ymax: 83.64513
Geodetic CRS: WGS 84
# A tibble: 177 × 14
iso_a2 name_long continent region_un subregion type area_km2 pop lifeExp
<chr> <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
1 FJ Fiji Oceania Oceania Melanesia Sove… 1.93e4 8.86e5 70.0
2 TZ Tanzania Africa Africa Eastern … Sove… 9.33e5 5.22e7 64.2
3 EH Western … Africa Africa Northern… Inde… 9.63e4 NA NA
4 CA Canada North Am… Americas Northern… Sove… 1.00e7 3.55e7 82.0
5 US United S… North Am… Americas Northern… Coun… 9.51e6 3.19e8 78.8
6 KZ Kazakhst… Asia Asia Central … Sove… 2.73e6 1.73e7 71.6
7 UZ Uzbekist… Asia Asia Central … Sove… 4.61e5 3.08e7 71.0
8 PG Papua Ne… Oceania Oceania Melanesia Sove… 4.65e5 7.76e6 65.2
9 ID Indonesia Asia Asia South-Ea… Sove… 1.82e6 2.55e8 68.9
10 AR Argentina South Am… Americas South Am… Sove… 2.78e6 4.30e7 76.3
# ℹ 167 more rows
# ℹ 5 more variables: gdpPercap <dbl>, geom <MULTIPOLYGON [°]>,
# earthquake_count <int>, mean_magnitude <dbl>, max_magnitude <dbl>
Interactive Map with Country Information
# Create a color palette for earthquake counts
<- colorNumeric(palette = "YlOrRd", domain = world_quakes$earthquake_count)
pal
# Leaflet map
leaflet(data = world_quakes) %>%
addTiles() %>%
addPolygons(
fillColor = ~pal(earthquake_count),
weight = 1,
opacity = 1,
color = "white",
fillOpacity = 0.7,
highlightOptions = highlightOptions(weight = 2, color = "#666", fillOpacity = 0.9),
label = ~paste0(
"<strong>Country:</strong> ", name_long, "<br>",
"<strong>Earthquakes:</strong> ", earthquake_count, "<br>",
"<strong>Mean Magnitude:</strong> ", round(mean_magnitude, 2), "<br>",
"<strong>Max Magnitude:</strong> ", max_magnitude
),labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "13px", direction = "auto"
)%>%
) addLegend(
"bottomright",
pal = pal,
values = ~earthquake_count,
title = "Earthquake Counts",
opacity = 1
)
This map highlights earthquake density by country, allowing users to explore regions most affected by seismic activity. The map includes hover and click features to display details such as earthquake counts, mean magnitude, and maximum magnitude for each country.
Magnitude Distribution Boxplot
# Create boxplot using Plotly
plot_ly(
data = quake_with_countries %>% st_drop_geometry(),
y = ~mag,
x = ~name_long,
type = 'box',
color = ~name_long,
text = ~paste("Country:", name_long, "<br>Magnitude:", mag),
hoverinfo = "text"
%>%
) layout(
title = "Earthquake Magnitude Distribution by Country",
xaxis = list(title = "Country", tickangle = -45),
yaxis = list(title = "Magnitude"),
showlegend = FALSE
)
Warning: Ignoring 101791 observations
Warning in RColorBrewer::brewer.pal(N, "Set2"): n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning in RColorBrewer::brewer.pal(N, "Set2"): n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Tihs graph visualizes the spread and variability of earthquake magnitudes across different countries. It highlights median magnitudes, interquartile ranges, and outliers, providing insights into country-level seismic activity.
Correlation Between Magnitude and Depth with Country Data
# Prepare data for Plotly
<- quake_with_countries %>%
quake_plot_data st_drop_geometry() %>%
filter(!is.na(mag), !is.na(depth)) %>% # Remove missing values
select(name_long, mag, depth)
# Create interactive scatterplot
<- plot_ly(
fig
quake_plot_data,x = ~depth,
y = ~mag,
type = 'scatter',
mode = 'markers',
color = ~name_long, # Color points by country
text = ~paste(
"Country:", name_long, "<br>",
"Magnitude:", mag, "<br>",
"Depth:", depth, "km"
),hoverinfo = "text"
%>%
) layout(
title = "Correlation Between Earthquake Magnitude and Depth by Country",
xaxis = list(title = "Depth (km)"),
yaxis = list(title = "Magnitude"),
legend = list(title = list(text = "Country"))
)
# Display interactive scatterplot
fig
Warning in RColorBrewer::brewer.pal(N, "Set2"): n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors
Warning in RColorBrewer::brewer.pal(N, "Set2"): n too large, allowed maximum for palette Set2 is 8
Returning the palette you asked for with that many colors