Skip to content

Commit d856bb1

Browse files
committed
DEPR/API: Non-ns precision in Index constructors
This deprecates passing dtypes without a precision to DatetimeIndex and TimedeltaIndex ```python In [2]: pd.DatetimeIndex(['2000'], dtype='datetime64') /Users/taugspurger/.virtualenvs/pandas-dev/bin/ipython:1: FutureWarning: Passing in 'datetime64' dtype with no precision is deprecated and will raise in a future version. Please pass in 'datetime64[ns]' instead. #!/Users/taugspurger/Envs/pandas-dev/bin/python3 Out[2]: DatetimeIndex(['2000-01-01'], dtype='datetime64[ns]', freq=None) ``` Previously, we ignored the precision, so that things like ``` In [3]: pd.DatetimeIndex(['2000'], dtype='datetime64[us]') Out[3]: DatetimeIndex(['2000-01-01'], dtype='datetime64[ns]', freq=None) ``` worked. That is deprecated as well. Closes pandas-dev#24739 Closes pandas-dev#24753
1 parent 5a15a37 commit d856bb1

File tree

7 files changed

+82
-18
lines changed

7 files changed

+82
-18
lines changed

doc/source/whatsnew/v0.24.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ Backwards incompatible API changes
436436
- Incorrectly passing a :class:`DatetimeIndex` to :meth:`MultiIndex.from_tuples`, rather than a sequence of tuples, now raises a ``TypeError`` rather than a ``ValueError`` (:issue:`24024`)
437437
- :func:`pd.offsets.generate_range` argument ``time_rule`` has been removed; use ``offset`` instead (:issue:`24157`)
438438
- In 0.23.x, pandas would raise a ``ValueError`` on a merge of a numeric column (e.g. ``int`` dtyped column) and an ``object`` dtyped column (:issue:`9780`). We have re-enabled the ability to merge ``object`` and other dtypes; pandas will still raise on a merge between a numeric and an ``object`` dtyped column that is composed only of strings (:issue:`21681`)
439+
- :class:`DatetimeIndex` and :class:`TimedeltaIndex` no longer ignore the dtype precision. Passing a non-nanosecond resolution dtype will raise a ``ValueError`` (:issue:`24753`)
439440

440441
Percentage change on groupby
441442
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1310,6 +1311,7 @@ Deprecations
13101311
- :meth:`Series.nonzero` is deprecated and will be removed in a future version (:issue:`18262`)
13111312
- Passing an integer to :meth:`Series.fillna` and :meth:`DataFrame.fillna` with ``timedelta64[ns]`` dtypes is deprecated, will raise ``TypeError`` in a future version. Use ``obj.fillna(pd.Timedelta(...))`` instead (:issue:`24694`)
13121313
- ``Series.cat.categorical``, ``Series.cat.name`` and ``Sersies.cat.index`` have been deprecated. Use the attributes on ``Series.cat`` or ``Series`` directly. (:issue:`24751`).
1314+
- Passing a dtype without a precision like ``np.dtype('datetime64')`` or ``timedelta64`` to :class:`DatetimeIndex` and :class:`TimedeltaIndex` is now deprecated. Use the nanosecond-precision dtype instead (:issue:`24753`).
13131315

13141316
.. _whatsnew_0240.deprecations.datetimelike_int_ops:
13151317

pandas/core/arrays/datetimes.py

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
22
from datetime import datetime, time, timedelta
3+
import textwrap
34
import warnings
45

