Planet-Scale Earthquake Mining with PySpark

49 years of global seismic data, 3.27 million records, processed in a distributed way with Apache Spark: model-based imputation instead of throwing information away, clustering in feature space and on the map, and magnitude classification.

Spark, live. Click to run the job again.8 partitions in parallel
raw partitionimputedclustered
3.27Mseismic records, 1970 to 2019
41%of one key column missing, recovered by imputation
2.61Mearthquakes in the clean catalog
735 kmdepth of the deepest events, isolated by clustering
94%recall on strong events with a tuned threshold
01 / The data

Millions of records, one Spark job.

The catalog covers seismic events recorded between January 1970 and March 2019: coordinates, depth, magnitude and its scale, station counts and recording-quality measures. At this size, pandas on one core is not the right tool, so everything runs on Spark DataFrames and MLlib, in parallel across all local cores.

StageRecordsWhat happens
Original catalog3,272,77423 columns, earthquakes and other seismic events
After deduplication3,256,955duplicate rows removed
Clean earthquake catalog2,607,588earthquakes only, 13 columns, no missing values
After outlier filtering2,607,55632 extreme points removed after PCA

The four stages the demo at the top animates, with the record counts saved in the notebooks.

02 / Imputation

Recover the data, do not drop it.

Four informative columns had large holes. Dropping them, or dropping every row with a gap, would have thrown away most of what the catalog says about how each event was recorded:

Missing values in the original catalogscale 0 to 50%
dmindistance to the nearest station
41.15%
magNststations used for the magnitude
30.22%
nststations used for the location
26.94%
gapazimuthal gap between stations
25.62%

So I imputed them, each with the simplest method that fits: the mean for dmin and gap, a linear regression for nst and a decision-tree regression for magNst, both trained on latitude, longitude, depth, magnitude and RMS residual. Observed values were never touched; only the missing ones received a prediction. At the end, no column has a single null left.

spark_understanding_preparation_regression.ipynb · imputing nst
# 'nst' is imputed by linear regression on five columns that are always presentcolumns_to_use = ['latitude', 'longitude', 'depth', 'mag', 'rms']assembler = VectorAssembler(inputCols=columns_to_use, outputCol="features")scaler = StandardScaler(inputCol="features", outputCol="scaledFeatures")lr = LinearRegression(featuresCol="scaledFeatures", labelCol='nst')pipeline = Pipeline(stages=[assembler, scaler, lr])# train on the rows where 'nst' is known, then predict for every rowmodel = pipeline.fit(df.filter(df.nst.isNotNull()))full_predictions = model.transform(df)# observed values are kept; only the missing ones take the predictiondf_imputed = full_predictions.withColumn(    "nst_imputed",    when(full_predictions["nst"].isNull(), full_predictions["prediction"])    .otherwise(full_predictions["nst"]))
From the notebook, shortened, with the comments translated into English.
03 / Seismic profiles

Eight kinds of earthquake.

I clustered the catalog with Spark's K-Means (k-means|| initialization) on nine standardized features: position, depth, magnitude, station counts, azimuthal gap, nearest-station distance and RMS residual. SSE and silhouette curves guided the choice of eight groups. A PCA projection, applied after clustering to look at the result, exposed 32 extreme points, which I filtered out.

The eight K-Means groups projected onto the first two principal components
The eight groups on the first two principal components. K-Means works on all nine features; PCA only provides the view.

The cluster statistics read like a seismology summary:

GroupEventsDepth (km)MagnitudeStationsGap (°)dmin
0 shallow, small1,228,0396.081.3115.6090.950.09
1199,23857.694.5239.21121.430.83
2 well recorded38,94439.884.21131.6968.640.65
3 deep25,368498.414.4456.31118.631.41
4469,40035.331.6824.17124.570.31
5 poor coverage472,0588.511.307.71217.020.18
6 remote4,61236.494.6242.97116.9520.91
7169,89733.483.9141.46125.100.48

Means per group. Group 3 gathers the deep earthquakes, from 242 down to 735.8 km; group 6 the events far from any station; group 2 the best-recorded ones.

04 / On the map

The same catalog, by geography.

A second K-Means, on latitude and longitude only, organized the events into eight geographical areas, then Folium turned them into interactive world maps. The largest area holds 1,655,188 events, 63.48% of the catalog, in the western United States: that is where the densest station networks are. The others separate Alaska, the western Pacific, Europe, South America and New Zealand.

World map of sampled earthquakes colored by eight geographical K-Means groups, with the cluster centroids marked in red
Sampled events colored by geographical group, centroids in red. The Ring of Fire and the Mediterranean belt appear on their own, from coordinates alone.
05 / Classification

Strong events, and the threshold that finds them.

The last notebook classifies each recorded event as magnitude 3 or above (17.61% of the catalog) or below 3, from a 19-feature vector, with an 80/20 split, four-fold cross-validation and ROC AUC as the metric:

0.969ROC AUC of a decision tree, depth 5
0.992ROC AUC of a random forest, 10 trees, depth 10
521,052events in the test set

With only 17.6% of positives, accuracy hides what matters: how many strong events the model misses. Lowering the forest's decision threshold from the default to 0.3 changes the trade-off:

ThresholdMissed strong eventsFalse alarmsRecall, strong events
Default7,4251,18391.89%
0.35,3683,45894.14%

The lower threshold misses 27.7% fewer strong events at the cost of more false alarms. Which side to favor is a decision about the cost of each error, and the analysis puts both numbers on the table.

ROC curve of the random forest with an area of 0.9915
ROC curve of the random forest on the test set.

Four notebooks, one catalog.

The report and the notebooks document every step, from the first schema inspection to the threshold analysis, with the interactive Folium maps.

Next case study · 06 / 08Native Categorical Multiway-Split Decision Trees