Skip to content

fix(labels): accept missing labels, keep label identity, reject duplicates alike - #468

Merged
kevincostner17 merged 1 commit into
mainfrom
fix/label-robustness
Sep 16, 2026
Merged

kevincostner17 merged 1 commit into
mainfrom
fix/label-robustness

Conversation

@kevincostner17

Copy link
Copy Markdown
Contributor

Summary

Four column-label defects found by fuzzing. Stacked on #466 — review that first; this PR targets its branch and I'll retarget to main once it merges.

Root cause

Behaviour change

  • Missing labels: duplicate detection addresses columns by position when any label is missing, so detection, drop_duplicates, duplicate_subset and the keep-modes all work on such frames. Frames without missing labels take exactly the previous code path.
  • Label identity: infer_roles(...)["column"] now holds the frame's own labels with object dtype, so frame[row["column"]] round-trips. Rows are still ordered by label text, as before.
  • Duplicate labels: suggest_plan, plan, clean_text and lint_text_encoding raise ValueError("<func> requires unique column labels; duplicated: [...]"), matching fd.validate, fd.infer_roles, explain_clean, detect_pii and build_baseline. The guard moved to _util.require_unique_labels; api._require_unique_labels stays as an alias.

Default-output changes

  • Frames with a missing column label ({0: ..., None: ...}) now return a result from clean, profile, infer_roles and explain_clean instead of raising, and their duplicate rows are detected and removed like any other frame's.
  • infer_roles: the column column is object dtype rather than float64 when labels are mixed numeric/None or mixed int/float. Values are the frame's real labels. For all-string labels, output is unchanged.
  • suggest_plan/plan/clean_text/lint_text_encoding on duplicate labels: a clear ValueError instead of TypeError/AttributeError. Unique-label frames are unaffected.

One correction to #462 as filed

The issue's repro expects [0, None] back from pd.DataFrame({0: ..., None: ...}), but pandas coerces those labels to [0.0, nan] at construction, before any freshdata code runs — df.columns.dtype is already float64, and df[nan] cannot round-trip in pandas either. That part is not recoverable. What this PR fixes is the coercion freshdata itself introduced: when a frame's columns are genuinely object dtype (pd.Index([0, None], dtype=object), [-2, 0.78], ["a", 1]), the labels now come back exactly as they went in and round-trip. The repro therefore still exits 1; the regression test covers the recoverable case.

Tests

tests/test_label_robustness.py (new):

Verification

@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: f66e8ad2-6aa1-4dac-9a0f-74fdcf11f4e0


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

FreshData benchmark report — performance

  • freshdata: ?
  • python: ?
  • platform: ?
fixture n_rows n_cols p50 s p95 s peak MB repair % false-repair % preserve % trust monotonic export %

Authored-code reduction (Metric 6)

@kevincostner17
kevincostner17 changed the base branch from fix/report-json-and-memory to main September 16, 2026 18:47
…cates alike

Four label defects found by fuzzing:

* pandas coerces Index([0, None]) to float64 with NaN, and then cannot match
  that label against the frame's own columns, so DataFrame.duplicated()
  raised KeyError(Index([nan])) - breaking clean, profile, infer_roles and
  explain_clean on frames every other step handles. Duplicate detection now
  addresses columns by position when a label is missing.
* infer_roles collected the labels into one Series, which coerces a mixed
  numeric/None or int/float label set, so frame[row["column"]] no longer
  round-tripped. The column is built with object dtype instead.
* suggest_plan and plan raised TypeError("cannot convert the series to int")
  on duplicate column labels with the semantic layer active, where
  infer_roles and explain_clean raise a clear ValueError.
* clean_text and lint_text_encoding raised AttributeError on the same input.

The guard moves to _util.require_unique_labels so every entry point shares
one message; api keeps its private alias.

Closes #459
Closes #461
Closes #462
Closes #437
@kevincostner17
kevincostner17 merged commit 58f1558 into main Sep 16, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment