Skip to content

Commit 67efbda

Browse files
committed
Make common impl. with Index.searchsorted
1 parent 117da18 commit 67efbda

File tree

4 files changed

+67
-11
lines changed

4 files changed

+67
-11
lines changed

doc/source/whatsnew/v0.24.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,7 @@ Performance Improvements
496496
both when indexing by label (using .loc) and position(.iloc).
497497
Likewise, slicing a ``CategoricalIndex`` itself (i.e. ``ci[100:200]``) shows similar speed improvements (:issue:`21659`)
498498
- Improved performance of :func:`Series.searchsorted` (:issue:`22034`)
499+
- Improved performance of :func:`Index.searchsorted` when dtype is uint64, float64 or object (:issue:`22034`)
499500
- Improved performance of :func:`Series.describe` in case of numeric dtypes (:issue:`21274`)
500501
- Improved performance of :func:`pandas.core.groupby.GroupBy.rank` when dealing with tied rankings (:issue:`21237`)
501502
- Improved performance of :func:`DataFrame.set_index` with columns consisting of :class:`Period` objects (:issue:`21582`,:issue:`21606`)

pandas/core/base.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
is_list_like,
1616
is_scalar,
1717
is_extension_type,
18-
is_extension_array_dtype)
18+
is_extension_array_dtype,
19+
ensure_platform_int)
1920

2021
from pandas.util._validators import validate_bool_kwarg
2122
from pandas.errors import AbstractMethodError
@@ -1230,7 +1231,9 @@ def factorize(self, sort=False, na_sentinel=-1):
12301231
@Appender(_shared_docs['searchsorted'])
12311232
@deprecate_kwarg(old_arg_name='key', new_arg_name='value')
12321233
def searchsorted(self, value, side='left', sorter=None):
1233-
# needs coercion on the key (DatetimeIndex does already)
1234+
if sorter is not None:
1235+
sorter = ensure_platform_int(sorter)
1236+
value = com.maybe_convert_numeric_dtype(value, dtype=self.dtype)
12341237
return self.values.searchsorted(value, side=side, sorter=sorter)
12351238

12361239
def drop_duplicates(self, keep='first', inplace=False):

pandas/core/common.py

+47-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
from pandas import compat
1616
from pandas.compat import iteritems, PY36, OrderedDict
1717
from pandas.core.dtypes.generic import ABCSeries, ABCIndex, ABCIndexClass
18-
from pandas.core.dtypes.common import is_integer
18+
from pandas.core.dtypes.common import (is_integer, is_integer_dtype, is_float,
19+
is_float_dtype, is_object_dtype,
20+
is_scalar)
1921
from pandas.core.dtypes.inference import _iterable_not_string
2022
from pandas.core.dtypes.missing import isna, isnull, notnull # noqa
2123
from pandas.core.dtypes.cast import construct_1d_object_array_from_listlike
@@ -430,3 +432,47 @@ def _pipe(obj, func, *args, **kwargs):
430432
return func(*args, **kwargs)
431433
else:
432434
return func(obj, *args, **kwargs)
435+
436+
437+
def maybe_convert_numeric_dtype(value, dtype):
438+
"""
439+
Convert value to have dtype 'dtype' if 'dtype' is int or float.
440+
441+
:func:`numpy.searchsorted` is only fast if value is of same dtype
442+
as the searched array. Below we ensure that value has the right
443+
dtype for giving fast results for arr.searchsorted.
444+
445+
Notes
446+
-----
447+
We do not recast the value if it is a float and the array is dtype int.
448+
This is because loc of float 2.1 should be behind loc of int 2,
449+
*not* before it.
450+
451+
Parameters
452+
----------
453+
value : scalar or list-like
454+
dtype : a dtype
455+
456+
Returns
457+
-------
458+
return_value : value as array with appropriate dtype for fast calling
459+
of numpy.searchsorted
460+
"""
461+
if is_float_dtype(dtype):
462+
value = np.asarray(value, dtype=dtype)
463+
elif is_integer_dtype(dtype):
464+
# check bounds
465+
iinfo = np.iinfo(dtype)
466+
iinfo_val = np.array([value]) if is_scalar(value) else value
467+
if (iinfo_val < iinfo.min).any() or (iinfo_val > iinfo.max).any():
468+
msg = "Value {} out of bound for dtype {}".format(value, dtype)
469+
raise ValueError(msg)
470+
471+
# convert value
472+
if is_integer(value) or is_integer_dtype(value):
473+
value = np.asarray(value, dtype=dtype)
474+
elif hasattr(value, 'is_integer') and value.is_integer():
475+
# float 2.0 can be converted to int 2
476+
# but float 2.2 should *not* be converted to int 2.0
477+
value = np.asarray(value, dtype=dtype)
478+
return value

pandas/core/series.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
is_categorical_dtype,
2020
is_bool,
2121
is_integer, is_integer_dtype,
22+
is_numeric_dtype,
2223
is_float_dtype,
2324
is_extension_type,
2425
is_extension_array_dtype,
@@ -2082,14 +2083,19 @@ def __rmatmul__(self, other):
20822083
def searchsorted(self, value, side='left', sorter=None):
20832084
if sorter is not None:
20842085
sorter = ensure_platform_int(sorter)
2085-
if not is_extension_type(self._values):
2086-
# numpy searchsorted is only fast if value is of same dtype as the
2087-
# searched array. Below we ensure that value has the right dtype,
2088-
# and is not 0-dimensional.
2089-
value = np.asarray(value, dtype=self._values.dtype)
2090-
value = value[..., np.newaxis] if value.ndim == 0 else value
2091-
2092-
return self._values.searchsorted(value, side=side, sorter=sorter)
2086+
2087+
if is_numeric_dtype(self):
2088+
value = com.maybe_convert_numeric_dtype(value, dtype=self.dtype)
2089+
elif not (is_object_dtype(self) or is_categorical_dtype(self)):
2090+
value = Series(value)._values
2091+
2092+
result = self._values.searchsorted(value, side=side, sorter=sorter)
2093+
2094+
if is_scalar(result):
2095+
# ensure that a 1-dim array is returned
2096+
result = np.array([result])
2097+
2098+
return result
20932099

20942100
# -------------------------------------------------------------------
20952101
# Combination

0 commit comments

Comments
 (0)