Skip to content

fix(diff): correct row counts and NULL comparison in DataFrame diffs - #349

Merged
luabida merged 1 commit into
AlertaDengue:mainfrom
devgtv:fix/diff-position-rows-added-and-nan-compare
Oct 2, 2026
Merged

luabida merged 1 commit into
AlertaDengue:mainfrom
devgtv:fix/diff-position-rows-added-and-nan-compare

Conversation

@devgtv

@devgtv devgtv commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Two defects in pysus/api/diff/comparison.py produced incorrect change summaries.

1. Positional diff invented additions when rows were removed

_diff_by_position() seeded rows_added with abs(len(df_new) - len(df_old)) and only overwrote it inside the if len(df_new) > len(df_old) branch:

result.rows_added = abs(len(df_new) - len(df_old))
if len(df_new) > len(df_old):
    result.rows_added = len(df_new) - len(df_old)
else:
    result.rows_removed = len(df_old) - len(df_new)

With old=5, new=3 this yields rows_added=2, rows_removed=2. The correct result is 0 added and 2 removed, so every deletion was double-counted. Both counters are now computed independently with max(0, ...).

2. != was used as an equality test on DataFrame cells

if new_val != old_val:
    result.rows_modified += 1
  • float("nan") != float("nan") is True, so a row that did not change was counted as modified whenever the value was NULL in both frames.
  • With nullable dtypes (pd.NA) the comparison returns pd.NA, and if pd.NA: raises TypeError. The DuckLake path writes nullable dtypes, so this is reachable in normal use.

Comparisons now route through _values_differ(), which checks missingness before falling back to != — two missing cells compare equal, a missing/present pair is a change. _is_missing() is factored out and tolerates values pd.isna() cannot reduce to a scalar.

Tests

Added 9 tests to pysus/tests/api/test_diff.py covering shrinking frames, growing frames, equal-length frames, nan in both frames, null→value and value→null transitions, and nullable Int64 dtypes in both directions.

  • 1742 passed, 6 skipped (was 1733 passed, 6 skipped)
  • black and isort clean

Note

No behavior outside the two diff functions is changed.

Two defects in the diff engine produced wrong change summaries:

_diff_by_position() seeded rows_added with abs(len(new) - len(old) and only
overwrote it when the new frame was longer. When rows were removed
(old=5, new=3) the result was rows_added=2, rows_removed=2 instead of
0 added and 2 removed, double-counting deletions as additions. Both
counters are now clamped independently with max(0, ...).

_diff_by_key() compared cells with `!=`, which is not a valid equality
test for DataFrame values: `nan != nan` is True, so a row whose value did
not change was counted as modified whenever the column was nullable, and
comparing `pd.NA` yields `pd.NA`, whose truth value raises TypeError.
This is reachable on the DuckLake path, which writes nullable dtypes.

Comparisons now go through _values_differ(), which checks missingness
before falling back to `!=`, treating two missing cells as equal and a
missing/present pair as a change. _is_missing() is factored out and
tolerates values pd.isna cannot reduce to a scalar.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 91.39785% with 8 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@24f736b). Learn more about missing BASE report.

Files with missing lines Patch % Lines
pysus/api/diff/comparison.py 73.33% 8 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #349   +/-   ##
=======================================
  Coverage        ?   97.19%           
=======================================
  Files           ?      180           
  Lines           ?    22773           
  Branches        ?        0           
=======================================
  Hits            ?    22134           
  Misses          ?      639           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@luabida
luabida merged commit 0e999eb into AlertaDengue:main Oct 2, 2026
15 checks passed
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 2.11.6 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants