Skip to content

The diversion sum keeps working when pandas removes the copy keyword - #62

Merged
HughRunyan merged 2 commits into
mainfrom
divs-sum-without-deprecated-copy-keyword
Sep 9, 2026
Merged

The diversion sum keeps working when pandas removes the copy keyword#62
HughRunyan merged 2 commits into
mainfrom
divs-sum-without-deprecated-copy-keyword

Conversation

@HughRunyan

Copy link
Copy Markdown
Collaborator

DivsDF.sum — the helper that combines the compost / anaerobic / combustion /
recycling streams into the diverted-mass frame every DST path subtracts from
generation — passed copy=False to infer_objects() in four places. Since
pandas 3.0 made Copy-on-Write unconditional that keyword is ignored, and
pandas 4 removes it.

The keyword is already a no-op

Not assumed — read off pandas/core/generic.py at the pinned 3.0.5:

def infer_objects(self, copy: bool | lib.NoDefault = lib.no_default) -> Self:
    ...
    self._check_copy_deprecation(copy)   # warns; nothing else reads `copy`
    new_mgr = self._mgr.convert()
    res = self._constructor_from_mgr(new_mgr, axes=new_mgr.axes)
    return res.__finalize__(self, method="infer_objects")

_check_copy_deprecation only emits the warning. The method already returned a
new object either way, so .infer_objects() is exactly the call that was
running.

Why it mattered

Passing it produced 96 Pandas4Warnings per test run (24 real sum() calls
× 4 streams). More to the point, when pandas 4 removes it the call raises
TypeError — and the break is worse than a TypeError, because all three
callers wrap it in a bare except: whose fallback does not work:

try:
    diverted = divs_df.sum()
except:
    diverted = sum(divs_df.values())   # DivsDF is a pydantic model, not a dict

Simulated by making infer_objects reject its arguments, the removal surfaces as:

AttributeError: 'DivsDF' object has no attribute 'values'

— an error pointing nowhere near the cause. test_removal_of_the_keyword_is_not_masked
pins the sum and that caller against exactly this.

infer_objects() itself is kept — but not for the reason the code implied

The old comment ("Reindex and fill missing values, then infer object types")
suggested the call was there to clean up after reindex. It is not:
reindex never produces an object column. Added columns arrive as float64
NaN regardless of the source dtypes — verified against float64, int64,
0-row and 0-column sources.

What the call actually does is stop an input frame that already carries its
numbers in an object column (a Series built from None, a value that arrived
boxed) from surviving fillna(0) as object and dragging the summed frame with
it. Every column in every frame the suite feeds it is float64 (960/960), so it
is a no-op here — but it is cheap insurance on frames callers hand in, and this
repo has been bitten by pandas dtype behaviour before (2026-02 pd.NA crash,
2026-04 SDST oxidation hardening). Removing it would turn a provable no-op into
a latent dtype change, so it stays, with a comment that says what it is for.

No output change

Verified twice over, so this is not a model-output-change:

  1. Structurally — pandas never consults the keyword (source above).
  2. Empirically, end to end — five city-DST scenarios (no diversion;
    compost+recycling; +25% and +50% food-waste prevention, the path that drives
    this code; all four streams) dumped at full float precision: net masses,
    waste masses, per-stream divs_df frames, DivsDF.sum() output, and every
    landfill's waste_mass / ch4 / captured / emissions series.
    73,760 numbers across 165 model outputs, byte-identical
    sha256 83e2019… before and after.

Also instrumented all 24 real sum() calls the suite makes and compared three
variants (copy=False / no keyword / no infer_objects at all): identical
dtypes and identical values on every one.

Scope check — other deprecated pandas usage

Grepped the package for the same category and tested each pattern under 3.0.5:

Pattern Sites Status
infer_objects(copy=False) class_defs.py ×4 fixed here (only warning in the package)
fillna/rename/drop(inplace=True) city_params.py ×4 not deprecated in 3.0; all on owned objects, no chained assignment — no change
pd.to_numeric(errors="coerce") city_params.py ×3 supported (errors="ignore" was the removed one; unused)
downcast=, astype(..., errors=) none present
pandas chained assignment none; all a[x][y] = … hits are plain nested dicts
DataFrame.append, applymap, fillna(method=), iteritems, … none (.append hits are all list appends)

Suite now runs clean under -W error::DeprecationWarning -W error::FutureWarning.

Acceptance Criteria

  • No copy= keyword passed to infer_objects anywhere in the package.
  • Pandas deprecation warnings from this cause drop from 96 to 0; the
    whole suite passes with pandas deprecations promoted to errors.
  • .infer_objects() is retained, and the comment explains what it actually
    guards (object-dtype input, not the reindex).
  • Model output is unchanged, demonstrated on real DST scenarios rather than
    asserted — hence no model-output-change label.
  • Regression tests fail on the pre-fix code (the two keyword tests) and pass
    after; the dtype tests pass on both, which is the point.
  • Rest of the package audited for the same category of deprecated usage.
  • Changelog entry added to changelog/2026-09.md per CLAUDE.md.

