Skip to content

Fix to_numpy object null handling - #22621

Merged
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
aryansri05:fix-to-numpy-object-none
Jun 2, 2026
Merged

rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
aryansri05:fix-to-numpy-object-none

Conversation

@aryansri05

@aryansri05 aryansri05 commented May 21, 2026

Copy link
Copy Markdown
Contributor

Description

Closes #22419.

This PR fixes to_numpy(dtype=object) for string/object data containing nulls.

Previously, when dtype=object was explicitly requested, cuDF could return pandas.NA/cudf.NA for null string values, while pandas returns None. The conversion path already boxes values correctly for object dtype, but then overwrote string nulls with cudf.NA.

This change skips that null overwrite for explicit NumPy object dtype so nulls remain None, matching pandas behavior. Explicit na_value= is still honored.

A regression test was added for both DataFrame.to_numpy(dtype=object) and Series.to_numpy(dtype=object), including the default null behavior and explicit na_value.

Checklist

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

Validation

Local checks run:

PYTHONPYCACHEPREFIX=/tmp/cudf_pycache python3.11 -m py_compile \
  python/cudf/cudf/core/frame.py \
  python/cudf/cudf/tests/dataframe/methods/test_to_cupy.py

git diff --check

Could not run targeted pytest locally; this requires RAPIDS CI or a RAPIDS devcontainer.

@aryansri05
aryansri05 requested a review from a team as a code owner May 21, 2026 13:49
@aryansri05
aryansri05 requested review from galipremsagar and vyasr May 21, 2026 13:49
@copy-pr-bot

copy-pr-bot Bot commented May 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 the Python Affects Python cuDF API. label May 21, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 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

Adds an internal is_numpy_object_dtype helper and refines Frame._to_array null-filling so converting to NumPy object dtype preserves Python None when no explicit na_value is provided; adds tests validating preservation and na_value replacement for DataFrame and Series.

Changes

Object dtype null preservation

Layer / File(s) Summary
Helper and conditional refinement
python/cudf/cudf/core/frame.py
Adds is_numpy_object_dtype helper that detects NumPy object dtype and updates Frame._to_array to skip null replacement for object dtype unless na_value is explicitly provided.
Test validating object-dtype behavior
python/cudf/cudf/tests/dataframe/methods/test_to_cupy.py
Parametrized test test_to_numpy_object_dtype_preserves_none_string_nulls confirms to_numpy(dtype=object) preserves None and that na_value="missing" replaces nulls, for both DataFrame and Series.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • rapidsai/cudf#22485: Modifies Frame._to_array dtype/object-specific handling for to_numpy/to_cupy, adding casting logic for non-object targets while avoiding in-cudf cast for NumPy object.

Suggested reviewers

  • vyasr
  • TomAugspurger
🚥 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 'Fix to_numpy object null handling' is concise, clear, and accurately describes the main change: fixing null handling behavior in the to_numpy method for object dtype.
Description check ✅ Passed The description clearly explains the bug, the fix, and includes details about test coverage and validation. It directly relates to the code changes and linked issue.
Linked Issues check ✅ Passed The PR successfully addresses all coding objectives from issue #22419: preserving None for null entries in object dtype arrays, avoiding cudf.NA overwrites, honoring explicit na_value parameters, and adding comprehensive regression tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue: the helper function and null-handling adjustment in Frame._to_array are targeted fixes, and the test addition directly validates the bug fix.

✏️ 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.

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/frame.py (1)

645-791: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add a regression benchmark for the new object-dtype null path in Frame._to_array.

This behavior change is in a frequently used conversion path; please add a unit benchmark to guard against regressions for to_numpy(dtype=object) with null string/object data (with and without explicit na_value).

As per coding guidelines, "**/*.{cpp,cu,py,pyx}: Add unit tests and unit benchmarks for code contributions".

