Skip to content
Merged
33 changes: 29 additions & 4 deletions python/cudf/cudf/core/column/column.py
Original file line number Diff line number Diff line change
Expand Up @@ -1342,7 +1342,7 @@ def all(
result = self._reduce(
"all", skipna=True, min_count=min_count, **kwargs
)
if np.isnan(result):
if result is pd.NA or np.isnan(result):
# Empty after dropping NaN/nulls - return np.bool_
result = np.bool_(True)

Expand All @@ -1365,20 +1365,40 @@ def any(
)
if self.size == 0:
return False
if not skipna and (self.has_nulls() or self.nan_count > 0):
is_masked_dtype = is_pandas_nullable_extension_dtype(self.dtype)
if not skipna and (
self.nan_count > 0 or (not is_masked_dtype and self.has_nulls())
):
# NaN values (and the NaN null sentinel of numpy dtypes) are
# truthy. For pandas nullable extension dtypes <NA> is not
# truthy; Kleene logic below decides between True and <NA>.
return True
elif skipna and self.null_count == self.size:
if self.null_count == self.size:
if not skipna:
# All-null nullable column with skipna=False: Kleene
# any([NA, ...]) with no True values is <NA>.
return _get_nan_for_dtype(self.dtype)
return False

# For any(), we want NaN values to be treated as truthy.
# Call _reduce() with skipna=True to get the boolean result.
result = self._reduce(
"any", skipna=True, min_count=min_count, **kwargs
)
if np.isnan(result):
if result is pd.NA or np.isnan(result):
# Empty after dropping NaN/nulls
# If skipna=False, NaN values should be treated as truthy
result = np.bool_(not skipna)

# For pandas nullable extension dtypes with skipna=False, a False
# result in the presence of nulls is <NA> under Kleene logic.
if (
not result
and not skipna
and self.null_count > 0
and is_masked_dtype
):
return _get_nan_for_dtype(self.dtype)
return result

def dropna(self) -> Self:
Expand Down Expand Up @@ -2960,6 +2980,11 @@ def _reduce(
return col_dtype.type(0)
if op == "product":
return col_dtype.type(1)
if is_pandas_nullable_extension_dtype(self.dtype):
# pandas returns <NA> for empty/all-null reductions of
# nullable dtypes even when the reduction result dtype is
# a plain numpy dtype (e.g. Int64.mean() -> float64).
return _get_nan_for_dtype(self.dtype)
return _get_nan_for_dtype(col_dtype)

# Perform the actual reduction
Expand Down
13 changes: 7 additions & 6 deletions python/cudf/cudf/core/column/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,13 @@ def all(
self, skipna: bool = True, min_count: int = 0, **kwargs: Any
) -> ScalarLike:
"""Check if all string values are truthy (non-empty)."""
if skipna and self.null_count == self.size:
return True
elif not skipna and self.has_nulls():
# pandas 3 treats the NaN null sentinel as truthy, matching
# numpy semantics, so all(skipna=False) returns True when all
# values are null.
if self.null_count == self.size:
# With skipna=True nulls are dropped, so all-null is vacuously
# True. pandas 3 treats the NaN null sentinel as truthy,
# matching numpy semantics, so all(skipna=False) is True too.
# A partially-null column must NOT short-circuit here: the
# result depends on the truthiness of the non-null strings
# (e.g. all([NaN, ""], skipna=False) is False).
return True
raise NotImplementedError("`all` not implemented for `StringColumn`")

Expand Down
16 changes: 15 additions & 1 deletion python/cudf/cudf/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -2857,8 +2857,22 @@ def mode(self, dropna=True):
if len(val_counts) > 0:
val_counts = val_counts[val_counts == val_counts.iloc[0]]

# pandas sorts mode results on the underlying representation:
# NaT (INT64_MIN as i8) and the categorical null code (-1) sort
# before valid values, while float NaN and the <NA> of
# nullable/arrow dtypes (including arrow timestamps/durations)
# sort last.
na_position = (
"first"
if (
self.dtype.kind in "mM"
and isinstance(self.dtype, (np.dtype, pd.DatetimeTZDtype))
)
or isinstance(self.dtype, cudf.CategoricalDtype)
else "last"
)
return Series._from_column(
val_counts.index.sort_values()._column,
val_counts.index.sort_values(na_position=na_position)._column,
name=self.name,
attrs=self.attrs,
)
Expand Down
Loading
Loading