diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 38e28e8b77359..d1e6d979b554c 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- +from __future__ import division + from datetime import timedelta +import operator import warnings import numpy as np @@ -16,9 +19,11 @@ _TD_DTYPE, ensure_int64, is_datetime64_dtype, is_float_dtype, is_integer_dtype, is_list_like, is_object_dtype, is_string_dtype, is_timedelta64_dtype) -from pandas.core.dtypes.generic import ABCSeries, ABCTimedeltaIndex +from pandas.core.dtypes.generic import ( + ABCDataFrame, ABCIndexClass, ABCSeries, ABCTimedeltaIndex) from pandas.core.dtypes.missing import isna +from pandas.core import ops from pandas.core.algorithms import checked_add_with_arr import pandas.core.common as com @@ -101,8 +106,32 @@ def wrapper(self, other): return compat.set_function_name(wrapper, opname, cls) +def _wrap_tdi_op(op): + """ + Instead of re-implementing multiplication/division etc operations + in the Array class, for now we dispatch to the TimedeltaIndex + implementations. + """ + # TODO: implement directly here and wrap in TimedeltaIndex, instead of + # the other way around + def method(self, other): + if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): + return NotImplemented + + from pandas import TimedeltaIndex + obj = TimedeltaIndex(self) + result = op(obj, other) + if is_timedelta64_dtype(result): + return type(self)(result) + return np.array(result) + + method.__name__ = '__{name}__'.format(name=op.__name__) + return method + + class TimedeltaArrayMixin(dtl.DatetimeLikeArrayMixin): _typ = "timedeltaarray" + __array_priority__ = 1000 @property def _box_func(self): @@ -293,11 +322,25 @@ def _evaluate_with_timedelta_like(self, other, op): return NotImplemented + __mul__ = _wrap_tdi_op(operator.mul) + __rmul__ = __mul__ + __truediv__ = _wrap_tdi_op(operator.truediv) + __floordiv__ = _wrap_tdi_op(operator.floordiv) + __rfloordiv__ = _wrap_tdi_op(ops.rfloordiv) + + if compat.PY2: + __div__ = __truediv__ + + # Note: TimedeltaIndex overrides this in call to cls._add_numeric_methods def __neg__(self): if self.freq is not None: return type(self)(-self._data, freq=-self.freq) return type(self)(-self._data) + def __abs__(self): + # Note: freq is not preserved + return type(self)(np.abs(self._data)) + # ---------------------------------------------------------------- # Conversion Methods - Vectorized analogues of Timedelta methods diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 0fa6973b717e9..f608439607904 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -19,7 +19,7 @@ ABCSeries, ABCDataFrame, ABCMultiIndex, ABCPeriodIndex, ABCTimedeltaIndex, ABCDatetimeIndex, - ABCDateOffset, ABCIndexClass) + ABCDateOffset, ABCIndexClass, ABCTimedeltaArray) from pandas.core.dtypes.missing import isna, array_equivalent from pandas.core.dtypes.cast import maybe_cast_to_integer_array from pandas.core.dtypes.common import ( @@ -123,7 +123,8 @@ def index_arithmetic_method(self, other): elif isinstance(other, ABCTimedeltaIndex): # Defer to subclass implementation return NotImplemented - elif isinstance(other, np.ndarray) and is_timedelta64_dtype(other): + elif (isinstance(other, (np.ndarray, ABCTimedeltaArray)) and + is_timedelta64_dtype(other)): # GH#22390; wrap in Series for op, this will in turn wrap in # TimedeltaIndex, but will correctly raise TypeError instead of # NullFrequencyError for add/sub ops diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 4a0d1231444dc..8f50b40a20738 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -227,6 +227,14 @@ def _format_native_types(self, na_rep=u'NaT', date_format=None, **kwargs): # ------------------------------------------------------------------- # Wrapping TimedeltaArray + __mul__ = Index.__mul__ + __rmul__ = Index.__rmul__ + __truediv__ = Index.__truediv__ + __floordiv__ = Index.__floordiv__ + __rfloordiv__ = Index.__rfloordiv__ + if compat.PY2: + __div__ = Index.__div__ + days = wrap_field_accessor(TimedeltaArray.days) seconds = wrap_field_accessor(TimedeltaArray.seconds) microseconds = wrap_field_accessor(TimedeltaArray.microseconds) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 07c48554c65b8..58c7216f0eece 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -2,7 +2,6 @@ # Arithmetc tests for DataFrame/Series/Index/Array classes that should # behave identically. from datetime import datetime, timedelta -import operator import pytest import numpy as np @@ -10,7 +9,6 @@ import pandas as pd import pandas.util.testing as tm -from pandas.core import ops from pandas.errors import NullFrequencyError, PerformanceWarning from pandas import ( timedelta_range, @@ -18,6 +16,19 @@ DataFrame) +def get_upcast_box(box, vector): + """ + Given two box-types, find the one that takes priority + """ + if box is DataFrame or isinstance(vector, DataFrame): + return DataFrame + if box is Series or isinstance(vector, Series): + return Series + if box is pd.Index or isinstance(vector, pd.Index): + return pd.Index + return box + + # ------------------------------------------------------------------ # Timedelta64[ns] dtype Comparisons @@ -365,15 +376,18 @@ def test_td64arr_add_str_invalid(self, box_with_array): 'a' + tdi @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_with_array, op, other): + def test_td64arr_add_sub_float(self, box_with_array, other): tdi = TimedeltaIndex(['-1 days', '-1 days']) - tdi = tm.box_expected(tdi, box_with_array) + tdarr = tm.box_expected(tdi, box_with_array) with pytest.raises(TypeError): - op(tdi, other) + tdarr + other + with pytest.raises(TypeError): + other + tdarr + with pytest.raises(TypeError): + tdarr - other + with pytest.raises(TypeError): + other - tdarr @pytest.mark.parametrize('freq', [None, 'H']) def test_td64arr_sub_period(self, box_with_array, freq): @@ -417,6 +431,7 @@ def test_td64arr_sub_timestamp_raises(self, box_with_array): def test_td64arr_add_timestamp(self, box_with_array, tz_naive_fixture): # GH#23215 + # TODO: parametrize over scalar datetime types? tz = tz_naive_fixture other = Timestamp('2011-01-01', tz=tz) @@ -444,33 +459,32 @@ def test_td64arr_add_sub_timestamp(self, box_with_array): ts = Timestamp('2012-01-01') # TODO: parametrize over types of datetime scalar? - tdser = Series(timedelta_range('1 day', periods=3)) - expected = Series(pd.date_range('2012-01-02', periods=3)) + tdarr = timedelta_range('1 day', periods=3) + expected = pd.date_range('2012-01-02', periods=3) - tdser = tm.box_expected(tdser, box_with_array) + tdarr = tm.box_expected(tdarr, box_with_array) expected = tm.box_expected(expected, box_with_array) - tm.assert_equal(ts + tdser, expected) - tm.assert_equal(tdser + ts, expected) + tm.assert_equal(ts + tdarr, expected) + tm.assert_equal(tdarr + ts, expected) - expected2 = Series(pd.date_range('2011-12-31', - periods=3, freq='-1D')) + expected2 = pd.date_range('2011-12-31', periods=3, freq='-1D') expected2 = tm.box_expected(expected2, box_with_array) - tm.assert_equal(ts - tdser, expected2) - tm.assert_equal(ts + (-tdser), expected2) + tm.assert_equal(ts - tdarr, expected2) + tm.assert_equal(ts + (-tdarr), expected2) with pytest.raises(TypeError): - tdser - ts + tdarr - ts - def test_tdi_sub_dt64_array(self, box): + def test_tdi_sub_dt64_array(self, box_with_array): dti = pd.date_range('2016-01-01', periods=3) tdi = dti - dti.shift(1) dtarr = dti.values expected = pd.DatetimeIndex(dtarr) - tdi - tdi = tm.box_expected(tdi, box) - expected = tm.box_expected(expected, box) + tdi = tm.box_expected(tdi, box_with_array) + expected = tm.box_expected(expected, box_with_array) with pytest.raises(TypeError): tdi - dtarr @@ -479,29 +493,29 @@ def test_tdi_sub_dt64_array(self, box): result = dtarr - tdi tm.assert_equal(result, expected) - def test_tdi_add_dt64_array(self, box): + def test_tdi_add_dt64_array(self, box_with_array): dti = pd.date_range('2016-01-01', periods=3) tdi = dti - dti.shift(1) dtarr = dti.values expected = pd.DatetimeIndex(dtarr) + tdi - tdi = tm.box_expected(tdi, box) - expected = tm.box_expected(expected, box) + tdi = tm.box_expected(tdi, box_with_array) + expected = tm.box_expected(expected, box_with_array) result = tdi + dtarr tm.assert_equal(result, expected) result = dtarr + tdi tm.assert_equal(result, expected) - def test_td64arr_add_datetime64_nat(self, box): + def test_td64arr_add_datetime64_nat(self, box_with_array): # GH#23215 other = np.datetime64('NaT') tdi = timedelta_range('1 day', periods=3) expected = pd.DatetimeIndex(["NaT", "NaT", "NaT"]) - tdser = tm.box_expected(tdi, box) - expected = tm.box_expected(expected, box) + tdser = tm.box_expected(tdi, box_with_array) + expected = tm.box_expected(expected, box_with_array) tm.assert_equal(tdser + other, expected) tm.assert_equal(other + tdser, expected) @@ -524,11 +538,14 @@ def test_td64arr_add_int_series_invalid(self, box): with pytest.raises(err): int_ser - tdser - def test_td64arr_add_intlike(self, box): + def test_td64arr_add_intlike(self, box_with_array): # 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 + ser = tm.box_expected(tdi, box_with_array) + + err = TypeError + if box_with_array in [pd.Index, tm.to_array]: + err = NullFrequencyError other = Series([20, 30, 40], dtype='uint8') @@ -554,11 +571,14 @@ def test_td64arr_add_intlike(self, box): ser - pd.Index(other) @pytest.mark.parametrize('scalar', [1, 1.5, np.array(2)]) - def test_td64arr_add_sub_numeric_scalar_invalid(self, box, scalar): + def test_td64arr_add_sub_numeric_scalar_invalid(self, box_with_array, + scalar): + box = box_with_array + tdser = pd.Series(['59 Days', '59 Days', 'NaT'], dtype='m8[ns]') tdser = tm.box_expected(tdser, box) err = TypeError - if box is pd.Index and not isinstance(scalar, float): + if box in [pd.Index, tm.to_array] and not isinstance(scalar, float): err = NullFrequencyError with pytest.raises(err): @@ -587,7 +607,6 @@ def test_td64arr_add_sub_numeric_arr_invalid(self, box, vec, dtype): err = NullFrequencyError vector = vec.astype(dtype) - # TODO: parametrize over these four ops? with pytest.raises(err): tdser + vector with pytest.raises(err): @@ -1007,9 +1026,9 @@ def test_timedelta64_conversions(self, m, unit): # Multiplication # organized with scalar others first, then array-like - def test_td64arr_mul_int(self, box): + def test_td64arr_mul_int(self, box_with_array): idx = TimedeltaIndex(np.arange(5, dtype='int64')) - idx = tm.box_expected(idx, box) + idx = tm.box_expected(idx, box_with_array) result = idx * 1 tm.assert_equal(result, idx) @@ -1023,48 +1042,51 @@ def test_td64arr_mul_tdlike_scalar_raises(self, two_hours, box_with_array): with pytest.raises(TypeError): rng * two_hours - def test_tdi_mul_int_array_zerodim(self, box): + def test_tdi_mul_int_array_zerodim(self, box_with_array): rng5 = np.arange(5, dtype='int64') idx = TimedeltaIndex(rng5) expected = TimedeltaIndex(rng5 * 5) - idx = tm.box_expected(idx, box) - expected = tm.box_expected(expected, box) + idx = tm.box_expected(idx, box_with_array) + expected = tm.box_expected(expected, box_with_array) result = idx * np.array(5, dtype='int64') tm.assert_equal(result, expected) - def test_tdi_mul_int_array(self, box): + def test_tdi_mul_int_array(self, box_with_array): rng5 = np.arange(5, dtype='int64') idx = TimedeltaIndex(rng5) expected = TimedeltaIndex(rng5 ** 2) - idx = tm.box_expected(idx, box) - expected = tm.box_expected(expected, box) + idx = tm.box_expected(idx, box_with_array) + expected = tm.box_expected(expected, box_with_array) result = idx * rng5 tm.assert_equal(result, expected) - def test_tdi_mul_int_series(self, box): + def test_tdi_mul_int_series(self, box_with_array): + box = box_with_array + xbox = pd.Series if box in [pd.Index, tm.to_array] else box + idx = TimedeltaIndex(np.arange(5, dtype='int64')) expected = TimedeltaIndex(np.arange(5, dtype='int64') ** 2) idx = tm.box_expected(idx, box) - - box2 = pd.Series if box is pd.Index else box - expected = tm.box_expected(expected, box2) + expected = tm.box_expected(expected, xbox) result = idx * pd.Series(np.arange(5, dtype='int64')) tm.assert_equal(result, expected) - def test_tdi_mul_float_series(self, box): + def test_tdi_mul_float_series(self, box_with_array): + box = box_with_array + xbox = pd.Series if box in [pd.Index, tm.to_array] else box + idx = TimedeltaIndex(np.arange(5, dtype='int64')) idx = tm.box_expected(idx, box) rng5f = np.arange(5, dtype='float64') expected = TimedeltaIndex(rng5f * (rng5f + 1.0)) - box2 = pd.Series if box is pd.Index else box - expected = tm.box_expected(expected, box2) + expected = tm.box_expected(expected, xbox) result = idx * Series(rng5f + 1.0) tm.assert_equal(result, expected) @@ -1077,12 +1099,15 @@ def test_tdi_mul_float_series(self, box): pd.Float64Index(range(1, 11)), pd.RangeIndex(1, 11) ], ids=lambda x: type(x).__name__) - def test_tdi_rmul_arraylike(self, other, box): + def test_tdi_rmul_arraylike(self, other, box_with_array): + box = box_with_array + xbox = get_upcast_box(box, other) + tdi = TimedeltaIndex(['1 Day'] * 10) expected = timedelta_range('1 days', '10 days') tdi = tm.box_expected(tdi, box) - expected = tm.box_expected(expected, box) + expected = tm.box_expected(expected, xbox) result = other * tdi tm.assert_equal(result, expected) @@ -1099,30 +1124,30 @@ def test_td64arr_div_nat_invalid(self, box_with_array): with pytest.raises(TypeError): rng / pd.NaT - def test_td64arr_div_int(self, box): + def test_td64arr_div_int(self, box_with_array): idx = TimedeltaIndex(np.arange(5, dtype='int64')) - idx = tm.box_expected(idx, box) + idx = tm.box_expected(idx, box_with_array) result = idx / 1 tm.assert_equal(result, idx) - def test_tdi_div_tdlike_scalar(self, two_hours, box): + def test_tdi_div_tdlike_scalar(self, two_hours, box_with_array): # GH#20088, GH#22163 ensure DataFrame returns correct dtype rng = timedelta_range('1 days', '10 days', name='foo') expected = pd.Float64Index((np.arange(10) + 1) * 12, name='foo') - rng = tm.box_expected(rng, box) - expected = tm.box_expected(expected, box) + rng = tm.box_expected(rng, box_with_array) + expected = tm.box_expected(expected, box_with_array) result = rng / two_hours tm.assert_equal(result, expected) - def test_tdi_div_tdlike_scalar_with_nat(self, two_hours, box): + def test_tdi_div_tdlike_scalar_with_nat(self, two_hours, box_with_array): rng = TimedeltaIndex(['1 days', pd.NaT, '2 days'], name='foo') expected = pd.Float64Index([12, np.nan, 24], name='foo') - rng = tm.box_expected(rng, box) - expected = tm.box_expected(expected, box) + rng = tm.box_expected(rng, box_with_array) + expected = tm.box_expected(expected, box_with_array) result = rng / two_hours tm.assert_equal(result, expected) @@ -1130,59 +1155,60 @@ def test_tdi_div_tdlike_scalar_with_nat(self, two_hours, box): # ------------------------------------------------------------------ # __floordiv__, __rfloordiv__ - def test_td64arr_floordiv_tdscalar(self, box, scalar_td): + def test_td64arr_floordiv_tdscalar(self, box_with_array, 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, transpose=False) - expected = tm.box_expected(expected, box, transpose=False) + td1 = tm.box_expected(td1, box_with_array, transpose=False) + expected = tm.box_expected(expected, box_with_array, transpose=False) result = td1 // scalar_td tm.assert_equal(result, expected) - def test_td64arr_rfloordiv_tdscalar(self, box, scalar_td): + def test_td64arr_rfloordiv_tdscalar(self, box_with_array, 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, transpose=False) - expected = tm.box_expected(expected, box, transpose=False) + td1 = tm.box_expected(td1, box_with_array, transpose=False) + expected = tm.box_expected(expected, box_with_array, transpose=False) result = scalar_td // td1 tm.assert_equal(result, expected) - def test_td64arr_rfloordiv_tdscalar_explicit(self, box, scalar_td): + def test_td64arr_rfloordiv_tdscalar_explicit(self, box_with_array, + 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, transpose=False) - expected = tm.box_expected(expected, box, transpose=False) + td1 = tm.box_expected(td1, box_with_array, transpose=False) + expected = tm.box_expected(expected, box_with_array, transpose=False) # We can test __rfloordiv__ using this syntax, # see `test_timedelta_rfloordiv` result = td1.__rfloordiv__(scalar_td) tm.assert_equal(result, expected) - def test_td64arr_floordiv_int(self, box): + def test_td64arr_floordiv_int(self, box_with_array): idx = TimedeltaIndex(np.arange(5, dtype='int64')) - idx = tm.box_expected(idx, box) + idx = tm.box_expected(idx, box_with_array) result = idx // 1 tm.assert_equal(result, idx) - def test_td64arr_floordiv_tdlike_scalar(self, two_hours, box): + def test_td64arr_floordiv_tdlike_scalar(self, two_hours, box_with_array): tdi = timedelta_range('1 days', '10 days', name='foo') expected = pd.Int64Index((np.arange(10) + 1) * 12, name='foo') - tdi = tm.box_expected(tdi, box) - expected = tm.box_expected(expected, box) + tdi = tm.box_expected(tdi, box_with_array) + expected = tm.box_expected(expected, box_with_array) result = tdi // two_hours tm.assert_equal(result, expected) @@ -1193,19 +1219,19 @@ def test_td64arr_floordiv_tdlike_scalar(self, two_hours, box): Timedelta('10m7s'), Timedelta('10m7s').to_timedelta64() ], ids=lambda x: type(x).__name__) - def test_td64arr_rfloordiv_tdlike_scalar(self, scalar_td, box): + def test_td64arr_rfloordiv_tdlike_scalar(self, scalar_td, box_with_array): # GH#19125 tdi = TimedeltaIndex(['00:05:03', '00:05:03', pd.NaT], freq=None) expected = pd.Index([2.0, 2.0, np.nan]) - tdi = tm.box_expected(tdi, box, transpose=False) - expected = tm.box_expected(expected, box, transpose=False) + tdi = tm.box_expected(tdi, box_with_array, transpose=False) + expected = tm.box_expected(expected, box_with_array, transpose=False) res = tdi.__rfloordiv__(scalar_td) tm.assert_equal(res, expected) expected = pd.Index([0.0, 0.0, np.nan]) - expected = tm.box_expected(expected, box, transpose=False) + expected = tm.box_expected(expected, box_with_array, transpose=False) res = tdi // (scalar_td) tm.assert_equal(res, expected) @@ -1246,15 +1272,15 @@ def test_td64arr_mul_td64arr_raises(self, box_with_array): # Operations with numeric others @pytest.mark.parametrize('one', [1, np.array(1), 1.0, np.array(1.0)]) - def test_td64arr_mul_numeric_scalar(self, box, one): + def test_td64arr_mul_numeric_scalar(self, box_with_array, one): # GH#4521 # divide/multiply by integers tdser = pd.Series(['59 Days', '59 Days', 'NaT'], dtype='m8[ns]') expected = Series(['-59 Days', '-59 Days', 'NaT'], dtype='timedelta64[ns]') - tdser = tm.box_expected(tdser, box) - expected = tm.box_expected(expected, box) + tdser = tm.box_expected(tdser, box_with_array) + expected = tm.box_expected(expected, box_with_array) result = tdser * (-one) tm.assert_equal(result, expected) @@ -1263,7 +1289,7 @@ def test_td64arr_mul_numeric_scalar(self, box, one): expected = Series(['118 Days', '118 Days', 'NaT'], dtype='timedelta64[ns]') - expected = tm.box_expected(expected, box) + expected = tm.box_expected(expected, box_with_array) result = tdser * (2 * one) tm.assert_equal(result, expected) @@ -1271,14 +1297,14 @@ def test_td64arr_mul_numeric_scalar(self, box, one): tm.assert_equal(result, expected) @pytest.mark.parametrize('two', [2, 2.0, np.array(2), np.array(2.0)]) - def test_td64arr_div_numeric_scalar(self, box, two): + def test_td64arr_div_numeric_scalar(self, box_with_array, two): # GH#4521 # divide/multiply by integers tdser = pd.Series(['59 Days', '59 Days', 'NaT'], dtype='m8[ns]') expected = Series(['29.5D', '29.5D', 'NaT'], dtype='timedelta64[ns]') - tdser = tm.box_expected(tdser, box) - expected = tm.box_expected(expected, box) + tdser = tm.box_expected(tdser, box_with_array) + expected = tm.box_expected(expected, box_with_array) result = tdser / two tm.assert_equal(result, expected) @@ -1290,22 +1316,24 @@ def test_td64arr_div_numeric_scalar(self, box, two): pd.Index([20, 30, 40]), Series([20, 30, 40])], ids=lambda x: type(x).__name__) - @pytest.mark.parametrize('op', [operator.mul, ops.rmul]) - def test_td64arr_rmul_numeric_array(self, op, box, vector, dtype): + def test_td64arr_rmul_numeric_array(self, box_with_array, vector, dtype): # GH#4521 # divide/multiply by integers + xbox = get_upcast_box(box_with_array, vector) + tdser = pd.Series(['59 Days', '59 Days', 'NaT'], dtype='m8[ns]') 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) + tdser = tm.box_expected(tdser, box_with_array) + expected = tm.box_expected(expected, xbox) + + result = tdser * vector + tm.assert_equal(result, expected) - result = op(vector, tdser) + result = vector * tdser tm.assert_equal(result, expected) @pytest.mark.parametrize('dtype', ['int64', 'int32', 'int16', @@ -1315,17 +1343,17 @@ def test_td64arr_rmul_numeric_array(self, op, box, vector, dtype): pd.Index([20, 30, 40]), Series([20, 30, 40])], ids=lambda x: type(x).__name__) - def test_td64arr_div_numeric_array(self, box, vector, dtype): + def test_td64arr_div_numeric_array(self, box_with_array, vector, dtype): # GH#4521 # divide/multiply by integers + xbox = get_upcast_box(box_with_array, vector) tdser = pd.Series(['59 Days', '59 Days', 'NaT'], dtype='m8[ns]') 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) + tdser = tm.box_expected(tdser, box_with_array) + expected = tm.box_expected(expected, xbox) result = tdser / vector tm.assert_equal(result, expected) @@ -1376,8 +1404,8 @@ def test_float_series_rdiv_td64arr(self, box, names): 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) + xbox = Series if (box is pd.Index and type(ser) is Series) else box + expected = tm.box_expected(expected, xbox) result = ser.__rdiv__(tdi) if box is pd.DataFrame: diff --git a/pandas/tests/arrays/test_timedeltas.py b/pandas/tests/arrays/test_timedeltas.py index 3ff807daeeab9..4d2664054b1c1 100644 --- a/pandas/tests/arrays/test_timedeltas.py +++ b/pandas/tests/arrays/test_timedeltas.py @@ -1,5 +1,39 @@ # -*- coding: utf-8 -*- +import numpy as np + +import pandas as pd +from pandas.core.arrays import TimedeltaArrayMixin as TimedeltaArray +import pandas.util.testing as tm + class TestTimedeltaArray(object): - pass + + def test_abs(self): + vals = np.array([-3600 * 10**9, 'NaT', 7200 * 10**9], dtype='m8[ns]') + arr = TimedeltaArray(vals) + + evals = np.array([3600 * 10**9, 'NaT', 7200 * 10**9], dtype='m8[ns]') + expected = TimedeltaArray(evals) + + result = abs(arr) + tm.assert_timedelta_array_equal(result, expected) + + def test_neg(self): + vals = np.array([-3600 * 10**9, 'NaT', 7200 * 10**9], dtype='m8[ns]') + arr = TimedeltaArray(vals) + + evals = np.array([3600 * 10**9, 'NaT', -7200 * 10**9], dtype='m8[ns]') + expected = TimedeltaArray(evals) + + result = -arr + tm.assert_timedelta_array_equal(result, expected) + + def test_neg_freq(self): + tdi = pd.timedelta_range('2 Days', periods=4, freq='H') + arr = TimedeltaArray(tdi, freq=tdi.freq) + + expected = TimedeltaArray(-tdi._data, freq=-tdi.freq) + + result = -arr + tm.assert_timedelta_array_equal(result, expected)