Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Unidata’s notebooks split at random. Here the unit of the split is the ensemble member, and the cost of getting it wrong is measured rather than assumed.

Overview

  1. Build member-grouped validation folds with scikit-learn; verify no member sits on both sides of a fold.

  2. Measure the dependence that would make a member-blind split leak: within members (lag-1) and between micro-siblings.

  3. Score one model three ways — this cookbook’s pipeline, transforms fit before the split, a member-blind split — and read the gaps.

Loading and the transforms come from lensml.py; the splitter and scoring loop are written out here.

Prerequisites

ConceptsImportanceNotes
Chapter 2Helpfulthe test draw and the two transforms
scikit-learn User Guide, §3.1 Cross-validationHelpfulfolds, groups
AutocorrelationHelpfullag-1, in one line
  • Time to learn: about 15 minutes; the contrast cell takes under a minute.

  • System requirements: the environment.yml at the repository root; no account, no local data beyond the committed cache.

Imports

cache header has no AUTUMN_MONTHS field (it predates the field): taken as AUTUMN_MONTHS=[9, 10, 11], the only autumn the derivation code had: the file's autumn is assumed, not read from the header. A rebuild records it.
train: 80 members / 9200 winters   test: 20 members / 2300 winters   N_FOLDS = 5

Why the member is the unit

scikit-learn’s guidance on grouped cross-validation: “all the samples in the validation fold come from groups that are not represented at all in the paired training fold.” Three reasons make the member the group:

  1. The question is about members never seen. A member-blind split scores new years of members the model already knows.

  2. The transforms. Fit before the split, held-out winters would help fit the forced curve and wet threshold that label them — the leak of a StandardScaler fit on the whole table. Both are refit inside each fold.

  3. Dependence is measured, not assumed. After the fold check come the lag-1 table and a micro-sibling line that fails if members sharing a start year move together; that line licenses not grouping siblings.

Beside chapter 2’s test members, the training members are split into N_FOLDS = min(N_FOLDS_MAX, training members) folds by scikit-learn’s StratifiedGroupKFold: the groups keep each member whole, and stratifying balances wet winters across folds.

