From b770d7b2b2147b232e7e14a0807405ab98718df6 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Tue, 27 Nov 2018 12:43:25 -0800 Subject: [PATCH 1/3] implement TimedeltaArray._from_sequence --- pandas/core/arrays/timedeltas.py | 36 ++++++++++++++++----- pandas/core/indexes/timedeltas.py | 33 ++++--------------- pandas/tests/arithmetic/test_timedelta64.py | 1 + pandas/tests/arrays/test_timedeltas.py | 7 ++++ 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index d1e6d979b554c..925c6b549c895 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -162,16 +162,36 @@ def _simple_new(cls, values, freq=None, dtype=_TD_DTYPE): result._freq = freq return result - def __new__(cls, values, freq=None, dtype=_TD_DTYPE): + def __new__(cls, values, freq=None, dtype=_TD_DTYPE, copy=False): + return cls._from_sequence(values, freq=freq, dtype=dtype, copy=copy) - freq, freq_infer = dtl.maybe_infer_freq(freq) + @classmethod + def _from_sequence(cls, data, freq=None, unit=None, + dtype=_TD_DTYPE, copy=False): + if dtype != _TD_DTYPE: + raise ValueError("Only timedelta64[ns] dtype is valid.") - values = np.array(values, copy=False) - if values.dtype == np.object_: - values = array_to_timedelta64(values) + freq, freq_infer = dtl.maybe_infer_freq(freq) - result = cls._simple_new(values, freq=freq) - if freq_infer: + data, inferred_freq = sequence_to_td64ns(data, copy=copy, unit=unit) + if inferred_freq is not None: + if freq is not None and freq != inferred_freq: + raise ValueError('Inferred frequency {inferred} from passed ' + 'values does not conform to passed frequency ' + '{passed}' + .format(inferred=inferred_freq, + passed=freq.freqstr)) + elif freq is None: + freq = inferred_freq + freq_infer = False + + result = cls._simple_new(data, freq=freq) + + if inferred_freq is None and freq is not None: + # check that we are matching freqs + cls._validate_frequency(result, freq) + + elif freq_infer: result.freq = to_offset(result.inferred_freq) return result @@ -539,7 +559,7 @@ def sequence_to_td64ns(data, copy=False, unit="ns", errors="raise"): warnings.warn("Passing datetime64-dtype data to TimedeltaIndex is " "deprecated, will raise a TypeError in a future " "version", - FutureWarning, stacklevel=3) + FutureWarning, stacklevel=4) data = ensure_int64(data).view(_TD_DTYPE) else: diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 8f50b40a20738..5b5377585a38f 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -16,8 +16,7 @@ from pandas.core.arrays import datetimelike as dtl from pandas.core.arrays.timedeltas import ( - TimedeltaArrayMixin as TimedeltaArray, _is_convertible_to_td, _to_m8, - sequence_to_td64ns) + TimedeltaArrayMixin as TimedeltaArray, _is_convertible_to_td, _to_m8) from pandas.core.base import _shared_docs import pandas.core.common as com from pandas.core.indexes.base import Index, _index_shared_docs @@ -131,10 +130,9 @@ def __new__(cls, data=None, unit=None, freq=None, start=None, end=None, periods=None, closed=None, dtype=None, copy=False, name=None, verify_integrity=True): - freq, freq_infer = dtl.maybe_infer_freq(freq) - if data is None: # TODO: Remove this block and associated kwargs; GH#20535 + freq, freq_infer = dtl.maybe_infer_freq(freq) result = cls._generate_range(start, end, periods, freq, closed=closed) result.name = name @@ -153,29 +151,10 @@ def __new__(cls, data=None, unit=None, freq=None, start=None, end=None, # - Cases checked above all return/raise before reaching here - # - data, inferred_freq = sequence_to_td64ns(data, copy=copy, unit=unit) - if inferred_freq is not None: - if freq is not None and freq != inferred_freq: - raise ValueError('Inferred frequency {inferred} from passed ' - 'values does not conform to passed frequency ' - '{passed}' - .format(inferred=inferred_freq, - passed=freq.freqstr)) - elif freq_infer: - freq = inferred_freq - freq_infer = False - verify_integrity = False - - subarr = cls._simple_new(data, name=name, freq=freq) - # check that we are matching freqs - if verify_integrity and len(subarr) > 0: - if freq is not None and not freq_infer: - cls._validate_frequency(subarr, freq) - - if freq_infer: - subarr.freq = to_offset(subarr.inferred_freq) - - return subarr + result = cls._from_sequence(data, freq=freq, unit=unit, + dtype=dtype, copy=copy) + result.name = name + return result @classmethod def _simple_new(cls, values, name=None, freq=None, dtype=_TD_DTYPE): diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 58c7216f0eece..383f948936dc4 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -1105,6 +1105,7 @@ def test_tdi_rmul_arraylike(self, other, box_with_array): tdi = TimedeltaIndex(['1 Day'] * 10) expected = timedelta_range('1 days', '10 days') + expected._freq = None tdi = tm.box_expected(tdi, box) expected = tm.box_expected(expected, xbox) diff --git a/pandas/tests/arrays/test_timedeltas.py b/pandas/tests/arrays/test_timedeltas.py index 4d2664054b1c1..aef30c1bb7744 100644 --- a/pandas/tests/arrays/test_timedeltas.py +++ b/pandas/tests/arrays/test_timedeltas.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import numpy as np +import pytest import pandas as pd from pandas.core.arrays import TimedeltaArrayMixin as TimedeltaArray @@ -8,6 +9,12 @@ class TestTimedeltaArray(object): + def test_from_sequence_dtype(self): + msg = r"Only timedelta64\[ns\] dtype is valid" + with pytest.raises(ValueError, match=msg): + TimedeltaArray._from_sequence([], dtype=object) + with pytest.raises(ValueError, match=msg): + TimedeltaArray([], dtype=object) def test_abs(self): vals = np.array([-3600 * 10**9, 'NaT', 7200 * 10**9], dtype='m8[ns]') From 6f64870abd4015891e680eec974ac0e94d99b6a3 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 28 Nov 2018 16:38:32 -0800 Subject: [PATCH 2/3] fixup dtype --- pandas/core/indexes/timedeltas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index a2eef6b0a7df1..7cdc89e62c74f 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -131,7 +131,7 @@ def _join_i8_wrapper(joinf, **kwargs): # Constructors def __new__(cls, data=None, unit=None, freq=None, start=None, end=None, - periods=None, closed=None, dtype=None, copy=False, + periods=None, closed=None, dtype=_TD_DTYPE, copy=False, name=None, verify_integrity=None): if verify_integrity is not None: From 56b82fcb41e6fee15325f5b8f5e465eb6be0547e Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 28 Nov 2018 18:56:29 -0800 Subject: [PATCH 3/3] fix arg passed to TimedeltaIndex in test --- pandas/tests/dtypes/test_inference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/dtypes/test_inference.py b/pandas/tests/dtypes/test_inference.py index 7732c5815b115..0c22b595bc74d 100644 --- a/pandas/tests/dtypes/test_inference.py +++ b/pandas/tests/dtypes/test_inference.py @@ -1163,7 +1163,7 @@ def test_is_timedelta(self): assert not is_timedelta64_ns_dtype('timedelta64') assert is_timedelta64_ns_dtype('timedelta64[ns]') - tdi = TimedeltaIndex([1e14, 2e14], dtype='timedelta64') + tdi = TimedeltaIndex([1e14, 2e14], dtype='timedelta64[ns]') assert is_timedelta64_dtype(tdi) assert is_timedelta64_ns_dtype(tdi) assert is_timedelta64_ns_dtype(tdi.astype('timedelta64[ns]'))