Earthquake catalogues explained: magnitudes, completeness and declustering
On this page
Problem statement
An earthquake catalogue looks like the cleanest dataset in hazard work: a point, a time, a depth, a magnitude. It is not a sample of earthquakes, though. It is a record of what a particular network of instruments detected, with magnitudes computed by several different methods, over a period during which the network changed. Count events straight out of the download and you will measure the seismic network's history as much as the earth's.
Three properties decide whether a catalogue analysis means anything: the magnitude of completeness, below which small events are missed; the magnitude type, which can differ between events in the same file; and clustering, because aftershocks are not independent events and most statistical methods assume they are.
Quick answer
Download from a catalogue service, then check completeness, magnitude type and time span before counting anything:
import pandas as pd, requests
def usgs(bbox, start, end, minmag=2.0):
r = requests.get("https://earthquake.usgs.gov/fdsnws/event/1/query",
params={"format": "geojson", "starttime": start, "endtime": end,
"minlatitude": bbox[1], "maxlatitude": bbox[3],
"minlongitude": bbox[0], "maxlongitude": bbox[2],
"minmagnitude": minmag, "limit": 20000}, timeout=120)
feats = r.json()["features"]
return pd.DataFrame([{
"time": pd.to_datetime(f["properties"]["time"], unit="ms", utc=True),
"mag": f["properties"]["mag"], "magtype": f["properties"]["magType"],
"depth": f["geometry"]["coordinates"][2],
"lon": f["geometry"]["coordinates"][0], "lat": f["geometry"]["coordinates"][1],
"place": f["properties"]["place"]} for f in feats])
eq = usgs([-122.5, 35.5, -117.5, 38.5], "2000-01-01", "2024-12-31", 2.5) # central California
print(f"{len(eq):,} events, {eq.time.min().date()} to {eq.time.max().date()}")
print(f"(the service caps a query at 20,000 events; at M2.0 this box hits the cap and "
f"silently returns only the most recent decade)")
print("magnitude types:", eq.magtype.value_counts().head(5).to_dict())
print(f"magnitudes {eq.mag.min():.1f}–{eq.mag.max():.1f}, depths {eq.depth.min():.0f}–{eq.depth.max():.0f} km")
13,234 events, 2000-01-02 to 2024-12-29
(the service caps a query at 20,000 events; at M2.0 this box hits the cap and silently returns only the most recent decade)
magnitude types: {'ml': 6947, 'md': 5211, 'mw': 710, 'mlr': 185, 'mc': 106}
magnitudes 2.5–7.1, depths -3–45 km
Several magnitude types in one file is normal. They are not interchangeable: local magnitude, duration magnitude and moment magnitude are different measurements that happen to be scaled to agree roughly.
Network, completeness, magnitude types, aftershocks
1. Know which network produced it
A global catalogue (USGS ComCat, ISC) is complete to a much higher magnitude than a dense regional network. Mixing them gives a catalogue whose completeness changes with location.
2. Estimate the magnitude of completeness
Below some magnitude, small events are missed. The usual quick estimate is the magnitude at which the frequency–magnitude curve peaks; fit anything only above it.
3. Check the magnitude types
magType may be ml, md, mw, mb. Analyses that assume one scale should filter to it or accept the mixture explicitly.
4. Decide about aftershocks
Aftershock sequences make events strongly dependent in time and space. Hazard models use declustered catalogues — mainshocks only — while operational aftershock forecasting needs the opposite.
5. Watch the time span
Apparent trends in event counts usually have one of two causes that are not seismicity: a network upgrade that lowered the detection threshold, or a single large sequence whose aftershocks fill a year or two.
6. Keep depth in mind
Depth is the least well constrained parameter, especially offshore, and "10 km" is often a fixed default rather than a measurement.
Completeness and a rise that is one sequence
The data used on this page
Earthquake catalogues from the USGS ComCat FDSN event service, requested by the page's own code. ComCat revises events as they are reviewed, so a later download of the same query can differ by an event or two.
import requests
reply = requests.get("https://earthquake.usgs.gov/fdsnws/event/1/version", timeout=60)
print("FDSN event service version", reply.text.strip())
FDSN event service version 2.8.1
Example 1 — magnitude of completeness, the quick way
import numpy as np
bins = np.arange(2.5, 6.0, 0.1)
counts, _ = np.histogram(eq.mag, bins=bins)
mc = bins[np.argmax(counts)]
print(f"peak of the frequency-magnitude histogram: M{mc:.1f}")
print(f"events at or above it: {(eq.mag >= mc).sum():,} of {len(eq):,}")
for m in (2.5, 3.0, 3.5, 4.0, 5.0):
print(f" M >= {m}: {(eq.mag >= m).sum():6,} events")
peak of the frequency-magnitude histogram: M2.5
events at or above it: 13,234 of 13,234
M >= 2.5: 13,234 events
M >= 3.0: 4,076 events
M >= 3.5: 1,290 events
M >= 4.0: 361 events
M >= 5.0: 30 events
Example 2 — a "rise in seismicity" that is one sequence
per_year = eq.groupby(eq.time.dt.year).size()
print("events per year:", per_year.to_dict())
quiet = per_year.drop([2019, 2020])
print(f"mean per year 2000–2009: {per_year.loc[2000:2009].mean():.0f}")
print(f"mean per year 2015–2024: {per_year.loc[2015:2024].mean():.0f}")
print(f"mean per year 2015–2024 excluding 2019–20: {quiet.loc[2015:2024].mean():.0f}")
print("largest event:", eq.loc[eq.mag.idxmax(), ["time", "mag", "place"]].to_dict())
events per year: {2000: 369, 2001: 623, 2002: 296, 2003: 795, 2004: 929, 2005: 357, 2006: 318, 2007: 304, 2008: 271, 2009: 358, 2010: 291, 2011: 410, 2012: 247, 2013: 295, 2014: 381, 2015: 277, 2016: 340, 2017: 395, 2018: 254, 2019: 2434, 2020: 1848, 2021: 576, 2022: 331, 2023: 306, 2024: 229}
mean per year 2000–2009: 462
mean per year 2015–2024: 699
mean per year 2015–2024 excluding 2019–20: 338
largest event: {'time': Timestamp('2019-07-06 03:19:53.040000+0000', tz='UTC'), 'mag': 7.1, 'place': 'Ridgecrest Earthquake Sequence'}
Counting by decade says seismicity rose by half, from 462 events a year to 699. Counting by year says 2019 and 2020 happened: the M7.1 Ridgecrest sequence and its aftershocks. Drop those two years and the recent rate is 338 a year, below the 2000s — which had sequence years of their own, in 2003 and 2004. A rise caused by better detection looks much the same on a plot and has a different cause; the counts fix tells them apart.
Example 3 — how much of the catalogue is aftershocks
from scipy.spatial import cKDTree
eq_sorted = eq.sort_values("time").reset_index(drop=True)
main, xy = [], np.radians(eq_sorted[["lat", "lon"]].to_numpy())
R = 6371.0
coords = np.c_[R * np.cos(xy[:, 0]) * np.cos(xy[:, 1]), R * np.cos(xy[:, 0]) * np.sin(xy[:, 1]),
R * np.sin(xy[:, 0])]
is_aftershock = np.zeros(len(eq_sorted), bool)
for i, row in eq_sorted.iterrows():
if is_aftershock[i]:
continue
# Gardner-Knopoff style window: bigger events, bigger window
days = 10 ** (0.032 * row.mag + 2.7389) if row.mag >= 6.5 else 10 ** (0.5409 * row.mag - 0.547)
km = 10 ** (0.1238 * row.mag + 0.983)
later = (eq_sorted.time > row.time) & (eq_sorted.time <= row.time + pd.Timedelta(days=days))
near = np.linalg.norm(coords - coords[i], axis=1) <= km
is_aftershock |= (later & near & (eq_sorted.mag <= row.mag)).to_numpy()
print(f"declustered: {(~is_aftershock).sum():,} mainshocks of {len(eq_sorted):,} events "
f"({is_aftershock.mean():.0%} removed)")
declustered: 4,323 mainshocks of 13,234 events (67% removed)
Completeness is the first question
Why completeness is the first question
Every frequency–magnitude analysis — b-values, recurrence rates, hazard curves — assumes the catalogue contains all events above the magnitude being fitted. Below completeness the counts fall not because the earth produces fewer small earthquakes but because the network misses them, which bends the curve and biases every estimate taken from it.
Why magnitude types are not interchangeable
Local magnitude is measured from the amplitude of a seismogram, duration magnitude from how long the shaking lasts, moment magnitude from the physical moment of the rupture. They are calibrated to agree in the middle of their ranges and diverge at the ends, particularly for the largest events, where local magnitude saturates.
Why declustering changes the answer
Aftershocks are physically real and matter enormously for response. They are also not independent samples, so leaving them in a statistical model inflates the apparent rate of events and distorts the estimated tail. Standard hazard practice removes them with a declustering window and states which algorithm was used, because different algorithms remove different fractions.
Blasts, preliminary magnitudes and fixed depths
- Quarry blasts and other non-tectonic events are flagged in some catalogues and not others.
- Preliminary magnitudes are revised; a downloaded catalogue is a snapshot.
- Fixed depths of 5, 10 or 33 km are defaults where depth could not be resolved.
- Induced seismicity near wastewater injection breaks stationarity assumptions.
- Catalogue completeness varies in space, not just in time.
- ISC-GEM is the standard homogenised global catalogue for long-term work.
Internal links
- How to download and map an earthquake catalogue in Python — the workflow
- How to estimate b-value and completeness in Python — the frequency–magnitude fit
- Earthquake counts rise over time in the catalogue — the fix guide
- Hazard, exposure and vulnerability explained — where catalogues fit in
- Return periods and annual exceedance probability explained — recurrence from rates
- How to read a USGS ShakeMap and find who was shaken — from event to intensity
FAQ
What is the magnitude of completeness?
The lowest magnitude above which a catalogue contains essentially all events. Below it, detection fails.
Are ml, md and mw the same?
No. They are different measurements calibrated to agree approximately; they diverge at the extremes.
Should I remove aftershocks?
For hazard rates and frequency–magnitude fits, yes. For response and forecasting, no.
Why do older years have fewer small events?
Because the network was sparser. That is detection, not seismicity.
Is depth reliable?
Less than location. Fixed values such as 10 km are defaults, not measurements.
Which catalogue should I use?
A regional network catalogue for local work, ISC-GEM or USGS ComCat for global or long-term analysis.