This report provides a descriptive analysis of global earthquake activity using recorded seismic data from the United States Geological Survey (USGS) API spanning the full year of 2025 (2025-01-01 to 2026-01-01). By filtering for significant seismic events with a moment magnitude (>= 4.5), this study explores global spatial distributions, focal depth constraints, temporal frequency patterns, and associated hazard warning levels.
The dataset is queried directly from the USGS live GeoJSON API feed, parsing key properties such as earthquake magnitude, depth, geographic coordinates, alert tiers, and tsunami indicators.
import requests
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import folium
# Query USGS API for 2025 seismic events (magnitude >= 4.5)
url = 'https://earthquake.usgs.gov/fdsnws/event/1/query'
params = {
'format': 'geojson',
'starttime': '2025-01-01',
'endtime': '2026-01-01',
'minmagnitude': '4.5'
}
response = requests.get(url, params=params)
data = response.json()
# Parse features into a clean pandas DataFrame
records = []
for feature in data['features']:
props = feature['properties']
geom = feature['geometry']
records.append({
'place': props.get('place'),
'magnitude': props.get('mag'),
'time': pd.to_datetime(props.get('time'), unit='ms'),
'felt': props.get('felt'),
'alert': props.get('alert'),
'tsunami': props.get('tsunami'),
'longitude': geom['coordinates'][0],
'latitude': geom['coordinates'][1],
'depth': geom['coordinates'][2]
})
df = pd.DataFrame(records)
df_clean = df.dropna(subset=['magnitude', 'depth', 'latitude', 'longitude']).copy()
# Print summary statistics
print(df_clean[['magnitude', 'depth', 'felt']].describe())
## magnitude depth felt
## count 8561.000000 8561.000000 1403.000000
## mean 4.810057 53.226531 84.823236
## std 0.369243 98.443399 1225.778431
## min 4.500000 0.000000 0.000000
## 25% 4.500000 10.000000 1.000000
## 50% 4.700000 10.000000 3.000000
## 75% 4.900000 51.297000 10.000000
## max 8.800000 669.556000 42638.000000
#Code for Figure 1
fig, ax = plt.subplots(figsize=(9, 5))
no_tsunami = df_clean[df_clean['tsunami'] == 0]
yes_tsunami = df_clean[df_clean['tsunami'] == 1]
ax.scatter(no_tsunami['depth'], no_tsunami['magnitude'], color='royalblue', alpha=0.3, s=30, edgecolors='none', label='No Tsunami (0)')
ax.scatter(yes_tsunami['depth'], yes_tsunami['magnitude'], color='darkorange', alpha=0.8, s=40, edgecolors='k', linewidths=0.5, label='Tsunami Warning (1)')
ax.set_title('Earthquake Depth vs. Magnitude (Highlighted Tsunami Events)', fontsize=13, fontweight='bold')
ax.set_xlabel('Depth (km)', fontsize=11)
ax.set_ylabel('Moment Magnitude (MW)', fontsize=11)
ax.grid(True, linestyle='--', alpha=0.6)
ax.legend(loc='upper right')
plt.tight_layout()
plt.savefig('plot1_scatter_fixed.png')
plt.show()
This scatter plot illustrates the relationship between earthquake focal depth and moment magnitude (MW), with events prompting tsunami warnings highlighted in orange. The visualization clearly demonstrates two key physical constraints of tsunami generation:
#Code for Figure 2
alert_order = ['green', 'yellow', 'orange', 'red']
df_alert = df_clean[df_clean['alert'].isin(alert_order)].copy()
active_labels = [lvl.upper() for lvl in alert_order if len(df_alert[df_alert['alert'] == lvl]) > 0]
active_data = [df_alert[df_alert['alert'] == lvl]['magnitude'].values for lvl in alert_order if len(df_alert[df_alert['alert'] == lvl]) > 0]
fig, ax = plt.subplots(figsize=(9, 5))
bp = ax.boxplot(active_data, patch_artist=True, medianprops=dict(color='black', linewidth=1.5))
ax.set_xticklabels(active_labels)
box_colors = ['mediumseagreen', 'gold', 'darkorange', 'crimson']
for patch, color in zip(bp['boxes'], box_colors[:len(active_data)]):
patch.set_facecolor(color)
ax.set_title('Moment Magnitude Distribution Across PAGER Alert Levels', fontsize=13, fontweight='bold')
ax.set_xlabel('PAGER Alert Level', fontsize=11)
ax.set_ylabel('Moment Magnitude (MW)', fontsize=11)
ax.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
plt.savefig('plot2_boxplot_fixed.png')
plt.show()
This box plot examines how moment magnitudes map across USGS PAGER alert severity categories. The distribution shows a clear upward progression. Higher alert tiers (Orange and Red) correspond to systematically higher median magnitudes and narrower upper-tail spreads, confirming that automated impact alert models scale heavily with raw energy release.
#Code for Figure 3
fig, ax = plt.subplots(figsize=(11, 5))
scat = ax.scatter(df_clean['longitude'], df_clean['latitude'], c=df_clean['depth'], cmap='viridis_r', alpha=0.6, s=20, edgecolors='none')
ax.set_title('Global Geographic Distribution of Earthquakes (Latitude vs. Longitude)', fontsize=13, fontweight='bold')
ax.set_xlabel('Longitude (°)', fontsize=11)
ax.set_ylabel('Latitude (°)', fontsize=11)
xa = ax.set_xlim(-180, 180)
ya = ax.set_ylim(-90, 90)
ax.grid(True, linestyle='--', alpha=0.4)
cbar = plt.colorbar(scat, ax=ax, pad=0.02)
cbar.set_label('Focal Depth (km)', fontsize=10)
plt.tight_layout()
plt.savefig('plot3_geographic_map.png')
plt.show()
This geographic scatter plot maps global earthquake locations by latitude and longitude, with markers colored by focal depth. The visualization clearly reveals the structural outlines of Earth’s major tectonic plates most notably tracing the Pacific “Ring of Fire” along the eastern and western rims of the Pacific Basin. Furthermore, the depth gradient highlights that deep focus earthquakes (darker purple/blue points) are tightly restricted to specific subduction zones rather than randomly distributed.
#Code for Figure 4
m = folium.Map(location=[20, 0], zoom_start=2, tiles='Esri.WorldStreetMap')
for idx, row in df_clean.head(500).iterrows():
mag = row['magnitude']
depth = row['depth']
place = row.get('place', 'Unknown')
color = 'darkred' if mag >= 6.5 else ('orange' if mag >= 5.5 else 'royalblue')
radius = 8 if mag >= 6.5 else (5 if mag >= 5.5 else 3)
marker = folium.CircleMarker(
location=[row['latitude'], row['longitude']],
radius=radius,
color=color, fill=True, fill_color=color, fill_opacity=0.7,
popup=f"<b>Place:</b> {place}<br><b>Mag:</b> {mag}<br><b>Depth:</b> {depth}km"
)
_ = marker.add_to(m)
m.save('earthquake_interactive_map.html')
library(base64enc)
library(htmltools)
html_content <- readLines('earthquake_interactive_map.html', warn = FALSE)
html_string <- paste(html_content, collapse = "\n")
data_uri <- sprintf("data:text/html;charset=utf-8;base64,%s", base64enc::base64encode(charToRaw(html_string)))
tags$iframe(src = data_uri, width = "100%", height = "500px", style = "border:none;")
This interactive map visualization plots global earthquake coordinates onto an open street map projection, with color and size scaled by magnitude severity. The spatial distribution clearly highlights active global tectonic margins, confirming that seismic energy release is concentrated along plate boundaries.
#Code for Figure 5
df_sorted = df_clean.sort_values('time').copy()
df_sorted['date'] = df_sorted['time'].dt.date
daily_counts = df_sorted.groupby('date').size().cumsum()
fig, ax = plt.subplots(figsize=(10, 5))
_ = ax.plot(daily_counts.index, daily_counts.values, color='darkgreen', linewidth=2)
_ = ax.set_title('Cumulative Growth of Recorded Earthquakes Over Time', fontsize=13, fontweight='bold')
_ = ax.set_xlabel('Date', fontsize=11)
_ = ax.set_ylabel('Cumulative Event Count', fontsize=11)
_ = ax.grid(True, linestyle='--', alpha=0.6)
plt.tight_layout()
plt.savefig('plot4_timeseries.png')
plt.show()
This cumulative time series plot tracks the growth of recorded global earthquakes over the sample timeframe. The visualization reveals two key insights:
#Code for Figure 6
bins = [4.5, 5.0, 6.0, 7.0, 10.0]
labels = ['Moderate (4.5-5.0)', 'Strong (5.0-6.0)', 'Major (6.0-7.0)', 'Great (7.0+)']
df_clean['mag_category'] = pd.cut(df_clean['magnitude'], bins=bins, labels=labels, right=False)
mag_cat_counts = df_clean['mag_category'].value_counts()
fig, ax = plt.subplots(figsize=(9, 5))
colors = ['lightblue', 'peachpuff', 'orange', 'crimson']
wedges, texts, autotexts = ax.pie(mag_cat_counts,
autopct='%1.1f%%',
pctdistance=0.75,
colors=colors,
startangle=140,
wedgeprops={'edgecolor': 'white', 'linewidth': 1.5, 'width': 0.6})
for autotext in autotexts:
autotext.set_color('black')
autotext.set_fontweight('bold')
_ = ax.legend(wedges, labels, title="Magnitude Tiers", loc="center left", bbox_to_anchor=(1, 0, 0.5, 1))
_ = ax.set_title('Proportion of Earthquakes by Moment Magnitude Category', fontsize=13, fontweight='bold', pad=15)
plt.tight_layout()
plt.savefig('plot5_donut_magnitude.png')
plt.show()
This donut chart categorizes global seismic events into distinct moment magnitude tiers. The proportional breakdown vividly demonstrates the exponential decay law governing seismic energy release (consistent with the Gutenberg-Richter relation). Moderate (75.1%) and strong (23.2%) account for the vast majority of cataloged global activity where major (1.5%) and great (0.2%) represent rare high-magnitude events.