From 769a0f93937e49790aba9288334cdcca875bb25f Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 21 Dec 2018 14:50:06 -0800 Subject: [PATCH 1/8] Fix+test DatetimeArray.round for tz-aware --- pandas/core/arrays/datetimelike.py | 3 ++- pandas/tests/arrays/test_datetimelike.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index c01b04991e52b..866dc348b44f9 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -1263,7 +1263,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/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index 902a3dda92bd6..ee9d264f8c806 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -119,6 +119,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 From 109671a4f3044c76cbabf53263b0d8f57ad5d521 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 21 Dec 2018 15:02:22 -0800 Subject: [PATCH 2/8] Fix+test failure to raise with mismatch ed length --- pandas/core/arrays/datetimes.py | 3 +++ pandas/core/arrays/period.py | 3 +++ pandas/core/arrays/timedeltas.py | 3 +++ pandas/tests/arrays/test_datetimelike.py | 11 +++++++++++ 4 files changed, 20 insertions(+) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index c197d6d6e634b..b80ad94b898ea 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -113,6 +113,9 @@ def wrapper(self, other): elif lib.is_scalar(other): return ops.invalid_comparison(self, other, op) else: + if len(other) != len(self): + raise ValueError("Lengths must match") + if isinstance(other, list): try: other = type(self)(other) diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 60febc5f5636d..ac2db6521bf6b 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -57,6 +57,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 a5d074df338ee..b7907a37e0c56 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -90,6 +90,9 @@ def wrapper(self, other): return ops.invalid_comparison(self, other, op) else: + if len(other) != len(self): + raise ValueError("Lengths must match") + try: other = type(self)._from_sequence(other)._data except (ValueError, TypeError): diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index ee9d264f8c806..c3890047765cc 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -59,6 +59,17 @@ 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] + def test_take(self): data = np.arange(100, dtype='i8') np.random.shuffle(data) From be221d78930f0a3c9c7ed762321f2ff6eb30294b Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 21 Dec 2018 16:12:43 -0800 Subject: [PATCH 3/8] Set function names --- pandas/core/indexes/base.py | 1 + pandas/core/ops.py | 3 +++ pandas/core/series.py | 1 + 3 files changed, 5 insertions(+) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index a2cf88fa9cb1a..b6cb30514eaeb 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -5085,6 +5085,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 d642a221e4494..ff8d242e78ab1 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 # ---------------------------------------------------------------------- From 6361a32e04e75851c2f4b599f52a99ae9d1ccf62 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 22 Dec 2018 09:42:04 -0800 Subject: [PATCH 4/8] test index classes explicitly --- pandas/tests/arrays/test_datetimelike.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pandas/tests/arrays/test_datetimelike.py b/pandas/tests/arrays/test_datetimelike.py index c3890047765cc..ebe84232d7f6d 100644 --- a/pandas/tests/arrays/test_datetimelike.py +++ b/pandas/tests/arrays/test_datetimelike.py @@ -70,6 +70,10 @@ def test_compare_len1_raises(self): 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) From 12a4c25885856f91264cb15ca86b0e9813f5a5bc Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 22 Dec 2018 10:30:05 -0800 Subject: [PATCH 5/8] catch odd objects in py3 --- pandas/core/arrays/datetimes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index b80ad94b898ea..3564a2f5eec71 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -110,7 +110,7 @@ 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) else: if len(other) != len(self): From dc37dfb94f8eca5af3de9c903d1547192bf7a50d Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 22 Dec 2018 10:41:19 -0800 Subject: [PATCH 6/8] py2/py3 compat test fixes --- pandas/core/arrays/datetimes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 3564a2f5eec71..2ba3639e46f66 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -96,6 +96,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 From 0b0afd987111a987934e4b6db1ad9d4cdb753fb2 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 23 Dec 2018 08:50:44 -0800 Subject: [PATCH 7/8] move checks to separate elifs --- pandas/core/arrays/datetimes.py | 5 ++--- pandas/core/arrays/timedeltas.py | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 2ba3639e46f66..e3e7d8156c2d1 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -114,10 +114,9 @@ def wrapper(self, other): result.fill(nat_result) 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 len(other) != len(self): - raise ValueError("Lengths must match") - if isinstance(other, list): try: other = type(self)(other) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index b7907a37e0c56..00d0ead551f27 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -89,10 +89,10 @@ def wrapper(self, other): elif not is_list_like(other): return ops.invalid_comparison(self, other, op) - else: - if len(other) != len(self): - raise ValueError("Lengths must match") + elif len(other) != len(self): + raise ValueError("Lengths must match") + else: try: other = type(self)._from_sequence(other)._data except (ValueError, TypeError): From c38be83b9c6eb307b2e878b1be5379f774bd95b3 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 23 Dec 2018 08:52:11 -0800 Subject: [PATCH 8/8] whatsnew --- doc/source/whatsnew/v0.24.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v0.24.0.rst b/doc/source/whatsnew/v0.24.0.rst index 724cfddb1b94c..426acb412d7c4 100644 --- a/doc/source/whatsnew/v0.24.0.rst +++ b/doc/source/whatsnew/v0.24.0.rst @@ -1317,6 +1317,7 @@ Datetimelike - Bug in :class:`DatetimeIndex` where constructing a :class:`DatetimeIndex` from a :class:`Categorical` or :class:`CategoricalIndex` would incorrectly drop timezone information (:issue:`18664`) - 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 :class:`PeriodIndex` where comparisons against an array-like object with length 1 failed to raise ``ValueError`` (:issue:`23078`) Timedelta ^^^^^^^^^