Skip to content

Commit 4e266ab

Browse files
committed
API: Disallow dtypes w/o frequency when casting
Previously deprecated for Series constructor and the `.astype` method. Now being enforced. xref pandas-devgh-15987.
1 parent da9d851 commit 4e266ab

File tree

4 files changed

+46
-64
lines changed

4 files changed

+46
-64
lines changed

doc/source/whatsnew/v0.24.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -942,6 +942,7 @@ Removal of prior version deprecations/changes
942942
- Removal of the previously deprecated module ``pandas.core.datetools`` (:issue:`14105`, :issue:`14094`)
943943
- Strings passed into :meth:`DataFrame.groupby` that refer to both column and index levels will raise a ``ValueError`` (:issue:`14432`)
944944
- :meth:`Index.repeat` and :meth:`MultiIndex.repeat` have renamed the ``n`` argument to ``repeats`` (:issue:`14645`)
945+
- The ``Series`` constructor and ``.astype`` method will now raise a ``ValueError`` if timestamp dtypes are passed in without a frequency (e.g. ``np.datetime64``) for the ``dtype`` parameter (:issue:`15987`)
945946
- Removal of the previously deprecated ``as_indexer`` keyword completely from ``str.match()`` (:issue:`22356`, :issue:`6581`)
946947
- Removed the ``pandas.formats.style`` shim for :class:`pandas.io.formats.style.Styler` (:issue:`16059`)
947948
- :meth:`Categorical.searchsorted` and :meth:`Series.searchsorted` have renamed the ``v`` argument to ``value`` (:issue:`14645`)

pandas/core/dtypes/cast.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from datetime import datetime, timedelta
44

55
import numpy as np
6-
import warnings
76

