Lab · reproduction of an outside note
Reproduction: Does a tornado warning's shape predict whether it verifies?
Independent rerun of the 2026-09-15 note, done 2026-09-15 in the kiosks lab against TerraPulse exports cached 2026-09-12. Script: lab/false_alarm.py. Nothing from the author's run was reused except the note itself; the feature code, join and model were rewritten from its description and its code block.
Verdict: reproduced. Every reported number lands within rounding or within the model's own seed-to-seed spread. The conclusions stand as written. Two things in the note are underspecified and one finding deserves a second look; all three are below.
Data
| note | this run | |
|---|---|---|
| polygon tornado warnings, 2008 to 2025 | 50,469 | 50,469 |
| verified (rated tornado inside during validity) | 20.9% | 20.85% (10,523) |
| train, 2008 to 2020 | 36,524 at 21.1% | |
| test, 2021 to 2025 | n about 15k | 13,945 at 20.2% |
| issuing offices in train | 116 |
Rated tornadoes with coordinates: 71,912 (2,272 preliminary 2026 reports with no rating excluded, as the note's "rated" implies).
Results, test set
| model | note AUC | this run | seeds 0, 1, 2 |
|---|---|---|---|
| MLP, all features | 0.619 | 0.620 | 0.620, 0.613, 0.619 |
| logistic, all features | 0.625 | 0.627 | 0.627, 0.628, 0.628 |
| MLP without office | 0.609 | 0.608 | |
| MLP without office, lat, lon | 0.614 | 0.611 | |
| MLP without area, shape, duration | 0.590 | 0.588 |
Seed spread on the MLP is about 0.007, so the differences between the three ablations and the full model (0.01, 0.01, 0.03) are at the edge of what one seed can resolve. The ordering matches the note. The logistic model is stable to 0.001 across seeds and beats the MLP by the same small margin the note found, which supports the "it is linear" reading.
Verified rate by predicted quintile, low to high: note 11, 17, 20, 24, 30; this run 11, 16, 20, 23, 30.
Permutation importance, AUC drop when one column is shuffled on the test set:
| feature | note | this run |
|---|---|---|
| area | 0.057 | 0.051 |
| office (target encoding) | 0.045 | 0.041 |
| season (cos, sin of day of year) | 0.014, 0.013 | 0.013, 0.011 |
| vertex count | ≤ 0.001 | 0.003 |
| initial duration | ≤ 0.001 | 0.002 |
| everything else | ≤ 0.001 | between -0.005 and 0.001 |
Raw rates:
| note | this run | |
|---|---|---|
| area quartile, smallest to largest | 14, 19, 22, 28% | 14.3, 18.8, 22.1, 28.2% (edges 466, 761, 1,148 km²) |
| initial duration ≤30, 30 to 45, 45 to 60, 60+ min | 16, 21, 26, 32% | 16.3, 20.8, 25.9, 32.1% (n 15,304, 23,261, 10,142, 1,762) |
| best offices, ≥300 warnings | Grand Forks 46, Wichita 39, Chicago 32, Dodge City 29 | FGF 46.1, ICT 38.5, LOT 31.6, DDC 29.4 |
| worst offices | Jackson KY 7, Roanoke 7, Pueblo 10, Corpus Christi 11 | JKL 7.0, RNK 7.1, PUB 10.3, CRP 11.5 |
66 offices have 300 or more warnings in the period.
Where the note was silent, and what this run assumed
These did not change the answer, but a second reproducer could pick differently, so they belong in the note.
- Units for the shape features. Aspect and compactness need a perimeter and a bounding box in consistent units. This run projects each polygon locally (longitude scaled by the cosine of its mean latitude, 111.32 km per degree) before measuring. In raw degrees the aspect ratio is stretched by up to 30% at northern offices. Both features are noise either way, so it did not matter here; it would for a paper that reported them.
- Vertex count is taken as the exterior ring's coordinates minus the closing point. Every polygon in this export is a simple Polygon, so no multipolygon rule was needed.
- Solar hour is UTC hour plus longitude over 15, with no equation of time. The note says "local solar hour" without the formula.
- The label bracket. Touchdown time between
start_utcandend_utcinclusive, inside the polygon by shapely'swithin(a point exactly on the boundary does not count).end_utcis the actual end after any cancellation, which is what the note's exclusion list implies: the label may use post-issuance information, the features may not. - Target-encoding smoothing of 20 pseudo-warnings is in the code block but not the text. Offices unseen in training (none here) fall back to the train base rate.
- Weight initialisation. The note seeds tinygrad and numpy but the code constructs the network before seeding, so the seed governs the batch order and not the initial weights. This run re-initialises the layers under the seed. Either way lands within 0.005.
Worth a second look
Duration's raw effect is area in disguise. The note's point 2 reads the duration bins (16% to 32%) as forecaster commitment. In the model, initial duration has a permutation importance of 0.002 while area has 0.051, and the two are strongly related: rank correlation 0.41, median area 561 km² for warnings of 30 minutes or less against 1,339 km² for 60 minutes or more. Cross-tabulating the two:
| verified rate | ≤30 min | 30 to 45 | 45 to 60 | 60+ |
|---|---|---|---|---|
| smallest area quartile | 13.3% | 15.1% | 16.4% | 14.7% |
| second | 16.3% | 19.1% | 22.7% | 25.7% |
| third | 18.7% | 22.2% | 25.3% | 26.1% |
| largest quartile | 23.0% | 26.6% | 30.0% | 36.7% |
Area climbs inside every duration bin. Duration does nothing inside the smallest quartile and adds about 14 points inside the largest. So the "60 minute warning verifies twice as often" line is mostly the size of the polygons that get 60 minutes, and the interaction (duration matters only when the box is big) is the one piece of non-linear structure the MLP could have used and apparently did not. This sharpens rather than contradicts the note's policy point: the thing that predicts verification is area, and area is what the 2012 policy shrank.
One bad initial expiry. LIX-TO-W-2-2011 carries an initial expiry 63 days after issuance (2011-01-01 to 2011-03-05). It is one row in 50,469 and survives standardisation harmlessly, but a duration feature should be clipped, and the dex row is worth a fix.
The mechanical area caveat is checkable now. The note's main open question is how much of the area effect is a bigger net catching more points. The tor export carries length_miles for rated tornadoes, so a partial answer needs no dossier fetch: restrict verification to tornadoes with a path under 2 miles (near-points) and see whether the area gradient survives. Not done here because it changes the label, and this run's job was to match the note.
Environment
tinygrad 0.14.0 on the CPU backend with clang present, shapely 2.1.2, pandas 2.2, one core. Join plus five models plus three extra seeds in 102 seconds. Tensor.binary_crossentropy_logits exists in 0.14.0 with a reduction argument, as the note's code assumes.