From bfb5cd2d42ad7d9b2cbec603ee78d7c810bfd10e Mon Sep 17 00:00:00 2001 From: Richard Shadrach Date: Fri, 19 Dec 2025 07:31:39 -0500 Subject: [PATCH 1/2] ENH: More usage of maybe_unbox_numpy_scalars --- pandas/core/frame.py | 3 ++- pandas/core/indexes/base.py | 8 +++++--- pandas/core/indexes/interval.py | 3 ++- pandas/core/indexes/multi.py | 11 ++++++++--- pandas/core/series.py | 17 +++++++++++------ pandas/tests/series/test_ufunc.py | 7 +++++-- 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 9d6af3c7b9917..c3fbcf32e62e6 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -90,6 +90,7 @@ infer_dtype_from_scalar, invalidate_string_dtypes, maybe_downcast_to_dtype, + maybe_unbox_numpy_scalar, ) from pandas.core.dtypes.common import ( infer_dtype_from_object, @@ -12169,7 +12170,7 @@ def _get_data() -> DataFrame: df = df.astype(dtype) arr = concat_compat(list(df._iter_column_arrays())) return arr._reduce(name, skipna=skipna, keepdims=False, **kwds) - return func(df.values) + return maybe_unbox_numpy_scalar(func(df.values)) elif axis == 1: if len(df.index) == 0: # Taking a transpose would result in no columns, losing the dtype. diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index dc37f3bfa6a4c..2402b9638a9ac 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -6815,7 +6815,7 @@ def _searchsorted_monotonic(self, label, side: Literal["left", "right"] = "left" pos = self[::-1].searchsorted( label, side="right" if side == "left" else "left" ) - return len(self) - pos + return maybe_unbox_numpy_scalar(len(self) - pos) raise ValueError("index must be monotonic increasing or decreasing") @@ -7005,6 +7005,8 @@ def slice_locs( if start_slice == -1: start_slice -= len(self) + start_slice = maybe_unbox_numpy_scalar(start_slice) + end_slice = maybe_unbox_numpy_scalar(end_slice) return start_slice, end_slice def delete( @@ -7412,7 +7414,7 @@ def any(self, *args, **kwargs): # i.e. EA, call _reduce instead of "any" to get TypeError instead # of AttributeError return vals._reduce("any") - return np.any(vals) + return maybe_unbox_numpy_scalar(np.any(vals)) def all(self, *args, **kwargs): """ @@ -7460,7 +7462,7 @@ def all(self, *args, **kwargs): # i.e. EA, call _reduce instead of "all" to get TypeError instead # of AttributeError return vals._reduce("all") - return np.all(vals) + return maybe_unbox_numpy_scalar(np.all(vals)) @final def _maybe_disable_logical_methods(self, opname: str_t) -> None: diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 1def317bc1a88..55c5002265b48 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -40,6 +40,7 @@ infer_dtype_from_scalar, maybe_box_datetimelike, maybe_downcast_numeric, + maybe_unbox_numpy_scalar, maybe_upcast_numeric_to_64bit, ) from pandas.core.dtypes.common import ( @@ -812,7 +813,7 @@ def get_loc(self, key) -> int | slice | np.ndarray: if matches == 0: raise KeyError(key) if matches == 1: - return mask.argmax() + return maybe_unbox_numpy_scalar(mask.argmax()) res = lib.maybe_booleans_to_slice(mask.view("u1")) if isinstance(res, slice) and res.stop is None: diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 90f710b0de4de..1d4cc16298720 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -58,7 +58,10 @@ ) from pandas.util._exceptions import find_stack_level -from pandas.core.dtypes.cast import coerce_indexer_dtype +from pandas.core.dtypes.cast import ( + coerce_indexer_dtype, + maybe_unbox_numpy_scalar, +) from pandas.core.dtypes.common import ( ensure_int64, ensure_platform_int, @@ -3130,7 +3133,9 @@ def get_slice_bound( """ if not isinstance(label, tuple): label = (label,) - return self._partial_tup_index(label, side=side) + result = self._partial_tup_index(label, side=side) + result = maybe_unbox_numpy_scalar(result) + return result def slice_locs(self, start=None, end=None, step=None) -> tuple[int, int]: """ @@ -3717,7 +3722,7 @@ def convert_indexer(start, stop, step, indexer=indexer, codes=level_codes): if start == end: # The label is present in self.levels[level] but unused: raise KeyError(key) - return slice(start, end) + return slice(maybe_unbox_numpy_scalar(start), maybe_unbox_numpy_scalar(end)) def get_locs(self, seq) -> npt.NDArray[np.intp]: """ diff --git a/pandas/core/series.py b/pandas/core/series.py index 512c24cc02f60..b339143b10ba1 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2667,7 +2667,7 @@ def quantile( return self._constructor(result, index=idx, name=self.name) else: # scalar - return result.iloc[0] + return maybe_unbox_numpy_scalar(result.iloc[0]) def corr( self, @@ -2754,9 +2754,11 @@ def corr( other_values = other.to_numpy(dtype=float, na_value=np.nan, copy=False) if method in ["pearson", "spearman", "kendall"] or callable(method): - return nanops.nancorr( + result = nanops.nancorr( this_values, other_values, method=method, min_periods=min_periods ) + result = maybe_unbox_numpy_scalar(result) + return result raise ValueError( "method must be either 'pearson', " @@ -2808,9 +2810,11 @@ def cov( return np.nan this_values = this.to_numpy(dtype=float, na_value=np.nan, copy=False) other_values = other.to_numpy(dtype=float, na_value=np.nan, copy=False) - return nanops.nancov( + result = nanops.nancov( this_values, other_values, min_periods=min_periods, ddof=ddof ) + result = maybe_unbox_numpy_scalar(result) + return result @doc( klass="Series", @@ -3023,11 +3027,12 @@ def dot(self, other: AnyArrayLike | DataFrame) -> Series | np.ndarray: np.dot(lvals, rvals), index=other.columns, copy=False, dtype=common_type ).__finalize__(self, method="dot") elif isinstance(other, Series): - return np.dot(lvals, rvals) + result = np.dot(lvals, rvals) elif isinstance(rvals, np.ndarray): - return np.dot(lvals, rvals) + result = np.dot(lvals, rvals) else: # pragma: no cover raise TypeError(f"unsupported type: {type(other)}") + return maybe_unbox_numpy_scalar(result) def __matmul__(self, other): """ @@ -5701,7 +5706,7 @@ def pop(self, item: Hashable) -> Any: 2 3 dtype: int64 """ - return super().pop(item=item) + return maybe_unbox_numpy_scalar(super().pop(item=item)) def info( self, diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index c3e536ba95053..14d4347d476ce 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -433,10 +433,13 @@ def test_np_matmul(): @pytest.mark.parametrize("box", [pd.Index, pd.Series]) -def test_np_matmul_1D(box): +def test_np_matmul_1D(box, using_python_scalars): result = np.matmul(box([1, 2]), box([2, 3])) assert result == 8 - assert isinstance(result, np.int64) + if using_python_scalars: + assert type(result) == int + else: + assert type(result) == np.int64 def test_array_ufuncs_for_many_arguments(): From c9cf0130e94e627a24f8f25b54337b9829725d1d Mon Sep 17 00:00:00 2001 From: Richard Shadrach Date: Fri, 19 Dec 2025 15:43:39 -0500 Subject: [PATCH 2/2] fixup --- pandas/core/indexes/base.py | 4 ++-- pandas/tests/series/test_ufunc.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 2402b9638a9ac..5d8895f505878 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -962,10 +962,10 @@ def __array_ufunc__(self, ufunc: np.ufunc, method: str_t, *inputs, **kwargs): return tuple(self.__array_wrap__(x) for x in result) elif method == "reduce": result = lib.item_from_zerodim(result) - return result + return maybe_unbox_numpy_scalar(result) elif is_scalar(result): # e.g. matmul - return result + return maybe_unbox_numpy_scalar(result) if result.dtype == np.float16: result = result.astype(np.float32) diff --git a/pandas/tests/series/test_ufunc.py b/pandas/tests/series/test_ufunc.py index 14d4347d476ce..9eaf1632528d8 100644 --- a/pandas/tests/series/test_ufunc.py +++ b/pandas/tests/series/test_ufunc.py @@ -437,9 +437,9 @@ def test_np_matmul_1D(box, using_python_scalars): result = np.matmul(box([1, 2]), box([2, 3])) assert result == 8 if using_python_scalars: - assert type(result) == int + assert type(result) == int, type(result) else: - assert type(result) == np.int64 + assert type(result) == np.int64, type(result) def test_array_ufuncs_for_many_arguments():