Repository navigation
fix: a repeated index label no longer breaks cleaning - #505
Merged
Merged
Conversation
fd.clean(df) and fd.suggest_plan(df) raised on ordinary input whenever the frame's index repeated a label -- the shape two exports concatenated without reset_index produce. Two steps selected rows by label, and .loc expands a repeated label to every row carrying it: - the formatted-number rescue (steps/dtypes.py) assigned with parsed.loc[rescued.index] = ..., so the left-hand side outgrew the values: ValueError: cannot set using a list-like indexer with a different length. - the coercion-casualty scan (engine/missing.py) narrowed with df[col].loc[rows].isna(), where .loc returned more rows than the mask: IndexError: boolean index did not match indexed array. Both now work by position, as the undo-log revert and fieldcheck already did. The index itself is deliberately left alone. Running the whole pipeline on a RangeIndex and restoring labels afterwards was tried first and withdrawn: it silently dropped a real row, because steps/duplicates.py reads the index to decide that repeated timestamps in a DatetimeIndex are real observations, not duplicates (caught by test_timeseries_duplicates_preserved_even_when_ removal_requested). engine/context.py reads the index type as well. So the index carries semantics, and replacing it would change decisions, not only mechanics. tests/test_duplicate_index.py pairs identical data under a unique and a repeated index. 8 of its 11 tests fail without this change, on both py3.12/pandas 2.3.3 and py3.9/pandas 1.5.3. The characterization test that pinned FD2-011 as "currently breaks" is converted to "no longer breaks".
Contributor
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
FreshData benchmark report —
|
| fixture | n_rows | n_cols | p50 s | p95 s | peak MB | repair % | false-repair % | preserve % | trust | monotonic | export % |
|---|
Authored-code reduction (Metric 6)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
fd.clean(df)andfd.suggest_plan(df)raise on ordinary input whenever the frame's index repeats a label — the shape you get from concatenating two exports withoutreset_index. Default settings, no unusual options:Two steps selected rows by index label, and
.locexpands a repeated label to every row carrying it:steps/dtypes.pyformatted-number rescueparsed.loc[rescued.index] = rescued.to_numpy()ValueError— left side longer than the valuesengine/missing.pycoercion-casualty scandf[col].loc[rows].isna()IndexError: boolean index did not match indexed arrayHow the sites were found
Not by patching the first crash. 17 public-API calls were each run on identical data twice — unique index vs. every label repeated — and any difference counted. Exactly these two sites differed;
suggest_planreaches the first one too. The other.loc[sites insrc/take boolean masks or already have a non-unique fallback.Fix
Both sites now work by position — the approach the undo-log revert (
report.py), the guard (guard.py), andfieldcheckalready take for the same reason.The index itself is deliberately left alone. The first attempt ran the whole pipeline on a
RangeIndexand restored labels afterwards (asvalidate_domaindoes). It fixed both crashes — and silently dropped a real row:steps/duplicates.pyreadsisinstance(df.index, pd.DatetimeIndex)to decide that repeated timestamps are real observations rather than duplicates, and replacing the index disarmed that rule.test_timeseries_duplicates_preserved_even_when_removal_requestedcaught it.engine/context.pyreads the index type as well. So the index carries meaning, and replacing it would change decisions, not just mechanics. Withdrawn in favour of the narrower fix.Tests
tests/test_duplicate_index.py— every test is a pair: identical data under a unique index and a repeated one, asserting the two agree. That keeps the tests meaningful if the implementation changes again.src/change, on both py3.12 / pandas 2.3.3 and py3.9 / pandas 1.5.3. The other 3 are unique-index controls."not a date", not a date-like string:"5 March 2021"was tried first and is a casualty only on pandas 2 (which infers one format per column); pandas 1.x parses it, so the casualty-scan test passed vacuously there. Found by running the full suite on py3.9.tests/test_dtypes_boolean_mutants.py: the characterization test that pinned this bug as "currently breaks" is converted to "no longer breaks".Verification
Full suite (
-m "not online and not large") on the branch rebased ontobdff821:[dev]extra, so 14 modules failed collectionruff checkclean.Compatibility impact
None for a unique index — that path is unchanged. Frames with repeated labels that previously raised now clean, and produce the same result as the identical frame with a unique index.