base rate train 0.333   (the test members' base rate is printed once, in chapter 6; nothing here reads their labels)
N_FOLDS = min(N_FOLDS_MAX=5, 80 training members) = 5
5 member-grouped folds verified: no member appears in both the fitting and the scoring side of any fold

lag-1 autocorrelation within training members (what consecutive winters of one member share):
              lag1_nino34_anom  lag1_wet
member_id                               
r10i1181p1f1            -0.202     0.065
r10i1191p1f2            -0.113    -0.108
r10i1231p1f1            -0.235     0.036
r10i1281p1f1            -0.181    -0.024
r11i1231p1f2            -0.212     0.039
r11i1281p1f2            -0.191     0.121
r11i1301p1f2            -0.129    -0.040
r12i1231p1f2            -0.237    -0.162
r12i1251p1f2            -0.153    -0.091
r12i1281p1f2            -0.178    -0.022
r12i1301p1f2            -0.146     0.013
r13i1251p1f2            -0.079    -0.206
r13i1281p1f2            -0.189     0.143
r13i1301p1f2            -0.098     0.052
r14i1251p1f2            -0.202    -0.130
r14i1281p1f2            -0.173     0.040
r15i1231p1f2            -0.149     0.134
r15i1251p1f2            -0.205    -0.125
r15i1281p1f2            -0.090    -0.126
r15i1301p1f2            -0.143     0.134
r16i1231p1f2            -0.155     0.128
r16i1251p1f2            -0.180    -0.137
r16i1301p1f2            -0.214    -0.053
r17i1231p1f2            -0.028     0.081
r17i1251p1f2            -0.076    -0.113
r17i1281p1f2            -0.168     0.072
r18i1231p1f2            -0.204     0.078
r18i1281p1f2            -0.106    -0.073
r18i1301p1f2            -0.135    -0.057
r19i1251p1f2            -0.189    -0.107
r19i1281p1f2            -0.141     0.066
r19i1301p1f2            -0.063     0.053
r1i1001p1f1             -0.065    -0.013
r1i1231p1f1             -0.231     0.215
r1i1251p1f1             -0.188    -0.152
r1i1281p1f1             -0.255     0.038
r1i1301p1f1             -0.258     0.049
r20i1231p1f2            -0.194     0.078
r20i1251p1f2            -0.197    -0.086
r20i1281p1f2            -0.188    -0.145
r20i1301p1f2            -0.163    -0.004
r2i1031p1f2             -0.210     0.040
r2i1251p1f1             -0.237     0.171
r2i1281p1f1             -0.153     0.025
r2i1301p1f1             -0.093     0.161
r3i1041p1f1             -0.212     0.072
r3i1051p1f2             -0.162    -0.053
r3i1231p1f1             -0.115     0.081
r3i1251p1f1             -0.194     0.088
r3i1281p1f1             -0.090    -0.124
r3i1301p1f1             -0.051     0.036
r4i1061p1f1             -0.151    -0.000
r4i1071p1f2             -0.205    -0.039
r4i1231p1f1             -0.115    -0.056
r4i1281p1f1             -0.227    -0.015
r4i1301p1f1             -0.108     0.162
r5i1081p1f1             -0.196     0.013
r5i1231p1f1             -0.071    -0.078
r5i1301p1f1             -0.223    -0.060
r6i1101p1f1             -0.048     0.058
r6i1111p1f2             -0.126    -0.060
r6i1251p1f1             -0.185     0.053
r6i1281p1f1             -0.256    -0.119
r6i1301p1f1             -0.183    -0.080
r7i1121p1f1             -0.114    -0.078
r7i1131p1f2             -0.162    -0.002
r7i1231p1f1             -0.089    -0.027
r7i1251p1f1             -0.201     0.086
r7i1301p1f1             -0.075     0.053
r8i1141p1f1             -0.038    -0.053
r8i1151p1f2             -0.034     0.134
r8i1251p1f1             -0.236    -0.031
r8i1281p1f1             -0.183    -0.101
r8i1301p1f1             -0.108     0.001
r9i1161p1f1             -0.167     0.052
r9i1171p1f2             -0.177     0.191
r9i1231p1f1             -0.162    -0.013
r9i1251p1f1             -0.141     0.001
r9i1281p1f1             -0.171    -0.132
r9i1301p1f1             -0.105    -0.022
mean: {'lag1_nino34_anom': -0.156, 'lag1_wet': -0.0}

correlation between training members over 1900–2014 (micro-siblings share a start year; the memory check):
nino34_anom  micro-siblings -0.001 (468 pairs, SD 0.100) · unrelated +0.001 (2692 pairs, SD 0.104) · siblings − unrelated -0.002 (±0.005, a lower bound: the pairs share members, so they are not independent draws) → PASS: no sibling excess detectable (|excess| ≤ 2 SE, SE a lower bound) — the member is taken as the unit
prect_anom   micro-siblings -0.003 (468 pairs, SD 0.095) · unrelated -0.002 (2692 pairs, SD 0.095) · siblings − unrelated -0.001 (±0.005, a lower bound: the pairs share members, so they are not independent draws) → PASS: no sibling excess detectable (|excess| ≤ 2 SE, SE a lower bound) — the member is taken as the unit

The leak contrast: one model, three ways

One loop scores every validation run in this cookbook. Per fold: refit the transforms on the fold’s training rows (unless refit_transforms=False), .fit the model, .predict the held-out rows, and score them for the wet class.

ArmSplit unitForced curve and wet threshold fit onA held-out score measures
A — this cookbook’s pipelinememberthe fold’s training membersnew members
B — transforms fit before the splitmemberall training membersnew members, with transforms they helped fit
C — member-blind splitwinter (StratifiedKFold)the fold’s training wintersnew years of known members

B differs from A only in where the transforms are fit; C only in the split unit. The model is gradient boosting (chapter 4) at the base-rate threshold, so that it calls wet: the for reference line shows that at the default 0.5 it almost never does.

Reading this cell un-blinds chapter 5’s trial of this model (see the note).

for reference — the same model at sklearn's default threshold 0.5 (arm A's split, one fold draw): wet calls 7 of 9200 scored winters (0.08%), recall 0.000 — too few calls to read a gap in
boosting at threshold 0.333 (the base rate; 0.5 is sklearn's default), 10 fold draw(s) × 5 folds per arm. Per arm: scores (mean ± per-fold SD) and wet calls; then gap = arm − A (mean ± SD of the per-fold gaps)
A member split, transforms refit in each fold    AP 0.355   precision 0.353 ± 0.009   recall 0.634 ± 0.045   accuracy 0.491   wet calls 55064 of 92000 scored winters (59.9%)
                                                 gap vs A: AP +0.0000 ± 0.0000   precision +0.0000 ± 0.0000   recall +0.0000 ± 0.0000   → gaps readable (wet calls ≥ 1% of winters)
B member split, transforms fit before the split  AP 0.354   precision 0.352 ± 0.009   recall 0.636 ± 0.045   accuracy 0.489   wet calls 55405 of 92000 scored winters (60.2%)
                                                 gap vs A: AP -0.0010 ± 0.0061   precision -0.0010 ± 0.0033   recall +0.0019 ± 0.0306   → gaps readable (wet calls ≥ 1% of winters)
C member-blind split, transforms refit in fold   AP 0.355   precision 0.353 ± 0.008   recall 0.644 ± 0.036   accuracy 0.488   wet calls 55941 of 92000 scored winters (60.8%)
                                                 gap vs A: AP -0.0004 ± 0.0111   precision +0.0000 ± 0.0107   recall +0.0100 ± 0.0584   → gaps readable (wet calls ≥ 1% of winters)

Why the gaps are small here

Read each arm’s gap vs A line against the ± beside it: the leaks barely bite. The mean: line under the lag-1 table gives lag1_wet near zero, so arm C has little to learn from a member’s other years. A small gap measures this design; it is not a licence.

Summary

Validation folds are grouped by member with StratifiedGroupKFold and verified leak-free; the sibling check licenses the member as the unit. Scoring one model three ways turned the two leaks into numbers — small on this design.

What’s next?

Chapter 4 scores the simple rules and a three-rung ladder on these folds.

Resources and references

ResourceWhere it bearsLicence
scikit-learn User Guide, §3.1 Cross-validation (grouped splits). https://scikit-learn.org/stable/modules/cross_validation.htmlmember-grouped foldsBSD-3-Clause
scikit-learn User Guide, §12 Common pitfalls. https://scikit-learn.org/stable/common_pitfalls.htmltransforms fit inside the splitBSD-3-Clause
Rodgers, K. B. et al. (2021). Earth System Dynamics 12, 1393–1411. doi:10.5194/esd-12-1393-2021§2.2: initial-condition memoryCC BY 4.0
CESM2 Large Ensemble project page. https://www.cesm.ucar.edu/community-projects/lens2micro-members and start yearsUCAR website terms of use
NSF Unidata, Supervised Machine Learning Readiness. https://github.com/Unidata/supervised-ml-readinessthe random split this chapter changesApache-2.0