87
from pandas._libs import tslib, lib, tslibs
98
from pandas._libs.tslibs import iNaT, OutOfBoundsDatetime, Period
@@ -664,6 +663,11 @@ def astype_nansafe(arr, dtype, copy=True, skipna=False):
664663
e.g. the item sizes don't align.
665664
skipna: bool, default False
666665
Whether or not we should skip NaN when casting as a string-type.
666+
667+
Raises
668+
------
669+
ValueError
670+
The dtype was a datetime /timedelta dtype, but it had no frequency.
667671
"""
668672

669673
# dispatch on extension dtype if needed
@@ -745,12 +749,9 @@ def astype_nansafe(arr, dtype, copy=True, skipna=False):
745749
return astype_nansafe(to_timedelta(arr).values, dtype, copy=copy)
746750

747751
if dtype.name in ("datetime64", "timedelta64"):
748-
msg = ("Passing in '{dtype}' dtype with no frequency is "
749-
"deprecated and will raise in a future version. "
752+
msg = ("The '{dtype}' dtype has no frequency. "
750753
"Please pass in '{dtype}[ns]' instead.")
751-
warnings.warn(msg.format(dtype=dtype.name),
752-
FutureWarning, stacklevel=5)
753-
dtype = np.dtype(dtype.name + "[ns]")
754+
raise ValueError(msg.format(dtype=dtype.name))
754755

755756
if copy or is_object_dtype(arr) or is_object_dtype(dtype):
756757
# Explicit copy, or required since NumPy can't view from / to object.
@@ -1019,16 +1020,14 @@ def maybe_cast_to_datetime(value, dtype, errors='raise'):
10191020

10201021
if is_datetime64 or is_datetime64tz or is_timedelta64:
10211022

1022-
# force the dtype if needed
1023-
msg = ("Passing in '{dtype}' dtype with no frequency is "
1024-
"deprecated and will raise in a future version. "
1023+
# Force the dtype if needed.
1024+
msg = ("The '{dtype}' dtype has no frequency. "
10251025
"Please pass in '{dtype}[ns]' instead.")
10261026

10271027
if is_datetime64 and not is_dtype_equal(dtype, _NS_DTYPE):
10281028
if dtype.name in ('datetime64', 'datetime64[ns]'):
10291029
if dtype.name == 'datetime64':
1030-
warnings.warn(msg.format(dtype=dtype.name),
1031-
FutureWarning, stacklevel=5)
1030+
raise ValueError(msg.format(dtype=dtype.name))
10321031
dtype = _NS_DTYPE
10331032
else:
10341033
raise TypeError("cannot convert datetimelike to "
@@ -1044,8 +1043,7 @@ def maybe_cast_to_datetime(value, dtype, errors='raise'):
10441043
elif is_timedelta64 and not is_dtype_equal(dtype, _TD_DTYPE):
10451044
if dtype.name in ('timedelta64', 'timedelta64[ns]'):
10461045
if dtype.name == 'timedelta64':
1047-
warnings.warn(msg.format(dtype=dtype.name),
1048-
FutureWarning, stacklevel=5)
1046+
raise ValueError(msg.format(dtype=dtype.name))
10491047
dtype = _TD_DTYPE
10501048
else:
10511049
raise TypeError("cannot convert timedeltalike to "

pandas/tests/series/test_constructors.py

+16-22
Original file line numberDiff line numberDiff line change
@@ -1193,32 +1193,26 @@ def test_constructor_cast_object(self, index):
11931193
exp = Series(index).astype(object)
11941194
tm.assert_series_equal(s, exp)
11951195

1196-
def test_constructor_generic_timestamp_deprecated(self):
1197-
# see gh-15524
1198-
1199-
with tm.assert_produces_warning(FutureWarning):
1200-
dtype = np.timedelta64
1201-
s = Series([], dtype=dtype)
1202-
1203-
assert s.empty
1204-
assert s.dtype == 'm8[ns]'
1205-
1206-
with tm.assert_produces_warning(FutureWarning):
1207-
dtype = np.datetime64
1208-
s = Series([], dtype=dtype)
1196+
@pytest.mark.parametrize("dtype", [
1197+
np.datetime64,
1198+
np.timedelta64,
1199+
])
1200+
def test_constructor_generic_timestamp_no_frequency(self, dtype):
1201+
# see gh-15524, gh-15987
1202+
msg = "dtype has no frequency. Please pass in"
12091203

1210-
assert s.empty
1211-
assert s.dtype == 'M8[ns]'
1204+
with tm.assert_raises_regex(ValueError, msg):
1205+
Series([], dtype=dtype)
12121206

1213-
# These timestamps have the wrong frequencies,
1214-
# so an Exception should be raised now.
1215-
msg = "cannot convert timedeltalike"
1216-
with tm.assert_raises_regex(TypeError, msg):
1217-
Series([], dtype='m8[ps]')
1207+
@pytest.mark.parametrize("dtype,msg", [
1208+
("m8[ps]", "cannot convert timedeltalike"),
1209+
("M8[ps]", "cannot convert datetimelike"),
1210+
])
1211+
def test_constructor_generic_timestamp_bad_frequency(self, dtype, msg):
1212+
# see gh-15524, gh-15987
12181213

1219-
msg = "cannot convert datetimelike"
12201214
with tm.assert_raises_regex(TypeError, msg):
1221-
Series([], dtype='M8[ps]')
1215+
Series([], dtype=dtype)
12221216

12231217
@pytest.mark.parametrize('dtype', [None, 'uint8', 'category'])
12241218
def test_constructor_range_dtype(self, dtype):

pandas/tests/series/test_dtypes.py

+18-29
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import sys
99
import string
10-
import warnings
1110

1211
from numpy import nan
1312
import pandas as pd
@@ -25,7 +24,7 @@
2524
import pandas.util.testing as tm
2625

2726

28-
class TestSeriesDtypes():
27+
class TestSeriesDtypes(object):
2928

3029
def test_dt64_series_astype_object(self):
3130
dt64ser = Series(date_range('20130101', periods=3))
@@ -400,40 +399,30 @@ def test_astype_categoricaldtype_with_args(self):
400399
with pytest.raises(TypeError):
401400
s.astype(type_, categories=['a', 'b'], ordered=False)
402401

403-
def test_astype_generic_timestamp_deprecated(self):
404-
# see gh-15524
402+
@pytest.mark.parametrize("dtype", [
403+
np.datetime64,
404+
np.timedelta64,
405+
])
406+
def test_astype_generic_timestamp_no_frequency(self, dtype):
407+
# see gh-15524, gh-15987
405408
data = [1]
409+
s = Series(data)
406410

407-
with tm.assert_produces_warning(FutureWarning,
408-
check_stacklevel=False):
409-
s = Series(data)
410-
dtype = np.datetime64
411-
result = s.astype(dtype)
412-
expected = Series(data, dtype=dtype)
413-
tm.assert_series_equal(result, expected)
414-
415-
with tm.assert_produces_warning(FutureWarning,
416-
check_stacklevel=False):
417-
s = Series(data)
418-
dtype = np.timedelta64
419-
result = s.astype(dtype)
420-
expected = Series(data, dtype=dtype)
421-
tm.assert_series_equal(result, expected)
411+
msg = "dtype has no frequency. Please pass in"
412+
with tm.assert_raises_regex(ValueError, msg):
413+
s.astype(dtype)
422414

423415
@pytest.mark.parametrize("dtype", np.typecodes['All'])
424416
def test_astype_empty_constructor_equality(self, dtype):
425417
# see gh-15524
426418

427-
if dtype not in ('S', 'V'): # poor support (if any) currently
428-
with warnings.catch_warnings(record=True):
429-
if dtype in ('M', 'm'):
430-
# Generic timestamp dtypes ('M' and 'm') are deprecated,
431-
# but we test that already in series/test_constructors.py
432-
warnings.simplefilter("ignore", FutureWarning)
433-
434-
init_empty = Series([], dtype=dtype)
435-
as_type_empty = Series([]).astype(dtype)
436-
tm.assert_series_equal(init_empty, as_type_empty)
419+
if dtype not in (
420+
"S", "V", # poor support (if any) currently
421+
"M", "m" # Generic timestamps raise a ValueError. Already tested.
422+
):
423+
init_empty = Series([], dtype=dtype)
424+
as_type_empty = Series([]).astype(dtype)
425+
tm.assert_series_equal(init_empty, as_type_empty)
437426

438427
def test_complex(self):
439428
# see gh-4819: complex access for ndarray compat

0 commit comments

Comments
 (0)