Skip to content

cudf-polars: preserve zero-column row counts - #23234

Merged
rapids-bot[bot] merged 8 commits into
NVIDIA:mainfrom
madsbk:cudf-polars-zero-column-with-multiple-rows
Jul 16, 2026
Merged

cudf-polars: preserve zero-column row counts#23234
rapids-bot[bot] merged 8 commits into
NVIDIA:mainfrom
madsbk:cudf-polars-zero-column-with-multiple-rows

Conversation

@madsbk

@madsbk madsbk commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Description

Follow-up to #22765. Propagate the row count of a zero-column table through the cudf-polars DataFrame container and Projection node, so a frame with no columns and N rows preserves its shape as (N, 0) instead of collapsing to
(0, 0).

@madsbk madsbk self-assigned this Jul 13, 2026
@madsbk madsbk added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Jul 13, 2026
@github-actions github-actions Bot added Python Affects Python cuDF API. cudf-polars Issues specific to cudf-polars labels Jul 13, 2026
@madsbk
madsbk force-pushed the cudf-polars-zero-column-with-multiple-rows branch from f51d8e7 to 4589c64 Compare July 13, 2026 14:40
@GPUtester GPUtester moved this to In Progress in cuDF Python Jul 13, 2026
@madsbk
madsbk marked this pull request as ready for review July 14, 2026 12:28
@madsbk
madsbk requested a review from a team as a code owner July 14, 2026 12:28
@madsbk
madsbk requested a review from mroeschke July 14, 2026 12:28
@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 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: 417bfe91-7be8-439d-90a3-de5a6a92174a

📥 Commits

Reviewing files that changed from the base of the PR and between f90c293 and 4589c64.

📒 Files selected for processing (8)
  • python/cudf_polars/cudf_polars/containers/dataframe.py
  • python/cudf_polars/cudf_polars/dsl/ir.py
  • python/cudf_polars/cudf_polars/testing/inject_gpu_engine.py
  • python/cudf_polars/tests/containers/test_dataframe.py
  • python/cudf_polars/tests/streaming/test_select.py
  • python/cudf_polars/tests/test_dataframescan.py
  • python/cudf_polars/tests/test_groupby.py
  • python/cudf_polars/tests/test_scan.py
💤 Files with no reviewable changes (1)
  • python/cudf_polars/cudf_polars/testing/inject_gpu_engine.py

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Preserved row counts for dataframes with no columns across copying, selection, projection, sorting, scanning, and conversion operations.
    • Ensured adding no columns to a zero-column dataframe retains its existing height.
    • Improved consistency when transforming empty or zero-width dataframes.
  • Tests

    • Expanded coverage for zero-column dataframe row-count preservation.
    • Updated GPU and streaming test expectations for current behavior.

Walkthrough

The DataFrame container now stores zero-column row counts through plc.Table, propagates them across constructors and transformations, and preserves them during empty projections. Tests and engine expectation tables are updated for the resulting behavior changes.

Changes

Zero-column row-count handling

Layer / File(s) Summary
DataFrame row-count propagation
python/cudf_polars/cudf_polars/containers/dataframe.py, python/cudf_polars/tests/containers/test_dataframe.py
DataFrame passes row counts to plc.Table and propagates them through copying, construction, sorting, column operations, and zero-column tests.
Projection zero-column preservation
python/cudf_polars/cudf_polars/dsl/ir.py, python/cudf_polars/tests/streaming/test_select.py, python/cudf_polars/tests/test_dataframescan.py, python/cudf_polars/tests/test_groupby.py, python/cudf_polars/tests/test_scan.py
Empty projections retain the input row count, and related tests remove conditional streaming-engine xfail handling.
Engine expectation updates
python/cudf_polars/cudf_polars/testing/inject_gpu_engine.py
GPU and streaming expected-failure entries are revised for updated aggregation, array, window, join, IO, and zero-column behaviors.

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

Suggested reviewers: matt711, wence-, 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: preserving row counts for zero-column cudf-polars frames.
Description check ✅ Passed The description directly matches the changeset by explaining zero-column row-count preservation in DataFrame and Projection.
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.

@mroeschke mroeschke left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just a suggestion for a cleanup (can be a follow up) otherwise LGTM

),
stream=stream,
num_rows=num_rows,
num_rows=table.num_rows() if num_rows is None else num_rows,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion: Now that pylibcudf.Table accepts a num_rows argument, would be good to see if the we can remove num_rows from this function and have the caller always pass in a table with the intended row count

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, done: ab5995a

@wence- wence- left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One question about the comment in with_columns

Comment on lines +397 to +404
# When the result has columns, the row count is derived from them (and the
# non-broadcast path deliberately allows mismatched lengths, so we must not
# force a num_rows here).
return type(self)(
merged.values(),
stream=stream,
num_rows=self.num_rows if not merged else None,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it is invalid to pass new columns that have a different length from the existing table. So what is this comment saying?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not always, updated the comment:

        # Only pass num_rows for a zero-column result. For results with columns, it
        # must remain None because HStack(should_broadcast=False) intentionally
        # produces mismatched column lengths that its Select parent reconciles later.
        return type(self)(
            merged.values(),
            stream=stream,
            num_rows=self.num_rows if not merged else None,
        )

@madsbk

madsbk commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 9d9fec8 into NVIDIA:main Jul 16, 2026
291 of 298 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jul 16, 2026
@madsbk
madsbk deleted the cudf-polars-zero-column-with-multiple-rows branch July 16, 2026 18:17
galipremsagar added a commit to galipremsagar/cudf that referenced this pull request Jul 17, 2026
…ction

DataFrame.from_table lost its num_rows parameter in NVIDIA#23234 (row counts
are now inferred from the pylibcudf table, which carries them even for
zero-column tables), but the duplicated-output path added in NVIDIA#23114
still passed num_rows=0, breaking mypy on every PR and raising
TypeError at runtime on that path. empty_like already produces a
0-row table (including for zero-column inputs), so the argument was
redundant.
rapids-bot Bot pushed a commit that referenced this pull request Jul 17, 2026
…utput path (#23303)

`DataFrame.from_table` lost its `num_rows` parameter in #23234 (row counts are now inferred from the pylibcudf table, which carries them even for zero-column tables), but the duplicated-output path added in #23114 still passes `num_rows=0`. The two PRs merged around the same time, so this surfaced only after both landed: mypy now fails on every PR's `check-style` job (`engine/core.py:840: Unexpected keyword argument "num_rows"`), and the path would raise `TypeError` at runtime.

`plc.copying.empty_like` already produces a 0-row table (including for zero-column inputs, verified), so dropping the argument preserves the intended "freshly-allocated empty same-schema frame" semantics exactly.

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

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

URL: #23303
davidwendt pushed a commit to wjxiz1992/cudf that referenced this pull request Jul 21, 2026
…utput path (NVIDIA#23303)

`DataFrame.from_table` lost its `num_rows` parameter in NVIDIA#23234 (row counts are now inferred from the pylibcudf table, which carries them even for zero-column tables), but the duplicated-output path added in NVIDIA#23114 still passes `num_rows=0`. The two PRs merged around the same time, so this surfaced only after both landed: mypy now fails on every PR's `check-style` job (`engine/core.py:840: Unexpected keyword argument "num_rows"`), and the path would raise `TypeError` at runtime.

`plc.copying.empty_like` already produces a 0-row table (including for zero-column inputs, verified), so dropping the argument preserves the intended "freshly-allocated empty same-schema frame" semantics exactly.

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

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

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

Labels

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

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants