Repository navigation
test_dunedin_pace_normalization fails on pandas 3.0 — stale pace_normalized.pkl fixture orientation (normalization output is correct) #209
Description
Summary
On pandas 3.0, biolearn/test/test_model.py::test_dunedin_pace_normalization fails with KeyError: 'cg00000029'. I traced it, and the good news is the DunedinPACE normalization output is correct — the failure is a stale .pkl reference fixture whose axis orientation predates the current loader, plus a diagnostic loop that assumes label-based lookups.
This is separate from and downstream of the read-only-array bug in #195 / PR #206. On master this same test aborts earlier with the #195 ValueError: assignment destination is read-only; with that fix applied it advances past normalization and reveals this KeyError.
CI doesn't currently catch it because CI runs Python 3.10 and resolves an older pandas (pyproject.toml pins pandas>=2.1.0, no upper bound), so pandas 3.0 is never installed there.
Reproduce
# Python 3.11.13, pandas 3.0.3, numpy 2.4.6
pytest biolearn/test/test_model.py::test_dunedin_pace_normalization
biolearn/test/test_model.py:125: in test_dunedin_pace_normalization
print(f"Location: {value[0]}, Actual: {actual.at[value[0]]}, Expected: {expected.at[value[0]]}")
E KeyError: 'cg00000029'
(On unpatched master the same command instead fails earlier with ValueError: assignment destination is read-only at dunedin_pace.py:61 — the #195 bug. This report assumes that fix is applied so normalization runs to completion.)
Root cause
The test compares the live normalization output against the reference fixture testset/pace_normalized.pkl. The two are in different orientations:
| index | columns | shape | |
|---|---|---|---|
actual (live loader output) |
CpG IDs — cg00000029, … (str) |
sample GSMs | (20000, 10) |
expected (.pkl fixture) |
integer RangeIndex — 0, 1, 2, … |
ID_REF + sample GSMs |
(20000, 11) |
The fixture was evidently saved before the loader promoted the probe-ID column (ID_REF) to the DataFrame index; the current loader returns CpG-indexed data.
Because the axes don't align:
np.abs(actual - expected) > 1e-6flags every cell as a mismatch (200000/200000).- The diagnostic print loop then does
expected.at[value[0]]wherevalue[0]is a(CpG, sample)tuple, e.g.('cg00000029', 'GSM1009660'). Looking up the CpG label inexpected's integerRangeIndexraisesKeyError.
The normalization math is not broken
Realigning the fixture (promoting its ID_REF column to the index) and comparing values directly:
max abs diff (realigned): 5.55e-16
values > 1e-6: 0 of 200000
NaNs: none
So the live output reproduces the reference to floating-point precision. The problem is purely the fixture's stored orientation plus the test's comparison logic — not the DunedinPACE computation.
Suggested fix (happy to open a PR)
Two independent things:
- Fixture orientation — regenerate
testset/pace_normalized.pklin the current loader orientation (CpG-indexed, noID_REFcolumn), or have the test normalize the reference's orientation on load (e.g.set_index('ID_REF')when that column is present) so it stays robust to how the fixture was saved. - Diagnostic loop — the mismatch-reporting block uses
DataFrame.at[...]with(row, col)tuples, which is fragile and, here, masks the real comparison result. Comparing on aligned axes avoids theKeyErrorand gives a meaningful diff if values ever do drift.
Happy to put up a small PR — let me know if you'd prefer the fixture regenerated vs. the test normalized on load.
Environment
- Python 3.11.13, pandas 3.0.3, numpy 2.4.6
- Reproduced with PR fix: handle read-only arrays in DunedinPACE quantile normalization (#195) #206 applied (so normalization runs); root cause and value equivalence verified as above.
Activity
test: fix test_dunedin_pace_normalization on pandas 3.0 (fixture orie…