From b959b6c25d0c52d6e02eb518bc6c91214ced91e1 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 17 Jan 2019 20:42:47 -0800 Subject: [PATCH 1/8] remove checks that are never possible --- pandas/_libs/tslibs/timedeltas.pyx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pandas/_libs/tslibs/timedeltas.pyx b/pandas/_libs/tslibs/timedeltas.pyx index 0476ba1c78efc..c4f1ed5e62be6 100644 --- a/pandas/_libs/tslibs/timedeltas.pyx +++ b/pandas/_libs/tslibs/timedeltas.pyx @@ -178,16 +178,12 @@ cpdef convert_to_timedelta64(object ts, object unit): if ts == NPY_NAT: return np.timedelta64(NPY_NAT) else: - if util.is_array(ts): - ts = ts.astype('int64').item() if unit in ['Y', 'M', 'W']: ts = np.timedelta64(ts, unit) else: ts = cast_from_unit(ts, unit) ts = np.timedelta64(ts) elif is_float_object(ts): - if util.is_array(ts): - ts = ts.astype('int64').item() if unit in ['Y', 'M', 'W']: ts = np.timedelta64(int(ts), unit) else: From 2f02ae1016ed1af9840dfb044846af627f17ce8b Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 17 Jan 2019 20:46:24 -0800 Subject: [PATCH 2/8] implement+test tick rdiv --- pandas/_libs/tslibs/offsets.pyx | 8 ++++++++ pandas/tests/tseries/offsets/test_ticks.py | 24 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 856aa52f82cf5..9341ffbea7612 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -533,12 +533,20 @@ class _Tick(object): can do isinstance checks on _Tick and avoid importing tseries.offsets """ + # ensure that reversed-ops with numpy scalars return NotImplemented + __array_priority__ = 1000 + def __truediv__(self, other): result = self.delta.__truediv__(other) return _wrap_timedelta_result(result) + def __rtruediv__(self, other): + result = self.delta.__rtruediv__(other) + return _wrap_timedelta_result(result) + if PY2: __div__ = __truediv__ + __rdiv__ = __rtruediv__ # ---------------------------------------------------------------------- diff --git a/pandas/tests/tseries/offsets/test_ticks.py b/pandas/tests/tseries/offsets/test_ticks.py index f4b012ec1897f..09244b5d664d7 100644 --- a/pandas/tests/tseries/offsets/test_ticks.py +++ b/pandas/tests/tseries/offsets/test_ticks.py @@ -15,6 +15,8 @@ from pandas.tseries import offsets from pandas.tseries.offsets import Hour, Micro, Milli, Minute, Nano, Second +import pandas.util.testing as tm + from .common import assert_offset_equal # --------------------------------------------------------------------- @@ -262,6 +264,28 @@ def test_tick_division(cls): assert result.delta == off.delta / .001 +@pytest.mark.parametrize('cls', tick_classes) +def test_tick_rdiv(cls): + off = cls(10) + delta = off.delta + td64 = delta.to_timedelta64() + + with pytest.raises(TypeError): + 2 / off + with pytest.raises(TypeError): + 2.0 / off + + assert (td64 * 2.5) / off == 2.5 + + if cls is not Nano: + # skip pytimedelta for Nano since it gets dropped + assert (delta.to_pytimedelta() * 2) / off == 2 + + result = np.array([2 * td64, td64]) / off + expected = np.array([2., 1.]) + tm.assert_numpy_array_equal(result, expected) + + @pytest.mark.parametrize('cls1', tick_classes) @pytest.mark.parametrize('cls2', tick_classes) def test_tick_zero(cls1, cls2): From 1d67160a09b098e29259622be0dcc7e80748c839 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 18 Jan 2019 17:30:16 -0800 Subject: [PATCH 3/8] defer in mul --- pandas/_libs/tslibs/offsets.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 9341ffbea7612..9c574a247d259 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -408,6 +408,8 @@ class _BaseOffset(object): return self.apply(other) def __mul__(self, other): + if hasattr(other, "_typ"): + return NotImplemented return type(self)(n=other * self.n, normalize=self.normalize, **self.kwds) From 4476477d9bcf63ad9c80e7fbb809b50327632dc6 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 19 Jan 2019 14:41:12 -0800 Subject: [PATCH 4/8] multiplication against ndarray --- pandas/_libs/tslibs/offsets.pyx | 4 ++++ pandas/tests/tseries/offsets/test_offsets.py | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 9c574a247d259..06df56b74a6a9 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -18,6 +18,7 @@ from numpy cimport int64_t cnp.import_array() +from pandas._libs.tslibs cimport util from pandas._libs.tslibs.util cimport is_string_object, is_integer_object from pandas._libs.tslibs.ccalendar import MONTHS, DAYS @@ -410,6 +411,9 @@ class _BaseOffset(object): def __mul__(self, other): if hasattr(other, "_typ"): return NotImplemented + if util.is_array(other): + result = [self * x for x in other.ravel()] + return np.array(result).reshape(other.shape) return type(self)(n=other * self.n, normalize=self.normalize, **self.kwds) diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index ac3955970587f..0ca2786a27cc9 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -257,6 +257,17 @@ def test_offset_n(self, offset_types): mul_offset = offset * 3 assert mul_offset.n == 3 + def test_offset_mul_ndarray(self, offset_types): + off = self._get_offset(offset_types) + + expected = np.array([[off, off * 2], [off * 3, off * 4]]) + + result = np.array([[1, 2], [3, 4]]) * off + tm.assert_numpy_array_equal(result, expected) + + result = off * np.array([[1, 2], [3, 4]]) + tm.assert_numpy_array_equal(result, expected) + def test_offset_freqstr(self, offset_types): offset = self._get_offset(offset_types) From 1dad74c3b8c8e71f7d96c8ee3a7250fc741914fb Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 19 Jan 2019 17:17:30 -0800 Subject: [PATCH 5/8] reraise as typeerror --- pandas/_libs/tslibs/offsets.pyx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 06df56b74a6a9..e9acad6f1d4a2 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -412,8 +412,11 @@ class _BaseOffset(object): if hasattr(other, "_typ"): return NotImplemented if util.is_array(other): - result = [self * x for x in other.ravel()] - return np.array(result).reshape(other.shape) + try: + return np.array([self * x for x in other]) + except ValueError: + # raised in self._validate_n, re-raise as TypeError + raise TypeError return type(self)(n=other * self.n, normalize=self.normalize, **self.kwds) From 05974377a75dd822dfe5e57a3d1a0661adf88f15 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 20 Jan 2019 10:23:16 -0800 Subject: [PATCH 6/8] isort fixup --- pandas/tests/tseries/offsets/test_ticks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/tests/tseries/offsets/test_ticks.py b/pandas/tests/tseries/offsets/test_ticks.py index 09244b5d664d7..9a8251201f75f 100644 --- a/pandas/tests/tseries/offsets/test_ticks.py +++ b/pandas/tests/tseries/offsets/test_ticks.py @@ -11,12 +11,11 @@ import pytest from pandas import Timedelta, Timestamp +import pandas.util.testing as tm from pandas.tseries import offsets from pandas.tseries.offsets import Hour, Micro, Milli, Minute, Nano, Second -import pandas.util.testing as tm - from .common import assert_offset_equal # --------------------------------------------------------------------- From 9028a685964b9ffb06cbc50002c1dce295acf5cc Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 21 Jan 2019 18:20:13 -0800 Subject: [PATCH 7/8] fix+test passing td64 to offset --- pandas/_libs/tslibs/offsets.pyx | 9 ++++----- pandas/tests/tseries/offsets/test_offsets.py | 9 +++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index e9acad6f1d4a2..e28462f7103b9 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -412,11 +412,7 @@ class _BaseOffset(object): if hasattr(other, "_typ"): return NotImplemented if util.is_array(other): - try: - return np.array([self * x for x in other]) - except ValueError: - # raised in self._validate_n, re-raise as TypeError - raise TypeError + return np.array([self * x for x in other]) return type(self)(n=other * self.n, normalize=self.normalize, **self.kwds) @@ -467,6 +463,9 @@ class _BaseOffset(object): TypeError if `int(n)` raises ValueError if n != int(n) """ + if util.is_timedelta64_object(n): + raise TypeError('`n` argument must be an integer, ' + 'got {ntype}'.format(ntype=type(n))) try: nint = int(n) except (ValueError, TypeError): diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 0ca2786a27cc9..829799439718b 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -257,6 +257,15 @@ def test_offset_n(self, offset_types): mul_offset = offset * 3 assert mul_offset.n == 3 + def test_offset_timedelta64_arg(self, offset_types): + # check that offset._validate_n raises TypeError on a timedelt64 + # object + off = self._get_offset(offset_types) + + td64 = np.timedelta64(4567, 's') + with pytest.raises(TypeError, match="argument must be an integer"): + type(off)(n=td64, **td64.kwds) + def test_offset_mul_ndarray(self, offset_types): off = self._get_offset(offset_types) From 12e1c1b9e0bcc2f60ca4d4696f7c6c9b6dadec3f Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 21 Jan 2019 19:01:56 -0800 Subject: [PATCH 8/8] typo fixup --- pandas/tests/tseries/offsets/test_offsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/tseries/offsets/test_offsets.py b/pandas/tests/tseries/offsets/test_offsets.py index 829799439718b..621572da57541 100644 --- a/pandas/tests/tseries/offsets/test_offsets.py +++ b/pandas/tests/tseries/offsets/test_offsets.py @@ -264,7 +264,7 @@ def test_offset_timedelta64_arg(self, offset_types): td64 = np.timedelta64(4567, 's') with pytest.raises(TypeError, match="argument must be an integer"): - type(off)(n=td64, **td64.kwds) + type(off)(n=td64, **off.kwds) def test_offset_mul_ndarray(self, offset_types): off = self._get_offset(offset_types)