Skip to content

Fix various pandas issues - #22705

Merged
rapids-bot[bot] merged 14 commits into
NVIDIA:mainfrom
vyasr:fix/pandas-misc-compat
Jun 1, 2026
Merged

Fix various pandas issues#22705
rapids-bot[bot] merged 14 commits into
NVIDIA:mainfrom
vyasr:fix/pandas-misc-compat

Conversation

@vyasr

@vyasr vyasr commented May 29, 2026

Copy link
Copy Markdown
Contributor

Description

These were fixes created using #22625.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

vyasr added 10 commits May 29, 2026 00:23
GroupBy._grouped passed internal storage keys (self.obj._index_names)
to _from_columns_like_self, which for a nameless MultiIndex produced
[0, 1] instead of the correct public names [None, None]. This caused
GroupBy.apply to return results with wrong index level names, making
the cudf.pandas accelerated test_cython_transform_frame_column fail
when comparing apply() expected values against transform() results.

Use self.obj.index.names (public metadata) instead.

Fixes the following failing pandas tests:
- tests/groupby/transform/test_transform.py::test_cython_transform_frame_column[*-frame_mi-*]
These tests assert that pandas raises NotImplementedError when using
engine='numba' with as_index=False for groupby reductions. cuDF computes
reductions on GPU without numba, so the pandas-specific limitation does
not apply and the operation succeeds. The divergence is intentional.

Skips the following pandas tests:
- tests/groupby/test_numba.py::TestEngine::test_as_index_false_unsupported[*]
@vyasr vyasr self-assigned this May 29, 2026
@vyasr
vyasr requested a review from a team as a code owner May 29, 2026 01:17
@vyasr
vyasr requested review from galipremsagar and mroeschke May 29, 2026 01:17
@vyasr vyasr added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels May 29, 2026
@github-actions github-actions Bot added Python Affects Python cuDF API. cudf.pandas Issues specific to cudf.pandas labels May 29, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python May 29, 2026
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 0f1d1d61-6ac5-4f01-87c8-f4595de1d188

📥 Commits

Reviewing files that changed from the base of the PR and between e642498 and c9d1606.

📒 Files selected for processing (7)
  • ci/test_narwhals.sh
  • conda/environments/all_cuda-129_arch-aarch64.yaml
  • conda/environments/all_cuda-129_arch-x86_64.yaml
  • conda/environments/all_cuda-132_arch-aarch64.yaml
  • conda/environments/all_cuda-132_arch-x86_64.yaml
  • dependencies.yaml
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
✅ Files skipped from review due to trivial changes (2)
  • conda/environments/all_cuda-129_arch-aarch64.yaml
  • conda/environments/all_cuda-132_arch-aarch64.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Fixed interval equality/inequality semantics (including null handling) and preserved MultiIndex names for GroupBy.apply.
  • Improvements

    • Improved Series dtype reporting for better pandas compatibility.
  • Tests

    • Added interval equality and GroupBy regression tests to ensure correct behavior.
  • CI/Tooling

    • Updated pandas test plugin expectations and adjusted test exclusions; added numexpr to test environments.

Walkthrough

Adds IntervalColumn equality/inequality support with correct null semantics, switches GroupBy to use public index names, refines Series.dtype wrapping for certain dtypes and adds an intermediate proxy type, updates pandas-testing expected-fail mappings, adjusts Narwhals CI exclusions, and adds numexpr to Conda/dependency manifests.

Changes

Interval equality operators and cuDF/pandas compatibility fixes

Layer / File(s) Summary
Interval equality operators with null semantics
python/cudf/cudf/core/column/interval.py, python/cudf/cudf/tests/indexes/test_interval.py
IntervalColumn._VALID_BINARY_OPERATIONS declares __eq__ and __ne__. _binaryop computes interval equality by AND-ing endpoint-wise NULL_EQUALS comparisons and inverts for not-equal operations; tests added for Series/Index equality, null semantics, and closed-boundary behavior.
GroupBy index names using public property
python/cudf/cudf/core/groupby/groupby.py, python/cudf/cudf/tests/groupby/test_apply.py
GroupBy._grouped uses self.obj.index.names instead of the private _index_names; regression test ensures MultiIndex name preservation for groupby.apply with group_keys=False.
Series dtype accessor and proxy types
python/cudf/cudf/pandas/_wrappers/pandas.py
_Series_dtype uses the slow proxy dtype for list/struct and decimal32/64/128 before wrapping the result; adds OnlineExponentialMovingWindow intermediate proxy type mapped to pandas' ewm OnlineExponentialMovingWindow.
Test plugin expected failure updates
python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
Refines expected-failure/xfail mappings and reasons across arithmetic, interval, numpy extension, numpy-compat ufuncs, and groupby/numba engine cases.
Narwhals CI exclusions and comments
ci/test_narwhals.sh
Adds explanatory comment and excludes test_self_equal[pandas] alongside existing exclusions for pandas-backed Narwhals runs.
Conda envs and dependency manifest
conda/environments/*, dependencies.yaml
Adds numexpr to multiple Conda environment files and creates test_cudf_pandas_pandas_tests dependency group (with numexpr) and includes it in files.all.includes in dependencies.yaml.

🎯 3 (Moderate) | ⏱️ ~20 minutes


Suggested reviewers

  • galipremsagar
  • KyleFromNVIDIA
  • TomAugspurger
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix various pandas issues' is vague and does not clearly convey the specific nature of the changes, using generic terminology that could apply to any pandas-related fix. Consider a more specific title that highlights the primary changes, such as 'Fix pandas interval equality ops and dtype handling' or 'Add numexpr to test environments and fix interval/groupby operations'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description is related to the changeset, referencing the parent issue #22625 and confirming that tests and documentation were updated, though it lacks specific details about individual changes.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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

Actionable comments posted: 3

🤖 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.

Inline comments:
In `@python/cudf/cudf/core/column/interval.py`:
- Around line 169-180: The equality branch in the interval column binary op
(method handling op values "__eq__", "__ne__", "NULL_EQUALS", "NULL_NOT_EQUALS")
currently compares only endpoints (self.left/right vs other.left/right) and
ignores the interval `closed` attribute; update the logic in that branch (the
block using self.left._binaryop, self.right._binaryop, and binaryop.binaryop) to
include a `closed` equality check—either short-circuit to a
False/NULL-equivalent when self.closed != other.closed for equality ops, or
incorporate a `closed_equal` predicate and AND it with the existing
endpoint-equality `result` (and invert for "__ne__"/"NULL_NOT_EQUALS"); also add
a regression test asserting intervals with identical bounds but differing
`closed` values are not equal.

In `@python/cudf/cudf/pandas/_wrappers/pandas.py`:
- Around line 337-350: Series.dtype currently reads self._fsproxy_slow.dtype in
a way that can mutate the proxy and pin it to slow mode; update _Series_dtype to
detect the complex dtypes (cudf.ListDtype, cudf.StructDtype, Decimal*Dtypes) and
then obtain the dtype from a non-mutating accessor so you don't swap
_fsproxy_wrapped to slow. Concretely, inside _Series_dtype use a
read-only/metadata-only call (or an existing non-mutating helper) to peek the
dtype from the slow-side representation (referencing _fsproxy_slow) without
assigning or replacing _fsproxy_wrapped, and return that value via
_maybe_wrap_result; do not perform any assignment to _fsproxy_wrapped or other
state changes.

In `@python/cudf/cudf/tests/groupby/test_apply.py`:
- Around line 857-873: The test
test_groupby_apply_series_preserves_multiindex_names should explicitly set
MultiIndex level names on pdf (e.g., use MultiIndex.from_product(...,
names=[...]) rather than anonymous levels) so the test verifies public level
names are preserved; update the pdf creation (and gdf from_pandas) to use named
levels and add an assertion that the resulting indexes' .names on expect and got
are equal (or ensure assert_eq compares index names) after the groupby apply,
referencing pdf, gdf, expect, and got to locate the changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 670005e7-cb14-4c0b-9264-604d055bc9ce

📥 Commits

Reviewing files that changed from the base of the PR and between ad987ea and e642498.

📒 Files selected for processing (6)
  • python/cudf/cudf/core/column/interval.py
  • python/cudf/cudf/core/groupby/groupby.py
  • python/cudf/cudf/pandas/_wrappers/pandas.py
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
  • python/cudf/cudf/tests/groupby/test_apply.py
  • python/cudf/cudf/tests/indexes/test_interval.py

Comment on lines +169 to +180
if op in {"__eq__", "__ne__", "NULL_EQUALS", "NULL_NOT_EQUALS"}:
lefts_equal = self.left._binaryop(other.left, "NULL_EQUALS")
rights_equal = self.right._binaryop(other.right, "NULL_EQUALS")
return binaryop.binaryop(
result = binaryop.binaryop(
lefts_equal,
rights_equal,
"__and__",
get_dtype_of_same_kind(self.dtype, lefts_equal.dtype),
)
if op in {"__ne__", "NULL_NOT_EQUALS"}:
result = ~result
return result

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Equality logic ignores interval closed semantics.

__eq__/__ne__ currently compare only endpoints, so intervals with identical bounds but different closed values can be misclassified as equal. Please include self.closed == other.closed in the equality predicate (or short-circuit unequal-closed before endpoint comparison), and add a regression test for comparing same breaks with different closed settings.

As per coding guidelines: "Validate algorithm correctness - detect logic errors producing wrong results, silent data corruption from type coercion, and incorrect null/NA handling (cuDF uses nullable dtypes throughout)".

🤖 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/interval.py` around lines 169 - 180, The
equality branch in the interval column binary op (method handling op values
"__eq__", "__ne__", "NULL_EQUALS", "NULL_NOT_EQUALS") currently compares only
endpoints (self.left/right vs other.left/right) and ignores the interval
`closed` attribute; update the logic in that branch (the block using
self.left._binaryop, self.right._binaryop, and binaryop.binaryop) to include a
`closed` equality check—either short-circuit to a False/NULL-equivalent when
self.closed != other.closed for equality ops, or incorporate a `closed_equal`
predicate and AND it with the existing endpoint-equality `result` (and invert
for "__ne__"/"NULL_NOT_EQUALS"); also add a regression test asserting intervals
with identical bounds but differing `closed` values are not equal.

Comment on lines 337 to +350
def _Series_dtype(self):
# Fast-path to extract dtype from the current
# object without round-tripping through the slow<->fast
return _maybe_wrap_result(self._fsproxy_wrapped.dtype, None)
dtype = self._fsproxy_wrapped.dtype
if isinstance(
dtype,
(
cudf.ListDtype,
cudf.StructDtype,
cudf.Decimal32Dtype,
cudf.Decimal64Dtype,
cudf.Decimal128Dtype,
),
):
dtype = self._fsproxy_slow.dtype
return _maybe_wrap_result(dtype, None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid mutating proxy state in Series.dtype slow fallback.

At Line 349, self._fsproxy_slow.dtype mutates _fsproxy_wrapped to slow, so a dtype read can pin the proxy to slow mode and introduce avoidable fast→slow transfers on subsequent paths.

Suggested fix
 def _Series_dtype(self):
     dtype = self._fsproxy_wrapped.dtype
     if isinstance(
         dtype,
         (
             cudf.ListDtype,
             cudf.StructDtype,
             cudf.Decimal32Dtype,
             cudf.Decimal64Dtype,
             cudf.Decimal128Dtype,
         ),
     ):
-        dtype = self._fsproxy_slow.dtype
+        dtype = self._fsproxy_fast_to_slow().dtype
     return _maybe_wrap_result(dtype, None)

As per coding guidelines: "Detect unnecessary host-device data transfers and repeated GPU-to-host round-trips in hot paths".

🤖 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/pandas/_wrappers/pandas.py` around lines 337 - 350,
Series.dtype currently reads self._fsproxy_slow.dtype in a way that can mutate
the proxy and pin it to slow mode; update _Series_dtype to detect the complex
dtypes (cudf.ListDtype, cudf.StructDtype, Decimal*Dtypes) and then obtain the
dtype from a non-mutating accessor so you don't swap _fsproxy_wrapped to slow.
Concretely, inside _Series_dtype use a read-only/metadata-only call (or an
existing non-mutating helper) to peek the dtype from the slow-side
representation (referencing _fsproxy_slow) without assigning or replacing
_fsproxy_wrapped, and return that value via _maybe_wrap_result; do not perform
any assignment to _fsproxy_wrapped or other state changes.

Comment on lines +857 to +873
def test_groupby_apply_series_preserves_multiindex_names():
pdf = pd.DataFrame(
{"value": [1.0, 2.0, 3.0, 4.0]},
index=pd.MultiIndex.from_product([range(2), range(2)]),
)
gdf = cudf.from_pandas(pdf)
by = np.array([0, 0, 1, 1])

expect = pdf.groupby(by=by, group_keys=False)["value"].apply(
lambda x: x.cumprod()
)
got = gdf.groupby(by=by, group_keys=False)["value"].apply(
lambda x: x.cumprod()
)

assert_eq(expect, got)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Strengthen this regression by using explicitly named MultiIndex levels.

Right now the test can pass without proving that public MultiIndex names are preserved. Set concrete index level names to assert the intended contract directly.

Proposed test tightening
 def test_groupby_apply_series_preserves_multiindex_names():
     pdf = pd.DataFrame(
         {"value": [1.0, 2.0, 3.0, 4.0]},
-        index=pd.MultiIndex.from_product([range(2), range(2)]),
+        index=pd.MultiIndex.from_product(
+            [range(2), range(2)],
+            names=["outer", "inner"],
+        ),
     )

As per coding guidelines: "python/**/test_*.py: Ensure test files provide comprehensive edge case coverage."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_groupby_apply_series_preserves_multiindex_names():
pdf = pd.DataFrame(
{"value": [1.0, 2.0, 3.0, 4.0]},
index=pd.MultiIndex.from_product([range(2), range(2)]),
)
gdf = cudf.from_pandas(pdf)
by = np.array([0, 0, 1, 1])
expect = pdf.groupby(by=by, group_keys=False)["value"].apply(
lambda x: x.cumprod()
)
got = gdf.groupby(by=by, group_keys=False)["value"].apply(
lambda x: x.cumprod()
)
assert_eq(expect, got)
def test_groupby_apply_series_preserves_multiindex_names():
pdf = pd.DataFrame(
{"value": [1.0, 2.0, 3.0, 4.0]},
index=pd.MultiIndex.from_product(
[range(2), range(2)],
names=["outer", "inner"],
),
)
gdf = cudf.from_pandas(pdf)
by = np.array([0, 0, 1, 1])
expect = pdf.groupby(by=by, group_keys=False)["value"].apply(
lambda x: x.cumprod()
)
got = gdf.groupby(by=by, group_keys=False)["value"].apply(
lambda x: x.cumprod()
)
assert_eq(expect, got)
🤖 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/tests/groupby/test_apply.py` around lines 857 - 873, The
test test_groupby_apply_series_preserves_multiindex_names should explicitly set
MultiIndex level names on pdf (e.g., use MultiIndex.from_product(...,
names=[...]) rather than anonymous levels) so the test verifies public level
names are preserved; update the pdf creation (and gdf from_pandas) to use named
levels and add an assertion that the resulting indexes' .names on expect and got
are equal (or ensure assert_eq compares index names) after the groupby apply,
referencing pdf, gdf, expect, and got to locate the changes.

vyasr added 2 commits May 29, 2026 23:45
…lf_equal

Re-add two pandas test xfails that were removed before the underlying
fixes landed:
- test_ser_div_ser[numexpr-float32-int64]: cudf promotes float32/int64
  division to float64
- test_numpy_ufuncs_basic[complex64-log1p]: GPU complex64 precision
  differs from CPU

Skip narwhals test_self_equal[pandas] in cudf.pandas CI: under the
proxy, narwhals identifies DataFrames as Implementation.PANDAS and
relies on str(series.dtype) starting with 'list'/'struct'/'decimal' to
detect cudf nested types. Since _Series_dtype correctly returns
pandas-compatible 'object', narwhals falls through to a generic
left != right comparison that is undefined for nested lists with NaN.
The pandas test suite uses numexpr (via pandas[performance]) which
changes dtype promotion behavior for float32/int64 division. Conda
environments don't use pip extras, so numexpr must be listed explicitly.

Create test_cudf_pandas_pandas_tests group (output_types: [conda]) and
add it to the 'all' includes so devcontainer environments match CI.
@vyasr
vyasr requested review from a team as code owners May 30, 2026 00:35
@vyasr
vyasr requested a review from KyleFromNVIDIA May 30, 2026 00:35
@vyasr vyasr changed the title Fix various pandas issues Fix various pandas issues May 30, 2026
@vyasr vyasr changed the title Fix various pandas issues Fix various pandas issues May 30, 2026
@jameslamb
jameslamb removed the request for review from KyleFromNVIDIA June 1, 2026 15:42
@vyasr

vyasr commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

/merge

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

Labels

cudf.pandas Issues specific to cudf.pandas improvement Improvement / enhancement to an existing function non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants