From 9a8360aa9bde668554c9627e12d1e13bb10c5326 Mon Sep 17 00:00:00 2001 From: galipremsagar Date: Wed, 9 Sep 2026 17:06:38 +0000 Subject: [PATCH 1/2] Preserve interval endpoint dtypes when converting to pandas Construct pandas intervals from dtype-aware endpoint conversions and respect sliced struct-child views. Cover timezones, extension dtypes, empty slices and nulls; remove three shard skips and eleven fixed xfails. --- python/cudf/cudf/core/column/interval.py | 11 ++-- .../pandas/scripts/pandas-testing-plugin.py | 14 ----- .../cudf/cudf/tests/indexes/test_interval.py | 52 +++++++++++++++++++ .../cudf_pandas_tests/test_cudf_pandas.py | 19 +++++++ 4 files changed, 79 insertions(+), 17 deletions(-) diff --git a/python/cudf/cudf/core/column/interval.py b/python/cudf/cudf/core/column/interval.py index 4cad2332b3c..d5a7fb1ed8f 100644 --- a/python/cudf/cudf/core/column/interval.py +++ b/python/cudf/cudf/core/column/interval.py @@ -83,7 +83,7 @@ def length(self) -> ColumnBase: @functools.cached_property def left(self) -> ColumnBase: return ColumnBase.create( - self.plc_column.children()[0], + self.plc_column.struct_view().get_sliced_child(0), self.subtype, ) @@ -98,7 +98,7 @@ def mid(self) -> ColumnBase: @functools.cached_property def right(self) -> ColumnBase: return ColumnBase.create( - self.plc_column.children()[1], + self.plc_column.struct_view().get_sliced_child(1), self.subtype, ) @@ -193,7 +193,12 @@ def to_pandas( f"pandas does not have a native nullable type for {self.dtype}." ) pd_type = cast("IntervalDtype", self.dtype).to_pandas() - return pd.Index(pd_type.__from_arrow__(self.to_arrow()), dtype=pd_type) + return pd.IntervalIndex.from_arrays( + self.left.to_pandas(), + self.right.to_pandas(), + closed=self.closed, + dtype=pd_type, + ) def element_indexing(self, index: int) -> pd.Interval | None: result = super().element_indexing(index) diff --git a/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py b/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py index dbbb83bdbef..9cacf29e5ae 100644 --- a/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py +++ b/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py @@ -2045,25 +2045,14 @@ def pytest_unconfigure(config): "tests/indexes/interval/test_astype.py::TestFloatSubtype::test_subtype_integer[uint64]": "TODO: Add a reason for failure", "tests/indexes/interval/test_astype.py::TestFloatSubtype::test_subtype_integer_errors": "TODO: Add a reason for failure", "tests/indexes/interval/test_constructors.py::TestFromBreaks::test_generic_errors": "TODO: Add a reason for failure", - "tests/indexes/interval/test_constructors.py::test_ea_dtype[Float64]": "https://github.com/pandas-dev/pandas/issues/64297", - "tests/indexes/interval/test_constructors.py::test_ea_dtype[float64[pyarrow]]": "https://github.com/pandas-dev/pandas/issues/64297", "tests/indexes/interval/test_indexing.py::TestGetIndexer::test_get_indexer_categorical_with_nans": "TODO: Add a reason for failure", "tests/indexes/interval/test_indexing.py::TestGetIndexer::test_get_indexer_datetime": "TODO: Add a reason for failure", - "tests/indexes/interval/test_indexing.py::TestGetLoc::test_get_loc_datetimelike_nonoverlapping[datetime64[us, US/Eastern]]": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", - "tests/indexes/interval/test_indexing.py::TestPutmask::test_putmask_dt64[US/Pacific]": "TypeError: Cannot interpret 'datetime64[us, US/Pacific]' as a data type", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_comparison": "TODO: Add a reason for failure", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_ensure_copied_data[both]": "TODO: Add a reason for failure", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_ensure_copied_data[left]": "TODO: Add a reason for failure", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_ensure_copied_data[neither]": "TODO: Add a reason for failure", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_ensure_copied_data[right]": "TODO: Add a reason for failure", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_insert[data0]": "TODO: Add a reason for failure", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8[datetime64[us, US/Eastern]]": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[Interval-datetime64[us, US/Eastern]-datetime64[us]]": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[Interval-datetime64[us, US/Eastern]-timedelta64[us]]": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[IntervalIndex-datetime64[us, US/Eastern]-datetime64[us]]": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[IntervalIndex-datetime64[us, US/Eastern]-timedelta64[us]]": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[IntervalIndex-datetime64[us]-datetime64[us, US/Eastern]]": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[IntervalIndex-timedelta64[us]-datetime64[us, US/Eastern]]": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_monotonic[both]": "TODO: Add a reason for failure", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_monotonic[left]": "TODO: Add a reason for failure", "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_monotonic[neither]": "TODO: Add a reason for failure", @@ -5318,9 +5307,6 @@ def pytest_unconfigure(config): "tests/groupby/methods/test_value_counts.py::test_against_frame_and_seriesgroupby[True-False-True-False-True-proportion-function]": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", "tests/groupby/methods/test_value_counts.py::test_against_frame_and_seriesgroupby[True-False-True-True-False-count-function]": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", "tests/groupby/methods/test_value_counts.py::test_against_frame_and_seriesgroupby[True-False-True-True-True-proportion-function]": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[Index-datetime64[us, US/Eastern]-datetime64[us]]": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[scalar-datetime64[us, US/Eastern]-datetime64[us]]": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", - "tests/indexes/interval/test_interval.py::TestIntervalIndex::test_maybe_convert_i8_errors[scalar-datetime64[us, US/Eastern]-timedelta64[us]]": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", "tests/indexes/multi/test_formats.py::TestRepr::test_tuple_width": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", "tests/plotting/frame/test_frame.py::TestDataFramePlots::test_memory_leak[area]": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", "tests/plotting/frame/test_frame.py::TestDataFramePlots::test_memory_leak[line]": "Skipped: failing in pandas-tests sharded CI (PR #22992, run 28204832469)", diff --git a/python/cudf/cudf/tests/indexes/test_interval.py b/python/cudf/cudf/tests/indexes/test_interval.py index 72cbc6dc2db..bb5da45fedb 100644 --- a/python/cudf/cudf/tests/indexes/test_interval.py +++ b/python/cudf/cudf/tests/indexes/test_interval.py @@ -22,6 +22,58 @@ def test_interval_to_arrow(): assert_eq(expect, got) +@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) +@pytest.mark.parametrize("unit", ["s", "ms", "us", "ns"]) +@pytest.mark.parametrize("tz", [None, "US/Eastern", "Asia/Kolkata"]) +@pytest.mark.parametrize("selection", [slice(None), slice(1, 3), slice(0, 0)]) +def test_interval_to_pandas_datetime(closed, unit, tz, selection): + breaks = pd.date_range("2025-03-08", periods=5, tz=tz, unit=unit) + expected = pd.Series( + pd.IntervalIndex.from_breaks(breaks, closed=closed), name="intervals" + ).iloc[selection] + intervals = cudf.IntervalIndex.from_breaks( + cudf.from_pandas(breaks), closed=closed, name="intervals" + ) + result = cudf.Series(intervals, name="intervals").iloc[selection] + + assert_eq(result.to_pandas(), expected) + assert_eq( + cudf.IntervalIndex(result, name="intervals").to_pandas(), + pd.IntervalIndex(expected, name="intervals"), + ) + + +@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) +@pytest.mark.parametrize("subtype", ["Float64", "float64[pyarrow]"]) +def test_interval_to_pandas_extension_subtype(closed, subtype): + breaks = pd.Series([0.0, 0.4, 0.6], dtype=subtype) + expected = pd.IntervalIndex.from_breaks( + breaks, closed=closed, dtype=pd.IntervalDtype(subtype, closed) + ) + result = cudf.IntervalIndex.from_breaks( + cudf.from_pandas(breaks), closed=closed + ) + + assert_eq(result.to_pandas(), expected) + + +@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) +@pytest.mark.parametrize( + "subtype", ["float64", "datetime64[ns]", "timedelta64[ns]"] +) +def test_interval_to_pandas_nulls(closed, subtype): + expected = pd.Series( + pd.IntervalIndex.from_breaks( + np.arange(5).astype(subtype), closed=closed + ) + ) + expected.iloc[1] = None + result = cudf.from_pandas(expected) + + assert_eq(result.to_pandas(), expected) + assert_eq(result.iloc[1:3].to_pandas(), expected.iloc[1:3]) + + INTERVAL_BOUNDARY_TYPES = [ int, np.int8, diff --git a/python/cudf/cudf_pandas_tests/test_cudf_pandas.py b/python/cudf/cudf_pandas_tests/test_cudf_pandas.py index 792e4f2cf31..ce9fd4bf2d9 100644 --- a/python/cudf/cudf_pandas_tests/test_cudf_pandas.py +++ b/python/cudf/cudf_pandas_tests/test_cudf_pandas.py @@ -1695,6 +1695,25 @@ def test_at_setitem_empty(): tm.assert_frame_equal(df, expected) +@pytest.mark.parametrize("closed", ["left", "right", "both", "neither"]) +@pytest.mark.parametrize("tz", ["US/Eastern", "Asia/Kolkata"]) +def test_timezone_interval_fast_to_slow(monkeypatch, closed, tz): + breaks = pd.DatetimeIndex( + list(pd.date_range("2025-03-08", periods=4, tz=tz)) + ) + expected = pd.IntervalIndex.from_breaks( + breaks, closed=closed, name="intervals" + ) + monkeypatch.setenv("CUDF_PANDAS_FAIL_ON_FALLBACK", "1") + + result = xpd.IntervalIndex.from_breaks( + breaks, closed=closed, name="intervals" + ) + + assert isinstance(result._fsproxy_fast, cudf.IntervalIndex) + pd.testing.assert_index_equal(result._fsproxy_slow, expected) + + @pytest.mark.parametrize( "index", [ From 7932d76f4963119ca7c25136f2621e2fab53a341 Mon Sep 17 00:00:00 2001 From: galipremsagar Date: Wed, 9 Sep 2026 21:19:11 +0000 Subject: [PATCH 2/2] Remove fixed timezone interval unique xfail The interval endpoint conversion fix makes test_tz_unique pass. Remove its stale strict xfail so pandas-tests shard 1 reports a pass instead of XPASS(strict). --- python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py b/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py index 9cacf29e5ae..af6645ca581 100644 --- a/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py +++ b/python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py @@ -3388,7 +3388,6 @@ def pytest_unconfigure(config): "tests/series/methods/test_sort_index.py::TestSeriesSortIndexKey::test_sort_index_na_position_key[None]": "AssertionError: Series.index are different", "tests/series/methods/test_truncate.py::TestTruncate::test_truncate_datetimeindex_tz": "AssertionError: Series.index are different", "tests/series/methods/test_tz_localize.py::TestTZLocalize::test_series_tz_localize_ambiguous_bool": "Failed: DID NOT RAISE ", - "tests/series/methods/test_unique.py::TestUnique::test_tz_unique": "TypeError: Cannot interpret 'datetime64[us, US/Eastern]' as a data type", "tests/series/test_api.py::TestSeriesMisc::test_index_tab_completion[index0]": "TODO: Add a reason for failure", "tests/series/test_api.py::TestSeriesMisc::test_index_tab_completion[index10]": "TODO: Add a reason for failure", "tests/series/test_api.py::TestSeriesMisc::test_index_tab_completion[index11]": "TODO: Add a reason for failure",