Definition of Done

  • Acceptance criteria met
  • Tests & checks pass — 138 passed (133 existing + 5 new), 0 warnings,
    Python 3.12 + the pinned requirements.txt (pandas 3.0.5), matching CI
  • Docs updated where relevant — changelog entry + changelog/README.md
    highlights; in-code comment corrected
  • Reviewed & merged

Notes for the reviewer

  • The test file is new and hermetic (no DB, no network); it adds ~0.1s.
  • CI will not run on this branch until pytest runs in CI on every push and pull request #61 merges — .github/workflows/tests.yml
    is not on main yet. The results above are from a local run reproducing that
    job exactly: Python 3.12, pip install -r requirements.txt, pip install -e ..
  • Unrelated, found while building the comparison harness and not touched
    here: model output frames' column order varies with PYTHONHASHSEED (a
    set feeding the waste-type ordering). Values and column sets are identical;
    only ordering moves. Worth its own change if anything downstream indexes
    positionally or diffs serialized output.

🤖 Generated with Claude Code

@HughRunyan HughRunyan added python Pull requests that update python code test Adds or modifies tests deprecation Removes or migrates off a deprecated upstream API before it is removed labels Sep 8, 2026
HughRunyan added a commit that referenced this pull request Sep 8, 2026
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@HughRunyan
HughRunyan requested a lite review from Copilot September 8, 2026 22:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The change is narrowly scoped, well-documented, and backed by targeted regression tests without introducing risky behavioral changes.

Pull request overview

This PR updates DivsDF.sum() to stop passing the deprecated/removed copy= keyword to pandas.DataFrame.infer_objects(), preventing future breakage under pandas 4 and eliminating related pandas deprecation warnings. It also adds regression tests to ensure the dtype behavior remains stable and that a pandas API change won’t get masked into a misleading error by existing callers.

Changes:

  • Remove copy=False from the four reindex(...).infer_objects(...).fillna(0) chains in DivsDF.sum() and clarify the rationale for keeping infer_objects().
  • Add a new hermetic test module covering (a) no pandas-change warnings, (b) simulated removal of accepted infer_objects arguments, and (c) dtype stability (float/object cases).
  • Document the change in the September 2026 changelog and the changelog index.
File summaries
File Description
SWEET_python/class_defs.py Drops the deprecated copy= argument from infer_objects() in DivsDF.sum() and updates the in-code comment to reflect the real purpose of infer_objects().
tests/test_divs_sum_dtypes.py Adds regression tests for warning-free behavior, simulated pandas-4-style API strictness, and dtype/value invariants of DivsDF.sum().
changelog/README.md Updates the “Entries” summary line for 2026-09 to include this change.
changelog/2026-09.md Adds a detailed changelog entry describing the deprecation/removal avoidance and the no-output-change verification.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

HughRunyan and others added 2 commits September 8, 2026 16:41
The four reindex(...).infer_objects(...).fillna(0) chains in DivsDF.sum
passed copy=False. Since pandas 3.0 made Copy-on-Write unconditional the
keyword is ignored: infer_objects hands it to _check_copy_deprecation,
which warns and returns, and nothing else in the method reads it. So
dropping it is exactly the call that was already running.

Keeping it was not free. It emitted 96 Pandas4Warnings per test run, and
in pandas 4 it raises TypeError. The break is worse than a TypeError,
because every caller of DivsDF.sum wraps it in a bare `except:` whose
fallback -- sum(divs_df.values()) -- does not work on a pydantic model.
The real failure would surface as

    AttributeError: 'DivsDF' object has no attribute 'values'

pointing nowhere near the cause. The new test simulates the removal and
pins both the sum and that caller.

infer_objects() itself stays. It does nothing for the reindex -- columns
that reindex adds arrive as float64 NaN whatever the source dtypes, which
is checked against float, int, 0-row and 0-column sources -- but it is
what stops an object-dtype input column from surviving fillna(0) and
dragging the summed frame to object.

No output change. Five city-DST scenarios, including the food-waste
prevention path that drives this code, produce a byte-identical dump of
73,760 numbers across 165 model outputs before and after (at a fixed
PYTHONHASHSEED; column order varies with the hash seed either way, which
is a separate pre-existing quirk and does not touch values).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@HughRunyan
HughRunyan force-pushed the divs-sum-without-deprecated-copy-keyword branch from 9c01bb6 to 89a5e21 Compare September 8, 2026 23:44
@HughRunyan
HughRunyan merged commit 23e1c66 into main Sep 9, 2026
@HughRunyan
HughRunyan deleted the divs-sum-without-deprecated-copy-keyword branch September 9, 2026 01:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deprecation Removes or migrates off a deprecated upstream API before it is removed python Pull requests that update python code test Adds or modifies tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants