diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index 1bc1a28ef0fe9..7615f45a76d90 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -1319,6 +1319,7 @@ Datetimelike - Bug in :class:`DatetimeIndex` and :class:`TimedeltaIndex` where indexing with ``Ellipsis`` would incorrectly lose the index's ``freq`` attribute (:issue:`21282`) - Clarified error message produced when passing an incorrect ``freq`` argument to :class:`DatetimeIndex` with ``NaT`` as the first entry in the passed data (:issue:`11587`) - Bug in :func:`to_datetime` where ``box`` and ``utc`` arguments were ignored when passing a :class:`DataFrame` or ``dict`` of unit mappings (:issue:`23760`) +- Bug in :class:`PeriodIndex` where comparisons against an array-like object with length 1 failed to raise ``ValueError`` (:issue:`23078`) Timedelta ^^^^^^^^^ diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index f82004747f0d0..149bb07d23254 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1298,7 +1298,8 @@ def _ensure_datetimelike_to_i8(other, to_utc=False): if lib.is_scalar(other) and isna(other): return iNaT - elif isinstance(other, (PeriodArray, ABCIndexClass)): + elif isinstance(other, (PeriodArray, ABCIndexClass, + DatetimeLikeArrayMixin)): # convert tz if needed if getattr(other, 'tz', None) is not None: if to_utc: diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index a933f41faab67..f7a8bdb201bfd 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -97,6 +97,8 @@ def _dt_array_cmp(cls, op): def wrapper(self, other): meth = getattr(dtl.DatetimeLikeArrayMixin, opname) + other = lib.item_from_zerodim(other) + if isinstance(other, (datetime, np.datetime64, compat.string_types)): if isinstance(other, (datetime, np.datetime64)): # GH#18435 strings get a pass from tzawareness compat @@ -111,8 +113,10 @@ def wrapper(self, other): result = op(self.asi8, other.view('i8')) if isna(other): result.fill(nat_result) - elif lib.is_scalar(other): + elif lib.is_scalar(other) or np.ndim(other) == 0: return ops.invalid_comparison(self, other, op) + elif len(other) != len(self): + raise ValueError("Lengths must match") else: if isinstance(other, list): try: diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 5f4d98a81e5f2..2d7feb8f7ef92 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -56,6 +56,9 @@ def wrapper(self, other): # return here with an unboxed PeriodArray). But before we do that, # we do a bit of validation on type (Period) and freq, so that our # error messages are sensible + if is_list_like(other) and len(other) != len(self): + raise ValueError("Lengths must match") + not_implemented = isinstance(other, (ABCSeries, ABCIndexClass)) if not_implemented: other = other._values diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 06a9627a290c6..8721e0ce3ace5 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -89,6 +89,9 @@ def wrapper(self, other): elif not is_list_like(other): return ops.invalid_comparison(self, other, op) + elif len(other) != len(self): + raise ValueError("Lengths must match") + else: try: other = type(self)._from_sequence(other)._data diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index e0ed0ca28c6ff..fd0c904ba2245 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -5094,6 +5094,7 @@ def _evaluate_numeric_unary(self): attrs = self._maybe_update_attributes(attrs) return Index(op(self.values), **attrs) + _evaluate_numeric_unary.__name__ = opstr return _evaluate_numeric_unary cls.__neg__ = _make_evaluate_unary(operator.neg, '__neg__') diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 61bf73cbc280f..7cab52ddda87f 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -1390,6 +1390,7 @@ def f(self, other): return self + f.__name__ = "__i{name}__".format(name=method.__name__.strip("__")) return f new_methods.update( @@ -1574,6 +1575,7 @@ def wrapper(left, right): return construct_result(left, result, index=left.index, name=res_name, dtype=None) + wrapper.__name__ = op_name return wrapper @@ -1762,6 +1764,7 @@ def wrapper(self, other, axis=None): return self._constructor(res_values, index=self.index, name=res_name, dtype='bool') + wrapper.__name__ = op_name return wrapper diff --git a/pandas/core/series.py b/pandas/core/series.py index dacd587e7e73f..4e20d34ae690a 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -91,6 +91,7 @@ def wrapper(self): raise TypeError("cannot convert the series to " "{0}".format(str(converter))) + wrapper.__name__ = "__{name}__".format(name=converter.__name__) return wrapper # ---------------------------------------------------------------------- diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index 902a3dda92bd6..ebe84232d7f6d 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -59,6 +59,21 @@ def timedelta_index(request): class SharedTests(object): index_cls = None + def test_compare_len1_raises(self): + # make sure we raise when comparing with different lengths, specific + # to the case where one has length-1, which numpy would broadcast + data = np.arange(10, dtype='i8') + + idx = self.index_cls._simple_new(data, freq='D') + arr = self.array_cls(idx) + + with pytest.raises(ValueError, match="Lengths must match"): + arr == arr[:1] + + # test the index classes while we're at it, GH#23078 + with pytest.raises(ValueError, match="Lengths must match"): + idx <= idx[[0]] + def test_take(self): data = np.arange(100, dtype='i8') np.random.shuffle(data) @@ -119,6 +134,15 @@ class TestDatetimeArray(SharedTests): index_cls = pd.DatetimeIndex array_cls = DatetimeArray + def test_round(self, tz_naive_fixture): + # GH#24064 + tz = tz_naive_fixture + dti = pd.date_range('2016-01-01 01:01:00', periods=3, freq='H', tz=tz) + + result = dti.round(freq='2T') + expected = dti - pd.Timedelta(minutes=1) + tm.assert_index_equal(result, expected) + def test_array_object_dtype(self, tz_naive_fixture): # GH#23524 tz = tz_naive_fixture