diff --git a/doc/source/user_guide/timeseries.rst b/doc/source/user_guide/timeseries.rst index 3fd6fe67772bc..6df234a027ee9 100644 --- a/doc/source/user_guide/timeseries.rst +++ b/doc/source/user_guide/timeseries.rst @@ -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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/whatsnew/v1.4.0.rst b/doc/source/whatsnew/v1.4.0.rst index a76077e8b46c1..58f3fee1805d7 100644 --- a/doc/source/whatsnew/v1.4.0.rst +++ b/doc/source/whatsnew/v1.4.0.rst @@ -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`) @@ -676,6 +675,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 diff --git a/pandas/core/arrays/period.py b/pandas/core/arrays/period.py index 6112ccccb89ff..487b51735bd4e 100644 --- a/pandas/core/arrays/period.py +++ b/pandas/core/arrays/period.py @@ -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, @@ -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( diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 8dcd379a4eb9b..aba834a47ffef 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -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) diff --git a/pandas/tests/arrays/period/test_astype.py b/pandas/tests/arrays/period/test_astype.py index 52cd28c8d5acc..e16220acd3210 100644 --- a/pandas/tests/arrays/period/test_astype.py +++ b/pandas/tests/arrays/period/test_astype.py @@ -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) diff --git a/pandas/tests/indexes/period/methods/test_astype.py b/pandas/tests/indexes/period/methods/test_astype.py index c44f2efed1fcc..e2340a2db02f7 100644 --- a/pandas/tests/indexes/period/methods/test_astype.py +++ b/pandas/tests/indexes/period/methods/test_astype.py @@ -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]") tm.assert_index_equal(res, exp) assert res.freq == exp.freq diff --git a/pandas/tests/indexes/test_common.py b/pandas/tests/indexes/test_common.py index 0407dc02833fd..50770f5bb38f2 100644 --- a/pandas/tests/indexes/test_common.py +++ b/pandas/tests/indexes/test_common.py @@ -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):