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
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.1.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Categorical
Datetimelike
^^^^^^^^^^^^
- Bug in :class:`Timestamp` where constructing :class:`Timestamp` from ambiguous epoch time and calling constructor again changed :meth:`Timestamp.value` property (:issue:`24329`)
-
- :meth:`DatetimeArray.searchsorted`, :meth:`TimedeltaArray.searchsorted`, :meth:`PeriodArray.searchsorted` not recognizing non-pandas scalars and incorrectly raising ``ValueError`` instead of ``TypeError`` (:issue:`30950`)
-

Timedelta
Expand Down
27 changes: 23 additions & 4 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,17 +743,36 @@ def searchsorted(self, value, side="left", sorter=None):
Array of insertion points with the same shape as `value`.
"""
if isinstance(value, str):
value = self._scalar_from_string(value)
try:
value = self._scalar_from_string(value)
except ValueError:
raise TypeError("searchsorted requires compatible dtype or scalar")

elif is_valid_nat_for_dtype(value, self.dtype):
value = NaT

elif isinstance(value, self._recognized_scalars):
value = self._scalar_type(value)

elif isinstance(value, np.ndarray):
if not type(self)._is_recognized_dtype(value):
raise TypeError(
"searchsorted requires compatible dtype or scalar, "
f"not {type(value).__name__}"
)
value = type(self)(value)
self._check_compatible_with(value)

if not (isinstance(value, (self._scalar_type, type(self))) or isna(value)):
raise ValueError(f"Unexpected type for 'value': {type(value)}")
if not (isinstance(value, (self._scalar_type, type(self))) or (value is NaT)):
raise TypeError(f"Unexpected type for 'value': {type(value)}")

self._check_compatible_with(value)
if isinstance(value, type(self)):
self._check_compatible_with(value)
value = value.asi8
else:
value = self._unbox_scalar(value)

# TODO: Use datetime64 semantics for sorting, xref GH#29844
return self.asi8.searchsorted(value, side=side, sorter=sorter)

def repeat(self, repeats, *args, **kwargs):
Expand Down
17 changes: 3 additions & 14 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,24 +826,13 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None):
@Substitution(klass="DatetimeIndex")
@Appender(_shared_docs["searchsorted"])
def searchsorted(self, value, side="left", sorter=None):
if isinstance(value, (np.ndarray, Index)):
if not type(self._data)._is_recognized_dtype(value):
raise TypeError(
"searchsorted requires compatible dtype or scalar, "
f"not {type(value).__name__}"
)
value = type(self._data)(value)
self._data._check_compatible_with(value)

elif isinstance(value, self._data._recognized_scalars):
self._data._check_compatible_with(value)
value = self._data._scalar_type(value)

elif not isinstance(value, DatetimeArray):
if isinstance(value, str):
raise TypeError(
"searchsorted requires compatible dtype or scalar, "
f"not {type(value).__name__}"
)
if isinstance(value, Index):
value = value._data

return self._data.searchsorted(value, side=side)

Expand Down
12 changes: 0 additions & 12 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -469,18 +469,6 @@ def astype(self, dtype, copy=True, how="start"):
@Substitution(klass="PeriodIndex")
@Appender(_shared_docs["searchsorted"])
def searchsorted(self, value, side="left", sorter=None):
if isinstance(value, Period) or value is NaT:
self._data._check_compatible_with(value)
elif isinstance(value, str):
try:
value = Period(value, freq=self.freq)
except DateParseError:
raise KeyError(f"Cannot interpret '{value}' as period")
elif not isinstance(value, PeriodArray):
raise TypeError(
"PeriodIndex.searchsorted requires either a Period or PeriodArray"
)

return self._data.searchsorted(value, side=side, sorter=sorter)

@property
Expand Down
17 changes: 3 additions & 14 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,24 +347,13 @@ def _partial_td_slice(self, key):
@Substitution(klass="TimedeltaIndex")
@Appender(_shared_docs["searchsorted"])
def searchsorted(self, value, side="left", sorter=None):
if isinstance(value, (np.ndarray, Index)):
if not type(self._data)._is_recognized_dtype(value):
raise TypeError(
"searchsorted requires compatible dtype or scalar, "
f"not {type(value).__name__}"
)
value = type(self._data)(value)
self._data._check_compatible_with(value)

elif isinstance(value, self._data._recognized_scalars):
self._data._check_compatible_with(value)
value = self._data._scalar_type(value)

elif not isinstance(value, TimedeltaArray):
if isinstance(value, str):
raise TypeError(
"searchsorted requires compatible dtype or scalar, "
f"not {type(value).__name__}"
)
if isinstance(value, Index):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can likely move this to the base class an use shared impl with Timedelta (and I guess with DTI?)

value = value._data

return self._data.searchsorted(value, side=side, sorter=sorter)

Expand Down
20 changes: 7 additions & 13 deletions pandas/tests/arrays/test_datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,25 +331,19 @@ def test_searchsorted_tzawareness_compat(self, index):
pd.Timestamp.now().to_period("D"),
],
)
@pytest.mark.parametrize(
"index",
[
True,
pytest.param(
False,
marks=pytest.mark.xfail(
reason="Raises ValueError instead of TypeError", raises=ValueError
),
),
],
)
@pytest.mark.parametrize("index", [True, False])
def test_searchsorted_invalid_types(self, other, index):
data = np.arange(10, dtype="i8") * 24 * 3600 * 10 ** 9
arr = DatetimeArray(data, freq="D")
if index:
arr = pd.Index(arr)

msg = "searchsorted requires compatible dtype or scalar"
msg = "|".join(
[
"searchsorted requires compatible dtype or scalar",
"Unexpected type for 'value'",
]
)
with pytest.raises(TypeError, match=msg):
arr.searchsorted(other)

Expand Down
20 changes: 7 additions & 13 deletions pandas/tests/arrays/test_timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,25 +154,19 @@ def test_setitem_objects(self, obj):
pd.Timestamp.now().to_period("D"),
],
)
@pytest.mark.parametrize(
"index",
[
True,
pytest.param(
False,
marks=pytest.mark.xfail(
reason="Raises ValueError instead of TypeError", raises=ValueError
),
),
],
)
@pytest.mark.parametrize("index", [True, False])
def test_searchsorted_invalid_types(self, other, index):
data = np.arange(10, dtype="i8") * 24 * 3600 * 10 ** 9
arr = TimedeltaArray(data, freq="D")
if index:
arr = pd.Index(arr)

msg = "searchsorted requires compatible dtype or scalar"
msg = "|".join(
[
"searchsorted requires compatible dtype or scalar",
"Unexpected type for 'value'",
]
)
with pytest.raises(TypeError, match=msg):
arr.searchsorted(other)

Expand Down
7 changes: 6 additions & 1 deletion pandas/tests/indexes/period/test_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,12 @@ def test_searchsorted_invalid(self):

other = np.array([0, 1], dtype=np.int64)

msg = "requires either a Period or PeriodArray"
msg = "|".join(
[
"searchsorted requires compatible dtype or scalar",
"Unexpected type for 'value'",
]
)
with pytest.raises(TypeError, match=msg):
pidx.searchsorted(other)

Expand Down