56
import numpy as np
@@ -1986,6 +1987,16 @@ def _validate_dt64_dtype(dtype):
19861987
"""
19871988
if dtype is not None:
19881989
dtype = pandas_dtype(dtype)
1990+
1991+
if isinstance(dtype, np.dtype) and dtype == np.dtype("M8"):
1992+
# no precision, warn
1993+
dtype = _NS_DTYPE
1994+
msg = textwrap.dedent("""\
1995+
Passing in 'datetime64' dtype with no precision is deprecated
1996+
and will raise in a future version. Please pass in
1997+
'datetime64[ns]' instead.""")
1998+
warnings.warn(msg, FutureWarning, stacklevel=5)
1999+
19892000
if ((isinstance(dtype, np.dtype) and dtype != _NS_DTYPE)
19902001
or not isinstance(dtype, (np.dtype, DatetimeTZDtype))):
19912002
raise ValueError("Unexpected value for 'dtype': '{dtype}'. "

pandas/core/arrays/timedeltas.py

+29-13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import division
33

44
from datetime import timedelta
5+
import textwrap
56
import warnings
67

78
import numpy as np
@@ -160,16 +161,8 @@ def __init__(self, values, dtype=_TD_DTYPE, freq=None, copy=False):
160161
# nanosecond UTC (or tz-naive) unix timestamps
161162
values = values.view(_TD_DTYPE)
162163

163-
if values.dtype != _TD_DTYPE:
164-
raise TypeError(_BAD_DTYPE.format(dtype=values.dtype))
165-
166-
try:
167-
dtype_mismatch = dtype != _TD_DTYPE
168-
except TypeError:
169-
raise TypeError(_BAD_DTYPE.format(dtype=dtype))
170-
else:
171-
if dtype_mismatch:
172-
raise TypeError(_BAD_DTYPE.format(dtype=dtype))
164+
_validate_td64_dtype(values.dtype)
165+
dtype = _validate_td64_dtype(dtype)
173166

174167
if freq == "infer":
175168
msg = (
@@ -204,9 +197,8 @@ def _simple_new(cls, values, freq=None, dtype=_TD_DTYPE):
204197
@classmethod
205198
def _from_sequence(cls, data, dtype=_TD_DTYPE, copy=False,
206199
freq=None, unit=None):
207-
if dtype != _TD_DTYPE:
208-
raise ValueError("Only timedelta64[ns] dtype is valid.")
209-
200+
if dtype:
201+
_validate_td64_dtype(dtype)
210202
freq, freq_infer = dtl.maybe_infer_freq(freq)
211203

212204
data, inferred_freq = sequence_to_td64ns(data, copy=copy, unit=unit)
@@ -997,6 +989,30 @@ def objects_to_td64ns(data, unit="ns", errors="raise"):
997989
return result.view('timedelta64[ns]')
998990

999991

992+
def _validate_td64_dtype(dtype):
993+
try:
994+
if dtype == np.dtype("timedelta64"):
995+
dtype = _TD_DTYPE
996+
msg = textwrap.dedent("""\
997+
Passing in 'timedelta' dtype with no precision is deprecated
998+
and will raise in a future version. Please pass in
999+
'timedelta64[ns]' instead.""")
1000+
warnings.warn(msg, FutureWarning, stacklevel=4)
1001+
except TypeError:
1002+
# extension dtype
1003+
pass
1004+
1005+
try:
1006+
dtype_mismatch = dtype != _TD_DTYPE
1007+
except TypeError:
1008+
raise ValueError(_BAD_DTYPE.format(dtype=dtype))
1009+
else:
1010+
if dtype_mismatch:
1011+
raise ValueError(_BAD_DTYPE.format(dtype=dtype))
1012+
1013+
return dtype
1014+
1015+
10001016
def _generate_regular_range(start, end, periods, offset):
10011017
stride = offset.nanos
10021018
if periods is None:

pandas/core/indexes/base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,8 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None,
311311
elif (is_timedelta64_dtype(data) or
312312
(dtype is not None and is_timedelta64_dtype(dtype))):
313313
from pandas import TimedeltaIndex
314-
result = TimedeltaIndex(data, copy=copy, name=name, **kwargs)
314+
result = TimedeltaIndex(data, copy=copy, name=name, dtype=dtype,
315+
**kwargs)
315316
if dtype is not None and _o_dtype == dtype:
316317
return Index(result.to_pytimedelta(), dtype=_o_dtype)
317318
else:

pandas/tests/arrays/test_timedeltas.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ def test_non_array_raises(self):
2323
TimedeltaArray([1, 2, 3])
2424

2525
def test_other_type_raises(self):
26-
with pytest.raises(TypeError,
26+
with pytest.raises(ValueError,
2727
match="dtype bool cannot be converted"):
2828
TimedeltaArray(np.array([1, 2, 3], dtype='bool'))
2929

3030
def test_incorrect_dtype_raises(self):
3131
# TODO: why TypeError for 'category' but ValueError for i8?
32-
with pytest.raises(TypeError,
32+
with pytest.raises(ValueError,
3333
match=r'category cannot be converted '
3434
r'to timedelta64\[ns\]'):
3535
TimedeltaArray(np.array([1, 2, 3], dtype='i8'), dtype='category')
3636

37-
with pytest.raises(TypeError,
37+
with pytest.raises(ValueError,
3838
match=r"dtype int64 cannot be converted "
3939
r"to timedelta64\[ns\]"):
4040
TimedeltaArray(np.array([1, 2, 3], dtype='i8'),
@@ -52,7 +52,7 @@ def test_copy(self):
5252

5353
class TestTimedeltaArray(object):
5454
def test_from_sequence_dtype(self):
55-
msg = r"Only timedelta64\[ns\] dtype is valid"
55+
msg = "dtype .*object.* cannot be converted to timedelta64"
5656
with pytest.raises(ValueError, match=msg):
5757
TimedeltaArray._from_sequence([], dtype=object)
5858

pandas/tests/indexes/datetimes/test_construction.py

+17
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,23 @@ def test_construction_with_nat_and_tzlocal(self):
634634
expected = DatetimeIndex([Timestamp('2018', tz=tz), pd.NaT])
635635
tm.assert_index_equal(result, expected)
636636

637+
def test_constructor_no_precision_warns(self):
638+
# GH-24753, GH-24739
639+
expected = pd.DatetimeIndex(['2000'], dtype='datetime64[ns]')
640+
641+
# we set the stacklevel for DatetimeIndex
642+
with tm.assert_produces_warning(FutureWarning):
643+
result = pd.DatetimeIndex(['2000'], dtype='datetime64')
644+
tm.assert_index_equal(result, expected)
645+
646+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
647+
result = pd.Index(['2000'], dtype='datetime64')
648+
tm.assert_index_equal(result, expected)
649+
650+
def test_constructor_wrong_precision_raises(self):
651+
with pytest.raises(ValueError):
652+
pd.DatetimeIndex(['2000'], dtype='datetime64[us]')
653+
637654

638655
class TestTimeSeries(object):
639656

pandas/tests/indexes/timedeltas/test_construction.py

+17
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,20 @@ def test_constructor_name(self):
180180
# GH10025
181181
idx2 = TimedeltaIndex(idx, name='something else')
182182
assert idx2.name == 'something else'
183+
184+
def test_constructor_no_precision_warns(self):
185+
# GH-24753, GH-24739
186+
expected = pd.TimedeltaIndex(['2000'], dtype='timedelta64[ns]')
187+
188+
# we set the stacklevel for DatetimeIndex
189+
with tm.assert_produces_warning(FutureWarning):
190+
result = pd.TimedeltaIndex(['2000'], dtype='timedelta64')
191+
tm.assert_index_equal(result, expected)
192+
193+
with tm.assert_produces_warning(FutureWarning, check_stacklevel=False):
194+
result = pd.Index(['2000'], dtype='timedelta64')
195+
tm.assert_index_equal(result, expected)
196+
197+
def test_constructor_wrong_precision_raises(self):
198+
with pytest.raises(ValueError):
199+
pd.TimedeltaIndex(['2000'], dtype='timedelta64[us]')

0 commit comments

Comments
 (0)