AI4EPS/CEED
CEED: California Earthquake Event Dataset 2.4 million analyst-labelled earthquake seismograms from California, 1977–2025. One record is one earthquake recorded at one station: a 122.88 s three-channel waveform with an analyst-picked P arrival, an analyst-picked S arrival, and the analyst's reading of the P first motion (up or down). Every trace carries the recording network's own hypocentre, magnitude and arrival-quality numbers, so the labels can be filtered on quality… See the full description on the dataset page: https://huggingface.co/datasets/AI4EPS/CEED.
CEED: California Earthquake Event Dataset
2.4 million analyst-labelled earthquake seismograms from California, 1977–2025.
One record is one earthquake recorded at one station: a 122.88 s three-channel waveform with an analyst-picked P arrival, an analyst-picked S arrival, and the analyst's reading of the P first motion (up or down). Every trace carries the recording network's own hypocentre, magnitude and arrival-quality numbers, so the labels can be filtered on quality rather than taken on trust.
Waveforms are 12288 samples at 100 Hz — 40.96 s before the origin time and 81.92 s after — stored as a (3, 12288) float32 array in channel order E, N, Z, in µm/s where the instrument response could be removed. Single-vertical and two-component stations are included; component says which channels hold real data and the rest are zero.
Quick start
from datasets import load_dataset
import numpy as np
ds = load_dataset("AI4EPS/CEED", "scedc", split="train")
r = ds[0]
w = np.asarray(r["waveform"], dtype="float32") # (3, 12288), channels E, N, Z at 100 Hz
p, s = r["p_phase_index"], r["s_phase_index"] # arrivals, as sample offsets
print(r["event_id"], f"M{r['event_magnitude']}", f"{r['distance_km']:.0f} km",
"first motion", r["p_phase_polarity"])
window = w[:, p - 100 : p + 400] # 1 s before to 4 s after PThe release is 190 GiB, so stream it unless you need it all:
ds = load_dataset("AI4EPS/CEED", "scedc", split="train", streaming=True)
for r in ds.take(10):
...Select before you download. Each year ships an index.parquet with every field except the waveform — a few MB against several GB — so you can choose rows first and fetch only the shards that hold them:
import pandas as pd
idx = pd.read_parquet("hf://datasets/AI4EPS/CEED/scedc/2019/index.parquet")
want = idx[(idx.event_magnitude > 4) & (idx.distance_km < 50) & (idx.component == "ENZ")]
shard = pd.read_parquet("hf://datasets/AI4EPS/CEED/scedc/2019/0000.parquet")
rows = shard.iloc[want[want.shard_id == 0].row_in_shard.values]ncedc and scedc are separate configs. train runs through 2023 and test is 2024–2025; the split is by year, and since an earthquake belongs to exactly one year no event straddles it. Split by event, never by trace — the traces of one earthquake are highly correlated. The calendar year in progress is not released, so no partial year skews a split or a statistic.
What is in a record
69 columns in all; the rest are the networks' location-quality fields — arrival residuals and weights, azimuthal gap, hypocentre uncertainties. index.parquet carries the full schema.
What is in it
Limitations
SCEDC is nearly absent for 12 years (1982, 1984–1987, 1989, 1993–1998), because its FDSN service publishes no arrival residuals or weights in that window and this release requires them. Those waveforms and picks do exist upstream. The two regions therefore cover different periods — check the coverage figure before assuming a common span.
The regions are not balanced year to year. SCEDC/NCEDC is 1.45 overall but ranges from 0.01× (1993) to 9× (2001). Build an era-balanced subset from index.parquet if that matters.
`takeoff_angle` is absent for SCEDC before 2007 — SCSN did not compute it then, and it is missing from the FDSN service, the archive phase files and the USGS copy alike. NCEDC has it throughout.
Magnitudes are not one scale. NCEDC is mostly duration magnitude (Md), SCEDC local magnitude (Ml); event_magnitude_type says which. Likewise p_phase_score and s_phase_score are each network's own analyst weighting — compare within a region, not across.
Focal-mechanism uncertainty columns are region-specific. SCEDC publishes plane1_uncertainty/plane2_uncertainty and fm_quality; NCEDC publishes strike_uncertainty/dip_uncertainty/rake_uncertainty. Each set is null in the other region, and strike/dip/rake are null wherever no mechanism was published.
An earthquake can appear in both regions. NCSN also locates southern events and SCSN northern ones, so the same earthquake may carry both an nc and a ci event_id with different hypocentres and picks. If you train on one region and test on the other, de-duplicate around 35–36°N; a cross-reference is not yet shipped.
How it was built
Waveforms and picks come from the NCEDC (Northern California Earthquake Data Center) and the SCEDC (Southern California Earthquake Data Center), via their public S3 archives and FDSN event services. A trace is included only if an analyst picked both P and S (evaluation_mode == "manual"), recorded a P first motion, and the network published complete arrival metadata for both picks. Instrument response is removed where the station metadata allows.
Built with QuakeFlow: datasets/build_ceed.py for the selection, datasets/cut_event_parquet.py for the underlying cut.
Citation
The waveforms and the analyst picks are the data centers' work — please cite them alongside this dataset.
