Skip to content

Stop mutating the source column dtype in equivalent-type numerical casts - #23364

Merged
rapids-bot[bot] merged 1 commit into
NVIDIA:release/26.08from
galipremsagar:numerical-cast-no-dtype-mutation
Jul 21, 2026
Merged

Stop mutating the source column dtype in equivalent-type numerical casts#23364
rapids-bot[bot] merged 1 commit into
NVIDIA:release/26.08from
galipremsagar:numerical-cast-no-dtype-mutation

Conversation

@galipremsagar

Copy link
Copy Markdown
Contributor

Description

Split out of #23255 (1/6).

NumericalColumn.as_numerical_column short-circuits casts between equivalent dtypes (same pylibcudf type, e.g. float64Float64), but implemented the shortcut by assigning the target dtype onto self._dtype in place. The column object is shared with the caller's Series/DataFrame, so the source object silently changed dtype as a side effect of the cast. This returns a fresh column over the same pylibcudf data instead (nans_to_nulls first for float → masked casts), and adds a classic regression test.

Fixes 5 pandas-tests (test_stack_nullable_dtype[*], test_loc_set_nan_in_categorical_series[Float64], test_assert_series_equal_extension_dtype_mismatch, test_assert_frame_equal_extension_dtype_mismatch); their xfail entries are removed. Attribution verified by running the node ids against an isolated build containing only this fix (they pass) and a clean build (they fail).

Independent of the other #23255 split PRs; can merge in any order.

Checklist

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

NumericalColumn.as_numerical_column short-circuits casts between
equivalent dtypes (same pylibcudf type, e.g. float64 -> Float64), but did
so by assigning the target dtype onto self._dtype in place. The column
object is shared with the caller's Series/DataFrame, so the source frame
silently changed dtype as a side effect of the cast. Return a fresh
column over the same pylibcudf data instead (nans_to_nulls first for
float -> masked casts).

Fixes 5 pandas-tests (stack on nullable dtypes, loc setitem with
Float64, assert_series/frame_equal extension-dtype mismatch); their
xfail entries are removed.
@galipremsagar
galipremsagar requested a review from a team as a code owner July 21, 2026 13:53
@galipremsagar
galipremsagar requested review from rjzamora and vyasr July 21, 2026 13:53
@copy-pr-bot

copy-pr-bot Bot commented Jul 21, 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 21, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Jul 21, 2026
@galipremsagar galipremsagar added bug Something isn't working 3 - Ready for Review Ready for review by team non-breaking Non-breaking change labels Jul 21, 2026
@galipremsagar

Copy link
Copy Markdown
Contributor Author

/okay to test 20f09d4

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Fixed equivalent-dtype conversions so they no longer alter the original Series.
    • Improved conversion from floating-point data to nullable dtypes by treating NaN values as nulls.
    • Ensured casting results remain consistent with pandas behavior.
  • Tests

    • Added regression coverage for nullable dtype conversions and source Series preservation.
    • Updated known test failure mappings for indexing, stacking, and equality scenarios.

Walkthrough

Changes

Nullable dtype casting

Layer / File(s) Summary
Equivalent dtype cast behavior
python/cudf/cudf/core/column/numerical.py, python/cudf/cudf/tests/series/methods/test_astype.py
Equivalent casts no longer mutate the source dtype, convert NaNs to nulls for nullable float targets, and test results against pandas.

Pandas failure mappings

Layer / File(s) Summary
Refresh pandas compatibility failure mappings
python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
Failure mappings are updated for stack/unstack, indexing, and utility assertion cases.

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

Possibly related PRs

  • rapidsai/cudf#23255: Applies the same source-dtype mutation fix in NumericalColumn.as_numerical_column.

Suggested labels: bug, non-breaking

Suggested reviewers: vyasr, rjzamora, mroeschke

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: preventing source dtype mutation during equivalent numerical casts.
Description check ✅ Passed The description is directly related and accurately describes the dtype mutation fix, tests, and xfail updates.
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.

Actionable comments posted: 2

🤖 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/tests/series/methods/test_astype.py`:
- Around line 1631-1636: Extend the parameterization for the Float64Dtype case
in test_astype.py to cover empty, all-null, single-element, and mixed numeric/NA
float inputs, preserving the expected source dtype and nullable masked dtype for
each case. Keep the existing integer case and current float case intact while
ensuring these additions exercise both shared-buffer and NaN-to-null conversion
paths.
- Around line 1638-1645: Update
test_astype_masked_equivalent_dtype_no_source_mutation to wrap Series
construction and the astype call in
cudf.option_context("mode.pandas_compatible", True), ensuring the test exercises
the equivalent-dtype short-circuit path.
🪄 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: fbb90d16-858e-45ba-8599-dbc058631c3e

📥 Commits

Reviewing files that changed from the base of the PR and between 738fb74 and 20f09d4.

📒 Files selected for processing (3)
  • python/cudf/cudf/core/column/numerical.py
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
  • python/cudf/cudf/tests/series/methods/test_astype.py
💤 Files with no reviewable changes (1)
  • python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py

Comment on lines +1631 to +1636
@pytest.mark.parametrize(
"data, src_dtype, masked_dtype",
[
([1.0, 2.0, float("nan")], "float64", pd.Float64Dtype()),
([1, 2, 3], "int64", pd.Int64Dtype()),
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add nullable-cast boundary cases.

Include empty, all-null, single-element, and mixed numeric/NA float inputs for the Float64Dtype case so the shared-buffer and NaN-to-null paths remain covered. As per coding guidelines, test files must provide comprehensive edge case coverage for “empty, all-null, single-element, mixed types.”

🤖 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/series/methods/test_astype.py` around lines 1631 -
1636, Extend the parameterization for the Float64Dtype case in test_astype.py to
cover empty, all-null, single-element, and mixed numeric/NA float inputs,
preserving the expected source dtype and nullable masked dtype for each case.
Keep the existing integer case and current float case intact while ensuring
these additions exercise both shared-buffer and NaN-to-null conversion paths.

