Skip to content

Commit ffb6f2d

Browse files
jbrockmendelMarcoGorelli
authored and
MarcoGorelli
committed
API: Float64Index.astype(datetimelike) (pandas-dev#49660)
* API: Float64Index.astype(datetimelike) * GH ref
1 parent a50b5af commit ffb6f2d

File tree

5 files changed

+30
-31
lines changed

5 files changed

+30
-31
lines changed

doc/source/whatsnew/v2.0.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ Other API changes
329329
- Default value of ``dtype`` in :func:`get_dummies` is changed to ``bool`` from ``uint8`` (:issue:`45848`)
330330
- :meth:`DataFrame.astype`, :meth:`Series.astype`, and :meth:`DatetimeIndex.astype` casting datetime64 data to any of "datetime64[s]", "datetime64[ms]", "datetime64[us]" will return an object with the given resolution instead of coercing back to "datetime64[ns]" (:issue:`48928`)
331331
- :meth:`DataFrame.astype`, :meth:`Series.astype`, and :meth:`DatetimeIndex.astype` casting timedelta64 data to any of "timedelta64[s]", "timedelta64[ms]", "timedelta64[us]" will return an object with the given resolution instead of coercing to "float64" dtype (:issue:`48963`)
332+
- :meth:`Index.astype` now allows casting from ``float64`` dtype to datetime-like dtypes, matching :class:`Series` behavior (:issue:`49660`)
332333
- Passing data with dtype of "timedelta64[s]", "timedelta64[ms]", or "timedelta64[us]" to :class:`TimedeltaIndex`, :class:`Series`, or :class:`DataFrame` constructors will now retain that dtype instead of casting to "timedelta64[ns]"; timedelta64 data with lower resolution will be cast to the lowest supported resolution "timedelta64[s]" (:issue:`49014`)
333334
- Passing ``dtype`` of "timedelta64[s]", "timedelta64[ms]", or "timedelta64[us]" to :class:`TimedeltaIndex`, :class:`Series`, or :class:`DataFrame` constructors will now retain that dtype instead of casting to "timedelta64[ns]"; passing a dtype with lower resolution for :class:`Series` or :class:`DataFrame` will be cast to the lowest supported resolution "timedelta64[s]" (:issue:`49014`)
334335
- Passing a ``np.datetime64`` object with non-nanosecond resolution to :class:`Timestamp` will retain the input resolution if it is "s", "ms", or "ns"; otherwise it will be cast to the closest supported resolution (:issue:`49008`)

pandas/core/arrays/interval.py

+9
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,15 @@ def astype(self, dtype, copy: bool = True):
919919
if dtype == self.dtype:
920920
return self.copy() if copy else self
921921

922+
if is_float_dtype(self.dtype.subtype) and needs_i8_conversion(
923+
dtype.subtype
924+
):
925+
# This is allowed on the Index.astype but we disallow it here
926+
msg = (
927+
f"Cannot convert {self.dtype} to {dtype}; subtypes are incompatible"
928+
)
929+
raise TypeError(msg)
930+
922931
# need to cast to different subtype
923932
try:
924933
# We need to use Index rules for astype to prevent casting

pandas/core/indexes/base.py

-8
Original file line numberDiff line numberDiff line change
@@ -1016,14 +1016,6 @@ def astype(self, dtype, copy: bool = True):
10161016
with rewrite_exception(type(values).__name__, type(self).__name__):
10171017
new_values = values.astype(dtype, copy=copy)
10181018

1019-
elif is_float_dtype(self.dtype) and needs_i8_conversion(dtype):
1020-
# NB: this must come before the ExtensionDtype check below
1021-
# TODO: this differs from Series behavior; can/should we align them?
1022-
raise TypeError(
1023-
f"Cannot convert dtype={self.dtype} to dtype {dtype}; integer "
1024-
"values are required for conversion"
1025-
)
1026-
10271019
elif isinstance(dtype, ExtensionDtype):
10281020
cls = dtype.construct_array_type()
10291021
# Note: for RangeIndex and CategoricalDtype self vs self._values

pandas/tests/arithmetic/test_interval.py

+1-11
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,8 @@ def test_compare_scalar_interval_mixed_closed(self, op, closed, other_closed):
133133
expected = self.elementwise_comparison(op, interval_array, other)
134134
tm.assert_numpy_array_equal(result, expected)
135135

136-
def test_compare_scalar_na(
137-
self, op, interval_array, nulls_fixture, box_with_array, request
138-
):
136+
def test_compare_scalar_na(self, op, interval_array, nulls_fixture, box_with_array):
139137
box = box_with_array
140-
141-
if box is pd.DataFrame:
142-
if interval_array.dtype.subtype.kind not in "iuf":
143-
mark = pytest.mark.xfail(
144-
reason="raises on DataFrame.transpose (would be fixed by EA2D)"
145-
)
146-
request.node.add_marker(mark)
147-
148138
obj = tm.box_expected(interval_array, box)
149139
result = op(obj, nulls_fixture)
150140

pandas/tests/indexes/numeric/test_astype.py

+19-12
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import re
2-
31
import numpy as np
42
import pytest
53

6-
from pandas.core.dtypes.common import pandas_dtype
7-
8-
from pandas import Index
4+
from pandas import (
5+
Index,
6+
to_datetime,
7+
to_timedelta,
8+
)
99
import pandas._testing as tm
1010

1111

@@ -66,15 +66,22 @@ def test_astype_float64_to_float_dtype(self, dtype):
6666
tm.assert_index_equal(result, expected, exact=True)
6767

6868
@pytest.mark.parametrize("dtype", ["M8[ns]", "m8[ns]"])
69-
def test_cannot_cast_to_datetimelike(self, dtype):
69+
def test_astype_float_to_datetimelike(self, dtype):
70+
# GH#49660 pre-2.0 Index.astype from floating to M8/m8/Period raised,
71+
# inconsistent with Series.astype
7072
idx = Index([0, 1.1, 2], dtype=np.float64)
7173

72-
msg = (
73-
f"Cannot convert dtype=float64 to dtype {pandas_dtype(dtype)}; "
74-
f"integer values are required for conversion"
75-
)
76-
with pytest.raises(TypeError, match=re.escape(msg)):
77-
idx.astype(dtype)
74+
result = idx.astype(dtype)
75+
if dtype[0] == "M":
76+
expected = to_datetime(idx.values)
77+
else:
78+
expected = to_timedelta(idx.values)
79+
tm.assert_index_equal(result, expected)
80+
81+
# check that we match Series behavior
82+
result = idx.to_series().set_axis(range(3)).astype(dtype)
83+
expected = expected.to_series().set_axis(range(3))
84+
tm.assert_series_equal(result, expected)
7885

7986
@pytest.mark.parametrize("dtype", [int, "int16", "int32", "int64"])
8087
@pytest.mark.parametrize("non_finite", [np.inf, np.nan])

0 commit comments

Comments
 (0)