Skip to content

BUG: allow Series[Period].astype(dt64) #45055

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions doc/source/user_guide/timeseries.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2102,19 +2102,14 @@ The ``period`` dtype can be used in ``.astype(...)``. It allows one to change th
# change monthly freq to daily freq
pi.astype("period[D]")

# convert to DatetimeIndex
pi.astype("datetime64[ns]")

# convert to PeriodIndex
dti = pd.date_range("2011-01-01", freq="M", periods=3)
dti
dti.astype("period[M]")

.. deprecated:: 1.4.0
Converting PeriodIndex to DatetimeIndex with ``.astype(...)`` is deprecated and will raise in a future version. Use ``obj.to_timestamp(how).tz_localize(dtype.tz)`` instead.

.. ipython:: python

# convert to DatetimeIndex
pi.to_timestamp(how="start")

PeriodIndex partial string indexing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion doc/source/whatsnew/v1.4.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,6 @@ Other Deprecations
- Deprecated casting behavior when passing an item with mismatched-timezone to :meth:`DatetimeIndex.insert`, :meth:`DatetimeIndex.putmask`, :meth:`DatetimeIndex.where` :meth:`DatetimeIndex.fillna`, :meth:`Series.mask`, :meth:`Series.where`, :meth:`Series.fillna`, :meth:`Series.shift`, :meth:`Series.replace`, :meth:`Series.reindex` (and :class:`DataFrame` column analogues). In the past this has cast to object dtype. In a future version, these will cast the passed item to the index or series's timezone (:issue:`37605`,:issue:`44940`)
- Deprecated the 'errors' keyword argument in :meth:`Series.where`, :meth:`DataFrame.where`, :meth:`Series.mask`, and :meth:`DataFrame.mask`; in a future version the argument will be removed (:issue:`44294`)
- Deprecated the ``prefix`` keyword argument in :func:`read_csv` and :func:`read_table`, in a future version the argument will be removed (:issue:`43396`)
- Deprecated :meth:`PeriodIndex.astype` to ``datetime64[ns]`` or ``DatetimeTZDtype``, use ``obj.to_timestamp(how).tz_localize(dtype.tz)`` instead (:issue:`44398`)
- Deprecated passing non boolean argument to sort in :func:`concat` (:issue:`41518`)
- Deprecated passing arguments as positional for :func:`read_fwf` other than ``filepath_or_buffer`` (:issue:`41485`):
- Deprecated passing ``skipna=None`` for :meth:`DataFrame.mad` and :meth:`Series.mad`, pass ``skipna=True`` instead (:issue:`44580`)
Expand Down Expand Up @@ -673,6 +672,7 @@ Conversion
- Bug in :meth:`DataFrame.astype` not propagating ``attrs`` from the original :class:`DataFrame` (:issue:`44414`)
- Bug in :meth:`DataFrame.convert_dtypes` result losing ``columns.names`` (:issue:`41435`)
- Bug in constructing a ``IntegerArray`` from pyarrow data failing to validate dtypes (:issue:`44891`)
- Bug in :meth:`Series.astype` not allowing converting from a ``PeriodDtype`` to ``datetime64`` dtype, inconsistent with the :class:`PeriodIndex` behavior (:issue:`45038`)
-

Strings
Expand Down
7 changes: 7 additions & 0 deletions pandas/core/arrays/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from pandas.core.dtypes.common import (
TD64NS_DTYPE,
ensure_object,
is_datetime64_any_dtype,
is_datetime64_dtype,
is_dtype_equal,
is_float_dtype,
Expand Down Expand Up @@ -666,6 +667,12 @@ def astype(self, dtype, copy: bool = True):
return self.copy()
if is_period_dtype(dtype):
return self.asfreq(dtype.freq)

if is_datetime64_any_dtype(dtype):
# GH#45038 match PeriodIndex behavior.
tz = getattr(dtype, "tz", None)
return self.to_timestamp().tz_localize(tz)

return super().astype(dtype, copy=copy)

def searchsorted(
Expand Down
11 changes: 3 additions & 8 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,14 +358,9 @@ def astype(self, dtype, copy: bool = True, how=lib.no_default):

if is_datetime64_any_dtype(dtype):
# 'how' is index-specific, isn't part of the EA interface.
# GH#44398 deprecate astype(dt64), matching Series behavior
warnings.warn(
f"Converting {type(self).__name__} to DatetimeIndex with "
"'astype' is deprecated and will raise in a future version. "
"Use `obj.to_timestamp(how).tz_localize(dtype.tz)` instead.",
FutureWarning,
stacklevel=find_stack_level(),
)
# GH#45038 implement this for PeriodArray (but without "how")
# once the "how" deprecation is enforced we can just dispatch
# directly to PeriodArray.
tz = getattr(dtype, "tz", None)
return self.to_timestamp(how=how).tz_localize(tz)

Expand Down
11 changes: 9 additions & 2 deletions pandas/tests/arrays/period/test_astype.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,12 @@ def test_astype_period():
def test_astype_datetime(other):
arr = period_array(["2000", "2001", None], freq="D")
# slice off the [ns] so that the regex matches.
with pytest.raises(TypeError, match=other[:-4]):
arr.astype(other)
if other == "timedelta64[ns]":
with pytest.raises(TypeError, match=other[:-4]):
arr.astype(other)

else:
# GH#45038 allow period->dt64 because we allow dt64->period
result = arr.astype(other)
expected = pd.DatetimeIndex(["2000", "2001", pd.NaT])._data
tm.assert_datetime_array_equal(result, expected)
5 changes: 1 addition & 4 deletions pandas/tests/indexes/period/methods/test_astype.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,7 @@ def test_period_astype_to_timestamp(self):
assert res.freq == exp.freq

exp = DatetimeIndex(["2011-01-01", "2011-02-01", "2011-03-01"], tz="US/Eastern")
msg = "Use `obj.to_timestamp"
with tm.assert_produces_warning(FutureWarning, match=msg):
# GH#44398
res = pi.astype("datetime64[ns, US/Eastern]")
res = pi.astype("datetime64[ns, US/Eastern]")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are there tests for Series here? (not in this file, but generally)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

tm.assert_index_equal(res, exp)
assert res.freq == exp.freq

Expand Down
3 changes: 0 additions & 3 deletions pandas/tests/indexes/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,6 @@ def test_astype_preserves_name(self, index, dtype):
):
# This astype is deprecated in favor of tz_localize
warn = FutureWarning
elif isinstance(index, PeriodIndex) and dtype == "datetime64[ns]":
# Deprecated in favor of to_timestamp GH#44398
warn = FutureWarning
try:
# Some of these conversions cannot succeed so we use a try / except
with tm.assert_produces_warning(warn):
Expand Down