Skip to content

Commit 87f29a0

Browse files
gfyoungPingviinituutti
authored andcommitted
API: Disallow dtypes w/o frequency when casting (pandas-dev#23392)
Previously deprecated for Series constructor and the `.astype` method. Now being enforced. xref pandas-devgh-15987.
1 parent 40d6ada commit 87f29a0

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
- :func:`pandas.pnow`, :func:`pandas.match`, :func:`pandas.groupby`, :func:`pd.get_store`, ``pd.Expr``, and ``pd.Term`` have been removed (:issue:`15538`, :issue:`15940`)

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
@@ -1192,32 +1192,26 @@ def test_constructor_cast_object(self, index):
11921192
exp = Series(index).astype(object)
11931193
tm.assert_series_equal(s, exp)
11941194

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

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

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

1218-
msg = "cannot convert datetimelike"
12191213
with tm.assert_raises_regex(TypeError, msg):
1220-
Series([], dtype='M8[ps]')
1214+
Series([], dtype=dtype)
12211215

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

pandas/tests/series/test_dtypes.py

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

44
import string
55
import sys
6-
import warnings
76
from datetime import datetime, timedelta
87

98
import numpy as np
@@ -21,7 +20,7 @@
2120
from pandas.compat import lrange, range, u
2221

2322

24-
class TestSeriesDtypes():
23+
class TestSeriesDtypes(object):
2524

2625
def test_dt64_series_astype_object(self):
2726
dt64ser = Series(date_range('20130101', periods=3))
@@ -396,40 +395,30 @@ def test_astype_categoricaldtype_with_args(self):
396395
with pytest.raises(TypeError):
397396
s.astype(type_, categories=['a', 'b'], ordered=False)
398397

399-
def test_astype_generic_timestamp_deprecated(self):
400-
# see gh-15524
398+
@pytest.mark.parametrize("dtype", [
399+
np.datetime64,
400+
np.timedelta64,
401+
])
402+
def test_astype_generic_timestamp_no_frequency(self, dtype):
403+
# see gh-15524, gh-15987
401404
data = [1]
405+
s = Series(data)
402406

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

419411
@pytest.mark.parametrize("dtype", np.typecodes['All'])
420412
def test_astype_empty_constructor_equality(self, dtype):
421413
# see gh-15524
422414

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

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

0 commit comments

Comments
 (0)