From 1eab96ff6cf0a26b971b4ba68a3831c1129a0b62 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 18 Dec 2017 17:53:20 -0800 Subject: [PATCH 1/9] Fix Series timedelta64 // timedelta --- doc/source/whatsnew/v0.22.0.txt | 1 + pandas/core/ops.py | 5 +++-- pandas/tests/series/test_operators.py | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index ae6d0816abc41..a01a146c22fff 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -346,3 +346,4 @@ Other ^^^^^ - Improved error message when attempting to use a Python keyword as an identifier in a ``numexpr`` backed query (:issue:`18221`) +- Bug in :func:`Series.__floordiv__` and :func:`Series.__rfloordiv__` where operating on a scalar ``timedelta`` raises an exception (:issue:`18824`) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 2fb0cbb14c225..39e7b5c8fe1ab 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -408,7 +408,7 @@ def _validate(self, lvalues, rvalues, name): if name not in ('__div__', '__rdiv__', '__truediv__', '__rtruediv__', '__add__', '__radd__', '__sub__', - '__rsub__'): + '__rsub__', '__floordiv__', '__rfloordiv__'): raise TypeError("can only operate on a timedeltas for addition" ", subtraction, and division, but the operator" " [{name}] was passed".format(name=name)) @@ -594,7 +594,8 @@ def _offset(lvalues, rvalues): # integer gets converted to timedelta in np < 1.6 if ((self.is_timedelta_lhs and self.is_timedelta_rhs) and not self.is_integer_rhs and not self.is_integer_lhs and - self.name in ('__div__', '__truediv__')): + self.name in ('__div__', '__truediv__', + '__floordiv__', '__rfloordiv__')): self.dtype = 'float64' self.fill_value = np.nan lvalues = lvalues.astype(np.float64) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index 89a6311153d15..ed2afb9bc89bd 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -977,8 +977,8 @@ def run_ops(ops, get_ser, test_ser): td1 = Series([timedelta(minutes=5, seconds=3)] * 3) td1.iloc[2] = np.nan td2 = timedelta(minutes=5, seconds=4) - ops = ['__mul__', '__floordiv__', '__pow__', '__rmul__', - '__rfloordiv__', '__rpow__'] + ops = ['__mul__', '__pow__', '__rmul__', + '__rpow__'] run_ops(ops, td1, td2) td1 + td2 td2 + td1 @@ -986,6 +986,8 @@ def run_ops(ops, get_ser, test_ser): td2 - td1 td1 / td2 td2 / td1 + tm.assert_series_equal(td1 // td2, Series([0, 0, np.nan])) + tm.assert_series_equal(td2 // td1, Series([1, 1, np.nan])) # ## datetime64 ### dt1 = Series([Timestamp('20111230'), Timestamp('20120101'), From 25071a8ab73cbf1b82726db37341dcae983097a4 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 18 Dec 2017 18:10:07 -0800 Subject: [PATCH 2/9] allow rtruediv --- pandas/core/ops.py | 3 ++- pandas/tests/series/test_operators.py | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index 39e7b5c8fe1ab..1497785ac7867 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -594,7 +594,8 @@ def _offset(lvalues, rvalues): # integer gets converted to timedelta in np < 1.6 if ((self.is_timedelta_lhs and self.is_timedelta_rhs) and not self.is_integer_rhs and not self.is_integer_lhs and - self.name in ('__div__', '__truediv__', + self.name in ('__div__', '__rdiv__', + '__truediv__', '__rtruediv__', '__floordiv__', '__rfloordiv__')): self.dtype = 'float64' self.fill_value = np.nan diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index ed2afb9bc89bd..3222bf8a299d1 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -977,8 +977,7 @@ def run_ops(ops, get_ser, test_ser): td1 = Series([timedelta(minutes=5, seconds=3)] * 3) td1.iloc[2] = np.nan td2 = timedelta(minutes=5, seconds=4) - ops = ['__mul__', '__pow__', '__rmul__', - '__rpow__'] + ops = ['__mul__', '__pow__', '__rmul__', '__rpow__'] run_ops(ops, td1, td2) td1 + td2 td2 + td1 From 45c7260d0ae641543c477eb60c556709b5afb079 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 18 Dec 2017 18:19:56 -0800 Subject: [PATCH 3/9] amend test case with pytimedelta, timedelta64 --- pandas/tests/series/test_operators.py | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index 3222bf8a299d1..d5f7c848b69d4 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -976,17 +976,27 @@ def run_ops(ops, get_ser, test_ser): # ## timedelta64 ### td1 = Series([timedelta(minutes=5, seconds=3)] * 3) td1.iloc[2] = np.nan - td2 = timedelta(minutes=5, seconds=4) + tdscalar = Timedelta(minutes=5, seconds=4) ops = ['__mul__', '__pow__', '__rmul__', '__rpow__'] - run_ops(ops, td1, td2) - td1 + td2 - td2 + td1 - td1 - td2 - td2 - td1 - td1 / td2 - td2 / td1 - tm.assert_series_equal(td1 // td2, Series([0, 0, np.nan])) - tm.assert_series_equal(td2 // td1, Series([1, 1, np.nan])) + run_ops(ops, td1, tdscalar) + td1 + tdscalar + tdscalar + td1 + td1 - tdscalar + tdscalar - td1 + td1 / tdscalar + tdscalar / td1 + tm.assert_series_equal(td1 // tdscalar, Series([0, 0, np.nan])) + tm.assert_series_equal(td1 // tdscalar.to_pytimedelta(), + Series([0, 0, np.nan])) + tm.assert_series_equal(td1 // tdscalar.to_timedelta64(), + Series([0, 0, np.nan])) + # TODO: the Timedelta // td1 fails because of a bug + # in Timedelta.__floordiv__, see #18824 + # tm.assert_series_equal(tdscalar // td1, Series([1, 1, np.nan])) + tm.assert_series_equal(tdscalar.to_pytimedelta() // td1, + Series([1, 1, np.nan])) + tm.assert_series_equal(tdscalar.to_timedelta64() // td1, + Series([1, 1, np.nan])) # ## datetime64 ### dt1 = Series([Timestamp('20111230'), Timestamp('20120101'), From 95cc5f91e4abb26a597bbdfca4d57415c6266482 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Mon, 18 Dec 2017 21:38:17 -0800 Subject: [PATCH 4/9] dummy commit to force CI --- pandas/tests/series/test_operators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index d5f7c848b69d4..a3989d4fbc8a9 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -991,7 +991,7 @@ def run_ops(ops, get_ser, test_ser): tm.assert_series_equal(td1 // tdscalar.to_timedelta64(), Series([0, 0, np.nan])) # TODO: the Timedelta // td1 fails because of a bug - # in Timedelta.__floordiv__, see #18824 + # in Timedelta.__floordiv__, see GH#18824 # tm.assert_series_equal(tdscalar // td1, Series([1, 1, np.nan])) tm.assert_series_equal(tdscalar.to_pytimedelta() // td1, Series([1, 1, np.nan])) From 4fe8f748f473633d0a857c70d44cc2cbd2fc0c73 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 20 Dec 2017 10:28:09 -0800 Subject: [PATCH 5/9] xfail test --- pandas/tests/series/test_operators.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index a3989d4fbc8a9..24834a4953616 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -962,6 +962,14 @@ def test_timedelta64_ops_nat(self): class TestDatetimeSeriesArithmetic(object): + @pytest.mark.xfail(reason='GH#18824 bug in Timedelta.__floordiv__') + def test_timedelta_rfloordiv(self): + td1 = Series([timedelta(minutes=5, seconds=3)] * 3) + td1.iloc[2] = np.nan + tdscalar = Timedelta(minutes=5, seconds=4) + tm.assert_series_equal(tdscalar // td1, + Series([1, 1, np.nan])) + def test_operators_datetimelike(self): def run_ops(ops, get_ser, test_ser): @@ -990,9 +998,6 @@ def run_ops(ops, get_ser, test_ser): Series([0, 0, np.nan])) tm.assert_series_equal(td1 // tdscalar.to_timedelta64(), Series([0, 0, np.nan])) - # TODO: the Timedelta // td1 fails because of a bug - # in Timedelta.__floordiv__, see GH#18824 - # tm.assert_series_equal(tdscalar // td1, Series([1, 1, np.nan])) tm.assert_series_equal(tdscalar.to_pytimedelta() // td1, Series([1, 1, np.nan])) tm.assert_series_equal(tdscalar.to_timedelta64() // td1, From 4821f05b43f93bb143c3fadc5f875097e7a16c08 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 20 Dec 2017 10:35:13 -0800 Subject: [PATCH 6/9] floordiv example in timedeltas.rst, move whatsnew note to conversion section --- doc/source/timedeltas.rst | 8 ++++++++ doc/source/whatsnew/v0.22.0.txt | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/source/timedeltas.rst b/doc/source/timedeltas.rst index d055c49dc4721..778db17a56b58 100644 --- a/doc/source/timedeltas.rst +++ b/doc/source/timedeltas.rst @@ -267,6 +267,14 @@ yields another ``timedelta64[ns]`` dtypes Series. td * -1 td * pd.Series([1, 2, 3, 4]) +Rounded division (floor-division) of a ``timedelta64[ns]`` Series by a scalar +``Timedelta`` gives a series of integers. + +.. ipython:: python + + td // pd.Timedelta(days=3, hours=4) + pd.Timedelta(days=3, hours=4) // td + Attributes ---------- diff --git a/doc/source/whatsnew/v0.22.0.txt b/doc/source/whatsnew/v0.22.0.txt index 01b9d1ba876a7..6b6d13db27f82 100644 --- a/doc/source/whatsnew/v0.22.0.txt +++ b/doc/source/whatsnew/v0.22.0.txt @@ -277,6 +277,7 @@ Conversion - Fixed a bug where ``FY5253`` date offsets could incorrectly raise an ``AssertionError`` in arithmetic operatons (:issue:`14774`) - Bug in :meth:`Index.astype` with a categorical dtype where the resultant index is not converted to a :class:`CategoricalIndex` for all types of index (:issue:`18630`) - Bug in :meth:`Series.astype` and ``Categorical.astype()`` where an existing categorical data does not get updated (:issue:`10696`, :issue:`18593`) +- Bug in :func:`Series.__floordiv__` and :func:`Series.__rfloordiv__` where operating on a scalar ``timedelta`` raises an exception (:issue:`18824`) Indexing @@ -354,4 +355,3 @@ Other - Improved error message when attempting to use a Python keyword as an identifier in a ``numexpr`` backed query (:issue:`18221`) - Bug in :class:`Timestamp` where comparison with an array of ``Timestamp`` objects would result in a ``RecursionError`` (:issue:`15183`) -- Bug in :func:`Series.__floordiv__` and :func:`Series.__rfloordiv__` where operating on a scalar ``timedelta`` raises an exception (:issue:`18824`) From 8789be7b2d47bb51fdb239af3b71592f098d2697 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sat, 23 Dec 2017 16:18:16 -0800 Subject: [PATCH 7/9] edit issue reference --- doc/source/whatsnew/v0.23.0.txt | 2 +- pandas/tests/series/test_operators.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v0.23.0.txt b/doc/source/whatsnew/v0.23.0.txt index e1fd0dd5a6183..573b3552bacb5 100644 --- a/doc/source/whatsnew/v0.23.0.txt +++ b/doc/source/whatsnew/v0.23.0.txt @@ -285,7 +285,7 @@ Conversion - Bug in :class:`Series` constructor with an int or float list where specifying ``dtype=str``, ``dtype='str'`` or ``dtype='U'`` failed to convert the data elements to strings (:issue:`16605`) - Bug in :class:`Timestamp` where comparison with an array of ``Timestamp`` objects would result in a ``RecursionError`` (:issue:`15183`) - Bug in :class:`WeekOfMonth` and class:`Week` where addition and subtraction did not roll correctly (:issue:`18510`,:issue:`18672`,:issue:`18864`) -- Bug in :func:`Series.__floordiv__` and :func:`Series.__rfloordiv__` where operating on a scalar ``timedelta`` raises an exception (:issue:`18824`) +- Bug in :class:`Series` floor-division where operating on a scalar ``timedelta`` raises an exception (:issue:`18846`) Indexing diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index 3b1517e38887c..65962b2d2920e 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -1004,7 +1004,7 @@ def test_operators_timedelta64_with_timedelta_invalid(self, scalar_td): class TestDatetimeSeriesArithmetic(object): - @pytest.mark.xfail(reason='GH#18824 bug in Timedelta.__floordiv__') + @pytest.mark.xfail(reason='GH#18846 bug in Timedelta.__floordiv__') def test_timedelta_rfloordiv(self): td1 = Series([timedelta(minutes=5, seconds=3)] * 3) td1.iloc[2] = np.nan From 2b3484f7a95127477bb406b9deb9b3eeaadb11c9 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 28 Dec 2017 09:42:44 -0800 Subject: [PATCH 8/9] re-implement tests lost in rebase/merge mixups --- pandas/core/ops.py | 2 +- pandas/tests/series/test_operators.py | 45 +++++++++++++++++++-------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/pandas/core/ops.py b/pandas/core/ops.py index d5e264f315a2c..b7ae9606c32a1 100644 --- a/pandas/core/ops.py +++ b/pandas/core/ops.py @@ -425,7 +425,7 @@ def _validate_timedelta(self, name): # 2 timedeltas if name not in ('__div__', '__rdiv__', '__truediv__', '__rtruediv__', '__add__', '__radd__', '__sub__', - '__rsub__'): + '__rsub__', '__floordiv__', '__rfloordiv__'): raise TypeError("can only operate on a timedeltas for addition" ", subtraction, and division, but the operator" " [{name}] was passed".format(name=name)) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index 565d86a611ff6..f4537550fa39d 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -977,9 +977,7 @@ def test_operators_timedelta64_with_timedelta(self, scalar_td): @pytest.mark.parametrize('scalar_td', [ timedelta(minutes=5, seconds=4), - pytest.param(Timedelta('5m4s'), - marks=pytest.mark.xfail(reason="Timedelta.__floordiv__ " - "bug GH#18846")), + Timedelta('5m4s'), Timedelta('5m4s').to_timedelta64()]) def test_operators_timedelta64_with_timedelta_invalid(self, scalar_td): td1 = Series([timedelta(minutes=5, seconds=3)] * 3) @@ -993,25 +991,46 @@ def test_operators_timedelta64_with_timedelta_invalid(self, scalar_td): td1 * scalar_td with tm.assert_raises_regex(TypeError, pattern): scalar_td * td1 - with tm.assert_raises_regex(TypeError, pattern): - td1 // scalar_td - with tm.assert_raises_regex(TypeError, pattern): - scalar_td // td1 with tm.assert_raises_regex(TypeError, pattern): scalar_td ** td1 with tm.assert_raises_regex(TypeError, pattern): td1 ** scalar_td + @pytest.mark.parametrize('scalar_td', [ + timedelta(minutes=5, seconds=4), + pytest.param(Timedelta('5m4s'), + marks=pytest.mark.xfail(reason="Timedelta.__floordiv__ " + "bug GH#18846")), + Timedelta('5m4s').to_timedelta64()]) + def test_timedelta_rfloordiv(self, scalar_td): + # GH#18831 + td1 = Series([timedelta(minutes=5, seconds=3)] * 3) + td1.iloc[2] = np.nan + result = scalar_td // td1 + expected = Series([1, 1, np.nan]) + tm.assert_series_equal(result, expected) -class TestDatetimeSeriesArithmetic(object): - @pytest.mark.xfail(reason='GH#18846 bug in Timedelta.__floordiv__') - def test_timedelta_rfloordiv(self): + @pytest.mark.parametrize('scalar_td', [ + timedelta(minutes=5, seconds=4), + Timedelta('5m4s'), + Timedelta('5m4s').to_timedelta64()]) + def test_timedelta_floordiv(self, scalar_td): + # GH#18831 td1 = Series([timedelta(minutes=5, seconds=3)] * 3) td1.iloc[2] = np.nan - tdscalar = Timedelta(minutes=5, seconds=4) - tm.assert_series_equal(tdscalar // td1, - Series([1, 1, np.nan])) + result = td1 // scalar_td + expected = Series([0, 0, np.nan]) + tm.assert_series_equal(result, expected) + + # We can test __rfloordiv__ using this syntax, + # see `test_timedelta_rfloordiv` + result = td1.__rfloordiv__(scalar_td) + expected = Series([1, 1, np.nan]) + tm.assert_series_equal(result, expected) + + +class TestDatetimeSeriesArithmetic(object): @pytest.mark.parametrize( 'box, assert_func', [(Series, tm.assert_series_equal), From 729d2402cd386baca20fa09dd2f36d74cc551906 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 29 Dec 2017 08:57:04 -0800 Subject: [PATCH 9/9] separate explicit rfloordiv test --- pandas/tests/series/test_operators.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index f4537550fa39d..b1d0a749246a7 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -1014,21 +1014,30 @@ def test_timedelta_rfloordiv(self, scalar_td): timedelta(minutes=5, seconds=4), Timedelta('5m4s'), Timedelta('5m4s').to_timedelta64()]) - def test_timedelta_floordiv(self, scalar_td): + def test_timedelta_rfloordiv_explicit(self, scalar_td): # GH#18831 td1 = Series([timedelta(minutes=5, seconds=3)] * 3) td1.iloc[2] = np.nan - result = td1 // scalar_td - expected = Series([0, 0, np.nan]) - tm.assert_series_equal(result, expected) - # We can test __rfloordiv__ using this syntax, # see `test_timedelta_rfloordiv` result = td1.__rfloordiv__(scalar_td) expected = Series([1, 1, np.nan]) tm.assert_series_equal(result, expected) + @pytest.mark.parametrize('scalar_td', [ + timedelta(minutes=5, seconds=4), + Timedelta('5m4s'), + Timedelta('5m4s').to_timedelta64()]) + def test_timedelta_floordiv(self, scalar_td): + # GH#18831 + td1 = Series([timedelta(minutes=5, seconds=3)] * 3) + td1.iloc[2] = np.nan + + result = td1 // scalar_td + expected = Series([0, 0, np.nan]) + tm.assert_series_equal(result, expected) + class TestDatetimeSeriesArithmetic(object): @pytest.mark.parametrize(