🤖 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/frame.py` around lines 645 - 791, Add a regression
benchmark and unit test exercising the new object-dtype null path in
Frame._to_array (used by to_numpy/to_cupy) for conversions to dtype=object:
create tests that call DataFrame/Series.to_numpy(dtype=object) and
DataFrame/Series._to_array via to_numpy equivalence with string/object columns
containing nulls, covering both with and without an explicit na_value, and
assert performance/regression expectations and correctness of null boxing
(cudf.NA or provided na_value). Place the benchmark alongside existing Python
microbenchmarks (matching the repo pattern **/*.{py}) and add a unit test
ensuring behavior (including the branch where is_numpy_object_dtype and the
null-handling assignment code paths run), referencing Frame._to_array, to_array,
and the to_numpy entrypoint so CI will catch regressions.
🧹 Nitpick comments (1)
python/cudf/cudf/tests/dataframe/methods/test_to_cupy.py (1)

200-218: ⚡ Quick win

Broaden edge-case coverage for object-dtype null handling tests.

Please add cases for empty input, all-null input, and single-element input for both DataFrame and Series so the new dtype=object null behavior is fully pinned down.

As per coding guidelines, "python/**/test_*.py: Ensure test files provide comprehensive edge case coverage (empty, all-null, single-element, mixed types) and do not depend on external datasets".

🤖 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/dataframe/methods/test_to_cupy.py` around lines 200 -
218, Expand the test function
test_to_numpy_object_dtype_preserves_none_string_nulls to include edge-case
inputs (empty, all-null, and single-element) for both constructors ("DataFrame"
and "Series"): create pd_obj variants for each case, convert to cudf_obj via
getattr(cudf, constructor)(pd_obj), then run the same assertions
(dtype==np.dtype("O"), np.testing.assert_array_equal between
cudf_obj.to_numpy(...) and pd_obj.to_numpy(...), and checks for None or provided
na_value) for both dtype=object and dtype=object with na_value="missing" so
empty arrays, all-null arrays, and single-element arrays are covered for both
DataFrame and Series.
🤖 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/frame.py`:
- Around line 645-791: Add a regression benchmark and unit test exercising the
new object-dtype null path in Frame._to_array (used by to_numpy/to_cupy) for
conversions to dtype=object: create tests that call
DataFrame/Series.to_numpy(dtype=object) and DataFrame/Series._to_array via
to_numpy equivalence with string/object columns containing nulls, covering both
with and without an explicit na_value, and assert performance/regression
expectations and correctness of null boxing (cudf.NA or provided na_value).
Place the benchmark alongside existing Python microbenchmarks (matching the repo
pattern **/*.{py}) and add a unit test ensuring behavior (including the branch
where is_numpy_object_dtype and the null-handling assignment code paths run),
referencing Frame._to_array, to_array, and the to_numpy entrypoint so CI will
catch regressions.

---

Nitpick comments:
In `@python/cudf/cudf/tests/dataframe/methods/test_to_cupy.py`:
- Around line 200-218: Expand the test function
test_to_numpy_object_dtype_preserves_none_string_nulls to include edge-case
inputs (empty, all-null, and single-element) for both constructors ("DataFrame"
and "Series"): create pd_obj variants for each case, convert to cudf_obj via
getattr(cudf, constructor)(pd_obj), then run the same assertions
(dtype==np.dtype("O"), np.testing.assert_array_equal between
cudf_obj.to_numpy(...) and pd_obj.to_numpy(...), and checks for None or provided
na_value) for both dtype=object and dtype=object with na_value="missing" so
empty arrays, all-null arrays, and single-element arrays are covered for both
DataFrame and Series.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: a6f2063c-a988-427c-bff3-4227aacd760f

📥 Commits

Reviewing files that changed from the base of the PR and between 3938de6 and 430afe4.

📒 Files selected for processing (2)
  • python/cudf/cudf/core/frame.py
  • python/cudf/cudf/tests/dataframe/methods/test_to_cupy.py

@aryansri05
aryansri05 force-pushed the fix-to-numpy-object-none branch from 430afe4 to 3692539 Compare May 21, 2026 13:55

@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: 1

🤖 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/frame.py`:
- Around line 656-660: Add a regression benchmark that measures the hot
conversion path exercised by Frame._to_array / DataFrame.to_numpy(dtype=object)
when nulls are present: create a pytest-benchmark (or project-standard
benchmarking harness) test that constructs frames with nulls and calls
to_numpy(dtype=object) twice—once using the default na_value and once passing an
explicit na_value—to capture performance for both paths; name the test clearly
(e.g., bench_to_numpy_object_with_nulls) and place it with other Python
benchmarks so future changes to is_numpy_object_dtype / Frame._to_array will be
covered.
🪄 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: 8fdd0d42-4716-4c0f-b36d-3edba2c662ca

📥 Commits

Reviewing files that changed from the base of the PR and between 430afe4 and 3692539.

📒 Files selected for processing (2)
  • python/cudf/cudf/core/frame.py
  • python/cudf/cudf/tests/dataframe/methods/test_to_cupy.py

Comment on lines +656 to +660
def is_numpy_object_dtype(dtype: Dtype | None) -> bool:
try:
return np.dtype(dtype) == np.dtype("O")
except TypeError:
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Add a regression benchmark for the updated object-dtype null path.

This path changes core conversion behavior in Frame._to_array; please add a unit benchmark that covers to_numpy(dtype=object) with nulls (default na_value and explicit na_value) to guard against performance regressions on this hot conversion route.

As per coding guidelines **/*.{cpp,cu,py,pyx}: Add unit tests and unit benchmarks for code contributions.

Also applies to: 713-716

🤖 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/frame.py` around lines 656 - 660, Add a regression
benchmark that measures the hot conversion path exercised by Frame._to_array /
DataFrame.to_numpy(dtype=object) when nulls are present: create a
pytest-benchmark (or project-standard benchmarking harness) test that constructs
frames with nulls and calls to_numpy(dtype=object) twice—once using the default
na_value and once passing an explicit na_value—to capture performance for both
paths; name the test clearly (e.g., bench_to_numpy_object_with_nulls) and place
it with other Python benchmarks so future changes to is_numpy_object_dtype /
Frame._to_array will be covered.

@galipremsagar galipremsagar added bug Something isn't working non-breaking Non-breaking change labels May 22, 2026
@galipremsagar

Copy link
Copy Markdown
Contributor

pre-commit.ci autofix

@galipremsagar

Copy link
Copy Markdown
Contributor

/okay to test 78c7264

@galipremsagar

Copy link
Copy Markdown
Contributor

/okay to test 1d9e482

@vyasr

vyasr commented May 30, 2026

Copy link
Copy Markdown
Contributor

/ok to test cabc766

Comment thread python/cudf/cudf/core/frame.py Outdated
@galipremsagar

Copy link
Copy Markdown
Contributor

/okay to test 1e53275

@galipremsagar

Copy link
Copy Markdown
Contributor

/merge

@rapids-bot
rapids-bot Bot merged commit 413f124 into NVIDIA:main Jun 2, 2026
105 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] to_numpy(dtype=object) coerces None -> pandas.NA

4 participants