From 939c8b597db127baa6eebadbe6e65987d8d8743b Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Tue, 30 Jun 2026 22:46:32 +0000 Subject: [PATCH 1/2] cudf-classic and CI cleanups for NumPy/CuPy 2/14 version bump --- ci/cudf_pandas_scripts/run_tests.sh | 127 ++++++++---------- python/cudf/cudf/pandas/_wrappers/numpy.py | 8 +- .../cudf/cudf/tests/indexes/test_interval.py | 31 +---- python/cudf/cudf/tests/test_doctests.py | 16 +-- 4 files changed, 63 insertions(+), 119 deletions(-) diff --git a/ci/cudf_pandas_scripts/run_tests.sh b/ci/cudf_pandas_scripts/run_tests.sh index 35317e213c02..baaf9962b1b1 100755 --- a/ci/cudf_pandas_scripts/run_tests.sh +++ b/ci/cudf_pandas_scripts/run_tests.sh @@ -1,5 +1,5 @@ #!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 set -eoxu pipefail @@ -81,72 +81,63 @@ python -m pytest -p cudf.pandas \ -k "profiler" \ ./python/cudf/cudf_pandas_tests/ -version_lte() { - [ "$1" = "$(echo -e "$1\n$2" | sort -V | head -n1)" ] -} - read -r -a versions <<< "$(python ci/utils/get_matrix_values.py dependencies.yaml test_cudf_pandas_compat pandas_compat_version)" -if version_lte "${RAPIDS_PY_VERSION}" "3.13"; then - for version in "${versions[@]}"; do - rapids-logger "Testing cudf.pandas compatibility with pandas ${version}.*" - - # Generate requirements for this pandas compat version. - # Each entry pins numpy<2 + the specific pandas minor line + the CUDA-appropriate cupy<14. - # cupy>=14 dropped support for numpy<2 (see https://github.com/cupy/cupy/pull/9406). - pandas_requirements_txt="pandas-compat-${version}-requirements.txt" - rapids-dependency-file-generator \ - --config dependencies.yaml \ - --file-key test_cudf_pandas_compat \ - --output requirements \ - --matrix "cuda=${RAPIDS_CUDA_VERSION};pandas_compat_version=${version}" \ - > "${pandas_requirements_txt}" - - env_name="venv_pandas_${version}" - python -m venv --clear "${env_name}" - # shellcheck disable=SC1090 - source "${env_name}/bin/activate" - - # notes: - # - # * echo to expand wildcard before adding `[test,cudf-pandas-tests]` requires for pip - # * need to provide --constraint="${PIP_CONSTRAINT}" because that environment variable is - # ignored if any other --constraint are passed via the CLI - # - rapids-pip-retry install \ - -v \ - --constraint ./constraints.txt \ - --constraint "${PIP_CONSTRAINT}" \ - "$(echo "${CUDF_WHEELHOUSE}"/cudf_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)[test,cudf-pandas-tests]" \ - "$(echo "${LIBCUDF_WHEELHOUSE}"/libcudf_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)" \ - "$(echo "${PYLIBCUDF_WHEELHOUSE}"/pylibcudf_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)" \ - -r "${pandas_requirements_txt}" - - python -m pytest -p cudf.pandas \ - --ignore=./python/cudf/cudf_pandas_tests/third_party_integration_tests/ \ - --numprocesses=8 \ - --dist=worksteal \ - -k "not profiler" \ - -m "not serial" \ - --config-file=./python/cudf/pyproject.toml \ - --cov-config=./python/cudf/.coveragerc \ - --cov=cudf \ - --cov-report=xml:"${RAPIDS_COVERAGE_DIR}/cudf-pandas-coverage.xml" \ - --cov-report=term \ - ./python/cudf/cudf_pandas_tests/ - - # NOTE: We don't currently run serial tests (only 1 as of 2025-07-25) - # with multiple versions of pandas. - - python -m pytest -p cudf.pandas \ - --ignore=./python/cudf/cudf_pandas_tests/third_party_integration_tests/ \ - --numprocesses=0 \ - -k "profiler" \ - ./python/cudf/cudf_pandas_tests/ - - deactivate - rm -rf "${env_name}" "${pandas_requirements_txt}" - done -else - rapids-logger "Python ${RAPIDS_PY_VERSION} detected (>= 3.13). Skipping cudf.pandas compatibility tests with numpy<2" -fi +for version in "${versions[@]}"; do + rapids-logger "Testing cudf.pandas compatibility with pandas ${version}.*" + + # Generate requirements for this pandas compat version. + # Each entry pins the specific pandas minor line and the CUDA-appropriate cupy. + pandas_requirements_txt="pandas-compat-${version}-requirements.txt" + rapids-dependency-file-generator \ + --config dependencies.yaml \ + --file-key test_cudf_pandas_compat \ + --output requirements \ + --matrix "cuda=${RAPIDS_CUDA_VERSION};pandas_compat_version=${version}" \ + > "${pandas_requirements_txt}" + + env_name="venv_pandas_${version}" + python -m venv --clear "${env_name}" + # shellcheck disable=SC1090 + source "${env_name}/bin/activate" + + # notes: + # + # * echo to expand wildcard before adding `[test,cudf-pandas-tests]` requires for pip + # * need to provide --constraint="${PIP_CONSTRAINT}" because that environment variable is + # ignored if any other --constraint are passed via the CLI + # + rapids-pip-retry install \ + -v \ + --constraint ./constraints.txt \ + --constraint "${PIP_CONSTRAINT}" \ + "$(echo "${CUDF_WHEELHOUSE}"/cudf_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)[test,cudf-pandas-tests]" \ + "$(echo "${LIBCUDF_WHEELHOUSE}"/libcudf_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)" \ + "$(echo "${PYLIBCUDF_WHEELHOUSE}"/pylibcudf_"${RAPIDS_PY_CUDA_SUFFIX}"*.whl)" \ + -r "${pandas_requirements_txt}" + + python -m pytest -p cudf.pandas \ + --ignore=./python/cudf/cudf_pandas_tests/third_party_integration_tests/ \ + --numprocesses=8 \ + --dist=worksteal \ + -k "not profiler" \ + -m "not serial" \ + --config-file=./python/cudf/pyproject.toml \ + --cov-config=./python/cudf/.coveragerc \ + --cov=cudf \ + --cov-report=xml:"${RAPIDS_COVERAGE_DIR}/cudf-pandas-coverage.xml" \ + --cov-report=term \ + ./python/cudf/cudf_pandas_tests/ + + # NOTE: We don't currently run serial tests (only 1 as of 2025-07-25) + # with multiple versions of pandas. + + python -m pytest -p cudf.pandas \ + --ignore=./python/cudf/cudf_pandas_tests/third_party_integration_tests/ \ + --numprocesses=0 \ + -k "profiler" \ + ./python/cudf/cudf_pandas_tests/ + + deactivate + rm -rf "${env_name}" "${pandas_requirements_txt}" +done diff --git a/python/cudf/cudf/pandas/_wrappers/numpy.py b/python/cudf/cudf/pandas/_wrappers/numpy.py index eb9aa5301004..1df87e4a6e3d 100644 --- a/python/cudf/cudf/pandas/_wrappers/numpy.py +++ b/python/cudf/cudf/pandas/_wrappers/numpy.py @@ -6,7 +6,7 @@ import cupy import cupy._core.flags import numpy -from packaging import version +from numpy._core.multiarray import flagsobj as _numpy_flagsobj from cudf.options import _env_get_bool @@ -324,12 +324,6 @@ def _ndarray_fsproxy_fast_to_slow(self): }, ) -if version.parse(numpy.__version__) >= version.parse("2.0"): - # NumPy 2 introduced `_core` and gives warnings for access to `core`. - from numpy._core.multiarray import flagsobj as _numpy_flagsobj -else: - from numpy.core.multiarray import flagsobj as _numpy_flagsobj - # Mapping flags between slow and fast types _ndarray_flags = make_intermediate_proxy_type( "_ndarray_flags", diff --git a/python/cudf/cudf/tests/indexes/test_interval.py b/python/cudf/cudf/tests/indexes/test_interval.py index 892ba422ecdd..72cbc6dc2db5 100644 --- a/python/cudf/cudf/tests/indexes/test_interval.py +++ b/python/cudf/cudf/tests/indexes/test_interval.py @@ -1,10 +1,9 @@ -# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import numpy as np import pandas as pd import pyarrow as pa import pytest -from packaging.version import parse import cudf from cudf.core.index import IntervalIndex, interval_range @@ -121,14 +120,6 @@ def test_interval_range_periods_basic_dtype(start_t, end_t, periods_t): gindex = cudf.interval_range( start=start, end=end, periods=periods, closed="left" ) - if ( - parse(np.__version__) < parse("2") - and pindex.dtype.subtype != gindex.dtype.subtype - ): - # NEP 50 in numpy 2 changes pandas' subtype result to match cudf - pindex = pindex.astype( - pd.IntervalDtype(gindex.dtype.subtype, pindex.dtype.closed) - ) assert_eq(pindex, gindex) @@ -172,16 +163,6 @@ def test_interval_range_periods_freq_end_dtype(periods_t, freq_t, end_t): gindex = cudf.interval_range( end=end, freq=freq, periods=periods, closed="left" ) - if ( - parse(np.__version__) < parse("2") - and pindex.dtype.subtype != gindex.dtype.subtype - ): - # NEP 50 in numpy 2 changes pandas' subtype result to match cudf - pindex = pindex.astype( - pd.IntervalDtype( - subtype=gindex.dtype.subtype, closed=pindex.dtype.closed - ) - ) assert_eq(pindex, gindex) @@ -211,16 +192,6 @@ def test_interval_range_periods_freq_start_dtype(periods_t, freq_t, start_t): gindex = cudf.interval_range( start=start, freq=freq, periods=periods, closed="left" ) - if ( - parse(np.__version__) < parse("2") - and pindex.dtype.subtype != gindex.dtype.subtype - ): - # NEP 50 in numpy 2 changes pandas' subtype result to match cudf - pindex = pindex.astype( - pd.IntervalDtype( - subtype=gindex.dtype.subtype, closed=pindex.dtype.closed - ) - ) assert_eq(pindex, gindex) diff --git a/python/cudf/cudf/tests/test_doctests.py b/python/cudf/cudf/tests/test_doctests.py index 0f0bf449d21f..d8cde915e52d 100644 --- a/python/cudf/cudf/tests/test_doctests.py +++ b/python/cudf/cudf/tests/test_doctests.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import contextlib import doctest @@ -7,18 +7,9 @@ import numpy as np import pytest -from packaging import version import cudf -_SKIP_DOCTESTS = frozenset( - { - "register_dataframe_accessor", - "register_index_accessor", - "register_series_accessor", - } -) - # modules that will be searched for doctests tests = [ cudf, @@ -126,10 +117,7 @@ class TestDoctests: def printoptions(cls): # TODO: NumPy now prints scalars as `np.int8(1)`, etc. this should # be adapted evantually. - if version.parse(np.__version__) >= version.parse("2.0"): - with np.printoptions(legacy="1.25"): - yield - else: + with np.printoptions(legacy="1.25"): yield @pytest.mark.parametrize( From e50cbcbabeeef6e9bb11c2195bc7bf1debaf52a3 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Tue, 30 Jun 2026 23:23:37 +0000 Subject: [PATCH 2/2] Update docstrings for non legacy Numpy print behavior --- python/cudf/cudf/api/extensions/accessor.py | 16 ++++++++-------- python/cudf/cudf/core/frame.py | 2 +- python/cudf/cudf/core/indexed_frame.py | 10 +++++----- python/cudf/cudf/core/series.py | 12 ++++++------ python/cudf/cudf/tests/test_doctests.py | 7 ------- 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/python/cudf/cudf/api/extensions/accessor.py b/python/cudf/cudf/api/extensions/accessor.py index f7f49d397c83..4116fb3348a4 100644 --- a/python/cudf/cudf/api/extensions/accessor.py +++ b/python/cudf/cudf/api/extensions/accessor.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import warnings @@ -60,7 +60,7 @@ >>> df = cudf.DataFrame({'x': [1,2,3,4,5,6], 'y':[7,6,5,4,3,2]}) >>> df.point.bounding_box - (1, 2, 6, 7) + (np.int64(1), np.int64(2), np.int64(6), np.int64(7)) """ @@ -79,11 +79,11 @@ >>> gs = cudf.Index(list(range(0, 50))) >>> gs.odd[1] - 1 + np.int64(1) >>> gs.odd[2] - 3 + np.int64(3) >>> gs.odd[3] - 5 + np.int64(5) """ @@ -102,11 +102,11 @@ >>> gs = cudf.Series(list(range(0, 50))) >>> gs.odd[1] - 1 + np.int64(1) >>> gs.odd[2] - 3 + np.int64(3) >>> gs.odd[3] - 5 + np.int64(5) """ diff --git a/python/cudf/cudf/core/frame.py b/python/cudf/cudf/core/frame.py index ad3e3c131c1d..aadca5c19fc5 100644 --- a/python/cudf/cudf/core/frame.py +++ b/python/cudf/cudf/core/frame.py @@ -1945,7 +1945,7 @@ def min( b 7 dtype: int64 >>> min_series.min() - 1 + np.int64(1) .. pandas-compat:: :meth:`pandas.DataFrame.min`, :meth:`pandas.Series.min` diff --git a/python/cudf/cudf/core/indexed_frame.py b/python/cudf/cudf/core/indexed_frame.py index c4ab629b29e6..c0274d9d18d6 100644 --- a/python/cudf/cudf/core/indexed_frame.py +++ b/python/cudf/cudf/core/indexed_frame.py @@ -1223,7 +1223,7 @@ def dot(self, other, reflect=False): 1 26 dtype: int64 >>> [1, 2, 3, 4] @ s - 10 + np.int64(10) """ # TODO: This function does not currently support nulls. lhs = self.values @@ -1644,7 +1644,7 @@ def median( 5 6 dtype: int64 >>> ser.median() - 17.0 + np.float64(17.0) """ if "overwrite_input" in kwargs: raise ValueError( @@ -1800,7 +1800,7 @@ def kurtosis( >>> import cudf >>> series = cudf.Series([1, 2, 3, 4]) >>> series.kurtosis() - -1.200000000000001 + np.float64(-1.200000000000001) **DataFrame** @@ -2583,7 +2583,7 @@ def squeeze(self, axis: Literal["index", "columns", 0, 1, None] = None): dtype: int64 >>> even_primes.squeeze() - 2 + np.int64(2) Squeezing objects with more than one value in every axis does nothing: @@ -2641,7 +2641,7 @@ def squeeze(self, axis: Literal["index", "columns", 0, 1, None] = None): Squeezing all axes will project directly into a scalar: >>> df_0a.squeeze() - 1 + np.int64(1) """ axes = ( range(self.ndim) diff --git a/python/cudf/cudf/core/series.py b/python/cudf/cudf/core/series.py index a222185ff55b..505074adfcd1 100644 --- a/python/cudf/cudf/core/series.py +++ b/python/cudf/cudf/core/series.py @@ -2886,7 +2886,7 @@ def cov(self, other, min_periods=None, ddof: int | None = None): >>> ser1 = cudf.Series([0.9, 0.13, 0.62]) >>> ser2 = cudf.Series([0.12, 0.26, 0.51]) >>> ser1.cov(ser2) - -0.015750000000000004 + np.float64(-0.015750000000000004) .. pandas-compat:: :meth:`pandas.Series.cov` @@ -3021,9 +3021,9 @@ def corr(self, other, method="pearson", min_periods=None): >>> ser1 = cudf.Series([0.9, 0.13, 0.62]) >>> ser2 = cudf.Series([0.12, 0.26, 0.51]) >>> ser1.corr(ser2, method="pearson") - -0.20454263717316126 + np.float64(-0.20454263717316126) >>> ser1.corr(ser2, method="spearman") - -0.5 + np.float64(-0.5) """ if method not in {"pearson", "spearman"}: @@ -3069,9 +3069,9 @@ def autocorr(self, lag=1): >>> import cudf >>> s = cudf.Series([0.25, 0.5, 0.2, -0.05, 0.17]) >>> s.autocorr() - 0.1438853844... + np.float64(0.1438853844...) >>> s.autocorr(lag=2) - -0.9647548490... + np.float64(-0.9647548490...) """ return self.corr(self.shift(lag)) @@ -3417,7 +3417,7 @@ def quantile( 3 4 dtype: int64 >>> series.quantile(0.5) - 2.5 + np.float64(2.5) >>> series.quantile([0.25, 0.5, 0.75]) 0.25 1.75 0.50 2.50 diff --git a/python/cudf/cudf/tests/test_doctests.py b/python/cudf/cudf/tests/test_doctests.py index d8cde915e52d..b7058e7c5391 100644 --- a/python/cudf/cudf/tests/test_doctests.py +++ b/python/cudf/cudf/tests/test_doctests.py @@ -113,13 +113,6 @@ def _collect_doctests(): class TestDoctests: - @pytest.fixture(autouse=True) - def printoptions(cls): - # TODO: NumPy now prints scalars as `np.int8(1)`, etc. this should - # be adapted evantually. - with np.printoptions(legacy="1.25"): - yield - @pytest.mark.parametrize( "docstring", _all_doctests,