Source: Coding guidelines

Comment on lines +1638 to +1645
def test_astype_masked_equivalent_dtype_no_source_mutation(
data, src_dtype, masked_dtype
):
# casting to the equivalent masked dtype takes a short-circuit path;
# it must not mutate the source column's dtype in place (the column
# is shared with the source Series/frame)
ser = cudf.Series(data, dtype=src_dtype)
result = ser.astype(masked_dtype)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

python3 - <<'PY'
from pathlib import Path

paths = [
    Path("python/cudf/cudf/tests/series/methods/test_astype.py"),
    Path("python/cudf/cudf/core/column/numerical.py"),
]
for p in paths:
    print(f"\n=== {p} ===")
    if p.exists():
        text = p.read_text().splitlines()
        for i, line in enumerate(text, 1):
            if p.name == "test_astype.py" and 1618 <= i <= 1660:
                print(f"{i:4d}: {line}")
            if p.name == "numerical.py" and 1 <= i <= 260:
                # print only around as_numerical_column by search later
                pass
    else:
        print("missing")
PY

Repository: rapidsai/cudf

Length of output: 1594


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
import re

# Find as_numerical_column definition and nearby lines.
p = Path("python/cudf/cudf/core/column/numerical.py")
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if "def as_numerical_column" in line:
        start = max(1, i - 20)
        end = min(len(text), i + 120)
        print(f"\n=== {p} lines {start}-{end} ===")
        for j in range(start, end + 1):
            print(f"{j:4d}: {text[j-1]}")
        break
PY

Repository: rapidsai/cudf

Length of output: 7290


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path

p = Path("python/cudf/cudf/tests/series/methods/test_astype.py")
text = p.read_text().splitlines()
for i, line in enumerate(text, 1):
    if 1618 <= i <= 1660:
        print(f"{i:4d}: {line}")
PY

Repository: rapidsai/cudf

Length of output: 1481


🏁 Script executed:

sed -n '1628,1656p' python/cudf/cudf/tests/series/methods/test_astype.py

Repository: rapidsai/cudf

Length of output: 952


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
import re

p = Path("python/cudf/cudf/tests/series/methods/test_astype.py")
text = p.read_text().splitlines()

# Show imports and any option_context / pandas_compatible usage in the file.
for i, line in enumerate(text, 1):
    if i <= 80:
        print(f"{i:4d}: {line}")
print("\n--- matches ---")
for i, line in enumerate(text, 1):
    if "pandas_compatible" in line or "option_context" in line or "pytestmark" in line:
        print(f"{i:4d}: {line}")
PY

Repository: rapidsai/cudf

Length of output: 4224


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path

# Search nearby test infrastructure for automatic pandas-compatible mode setup.
for path in [
    Path("python/cudf/cudf/tests/conftest.py"),
    Path("python/cudf/cudf/tests/series/methods/conftest.py"),
]:
    print(f"\n=== {path} ===")
    if path.exists():
        text = path.read_text().splitlines()
        for i, line in enumerate(text, 1):
            if "pandas_compatible" in line or "option_context" in line or "autouse" in line:
                start = max(1, i - 5)
                end = min(len(text), i + 10)
                for j in range(start, end + 1):
                    print(f"{j:4d}: {text[j-1]}")
                print("---")
    else:
        print("missing")
PY

Repository: rapidsai/cudf

Length of output: 3679


🏁 Script executed:

rg -n "pandas_compatible|option_context|autouse" python/cudf/cudf/tests python/cudf/cudf/core -g '!**/__pycache__/**'

Repository: rapidsai/cudf

Length of output: 30266


Enable pandas-compatible mode for this case. Wrap the setup and astype call in cudf.option_context("mode.pandas_compatible", True) so the test exercises the equivalent-dtype short-circuit instead of the fallback cast path.

🤖 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/series/methods/test_astype.py` around lines 1638 -
1645, Update test_astype_masked_equivalent_dtype_no_source_mutation to wrap
Series construction and the astype call in
cudf.option_context("mode.pandas_compatible", True), ensuring the test exercises
the equivalent-dtype short-circuit path.

@vyasr

vyasr commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

/merge

@rapids-bot
rapids-bot Bot merged commit edbf8cf into NVIDIA:release/26.08 Jul 21, 2026
238 of 242 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
…sts (NVIDIA#23364)

Split out of NVIDIA#23255 (1/6).

`NumericalColumn.as_numerical_column` short-circuits casts between equivalent dtypes (same pylibcudf type, e.g. `float64` → `Float64`), but implemented the shortcut by assigning the target dtype onto `self._dtype` in place. The column object is shared with the caller's Series/DataFrame, so the *source* object silently changed dtype as a side effect of the cast. This returns a fresh column over the same pylibcudf data instead (`nans_to_nulls` first for float → masked casts), and adds a classic regression test.

Fixes 5 pandas-tests (`test_stack_nullable_dtype[*]`, `test_loc_set_nan_in_categorical_series[Float64]`, `test_assert_series_equal_extension_dtype_mismatch`, `test_assert_frame_equal_extension_dtype_mismatch`); their xfail entries are removed. Attribution verified by running the node ids against an isolated build containing only this fix (they pass) and a clean build (they fail).

Independent of the other NVIDIA#23255 split PRs; can merge in any order.

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

Approvers:
  - Vyas Ramasubramani (https://github.com/vyasr)

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

Labels

3 - Ready for Review Ready for review by team 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