Skip to content

Match pandas semantics for null reduction results and mode ordering - #23328

Merged
rapids-bot[bot] merged 8 commits into
NVIDIA:release/26.08from
galipremsagar:pandas-tests-reductions-fixes
Jul 21, 2026
Merged

Match pandas semantics for null reduction results and mode ordering#23328
rapids-bot[bot] merged 8 commits into
NVIDIA:release/26.08from
galipremsagar:pandas-tests-reductions-fixes

Conversation

@galipremsagar

Copy link
Copy Markdown
Contributor

Description

Running tests/reductions/test_reductions.py from the pandas test suite under cudf.pandas showed 38 failures. This PR fixes the 34 that are fixable in cudf classic and documents the 4 inherent ones. The same root causes also fixed 16 more pandas tests across frame/, arrays/boolean, arrays/timedeltas, extension/test_arrow.py, groupby/test_reductions.py, and frame/methods/test_replace.py.

Fixes

  • pd.NaT singleton for temporal null reductions (_get_nan_for_dtype): min/max/median etc. on empty or all-null datetime/timedelta columns returned a unit-qualified np.datetime64('NaT')/np.timedelta64('NaT'); pandas returns the pd.NaT singleton and its tests assert identity (result is NaT).
  • <NA> for empty/all-null reductions of nullable dtypes (ColumnBase._reduce): Series([], dtype="Int64").mean()/.var() returned np.float64(nan) because the all-null branch used the result dtype (float64); pandas returns pd.NA. sum/product identities (0/1) are unchanged, matching pandas.
  • Kleene logic for any(skipna=False) (ColumnBase.any): with nulls present it returned True unconditionally. For pandas nullable extension dtypes, a no-True result with nulls present is now <NA> (matching all); numpy dtypes keep the NaN-sentinel-truthy behavior.
  • pd.NA guard before np.isnan in any/all to avoid "boolean value of NA is ambiguous" now that _reduce can return pd.NA.
  • Series.mode(dropna=False) null position: pandas sorts mode results on the underlying representation, so NaT (INT64_MIN as i8) and the categorical null code (-1) sort first while float NaN and arrow/nullable <NA> sort last. cudf sorted nulls last everywhere. Nulls-first now applies to numpy datetime/timedelta, DatetimeTZDtype, and categorical dtypes only (arrow timestamp/duration keep nulls last, verified against pandas).
  • StringColumn.all(): no longer short-circuits True for partially-null columns with skipna=False — the result depends on the truthiness of the non-null strings (e.g. all([NaN, ""], skipna=False) is False); it now falls through so cudf.pandas computes the correct result. All-null columns still return True.
  • test_timedelta_reductions updated to assert pd.NaT identity like test_datetime_reductions already does.

Pandas-testing plugin

  • Removed 50 now-passing xfail entries.
  • Replaced the "TODO" reasons on the 4 remaining test_reductions.py entries with real ones: test_sum_overflow_float[float32-*] (GPU tree-reduction accumulates float32 in a different order than numpy pairwise summation) and test_any_all_object_dtype_missing[any-data0/1] (None-vs-np.nan distinction is lost when object data becomes a nulled bool column).

Testing

  • tests/reductions/ in CI mode: 498 passed, 10 xfailed, no strict-XPASS.
  • Full pandas-tests suite in CI mode (206k tests): no failures attributable to this change; every removed xfail entry verified as strict-XPASS solo (not just under xdist, to rule out GPU-contention fallback).
  • Classic cudf sweep (series/dataframe/indexes/groupby/reshape/text/dtypes/general_functions/window, ~71k tests): failure set identical to unmodified baseline (the only failures are pre-existing groupby-JIT ones).
  • Raw (plugin-less) before/after comparison over all affected pandas-test files confirmed 11 newly-passing tests and no newly-failing ones. The decimal128 stack/unstack failures that appeared in some xdist runs reproduce identically on unmodified cudf (order-dependent, pre-existing).

Fixes 34 pandas-tests failures in tests/reductions/test_reductions.py
(plus 16 more across frame/arrays/extension/groupby/replace tests):

- Datetime/timedelta reductions on empty or all-null columns now return
  the pd.NaT singleton instead of a unit-qualified numpy NaT
  (_get_nan_for_dtype), matching pandas identity semantics.
- Empty/all-null reductions of pandas nullable extension dtypes return
  the dtype's NA instead of the result-dtype nan (e.g. Int64.mean() on
  an empty series is now <NA>, not float64 nan).
- ColumnBase.any() implements Kleene logic for pandas nullable
  extension dtypes with skipna=False: a no-True result with nulls
  present is <NA>. Numpy dtypes keep NaN-sentinel-truthy behavior.
- ColumnBase.any/all guard the reduce result against pd.NA before
  calling np.isnan to avoid ambiguous-bool errors.
- Series.mode(dropna=False) sorts nulls first for numpy
  datetime/timedelta, DatetimeTZ and categorical dtypes (pandas sorts
  on the underlying representation where NaT/null codes sort first);
  arrow and nullable dtypes keep nulls last.
- StringColumn.all() only short-circuits True for all-null columns; a
  partially-null column with skipna=False falls back so empty strings
  are evaluated as falsy.

Removes 50 now-passing xfail entries from the pandas-testing plugin and
documents the 4 remaining reductions failures as inherent (float32 GPU
sum accumulation order; None-vs-NaN indistinguishability in object
data).
@galipremsagar
galipremsagar requested a review from a team as a code owner July 18, 2026 05:46
@copy-pr-bot

copy-pr-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added Python Affects Python cuDF API. cudf.pandas Issues specific to cudf.pandas labels Jul 18, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Jul 18, 2026
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Changes

The PR aligns nullable and datetime reduction behavior with pandas missing-value semantics, corrects string all() handling for partially-null columns, makes Series.mode NA ordering dtype-aware, adds reduction and mode coverage, and updates pandas test failure mappings.

Reduction and missing-value alignment

Layer / File(s) Summary
Nullable reduction semantics
python/cudf/cudf/core/column/column.py, python/cudf/cudf/utils/dtypes.py, python/cudf/cudf/tests/series/methods/test_reductions.py
all(), any(), and _reduce() now handle nullable missing results and Kleene semantics; datetime-like missing values use pd.NaT, with expanded reduction tests.
String all reduction handling
python/cudf/cudf/core/column/string.py, python/cudf/cudf/tests/series/methods/test_reductions.py
StringColumn.all() short-circuits only for entirely null columns, while tests cover partially-null skipna=False behavior.
Mode missing-value ordering
python/cudf/cudf/core/series.py, python/cudf/cudf/tests/series/methods/test_mode.py
Series.mode sorts mode indexes with a dtype-derived na_position, with parameterized pandas comparison tests.
Pandas test mapping updates
python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
Obsolete failure mappings are removed and remaining reduction mismatches receive explicit reasons.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested labels: 5 - Ready to Merge

Suggested reviewers: bdice, tomaugspurger

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: aligning null reduction results and mode ordering with pandas semantics.
Description check ✅ Passed The description is directly related to the changeset and accurately summarizes the fixes, test updates, and remaining xfails.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
python/cudf/cudf/core/column/column.py (1)

1331-1337: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Gate the all-null shortcut to nullable dtypes only all(skipna=False) should mirror pandas here: return True for numpy-backed all-null columns, and reserve _get_nan_for_dtype(self.dtype) for nullable extension dtypes. Add a regression test for an all-null float column.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/cudf/cudf/core/column/column.py` around lines 1331 - 1337, Update the
all-null branch in Column.all so _get_nan_for_dtype(self.dtype) is used only for
nullable extension dtypes; return True for numpy-backed all-null columns when
skipna=False, while preserving the existing skipna=True behavior. Add a
regression test covering all-null float columns.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@python/cudf/cudf/core/column/column.py`:
- Around line 1331-1337: Update the all-null branch in Column.all so
_get_nan_for_dtype(self.dtype) is used only for nullable extension dtypes;
return True for numpy-backed all-null columns when skipna=False, while
preserving the existing skipna=True behavior. Add a regression test covering
all-null float columns.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: bf131e1e-0f9f-4458-9c2c-6abe8951e290

📥 Commits

Reviewing files that changed from the base of the PR and between 3b8727c and 0203303.

📒 Files selected for processing (6)
  • python/cudf/cudf/core/column/column.py
  • python/cudf/cudf/core/column/string.py
  • python/cudf/cudf/core/series.py
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
  • python/cudf/cudf/tests/series/methods/test_reductions.py
  • python/cudf/cudf/utils/dtypes.py

@galipremsagar galipremsagar added bug Something isn't working non-breaking Non-breaking change labels Jul 18, 2026
@galipremsagar
galipremsagar requested a review from mroeschke July 18, 2026 11:23
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test b5e18b1

Covers the behaviors fixed in the previous commit, asserting against
live pandas results:

- min/max on empty/all-null datetime and timedelta Series/Index return
  the pd.NaT singleton (identity with the pandas result).
- mean/var/std/min/max on empty/all-null nullable dtypes return pd.NA.
- Kleene any/all logic for nullable dtypes across the full
  data x skipna matrix, and the NaN-sentinel-truthy behavior of numpy
  float dtypes with skipna=False.
- any/all on a nullable float column holding actual NaN values
  (exercises the pd.NA guard on the reduce result).
- String all(skipna=False) on a partially-null column is not computed
  natively (raises NotImplementedError) while any(skipna=False) is
  truthy, matching the pandas-3 str-dtype NaN sentinel semantics.
