From 7c67d9c990465953cac19427a44cdfc0210747a3 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Tue, 31 Jul 2018 05:23:41 -0700 Subject: [PATCH] Centralize m8[ns] Arithmetic Tests (#22118) --- .../indexes/timedeltas/test_arithmetic.py | 72 -- pandas/tests/series/test_arithmetic.py | 349 +------- pandas/tests/test_arithmetic.py | 753 +++++++++++++++++- 3 files changed, 743 insertions(+), 431 deletions(-) diff --git a/pandas/tests/indexes/timedeltas/test_arithmetic.py b/pandas/tests/indexes/timedeltas/test_arithmetic.py index d47d75d2f3485..a5e75de2a267e 100644 --- a/pandas/tests/indexes/timedeltas/test_arithmetic.py +++ b/pandas/tests/indexes/timedeltas/test_arithmetic.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -import operator import pytest import numpy as np @@ -13,7 +12,6 @@ Series, Timestamp, Timedelta) from pandas.errors import PerformanceWarning, NullFrequencyError -from pandas.core import ops @pytest.fixture(params=[pd.offsets.Hour(2), timedelta(hours=2), @@ -270,53 +268,6 @@ def test_tdi_floordiv_timedelta_scalar(self, scalar_td): class TestTimedeltaIndexArithmetic(object): # Addition and Subtraction Operations - # ------------------------------------------------------------- - # Invalid Operations - - @pytest.mark.parametrize('other', [3.14, np.array([2.0, 3.0])]) - @pytest.mark.parametrize('op', [operator.add, ops.radd, - operator.sub, ops.rsub]) - def test_tdi_add_sub_float(self, op, other): - dti = DatetimeIndex(['2011-01-01', '2011-01-02'], freq='D') - tdi = dti - dti.shift(1) - with pytest.raises(TypeError): - op(tdi, other) - - def test_tdi_add_str_invalid(self): - # GH 13624 - tdi = TimedeltaIndex(['1 day', '2 days']) - - with pytest.raises(TypeError): - tdi + 'a' - with pytest.raises(TypeError): - 'a' + tdi - - @pytest.mark.parametrize('freq', [None, 'H']) - def test_tdi_sub_period(self, freq): - # GH#13078 - # not supported, check TypeError - p = pd.Period('2011-01-01', freq='D') - - idx = pd.TimedeltaIndex(['1 hours', '2 hours'], freq=freq) - - with pytest.raises(TypeError): - idx - p - - with pytest.raises(TypeError): - p - idx - - @pytest.mark.parametrize('op', [operator.add, ops.radd, - operator.sub, ops.rsub]) - @pytest.mark.parametrize('pi_freq', ['D', 'W', 'Q', 'H']) - @pytest.mark.parametrize('tdi_freq', [None, 'H']) - def test_dti_sub_pi(self, tdi_freq, pi_freq, op): - # GH#20049 subtracting PeriodIndex should raise TypeError - tdi = pd.TimedeltaIndex(['1 hours', '2 hours'], freq=tdi_freq) - dti = pd.Timestamp('2018-03-07 17:16:40') + tdi - pi = dti.to_period(pi_freq) - with pytest.raises(TypeError): - op(dti, pi) - # ------------------------------------------------------------- # TimedeltaIndex.shift is used by __add__/__sub__ @@ -626,29 +577,6 @@ def test_tdi_isub_timedeltalike(self, delta): rng -= delta tm.assert_index_equal(rng, expected) - # ------------------------------------------------------------- - # Binary operations TimedeltaIndex and datetime-like - - def test_tdi_sub_timestamp_raises(self): - idx = TimedeltaIndex(['1 day', '2 day']) - msg = "cannot subtract a datelike from a TimedeltaIndex" - with tm.assert_raises_regex(TypeError, msg): - idx - Timestamp('2011-01-01') - - def test_tdi_add_timestamp(self): - idx = TimedeltaIndex(['1 day', '2 day']) - - result = idx + Timestamp('2011-01-01') - expected = DatetimeIndex(['2011-01-02', '2011-01-03']) - tm.assert_index_equal(result, expected) - - def test_tdi_radd_timestamp(self): - idx = TimedeltaIndex(['1 day', '2 day']) - - result = Timestamp('2011-01-01') + idx - expected = DatetimeIndex(['2011-01-02', '2011-01-03']) - tm.assert_index_equal(result, expected) - # ------------------------------------------------------------- # __add__/__sub__ with ndarray[datetime64] and ndarray[timedelta64] diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index 2571498ca802c..c091df63fcfc7 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -6,21 +6,13 @@ import numpy as np import pytest -from pandas import Series, Timestamp, Timedelta, Period, NaT +from pandas import Series, Timestamp, Period from pandas._libs.tslibs.period import IncompatibleFrequency import pandas as pd import pandas.util.testing as tm -@pytest.fixture -def tdser(): - """ - Return a Series with dtype='timedelta64[ns]', including a NaT. - """ - return Series(['59 Days', '59 Days', 'NaT'], dtype='timedelta64[ns]') - - # ------------------------------------------------------------------ # Comparisons @@ -552,342 +544,3 @@ def test_dt64ser_sub_datetime_dtype(self): ser = Series([ts]) result = pd.to_timedelta(np.abs(ser - dt)) assert result.dtype == 'timedelta64[ns]' - - -class TestTimedeltaSeriesAdditionSubtraction(object): - # Tests for Series[timedelta64[ns]] __add__, __sub__, __radd__, __rsub__ - - # ------------------------------------------------------------------ - # Operations with int-like others - - def test_td64series_add_int_series_invalid(self, tdser): - with pytest.raises(TypeError): - tdser + Series([2, 3, 4]) - - @pytest.mark.xfail(reason='GH#19123 integer interpreted as nanoseconds') - def test_td64series_radd_int_series_invalid(self, tdser): - with pytest.raises(TypeError): - Series([2, 3, 4]) + tdser - - def test_td64series_sub_int_series_invalid(self, tdser): - with pytest.raises(TypeError): - tdser - Series([2, 3, 4]) - - @pytest.mark.xfail(reason='GH#19123 integer interpreted as nanoseconds') - def test_td64series_rsub_int_series_invalid(self, tdser): - with pytest.raises(TypeError): - Series([2, 3, 4]) - tdser - - def test_td64_series_add_intlike(self): - # GH#19123 - tdi = pd.TimedeltaIndex(['59 days', '59 days', 'NaT']) - ser = Series(tdi) - - other = Series([20, 30, 40], dtype='uint8') - - pytest.raises(TypeError, ser.__add__, 1) - pytest.raises(TypeError, ser.__sub__, 1) - - pytest.raises(TypeError, ser.__add__, other) - pytest.raises(TypeError, ser.__sub__, other) - - pytest.raises(TypeError, ser.__add__, other.values) - pytest.raises(TypeError, ser.__sub__, other.values) - - pytest.raises(TypeError, ser.__add__, pd.Index(other)) - pytest.raises(TypeError, ser.__sub__, pd.Index(other)) - - @pytest.mark.parametrize('scalar', [1, 1.5, np.array(2)]) - def test_td64series_add_sub_numeric_scalar_invalid(self, scalar, tdser): - with pytest.raises(TypeError): - tdser + scalar - with pytest.raises(TypeError): - scalar + tdser - with pytest.raises(TypeError): - tdser - scalar - with pytest.raises(TypeError): - scalar - tdser - - @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', - 'uint64', 'uint32', 'uint16', 'uint8', - 'float64', 'float32', 'float16']) - @pytest.mark.parametrize('vector', [ - np.array([1, 2, 3]), - pd.Index([1, 2, 3]), - pytest.param(Series([1, 2, 3]), - marks=pytest.mark.xfail(reason='GH#19123 integer ' - 'interpreted as nanos')) - ]) - def test_td64series_add_sub_numeric_array_invalid(self, vector, - dtype, tdser): - vector = vector.astype(dtype) - with pytest.raises(TypeError): - tdser + vector - with pytest.raises(TypeError): - vector + tdser - with pytest.raises(TypeError): - tdser - vector - with pytest.raises(TypeError): - vector - tdser - - # ------------------------------------------------------------------ - # Operations with datetime-like others - - def test_td64series_add_sub_timestamp(self): - # GH#11925 - tdser = Series(pd.timedelta_range('1 day', periods=3)) - ts = Timestamp('2012-01-01') - expected = Series(pd.date_range('2012-01-02', periods=3)) - tm.assert_series_equal(ts + tdser, expected) - tm.assert_series_equal(tdser + ts, expected) - - expected2 = Series(pd.date_range('2011-12-31', periods=3, freq='-1D')) - tm.assert_series_equal(ts - tdser, expected2) - tm.assert_series_equal(ts + (-tdser), expected2) - - with pytest.raises(TypeError): - tdser - ts - - # ------------------------------------------------------------------ - # Operations with timedelta-like others (including DateOffsets) - - @pytest.mark.parametrize('names', [(None, None, None), - ('Egon', 'Venkman', None), - ('NCC1701D', 'NCC1701D', 'NCC1701D')]) - def test_td64_series_with_tdi(self, names): - # GH#17250 make sure result dtype is correct - # GH#19043 make sure names are propagated correctly - tdi = pd.TimedeltaIndex(['0 days', '1 day'], name=names[0]) - ser = Series([Timedelta(hours=3), Timedelta(hours=4)], name=names[1]) - expected = Series([Timedelta(hours=3), Timedelta(days=1, hours=4)], - name=names[2]) - - result = tdi + ser - tm.assert_series_equal(result, expected) - assert result.dtype == 'timedelta64[ns]' - - result = ser + tdi - tm.assert_series_equal(result, expected) - assert result.dtype == 'timedelta64[ns]' - - expected = Series([Timedelta(hours=-3), Timedelta(days=1, hours=-4)], - name=names[2]) - - result = tdi - ser - tm.assert_series_equal(result, expected) - assert result.dtype == 'timedelta64[ns]' - - result = ser - tdi - tm.assert_series_equal(result, -expected) - assert result.dtype == 'timedelta64[ns]' - - def test_td64_sub_NaT(self): - # GH#18808 - ser = Series([NaT, Timedelta('1s')]) - res = ser - NaT - expected = Series([NaT, NaT], dtype='timedelta64[ns]') - tm.assert_series_equal(res, expected) - - -class TestTimedeltaSeriesMultiplicationDivision(object): - # Tests for Series[timedelta64[ns]] - # __mul__, __rmul__, __div__, __rdiv__, __floordiv__, __rfloordiv__ - - # ------------------------------------------------------------------ - # __floordiv__, __rfloordiv__ - - @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) - - @pytest.mark.parametrize('scalar_td', [ - timedelta(minutes=5, seconds=4), - Timedelta('5m4s'), - 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) - - @pytest.mark.parametrize('scalar_td', [ - timedelta(minutes=5, seconds=4), - Timedelta('5m4s'), - Timedelta('5m4s').to_timedelta64()]) - def test_timedelta_rfloordiv_explicit(self, scalar_td): - # GH#18831 - td1 = Series([timedelta(minutes=5, seconds=3)] * 3) - td1.iloc[2] = np.nan - - # 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) - - # ------------------------------------------------------------------ - # Operations with int-like others - - @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', - 'uint64', 'uint32', 'uint16', 'uint8', - 'float64', 'float32', 'float16']) - @pytest.mark.parametrize('vector', [np.array([20, 30, 40]), - pd.Index([20, 30, 40]), - Series([20, 30, 40])]) - def test_td64series_div_numeric_array(self, vector, dtype, tdser): - # GH#4521 - # divide/multiply by integers - vector = vector.astype(dtype) - expected = Series(['2.95D', '1D 23H 12m', 'NaT'], - dtype='timedelta64[ns]') - - result = tdser / vector - tm.assert_series_equal(result, expected) - - with pytest.raises(TypeError): - vector / tdser - - @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', - 'uint64', 'uint32', 'uint16', 'uint8', - 'float64', 'float32', 'float16']) - @pytest.mark.parametrize('vector', [np.array([20, 30, 40]), - pd.Index([20, 30, 40]), - Series([20, 30, 40])]) - def test_td64series_mul_numeric_array(self, vector, dtype, tdser): - # GH#4521 - # divide/multiply by integers - vector = vector.astype(dtype) - - expected = Series(['1180 Days', '1770 Days', 'NaT'], - dtype='timedelta64[ns]') - - result = tdser * vector - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', - 'uint64', 'uint32', 'uint16', 'uint8', - 'float64', 'float32', 'float16']) - @pytest.mark.parametrize('vector', [ - np.array([20, 30, 40]), - pytest.param(pd.Index([20, 30, 40]), - marks=pytest.mark.xfail(reason='__mul__ raises ' - 'instead of returning ' - 'NotImplemented')), - Series([20, 30, 40]) - ]) - def test_td64series_rmul_numeric_array(self, vector, dtype, tdser): - # GH#4521 - # divide/multiply by integers - vector = vector.astype(dtype) - - expected = Series(['1180 Days', '1770 Days', 'NaT'], - dtype='timedelta64[ns]') - - result = vector * tdser - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize('one', [1, np.array(1), 1.0, np.array(1.0)]) - def test_td64series_mul_numeric_scalar(self, one, tdser): - # GH#4521 - # divide/multiply by integers - expected = Series(['-59 Days', '-59 Days', 'NaT'], - dtype='timedelta64[ns]') - - result = tdser * (-one) - tm.assert_series_equal(result, expected) - result = (-one) * tdser - tm.assert_series_equal(result, expected) - - expected = Series(['118 Days', '118 Days', 'NaT'], - dtype='timedelta64[ns]') - - result = tdser * (2 * one) - tm.assert_series_equal(result, expected) - result = (2 * one) * tdser - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize('two', [ - 2, 2.0, - pytest.param(np.array(2), - marks=pytest.mark.xfail(reason='GH#19011 is_list_like ' - 'incorrectly True.')), - pytest.param(np.array(2.0), - marks=pytest.mark.xfail(reason='GH#19011 is_list_like ' - 'incorrectly True.')), - ]) - def test_td64series_div_numeric_scalar(self, two, tdser): - # GH#4521 - # divide/multiply by integers - expected = Series(['29.5D', '29.5D', 'NaT'], dtype='timedelta64[ns]') - - result = tdser / two - tm.assert_series_equal(result, expected) - - # ------------------------------------------------------------------ - # Operations with timedelta-like others - - @pytest.mark.parametrize('names', [(None, None, None), - ('Egon', 'Venkman', None), - ('NCC1701D', 'NCC1701D', 'NCC1701D')]) - def test_tdi_mul_int_series(self, names): - # GH#19042 - tdi = pd.TimedeltaIndex(['0days', '1day', '2days', '3days', '4days'], - name=names[0]) - ser = Series([0, 1, 2, 3, 4], dtype=np.int64, name=names[1]) - - expected = Series(['0days', '1day', '4days', '9days', '16days'], - dtype='timedelta64[ns]', - name=names[2]) - - result = ser * tdi - tm.assert_series_equal(result, expected) - - # The direct operation tdi * ser still needs to be fixed. - result = ser.__rmul__(tdi) - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize('names', [(None, None, None), - ('Egon', 'Venkman', None), - ('NCC1701D', 'NCC1701D', 'NCC1701D')]) - def test_float_series_rdiv_tdi(self, names): - # GH#19042 - # TODO: the direct operation TimedeltaIndex / Series still - # needs to be fixed. - tdi = pd.TimedeltaIndex(['0days', '1day', '2days', '3days', '4days'], - name=names[0]) - ser = Series([1.5, 3, 4.5, 6, 7.5], dtype=np.float64, name=names[1]) - - expected = Series([tdi[n] / ser[n] for n in range(len(ser))], - dtype='timedelta64[ns]', - name=names[2]) - - result = ser.__rdiv__(tdi) - tm.assert_series_equal(result, expected) - - @pytest.mark.parametrize('scalar_td', [ - timedelta(minutes=5, seconds=4), - Timedelta('5m4s'), - Timedelta('5m4s').to_timedelta64()]) - def test_td64series_mul_timedeltalike_invalid(self, scalar_td): - td1 = Series([timedelta(minutes=5, seconds=3)] * 3) - td1.iloc[2] = np.nan - - # check that we are getting a TypeError - # with 'operate' (from core/ops.py) for the ops that are not - # defined - pattern = 'operate|unsupported|cannot|not supported' - with tm.assert_raises_regex(TypeError, pattern): - td1 * scalar_td - with tm.assert_raises_regex(TypeError, pattern): - scalar_td * td1 diff --git a/pandas/tests/test_arithmetic.py b/pandas/tests/test_arithmetic.py index f15b629f15ae3..8ee0bf9ec874a 100644 --- a/pandas/tests/test_arithmetic.py +++ b/pandas/tests/test_arithmetic.py @@ -2,6 +2,7 @@ # Arithmetc tests for DataFrame/Series/Index/Array classes that should # behave identically. from datetime import timedelta +import operator import pytest import numpy as np @@ -9,7 +10,22 @@ import pandas as pd import pandas.util.testing as tm -from pandas import Timedelta +from pandas.core import ops +from pandas.errors import NullFrequencyError +from pandas._libs.tslibs import IncompatibleFrequency +from pandas import ( + Timedelta, Timestamp, NaT, Series, TimedeltaIndex, DatetimeIndex) + + +# ------------------------------------------------------------------ +# Fixtures + +@pytest.fixture +def tdser(): + """ + Return a Series with dtype='timedelta64[ns]', including a NaT. + """ + return Series(['59 Days', '59 Days', 'NaT'], dtype='timedelta64[ns]') # ------------------------------------------------------------------ @@ -19,7 +35,7 @@ class TestNumericArraylikeArithmeticWithTimedeltaScalar(object): @pytest.mark.parametrize('box', [ pd.Index, - pd.Series, + Series, pytest.param(pd.DataFrame, marks=pytest.mark.xfail(reason="block.eval incorrect", strict=True)) @@ -35,10 +51,10 @@ class TestNumericArraylikeArithmeticWithTimedeltaScalar(object): Timedelta(days=1).to_timedelta64(), Timedelta(days=1).to_pytimedelta()], ids=lambda x: type(x).__name__) - def test_index_mul_timedelta(self, scalar_td, index, box): + def test_numeric_arr_mul_tdscalar(self, scalar_td, index, box): # GH#19333 - if (box is pd.Series and + if (box is Series and type(scalar_td) is timedelta and index.dtype == 'f8'): raise pytest.xfail(reason="Cannot multiply timedelta by float") @@ -53,7 +69,7 @@ def test_index_mul_timedelta(self, scalar_td, index, box): commute = scalar_td * index tm.assert_equal(commute, expected) - @pytest.mark.parametrize('box', [pd.Index, pd.Series, pd.DataFrame]) + @pytest.mark.parametrize('box', [pd.Index, Series, pd.DataFrame]) @pytest.mark.parametrize('index', [ pd.Int64Index(range(1, 3)), pd.UInt64Index(range(1, 3)), @@ -65,14 +81,14 @@ def test_index_mul_timedelta(self, scalar_td, index, box): Timedelta(days=1).to_timedelta64(), Timedelta(days=1).to_pytimedelta()], ids=lambda x: type(x).__name__) - def test_index_rdiv_timedelta(self, scalar_td, index, box): + def test_numeric_arr_rdiv_tdscalar(self, scalar_td, index, box): - if box is pd.Series and type(scalar_td) is timedelta: + if box is Series and type(scalar_td) is timedelta: raise pytest.xfail(reason="TODO: Figure out why this case fails") if box is pd.DataFrame and isinstance(scalar_td, timedelta): raise pytest.xfail(reason="TODO: Figure out why this case fails") - expected = pd.TimedeltaIndex(['1 Day', '12 Hours']) + expected = TimedeltaIndex(['1 Day', '12 Hours']) index = tm.box_expected(index, box) expected = tm.box_expected(expected, box) @@ -87,12 +103,727 @@ def test_index_rdiv_timedelta(self, scalar_td, index, box): # ------------------------------------------------------------------ # Timedelta64[ns] dtype Arithmetic Operations +class TestTimedeltaArraylikeAddSubOps(object): + # Tests for timedelta64[ns] __add__, __sub__, __radd__, __rsub__ + + # ------------------------------------------------------------- + # Invalid Operations + + @pytest.mark.parametrize('box', [pd.Index, Series, pd.DataFrame], + ids=lambda x: x.__name__) + def test_td64arr_add_str_invalid(self, box): + # GH#13624 + tdi = TimedeltaIndex(['1 day', '2 days']) + tdi = tm.box_expected(tdi, box) + + with pytest.raises(TypeError): + tdi + 'a' + with pytest.raises(TypeError): + 'a' + tdi + + @pytest.mark.parametrize('box', [pd.Index, Series, pd.DataFrame], + ids=lambda x: x.__name__) + @pytest.mark.parametrize('other', [3.14, np.array([2.0, 3.0])]) + @pytest.mark.parametrize('op', [operator.add, ops.radd, + operator.sub, ops.rsub], + ids=lambda x: x.__name__) + def test_td64arr_add_sub_float(self, box, op, other): + tdi = TimedeltaIndex(['-1 days', '-1 days']) + tdi = tm.box_expected(tdi, box) + + if box is pd.DataFrame and op in [operator.add, operator.sub]: + pytest.xfail(reason="Tries to align incorrectly, " + "raises ValueError") + + with pytest.raises(TypeError): + op(tdi, other) + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Tries to cast df to " + "Period", + strict=True, + raises=IncompatibleFrequency)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('freq', [None, 'H']) + def test_td64arr_sub_period(self, box, freq): + # GH#13078 + # not supported, check TypeError + p = pd.Period('2011-01-01', freq='D') + idx = TimedeltaIndex(['1 hours', '2 hours'], freq=freq) + idx = tm.box_expected(idx, box) + + with pytest.raises(TypeError): + idx - p + + with pytest.raises(TypeError): + p - idx + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="broadcasts along " + "wrong axis", + raises=ValueError, + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('pi_freq', ['D', 'W', 'Q', 'H']) + @pytest.mark.parametrize('tdi_freq', [None, 'H']) + def test_td64arr_sub_pi(self, box, tdi_freq, pi_freq): + # GH#20049 subtracting PeriodIndex should raise TypeError + tdi = TimedeltaIndex(['1 hours', '2 hours'], freq=tdi_freq) + dti = Timestamp('2018-03-07 17:16:40') + tdi + pi = dti.to_period(pi_freq) + + # TODO: parametrize over box for pi? + tdi = tm.box_expected(tdi, box) + with pytest.raises(TypeError): + tdi - pi + + # ------------------------------------------------------------- + # Binary operations td64 arraylike and datetime-like + + @pytest.mark.parametrize('box', [pd.Index, Series, pd.DataFrame], + ids=lambda x: x.__name__) + def test_td64arr_sub_timestamp_raises(self, box): + idx = TimedeltaIndex(['1 day', '2 day']) + idx = tm.box_expected(idx, box) + + msg = "cannot subtract a datelike from|Could not operate" + with tm.assert_raises_regex(TypeError, msg): + idx - Timestamp('2011-01-01') + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Returns object dtype", + strict=True)) + ], ids=lambda x: x.__name__) + def test_td64arr_add_timestamp(self, box): + idx = TimedeltaIndex(['1 day', '2 day']) + expected = DatetimeIndex(['2011-01-02', '2011-01-03']) + + idx = tm.box_expected(idx, box) + expected = tm.box_expected(expected, box) + + result = idx + Timestamp('2011-01-01') + tm.assert_equal(result, expected) + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Returns object dtype", + strict=True)) + ], ids=lambda x: x.__name__) + def test_td64_radd_timestamp(self, box): + idx = TimedeltaIndex(['1 day', '2 day']) + expected = DatetimeIndex(['2011-01-02', '2011-01-03']) + + idx = tm.box_expected(idx, box) + expected = tm.box_expected(expected, box) + + # TODO: parametrize over scalar datetime types? + result = Timestamp('2011-01-01') + idx + tm.assert_equal(result, expected) + + # ------------------------------------------------------------------ + # Operations with int-like others + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Attempts to broadcast " + "incorrectly", + strict=True, raises=ValueError)) + ], ids=lambda x: x.__name__) + def test_td64arr_add_int_series_invalid(self, box, tdser): + tdser = tm.box_expected(tdser, box) + err = TypeError if box is not pd.Index else NullFrequencyError + with pytest.raises(err): + tdser + Series([2, 3, 4]) + + @pytest.mark.parametrize('box', [ + pd.Index, + pytest.param(Series, + marks=pytest.mark.xfail(reason="GH#19123 integer " + "interpreted as " + "nanoseconds", + strict=True)), + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Attempts to broadcast " + "incorrectly", + strict=True, raises=ValueError)) + ], ids=lambda x: x.__name__) + def test_td64arr_radd_int_series_invalid(self, box, tdser): + tdser = tm.box_expected(tdser, box) + err = TypeError if box is not pd.Index else NullFrequencyError + with pytest.raises(err): + Series([2, 3, 4]) + tdser + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Attempts to broadcast " + "incorrectly", + strict=True, raises=ValueError)) + ], ids=lambda x: x.__name__) + def test_td64arr_sub_int_series_invalid(self, box, tdser): + tdser = tm.box_expected(tdser, box) + err = TypeError if box is not pd.Index else NullFrequencyError + with pytest.raises(err): + tdser - Series([2, 3, 4]) + + @pytest.mark.parametrize('box', [pd.Index, Series, pd.DataFrame], + ids=lambda x: x.__name__) + @pytest.mark.xfail(reason='GH#19123 integer interpreted as nanoseconds', + strict=True) + def test_td64arr_rsub_int_series_invalid(self, box, tdser): + tdser = tm.box_expected(tdser, box) + with pytest.raises(TypeError): + Series([2, 3, 4]) - tdser + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Tries to broadcast " + "incorrectly", + strict=True, raises=ValueError)) + ], ids=lambda x: x.__name__) + def test_td64arr_add_intlike(self, box): + # GH#19123 + tdi = TimedeltaIndex(['59 days', '59 days', 'NaT']) + ser = tm.box_expected(tdi, box) + err = TypeError if box is not pd.Index else NullFrequencyError + + other = Series([20, 30, 40], dtype='uint8') + + # TODO: separate/parametrize + with pytest.raises(err): + ser + 1 + with pytest.raises(err): + ser - 1 + + with pytest.raises(err): + ser + other + with pytest.raises(err): + ser - other + + with pytest.raises(err): + ser + np.array(other) + with pytest.raises(err): + ser - np.array(other) + + with pytest.raises(err): + ser + pd.Index(other) + with pytest.raises(err): + ser - pd.Index(other) + + @pytest.mark.parametrize('box', [pd.Index, Series, pd.DataFrame], + ids=lambda x: x.__name__) + @pytest.mark.parametrize('scalar', [1, 1.5, np.array(2)]) + def test_td64arr_add_sub_numeric_scalar_invalid(self, box, scalar, tdser): + + if box is pd.DataFrame and isinstance(scalar, np.ndarray): + # raises ValueError + pytest.xfail(reason="DataFrame to broadcast incorrectly") + + tdser = tm.box_expected(tdser, box) + err = TypeError + if box is pd.Index and not isinstance(scalar, float): + err = NullFrequencyError + + with pytest.raises(err): + tdser + scalar + with pytest.raises(err): + scalar + tdser + with pytest.raises(err): + tdser - scalar + with pytest.raises(err): + scalar - tdser + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Tries to broadcast " + "incorrectly", + strict=True, raises=ValueError)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', + 'uint64', 'uint32', 'uint16', 'uint8', + 'float64', 'float32', 'float16']) + @pytest.mark.parametrize('vec', [ + np.array([1, 2, 3]), + pd.Index([1, 2, 3]), + Series([1, 2, 3]) + # TODO: Add DataFrame in here? + ], ids=lambda x: type(x).__name__) + def test_td64arr_add_sub_numeric_arr_invalid(self, box, vec, dtype, tdser): + if type(vec) is Series and not dtype.startswith('float'): + pytest.xfail(reason='GH#19123 integer interpreted as nanos') + + tdser = tm.box_expected(tdser, box) + err = TypeError + if box is pd.Index and not dtype.startswith('float'): + err = NullFrequencyError + + vector = vec.astype(dtype) + # TODO: parametrize over these four ops? + with pytest.raises(err): + tdser + vector + with pytest.raises(err): + vector + tdser + with pytest.raises(err): + tdser - vector + with pytest.raises(err): + vector - tdser + + # ------------------------------------------------------------------ + # Operations with datetime-like others + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Returns object dtype " + "instead of " + "datetime64[ns]", + strict=True)) + ], ids=lambda x: x.__name__) + def test_td64arr_add_sub_timestamp(self, box): + # GH#11925 + ts = Timestamp('2012-01-01') + # TODO: parametrize over types of datetime scalar? + + tdser = Series(pd.timedelta_range('1 day', periods=3)) + expected = Series(pd.date_range('2012-01-02', periods=3)) + + tdser = tm.box_expected(tdser, box) + expected = tm.box_expected(expected, box) + + tm.assert_equal(ts + tdser, expected) + tm.assert_equal(tdser + ts, expected) + + expected2 = Series(pd.date_range('2011-12-31', + periods=3, freq='-1D')) + expected2 = tm.box_expected(expected2, box) + + tm.assert_equal(ts - tdser, expected2) + tm.assert_equal(ts + (-tdser), expected2) + + with pytest.raises(TypeError): + tdser - ts + + # ------------------------------------------------------------------ + # Operations with timedelta-like others (including DateOffsets) + + # TODO: parametrize over [add, sub, radd, rsub]? + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Tries to broadcast " + "incorrectly leading " + "to alignment error", + strict=True, raises=ValueError)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('names', [(None, None, None), + ('Egon', 'Venkman', None), + ('NCC1701D', 'NCC1701D', 'NCC1701D')]) + def test_td64arr_add_sub_tdi(self, box, names): + # GH#17250 make sure result dtype is correct + # GH#19043 make sure names are propagated correctly + tdi = TimedeltaIndex(['0 days', '1 day'], name=names[0]) + ser = Series([Timedelta(hours=3), Timedelta(hours=4)], name=names[1]) + expected = Series([Timedelta(hours=3), Timedelta(days=1, hours=4)], + name=names[2]) + + ser = tm.box_expected(ser, box) + expected = tm.box_expected(expected, box) + + result = tdi + ser + tm.assert_equal(result, expected) + if box is not pd.DataFrame: + assert result.dtype == 'timedelta64[ns]' + else: + assert result.dtypes[0] == 'timedelta64[ns]' + + result = ser + tdi + tm.assert_equal(result, expected) + if box is not pd.DataFrame: + assert result.dtype == 'timedelta64[ns]' + else: + assert result.dtypes[0] == 'timedelta64[ns]' + + expected = Series([Timedelta(hours=-3), Timedelta(days=1, hours=-4)], + name=names[2]) + expected = tm.box_expected(expected, box) + + result = tdi - ser + tm.assert_equal(result, expected) + if box is not pd.DataFrame: + assert result.dtype == 'timedelta64[ns]' + else: + assert result.dtypes[0] == 'timedelta64[ns]' + + result = ser - tdi + tm.assert_equal(result, -expected) + if box is not pd.DataFrame: + assert result.dtype == 'timedelta64[ns]' + else: + assert result.dtypes[0] == 'timedelta64[ns]' + + @pytest.mark.parametrize('box', [pd.Index, Series, pd.DataFrame], + ids=lambda x: x.__name__) + def test_td64arr_sub_NaT(self, box): + # GH#18808 + ser = Series([NaT, Timedelta('1s')]) + expected = Series([NaT, NaT], dtype='timedelta64[ns]') + + ser = tm.box_expected(ser, box) + expected = tm.box_expected(expected, box) + + res = ser - NaT + tm.assert_equal(res, expected) + + +class TestTimedeltaArraylikeMulDivOps(object): + # Tests for timedelta64[ns] + # __mul__, __rmul__, __div__, __rdiv__, __floordiv__, __rfloordiv__ + + # ------------------------------------------------------------------ + # __floordiv__, __rfloordiv__ + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Incorrectly returns " + "m8[ns] instead of f8", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('scalar_td', [ + timedelta(minutes=5, seconds=4), + Timedelta('5m4s'), + Timedelta('5m4s').to_timedelta64()]) + def test_td64arr_floordiv_tdscalar(self, box, scalar_td): + # GH#18831 + td1 = Series([timedelta(minutes=5, seconds=3)] * 3) + td1.iloc[2] = np.nan + + expected = Series([0, 0, np.nan]) + + td1 = tm.box_expected(td1, box) + expected = tm.box_expected(expected, box) + + result = td1 // scalar_td + tm.assert_equal(result, expected) + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Incorrectly casts to f8", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('scalar_td', [ + timedelta(minutes=5, seconds=4), + Timedelta('5m4s'), + Timedelta('5m4s').to_timedelta64()]) + def test_td64arr_rfloordiv_tdscalar(self, box, scalar_td): + # GH#18831 + td1 = Series([timedelta(minutes=5, seconds=3)] * 3) + td1.iloc[2] = np.nan + + expected = Series([1, 1, np.nan]) + + td1 = tm.box_expected(td1, box) + expected = tm.box_expected(expected, box) + + result = scalar_td // td1 + tm.assert_equal(result, expected) + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Returns m8[ns] dtype " + "instead of f8", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('scalar_td', [ + timedelta(minutes=5, seconds=4), + Timedelta('5m4s'), + Timedelta('5m4s').to_timedelta64()]) + def test_td64arr_rfloordiv_tdscalar_explicit(self, box, scalar_td): + # GH#18831 + td1 = Series([timedelta(minutes=5, seconds=3)] * 3) + td1.iloc[2] = np.nan + + expected = Series([1, 1, np.nan]) + + td1 = tm.box_expected(td1, box) + expected = tm.box_expected(expected, box) + + # We can test __rfloordiv__ using this syntax, + # see `test_timedelta_rfloordiv` + result = td1.__rfloordiv__(scalar_td) + tm.assert_equal(result, expected) + + # ------------------------------------------------------------------ + # Operations with timedelta-like others + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="__mul__ op treats " + "timedelta other as i8; " + "rmul OK", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('scalar_td', [ + timedelta(minutes=5, seconds=4), + Timedelta('5m4s'), + Timedelta('5m4s').to_timedelta64()]) + def test_td64arr_mul_tdscalar_invalid(self, box, scalar_td): + td1 = Series([timedelta(minutes=5, seconds=3)] * 3) + td1.iloc[2] = np.nan + + td1 = tm.box_expected(td1, box) + + # check that we are getting a TypeError + # with 'operate' (from core/ops.py) for the ops that are not + # defined + pattern = 'operate|unsupported|cannot|not supported' + with tm.assert_raises_regex(TypeError, pattern): + td1 * scalar_td + with tm.assert_raises_regex(TypeError, pattern): + scalar_td * td1 + + # ------------------------------------------------------------------ + # Operations with numeric others + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Returns object-dtype", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('one', [1, np.array(1), 1.0, np.array(1.0)]) + def test_td64arr_mul_numeric_scalar(self, box, one, tdser): + # GH#4521 + # divide/multiply by integers + expected = Series(['-59 Days', '-59 Days', 'NaT'], + dtype='timedelta64[ns]') + + tdser = tm.box_expected(tdser, box) + expected = tm.box_expected(expected, box) + + result = tdser * (-one) + tm.assert_equal(result, expected) + result = (-one) * tdser + tm.assert_equal(result, expected) + + expected = Series(['118 Days', '118 Days', 'NaT'], + dtype='timedelta64[ns]') + expected = tm.box_expected(expected, box) + + result = tdser * (2 * one) + tm.assert_equal(result, expected) + result = (2 * one) * tdser + tm.assert_equal(result, expected) + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="Returns object-dtype", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('two', [2, 2.0, np.array(2), np.array(2.0)]) + def test_td64arr_div_numeric_scalar(self, box, two, tdser): + # GH#4521 + # divide/multiply by integers + expected = Series(['29.5D', '29.5D', 'NaT'], dtype='timedelta64[ns]') + + tdser = tm.box_expected(tdser, box) + expected = tm.box_expected(expected, box) + + result = tdser / two + tm.assert_equal(result, expected) + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="broadcasts along " + "wrong axis", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', + 'uint64', 'uint32', 'uint16', 'uint8', + 'float64', 'float32', 'float16']) + @pytest.mark.parametrize('vector', [np.array([20, 30, 40]), + pd.Index([20, 30, 40]), + Series([20, 30, 40])]) + def test_td64arr_mul_numeric_array(self, box, vector, dtype, tdser): + # GH#4521 + # divide/multiply by integers + vector = vector.astype(dtype) + + expected = Series(['1180 Days', '1770 Days', 'NaT'], + dtype='timedelta64[ns]') + + tdser = tm.box_expected(tdser, box) + # TODO: Make this up-casting more systematic? + box = Series if (box is pd.Index and type(vector) is Series) else box + expected = tm.box_expected(expected, box) + + result = tdser * vector + tm.assert_equal(result, expected) + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="broadcasts along " + "wrong axis", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', + 'uint64', 'uint32', 'uint16', 'uint8', + 'float64', 'float32', 'float16']) + @pytest.mark.parametrize('vector', [np.array([20, 30, 40]), + pd.Index([20, 30, 40]), + Series([20, 30, 40])], + ids=lambda x: type(x).__name__) + def test_td64arr_rmul_numeric_array(self, box, vector, dtype, tdser): + # GH#4521 + # divide/multiply by integers + vector = vector.astype(dtype) + + expected = Series(['1180 Days', '1770 Days', 'NaT'], + dtype='timedelta64[ns]') + + tdser = tm.box_expected(tdser, box) + box = Series if (box is pd.Index and type(vector) is Series) else box + expected = tm.box_expected(expected, box) + + result = vector * tdser + tm.assert_equal(result, expected) + + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="broadcasts along " + "wrong axis", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', + 'uint64', 'uint32', 'uint16', 'uint8', + 'float64', 'float32', 'float16']) + @pytest.mark.parametrize('vector', [np.array([20, 30, 40]), + pd.Index([20, 30, 40]), + Series([20, 30, 40])]) + def test_td64arr_div_numeric_array(self, box, vector, dtype, tdser): + # GH#4521 + # divide/multiply by integers + vector = vector.astype(dtype) + expected = Series(['2.95D', '1D 23H 12m', 'NaT'], + dtype='timedelta64[ns]') + + tdser = tm.box_expected(tdser, box) + box = Series if (box is pd.Index and type(vector) is Series) else box + expected = tm.box_expected(expected, box) + + result = tdser / vector + tm.assert_equal(result, expected) + + with pytest.raises(TypeError): + vector / tdser + + # TODO: Should we be parametrizing over types for `ser` too? + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pytest.param(pd.DataFrame, + marks=pytest.mark.xfail(reason="broadcasts along " + "wrong axis", + strict=True)) + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('names', [(None, None, None), + ('Egon', 'Venkman', None), + ('NCC1701D', 'NCC1701D', 'NCC1701D')]) + def test_td64arr_mul_int_series(self, box, names): + # GH#19042 test for correct name attachment + tdi = TimedeltaIndex(['0days', '1day', '2days', '3days', '4days'], + name=names[0]) + ser = Series([0, 1, 2, 3, 4], dtype=np.int64, name=names[1]) + + expected = Series(['0days', '1day', '4days', '9days', '16days'], + dtype='timedelta64[ns]', + name=names[2]) + + tdi = tm.box_expected(tdi, box) + box = Series if (box is pd.Index and type(ser) is Series) else box + expected = tm.box_expected(expected, box) + + result = ser * tdi + tm.assert_equal(result, expected) + + # The direct operation tdi * ser still needs to be fixed. + result = ser.__rmul__(tdi) + tm.assert_equal(result, expected) + + # TODO: Should we be parametrizing over types for `ser` too? + @pytest.mark.parametrize('box', [ + pd.Index, + Series, + pd.DataFrame + ], ids=lambda x: x.__name__) + @pytest.mark.parametrize('names', [(None, None, None), + ('Egon', 'Venkman', None), + ('NCC1701D', 'NCC1701D', 'NCC1701D')]) + def test_float_series_rdiv_td64arr(self, box, names): + # GH#19042 test for correct name attachment + # TODO: the direct operation TimedeltaIndex / Series still + # needs to be fixed. + tdi = TimedeltaIndex(['0days', '1day', '2days', '3days', '4days'], + name=names[0]) + ser = Series([1.5, 3, 4.5, 6, 7.5], dtype=np.float64, name=names[1]) + + expected = Series([tdi[n] / ser[n] for n in range(len(ser))], + dtype='timedelta64[ns]', + name=names[2]) + + tdi = tm.box_expected(tdi, box) + box = Series if (box is pd.Index and type(ser) is Series) else box + expected = tm.box_expected(expected, box) + + result = ser.__rdiv__(tdi) + if box is pd.DataFrame: + # TODO: Should we skip this case sooner or test something else? + assert result is NotImplemented + else: + tm.assert_equal(result, expected) + class TestTimedeltaArraylikeInvalidArithmeticOps(object): @pytest.mark.parametrize('box', [ pd.Index, - pd.Series, + Series, pytest.param(pd.DataFrame, marks=pytest.mark.xfail(reason="raises ValueError " "instead of TypeError", @@ -102,8 +833,8 @@ class TestTimedeltaArraylikeInvalidArithmeticOps(object): timedelta(minutes=5, seconds=4), Timedelta('5m4s'), Timedelta('5m4s').to_timedelta64()]) - def test_td64series_pow_invalid(self, scalar_td, box): - td1 = pd.Series([timedelta(minutes=5, seconds=3)] * 3) + def test_td64arr_pow_invalid(self, scalar_td, box): + td1 = Series([timedelta(minutes=5, seconds=3)] * 3) td1.iloc[2] = np.nan td1 = tm.box_expected(td1, box)