Skip to content

Apply gini(negatives=...) option; guard degenerate cases - #289

Merged
MaxGhenis merged 1 commit into
mainfrom
fix/gini-negatives
Apr 17, 2026
Merged

Apply gini(negatives=...) option; guard degenerate cases#289
MaxGhenis merged 1 commit into
mainfrom
fix/gini-negatives

Conversation

@MaxGhenis

Copy link
Copy Markdown
Collaborator

Summary

Both branches of MicroSeries.gini sorted self directly rather than the local x that was mutated by the negatives option, so negatives='zero' and negatives='shift' were silently ignored. The raw formula with negatives can also return values outside [0, 1], and an all-zero series hit a ZeroDivisionError / RuntimeWarning.

Reproduction

import microdf as mdf

s = mdf.MicroSeries([-5, 0, 10], weights=[1, 1, 1])
s.gini(negatives="zero")    # was 2.0 (unchanged), now 0.6666...
s.gini(negatives="shift")   # was 2.0 (unchanged), now in [0, 1]

mdf.MicroSeries([0, 0, 0], weights=[1, 2, 3]).gini()   # was nan + RuntimeWarning, now 0.0

Fix

  • Use the mutated x throughout
  • Collapse the two weighted / unweighted branches into a single weighted implementation
  • Warn when called with negatives and negatives=None
  • Short-circuit all-zero totals to 0 instead of dividing by zero
  • Raise on an unknown negatives option

Test plan

  • Existing 51 tests still pass
  • New test_gini_negatives_option_applied covers negatives=None (warns), 'zero', 'shift', all-zero, and unknown option raising.

@MaxGhenis MaxGhenis left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Self-review. Verified:

  • x is now used throughout rather than self (the original bug). Both weighted/unweighted branches collapsed into one weighted implementation using np.average-style cumulative trapezoidal area.
  • negatives="zero" uses np.where(x<0, 0.0, x) (was buggy in-place which could have ducked the mutated-x bug). negatives="shift" subtracts np.amin(x) only when min < 0. Unknown negatives raises ValueError.
  • Degenerate cases: empty → NaN; all-zero total → 0.0 (not NaN/RuntimeWarning); negatives with negatives=None → warning that result may leave [0,1].
  • Uses kind="mergesort" for stable sort on ties.
  • Test covers warn path, zero clamp (closed-form = 2/3), shift bounded in [0,1], all-zero = 0, invalid arg raises.
  • CI green Py 3.9-3.13.

Both branches of MicroSeries.gini sorted self directly rather than the
local x that was mutated by the negatives option, so
negatives='zero' and negatives='shift' were silently ignored:

  s = MicroSeries([-5, 0, 10], weights=[1,1,1])
  s.gini(negatives='zero')    # returned 2.0 (unchanged)
  s.gini(negatives='shift')   # returned 2.0 (unchanged)

Fix: use the mutated x throughout, collapse the two branches into a
single weighted implementation, warn when called with negatives in the
data and negatives=None (the Lorenz formula can return values outside
[0, 1]), short-circuit all-zero and empty series to 0 (previously
raised RuntimeWarning and returned NaN), and raise on an unknown
negatives option.
@MaxGhenis
MaxGhenis force-pushed the fix/gini-negatives branch from 86f3984 to fa33129 Compare April 17, 2026 16:18
@MaxGhenis
MaxGhenis merged commit 7918fbd into main Apr 17, 2026
8 checks passed
@MaxGhenis
MaxGhenis deleted the fix/gini-negatives branch April 17, 2026 16:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant