diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index fe5e4a57c557a..6938065115f1f 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -1096,6 +1096,7 @@ Other API Changes has an improved ``KeyError`` message, and will not fail on duplicate column names with ``drop=True``. (:issue:`22484`) - Slicing a single row of a DataFrame with multiple ExtensionArrays of the same type now preserves the dtype, rather than coercing to object (:issue:`22784`) - :class:`DateOffset` attribute `_cacheable` and method `_should_cache` have been removed (:issue:`23118`) +- :meth:`Series.searchsorted`, when supplied a scalar value to search for, now returns a scalar instead of an array (:issue:`23801`). - :meth:`Categorical.searchsorted`, when supplied a scalar value to search for, now returns a scalar instead of an array (:issue:`23466`). - :meth:`Categorical.searchsorted` now raises a ``KeyError`` rather that a ``ValueError``, if a searched for key is not found in its categories (:issue:`23466`). - :meth:`Index.hasnans` and :meth:`Series.hasnans` now always return a python boolean. Previously, a python or a numpy boolean could be returned, depending on circumstances (:issue:`23294`). diff --git a/pandas/core/base.py b/pandas/core/base.py index 928e90977f95b..f3aa18674439b 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -1350,8 +1350,14 @@ def factorize(self, sort=False, na_sentinel=-1): Returns ------- - indices : array of ints - Array of insertion points with the same shape as `value`. + int or array of int + A scalar or array of insertion points with the + same shape as `value`. + + .. versionchanged :: 0.24.0 + If `value` is a scalar, an int is now always returned. + Previously, scalar inputs returned an 1-item array for + :class:`Series` and :class:`Categorical`. See Also -------- @@ -1372,7 +1378,7 @@ def factorize(self, sort=False, na_sentinel=-1): dtype: int64 >>> x.searchsorted(4) - array([3]) + 3 >>> x.searchsorted([0, 4]) array([0, 3]) @@ -1389,7 +1395,7 @@ def factorize(self, sort=False, na_sentinel=-1): Categories (4, object): [apple < bread < cheese < milk] >>> x.searchsorted('bread') - array([1]) # Note: an array, not a scalar + 1 >>> x.searchsorted(['bread'], side='right') array([3]) diff --git a/pandas/core/series.py b/pandas/core/series.py index 9ba9cdc818a5e..ec02a52c72c5d 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -2215,8 +2215,10 @@ def __rmatmul__(self, other): def searchsorted(self, value, side='left', sorter=None): if sorter is not None: sorter = ensure_platform_int(sorter) - return self._values.searchsorted(Series(value)._values, - side=side, sorter=sorter) + result = self._values.searchsorted(Series(value)._values, + side=side, sorter=sorter) + + return result[0] if is_scalar(value) else result # ------------------------------------------------------------------- # Combination diff --git a/pandas/tests/arrays/categorical/test_analytics.py b/pandas/tests/arrays/categorical/test_analytics.py index 4251273e424dd..b2c9151e1fa94 100644 --- a/pandas/tests/arrays/categorical/test_analytics.py +++ b/pandas/tests/arrays/categorical/test_analytics.py @@ -8,6 +8,7 @@ from pandas.compat import PYPY from pandas import Categorical, Index, Series +from pandas.api.types import is_scalar import pandas.util.testing as tm @@ -86,9 +87,11 @@ def test_searchsorted(self): # Searching for single item argument, side='left' (default) res_cat = c1.searchsorted('apple') assert res_cat == 2 + assert is_scalar(res_cat) res_ser = s1.searchsorted('apple') assert res_ser == 2 + assert is_scalar(res_ser) # Searching for single item array, side='left' (default) res_cat = c1.searchsorted(['bread']) diff --git a/pandas/tests/indexes/multi/test_monotonic.py b/pandas/tests/indexes/multi/test_monotonic.py index 3c7db70b7e242..72e9bcc1e2eb1 100644 --- a/pandas/tests/indexes/multi/test_monotonic.py +++ b/pandas/tests/indexes/multi/test_monotonic.py @@ -5,6 +5,7 @@ import pandas as pd from pandas import Index, IntervalIndex, MultiIndex +from pandas.api.types import is_scalar def test_is_monotonic_increasing(): @@ -182,22 +183,28 @@ def test_searchsorted_monotonic(indices): # test searchsorted only for increasing if indices.is_monotonic_increasing: ssm_left = indices._searchsorted_monotonic(value, side='left') + assert is_scalar(ssm_left) assert expected_left == ssm_left ssm_right = indices._searchsorted_monotonic(value, side='right') + assert is_scalar(ssm_right) assert expected_right == ssm_right ss_left = indices.searchsorted(value, side='left') + assert is_scalar(ss_left) assert expected_left == ss_left ss_right = indices.searchsorted(value, side='right') + assert is_scalar(ss_right) assert expected_right == ss_right elif indices.is_monotonic_decreasing: ssm_left = indices._searchsorted_monotonic(value, side='left') + assert is_scalar(ssm_left) assert expected_left == ssm_left ssm_right = indices._searchsorted_monotonic(value, side='right') + assert is_scalar(ssm_right) assert expected_right == ssm_right else: diff --git a/pandas/tests/series/test_analytics.py b/pandas/tests/series/test_analytics.py index 81d60aba44b0f..ab40bdc3ad6a0 100644 --- a/pandas/tests/series/test_analytics.py +++ b/pandas/tests/series/test_analytics.py @@ -16,6 +16,7 @@ from pandas import ( Categorical, CategoricalIndex, DataFrame, Series, bdate_range, compat, date_range, isna, notna) +from pandas.api.types import is_scalar from pandas.core.index import MultiIndex from pandas.core.indexes.datetimes import Timestamp from pandas.core.indexes.timedeltas import Timedelta @@ -1364,17 +1365,19 @@ def test_numpy_repeat(self): def test_searchsorted(self): s = Series([1, 2, 3]) - idx = s.searchsorted(1, side='left') - tm.assert_numpy_array_equal(idx, np.array([0], dtype=np.intp)) + result = s.searchsorted(1, side='left') + assert is_scalar(result) + assert result == 0 - idx = s.searchsorted(1, side='right') - tm.assert_numpy_array_equal(idx, np.array([1], dtype=np.intp)) + result = s.searchsorted(1, side='right') + assert is_scalar(result) + assert result == 1 def test_searchsorted_numeric_dtypes_scalar(self): s = Series([1, 2, 90, 1000, 3e9]) r = s.searchsorted(30) - e = 2 - assert r == e + assert is_scalar(r) + assert r == 2 r = s.searchsorted([30]) e = np.array([2], dtype=np.intp) @@ -1390,8 +1393,8 @@ def test_search_sorted_datetime64_scalar(self): s = Series(pd.date_range('20120101', periods=10, freq='2D')) v = pd.Timestamp('20120102') r = s.searchsorted(v) - e = 1 - assert r == e + assert is_scalar(r) + assert r == 1 def test_search_sorted_datetime64_list(self): s = Series(pd.date_range('20120101', periods=10, freq='2D'))