1. Introduction

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.

2. Data Retrieval and Summary Statistics

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

3. Visualizations and Analytical Interpretations

Figure 1: Scatter Plot of Earthquake Depth vs. Magnitude

#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()

Analytical Interpretation:

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:

  • Shallow-Depth Restriction: Nearly all tsunami-triggering earthquakes occur at shallow crustal depths (predominantly under 50 to 100 km), which is a physical prerequisite for substantial seafloor displacement.
  • Magnitude Thresholds: Tsunami warnings are strictly bounded to higher magnitudes (>= 6.5). Smaller magnitude events and deep-focus seismic activity (spanning up to 600+ km) lack the necessary energy release or displacement profile to pose an active tsunami threat.

Figure 2: Box Plot of Moment Magnitude Across PAGER Alert Levels

#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()

Analytical Interpretation:

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.

Figure 3: Global Geographic Distribution of Earthquakes

#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()

Analytical Interpretation:

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.

Figure 4: Interactive Geographic Distribution Map Using Folium

#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;")

Analytical Interpretation:

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.

Figure 5: Cumulative Time Series of Recorded Earthquakes

#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()

Analytical Interpretation:

This cumulative time series plot tracks the growth of recorded global earthquakes over the sample timeframe. The visualization reveals two key insights:

  • Steady Background Rate: The general trajectory maintains a consistent linear slope, indicating that global earthquakes above the threshold occur at a relatively stable, continuous baseline rate over long periods.
  • Temporal Clustering: A distinct steep inflection point appears around August 2025, reflecting a temporary surge in seismic productivity likely driven by a major mainshock-aftershock sequence or a global seismic swarm.

Figure 6: Proportional Breakdown of Earthquakes by Magnitude Category

#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()

Analytical Interpretation:

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.