Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions python/cudf/cudf/core/column/numerical_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ def kurtosis(self, skipna: bool = True) -> float:
)

if len(self) == 0 or self._can_return_nan(skipna=skipna):
return _get_nan_for_dtype(self.dtype) # type: ignore[return-value]
return _get_nan_for_dtype(self.dtype)

original_dtype = self.dtype
self = self.nans_to_nulls().dropna()

if len(self) < 4:
return _get_nan_for_dtype(original_dtype) # type: ignore[return-value]
return _get_nan_for_dtype(original_dtype)

if original_dtype.kind == "f" and original_dtype.itemsize < 8:
# Compute in float64 to match pandas precision for narrower
Expand Down Expand Up @@ -206,7 +206,7 @@ def quantile(
except (TypeError, ValueError):
pass
return (
_get_nan_for_dtype(self.dtype) # type: ignore[return-value]
_get_nan_for_dtype(self.dtype)
if scalar_result is NA
else scalar_result
)
Expand All @@ -221,7 +221,7 @@ def median(
)

if self._can_return_nan(skipna=skipna):
return _get_nan_for_dtype(self.dtype) # type: ignore[return-value]
return _get_nan_for_dtype(self.dtype)

# enforce linear in case the default ever changes
result = self.quantile(
Expand All @@ -240,21 +240,21 @@ def cov(self, other: NumericalBaseColumn) -> float:
or len(other) == 0
or (len(self) == 1 and len(other) == 1)
):
return _get_nan_for_dtype(self.dtype) # type: ignore[return-value]
return _get_nan_for_dtype(self.dtype)

result = (self - self.mean()) * (other - other.mean())
cov_sample = result.sum() / (len(self) - 1)
return cov_sample

def corr(self, other: NumericalBaseColumn) -> float:
if len(self) == 0 or len(other) == 0:
return _get_nan_for_dtype(self.dtype) # type: ignore[return-value]
return _get_nan_for_dtype(self.dtype)

cov = self.cov(other)
lhs_std, rhs_std = self.std(), other.std()

if not cov or lhs_std == 0 or rhs_std == 0:
return _get_nan_for_dtype(self.dtype) # type: ignore[return-value]
return _get_nan_for_dtype(self.dtype)
return cov / lhs_std / rhs_std

def round(
Expand Down
25 changes: 19 additions & 6 deletions python/cudf/cudf/core/column/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
pylibcudf_result_dtype_policy,
same_dtype_policy,
)
from cudf.core.dtype.validators import is_dtype_obj_string
from cudf.core.mixins import Scannable
from cudf.errors import MixedTypeError
from cudf.utils.dtypes import (
Expand All @@ -43,7 +44,7 @@
from cudf.utils.utils import is_na_like

if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Mapping
from collections.abc import Callable, Iterable, Mapping, Sequence

import cupy as cp

Expand Down Expand Up @@ -204,11 +205,11 @@ def sum(
if min_count > 0 and col.valid_count < min_count:
return pd.NA

return (
0
if len(col) == 0
else col.join_strings("", None).element_indexing(0)
)
if len(col) == 0:
# pandas sums an empty/all-null object series to the numeric
# identity 0; genuine string dtypes concatenate to "".
return 0 if self.dtype == np.dtype("object") else ""
return col.join_strings("", None).element_indexing(0)

def any(
self, skipna: bool = True, min_count: int = 0, **kwargs: Any
Expand Down Expand Up @@ -397,6 +398,18 @@ def as_string_column(self, dtype: DtypeObj) -> Self:
return cast("Self", ColumnBase.create(self.plc_column, dtype))
return self

def _process_values_for_isin(
self, values: Sequence | ColumnBase
) -> tuple[ColumnBase, ColumnBase]:
lhs, rhs = super()._process_values_for_isin(values)
if lhs.dtype != rhs.dtype and is_dtype_obj_string(rhs.dtype):
# Strings of any dtype flavor (object, pd.StringDtype with
# python/pyarrow storage and NaN/NA na_value, pd.ArrowDtype
# string) hold comparable values; align rhs with lhs's dtype
# so ColumnBase.isin does not treat them as disjoint types.
rhs = rhs.astype(lhs.dtype)
return lhs, rhs

@property
def values(self) -> cp.ndarray:
"""
Expand Down
13 changes: 13 additions & 0 deletions python/cudf/cudf/pandas/fast_slow_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,19 @@ def _transform_arg(
transformed: list[Any] = [
_transform_arg(a, attribute_name, seen) for a in arg.flat
]
if all(
new is old for new, old in zip(transformed, arg.flat, strict=True)
):
# No element needed transforming: return the original array
# (as we already do for non-object ndarrays below) to
# preserve buffer identity. Rebuilding an equivalent copy
# breaks aliasing checks such as
# ``np.may_share_memory(np.asarray(x), x)`` that numpy uses
# (e.g. in ``Generator.permutation``) to decide whether to
# defensively copy before an in-place shuffle; the views
# pandas returns under copy-on-write are read-only, so
# skipping that copy raises "ValueError: array is read-only".
return arg
# Keep the same memory layout as arg (the default is C_CONTIGUOUS)
if arg.flags["F_CONTIGUOUS"] and not arg.flags["C_CONTIGUOUS"]:
order = "F"
Expand Down
25 changes: 0 additions & 25 deletions python/cudf/cudf/pandas/scripts/pandas-testing-plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ def pytest_unconfigure(config):
"tests/arrays/categorical/test_indexing.py::TestCategoricalIndexing::test_where_new_category_raises": "TODO: Add a reason for failure",
"tests/arrays/categorical/test_missing.py::TestCategoricalMissing::test_compare_categorical_with_missing[a10-a20-categories0]": "AssertionError: Attributes of Series are different",
"tests/arrays/categorical/test_replace.py::test_replace_categorical_ea_dtype": "TODO: Add a reason for failure",
"tests/arrays/categorical/test_replace.py::test_replace_categorical_ea_dtype_different_cats_raises": "Failed: DID NOT RAISE <class 'TypeError'>",
"tests/arrays/categorical/test_sorting.py::TestCategoricalSort::test_sort_values": "TODO: Add a reason for failure",
"tests/arrays/datetimes/test_constructors.py::TestDatetimeArrayConstructor::test_copy": "TODO: Add a reason for failure",
"tests/arrays/floating/test_astype.py::test_astype_copy": "TODO: Add a reason for failure",
Expand Down Expand Up @@ -266,27 +265,6 @@ def pytest_unconfigure(config):
"tests/arrays/sparse/test_array.py::TestSparseArrayAnalytics::test_ufunc_args": "TODO: Add a reason for failure",
"tests/arrays/sparse/test_array.py::test_array_interface": "TODO: Add a reason for failure",
"tests/arrays/sparse/test_constructors.py::TestConstructors::test_constructor_copy": "TODO: Add a reason for failure",
"tests/arrays/string_/test_string.py::test_isin[string=str[python]]": "TODO: Add a reason for failure",
"tests/arrays/string_/test_string.py::test_isin[string=string[pyarrow]]": "TODO: Add a reason for failure",
"tests/arrays/string_/test_string.py::test_isin[string=string[python]]": "TODO: Add a reason for failure",
"tests/arrays/string_/test_string.py::test_min_max[False-string=str[pyarrow]-max]": "assert np.float64(nan) is nan",
"tests/arrays/string_/test_string.py::test_min_max[False-string=str[pyarrow]-min]": "assert np.float64(nan) is nan",
"tests/arrays/string_/test_string.py::test_min_max[False-string=str[python]-max]": "AssertionError: assert np.float64(nan) is nan",
"tests/arrays/string_/test_string.py::test_min_max[False-string=str[python]-min]": "AssertionError: assert np.float64(nan) is nan",
"tests/arrays/string_/test_string.py::test_min_max[False-string=string[pyarrow]-max]": "assert np.float64(nan) is <NA>",
"tests/arrays/string_/test_string.py::test_min_max[False-string=string[pyarrow]-min]": "assert np.float64(nan) is <NA>",
"tests/arrays/string_/test_string.py::test_min_max[False-string=string[python]-max]": "AssertionError: assert np.float64(nan) is <NA>",
"tests/arrays/string_/test_string.py::test_min_max[False-string=string[python]-min]": "AssertionError: assert np.float64(nan) is <NA>",
"tests/arrays/string_/test_string.py::test_numpy_random_permute[string=str[python]-Series]": "ValueError: array is read-only",
"tests/arrays/string_/test_string.py::test_numpy_random_permute[string=string[python]-Series]": "ValueError: array is read-only",
"tests/arrays/string_/test_string.py::test_reduce_empty[False-string=str[pyarrow]-0]": "AssertionError: assert 0 == ''",
"tests/arrays/string_/test_string.py::test_reduce_empty[False-string=str[python]-0]": "AssertionError: assert 0 == ''",
"tests/arrays/string_/test_string.py::test_reduce_empty[False-string=string[pyarrow]-0]": "AssertionError: assert 0 == ''",
"tests/arrays/string_/test_string.py::test_reduce_empty[False-string=string[python]-0]": "AssertionError: assert 0 == ''",
"tests/arrays/string_/test_string.py::test_reduce_empty[True-string=str[pyarrow]-0]": "AssertionError: assert 0 == ''",
"tests/arrays/string_/test_string.py::test_reduce_empty[True-string=str[python]-0]": "AssertionError: assert 0 == ''",
"tests/arrays/string_/test_string.py::test_reduce_empty[True-string=string[pyarrow]-0]": "AssertionError: assert 0 == ''",
"tests/arrays/string_/test_string.py::test_reduce_empty[True-string=string[python]-0]": "AssertionError: assert 0 == ''",
"tests/arrays/string_/test_string_arrow.py::test_pickle_roundtrip[na_value0]": "https://github.com/rapidsai/cudf/issues/18659#issuecomment-3710985854",
"tests/arrays/string_/test_string_arrow.py::test_pickle_roundtrip[nan]": "https://github.com/rapidsai/cudf/issues/18659#issuecomment-3710985854",
"tests/arrays/test_datetimelike.py::TestDatetimeArray::test_array_interface[B]": "TODO: Add a reason for failure",
Expand Down Expand Up @@ -366,9 +344,6 @@ def pytest_unconfigure(config):
"tests/copy_view/test_array.py::test_dataframe_values[array]": "TODO: Add a reason for failure",
"tests/copy_view/test_array.py::test_dataframe_values[asarray]": "TODO: Add a reason for failure",
"tests/copy_view/test_array.py::test_dataframe_values[values]": "TODO: Add a reason for failure",
"tests/copy_view/test_array.py::test_series_array_string_dtype[string=object]": "AssertionError: assert False",
"tests/copy_view/test_array.py::test_series_array_string_dtype[string=str[python]]": "AssertionError: assert False",
"tests/copy_view/test_array.py::test_series_array_string_dtype[string=string[python]]": "AssertionError: assert False",
"tests/copy_view/test_array.py::test_series_to_numpy": "TODO: Add a reason for failure",
"tests/copy_view/test_array.py::test_series_values[array]": "TODO: Add a reason for failure",
"tests/copy_view/test_array.py::test_series_values[np.array]": "AssertionError: assert False",
Expand Down
98 changes: 97 additions & 1 deletion python/cudf/cudf/tests/series/methods/test_isin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2025-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

import cudf
Expand Down Expand Up @@ -159,3 +160,98 @@ def test_isin_categorical(data, values):
got = gsr.isin(values)
expected = psr.isin(values)
assert_eq(got, expected)


@pytest.mark.parametrize(
"dtype",
[
np.dtype("object"),
pd.StringDtype(storage="python", na_value=pd.NA),
pd.StringDtype(storage="python", na_value=np.nan),
pd.StringDtype(storage="pyarrow", na_value=pd.NA),
pd.StringDtype(storage="pyarrow", na_value=np.nan),
pd.ArrowDtype(pa.string()),
pd.ArrowDtype(pa.large_string()),
],
)
@pytest.mark.parametrize(
"values",
[
["b", "x"],
np.array(["b", "x"], dtype=object),
pd.array(
["b", "x"], dtype=pd.StringDtype(storage="python", na_value=pd.NA)
),
pd.array(
["b", "x"],
dtype=pd.StringDtype(storage="python", na_value=np.nan),
),
pd.array(
["b", "x"],
dtype=pd.StringDtype(storage="pyarrow", na_value=pd.NA),
),
pd.array(
["b", "x"],
dtype=pd.StringDtype(storage="pyarrow", na_value=np.nan),
),
pd.array(["b", "x"], dtype=pd.ArrowDtype(pa.string())),
pa.array(["b", "x"]),
],
)
def test_isin_string_dtype_flavors(dtype, values):
# isin must match on element values regardless of which string dtype
# flavor the series and the values use (object, pd.StringDtype with
# python/pyarrow storage and NaN/NA na_value, pd.ArrowDtype string).
data = ["a", "b", "c"]
psr = pd.Series(data, dtype=dtype)
gsr = cudf.Series(data, dtype=dtype)

got = gsr.isin(values)
if isinstance(values, pa.Array) and (
dtype == np.dtype("object")
or (isinstance(dtype, pd.StringDtype) and dtype.storage == "python")
):
# pandas does not match pyarrow.Array values against
# object/python-storage series (returns all-False); cudf matches
# on element values.
assert got.to_pandas().tolist() == [False, True, False]
else:
expected = psr.isin(values)
assert_eq(got, expected)


@pytest.mark.parametrize(
"dtype",
[
np.dtype("object"),
pd.StringDtype(storage="python", na_value=pd.NA),
pd.StringDtype(storage="python", na_value=np.nan),
pd.StringDtype(storage="pyarrow", na_value=pd.NA),
pd.StringDtype(storage="pyarrow", na_value=np.nan),
pd.ArrowDtype(pa.string()),
pd.ArrowDtype(pa.large_string()),
],
)
def test_isin_string_null_values(dtype):
# cudf treats every NA-like value in ``values`` (None, pd.NA) as a
# match for nulls in the series. Results are compared with pandas
# except where pandas diverges: object dtype distinguishes the
# stored None from pd.NA, and ArrowDtype raises ArrowTypeError when
# the value set contains nulls.
data = ["a", "b", None]
psr = pd.Series(data, dtype=dtype)
gsr = cudf.Series(data, dtype=dtype)

got = gsr.isin([None])
if isinstance(dtype, pd.ArrowDtype):
assert got.to_pandas().tolist() == [False, False, True]
else:
assert_eq(got, psr.isin([None]))

got = gsr.isin(["a", pd.NA])
if isinstance(dtype, pd.ArrowDtype) or dtype == np.dtype("object"):
assert got.to_pandas().tolist() == [True, False, True]
else:
assert_eq(got, psr.isin(["a", pd.NA]))

assert_eq(gsr.isin([]), psr.isin([]))
Loading
Loading