- mode(dropna=False) null position matches pandas across numpy
  datetime/timedelta, ordered/unordered categorical, nullable, float,
  and arrow timestamp/duration dtypes.
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test c8565be

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 51a6b4c

Comment thread python/cudf/cudf/core/series.py Outdated
Comment thread python/cudf/cudf/tests/series/methods/test_mode.py Outdated
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/ok to test f089b92

@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test ac34ba1

@galipremsagar galipremsagar added the 5 - Ready to Merge Testing and reviews complete, ready to merge label Jul 20, 2026
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit d184e67 into NVIDIA:release/26.08 Jul 21, 2026
127 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jul 21, 2026
davidwendt pushed a commit to wjxiz1992/cudf that referenced this pull request Jul 21, 2026
…VIDIA#23328)

Running `tests/reductions/test_reductions.py` from the pandas test suite under `cudf.pandas` showed 38 failures. This PR fixes the 34 that are fixable in cudf classic and documents the 4 inherent ones. The same root causes also fixed 16 more pandas tests across `frame/`, `arrays/boolean`, `arrays/timedeltas`, `extension/test_arrow.py`, `groupby/test_reductions.py`, and `frame/methods/test_replace.py`.

### Fixes

- **`pd.NaT` singleton for temporal null reductions** (`_get_nan_for_dtype`): min/max/median etc. on empty or all-null datetime/timedelta columns returned a unit-qualified `np.datetime64('NaT')`/`np.timedelta64('NaT')`; pandas returns the `pd.NaT` singleton and its tests assert identity (`result is NaT`).
- **`<NA>` for empty/all-null reductions of nullable dtypes** (`ColumnBase._reduce`): `Series([], dtype="Int64").mean()`/`.var()` returned `np.float64(nan)` because the all-null branch used the *result* dtype (float64); pandas returns `pd.NA`. `sum`/`product` identities (0/1) are unchanged, matching pandas.
- **Kleene logic for `any(skipna=False)`** (`ColumnBase.any`): with nulls present it returned `True` unconditionally. For pandas nullable extension dtypes, a no-True result with nulls present is now `<NA>` (matching `all`); numpy dtypes keep the NaN-sentinel-truthy behavior.
- **`pd.NA` guard before `np.isnan`** in `any`/`all` to avoid "boolean value of NA is ambiguous" now that `_reduce` can return `pd.NA`.
- **`Series.mode(dropna=False)` null position**: pandas sorts mode results on the underlying representation, so NaT (`INT64_MIN` as i8) and the categorical null code (-1) sort *first* while float NaN and arrow/nullable `<NA>` sort *last*. cudf sorted nulls last everywhere. Nulls-first now applies to numpy datetime/timedelta, `DatetimeTZDtype`, and categorical dtypes only (arrow timestamp/duration keep nulls last, verified against pandas).
- **`StringColumn.all()`**: no longer short-circuits `True` for *partially*-null columns with `skipna=False` — the result depends on the truthiness of the non-null strings (e.g. `all([NaN, ""], skipna=False)` is `False`); it now falls through so `cudf.pandas` computes the correct result. All-null columns still return `True`.
- `test_timedelta_reductions` updated to assert `pd.NaT` identity like `test_datetime_reductions` already does.

### Pandas-testing plugin

- Removed **50** now-passing xfail entries.
- Replaced the "TODO" reasons on the 4 remaining `test_reductions.py` entries with real ones: `test_sum_overflow_float[float32-*]` (GPU tree-reduction accumulates float32 in a different order than numpy pairwise summation) and `test_any_all_object_dtype_missing[any-data0/1]` (None-vs-np.nan distinction is lost when object data becomes a nulled bool column).

### Testing

- `tests/reductions/` in CI mode: 498 passed, 10 xfailed, no strict-XPASS.
- Full pandas-tests suite in CI mode (206k tests): no failures attributable to this change; every removed xfail entry verified as strict-XPASS solo (not just under xdist, to rule out GPU-contention fallback).
- Classic cudf sweep (series/dataframe/indexes/groupby/reshape/text/dtypes/general_functions/window, ~71k tests): failure set identical to unmodified baseline (the only failures are pre-existing groupby-JIT ones).
- Raw (plugin-less) before/after comparison over all affected pandas-test files confirmed 11 newly-passing tests and no newly-failing ones. The `decimal128` stack/unstack failures that appeared in some xdist runs reproduce identically on unmodified cudf (order-dependent, pre-existing).

Authors:
  - GALI PREM SAGAR (https://github.com/galipremsagar)

Approvers:
  - Matthew Roeschke (https://github.com/mroeschke)

URL: NVIDIA#23328
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 - Ready to Merge Testing and reviews complete, ready to merge bug Something isn't working cudf.pandas Issues specific to cudf.pandas non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants