Skip to content

test_dunedin_pace_normalization fails on pandas 3.0 — stale pace_normalized.pkl fixture orientation (normalization output is correct) #209

Closed

Description

@IsraelAfangideh

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:

  1. np.abs(actual - expected) > 1e-6 flags every cell as a mismatch (200000/200000).
  2. The diagnostic print loop then does expected.at[value[0]] where value[0] is a (CpG, sample) tuple, e.g. ('cg00000029', 'GSM1009660'). Looking up the CpG label in expected's integer RangeIndex raises KeyError.

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:

  1. Fixture orientation — regenerate testset/pace_normalized.pkl in the current loader orientation (CpG-indexed, no ID_REF column), 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.
  2. 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 the KeyError and 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

Activity

  1. added a commit that references this issue on Jul 11, 2026
    0d714f5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions