diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 9968a103a13bf..c2ff7361ff48b 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -972,6 +972,7 @@ Other - Bug in :func:`pandas.util.show_versions` where console JSON output was not proper JSON (:issue:`39701`) - Bug in :meth:`DataFrame.convert_dtypes` incorrectly raised ValueError when called on an empty DataFrame (:issue:`40393`) - Bug in :meth:`DataFrame.clip` not interpreting missing values as no threshold (:issue:`40420`) +- Bug in :class:`Series` backed by :class:`DatetimeArray` or :class:`TimedeltaArray` sometimes failing to set the array's ``freq`` to ``None`` (:issue:`41425`) .. --------------------------------------------------------------------------- diff --git a/pandas/core/base.py b/pandas/core/base.py index 7a48b1fdfda1e..e2720fcbc7ec4 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -498,8 +498,8 @@ def to_numpy( >>> ser = pd.Series(pd.date_range('2000', periods=2, tz="CET")) >>> ser.to_numpy(dtype=object) - array([Timestamp('2000-01-01 00:00:00+0100', tz='CET', freq='D'), - Timestamp('2000-01-02 00:00:00+0100', tz='CET', freq='D')], + array([Timestamp('2000-01-01 00:00:00+0100', tz='CET'), + Timestamp('2000-01-02 00:00:00+0100', tz='CET')], dtype=object) Or ``dtype='datetime64[ns]'`` to return an ndarray of native diff --git a/pandas/core/internals/array_manager.py b/pandas/core/internals/array_manager.py index 0541b76b377f7..31e32b053367b 100644 --- a/pandas/core/internals/array_manager.py +++ b/pandas/core/internals/array_manager.py @@ -85,6 +85,7 @@ from pandas.core.internals.blocks import ( ensure_block_shape, external_values, + maybe_coerce_values, new_block, to_native_types, ) @@ -701,7 +702,7 @@ def __init__( if verify_integrity: self._axes = [ensure_index(ax) for ax in axes] - self.arrays = [ensure_wrapped_if_datetimelike(arr) for arr in arrays] + self.arrays = [maybe_coerce_values(arr) for arr in arrays] self._verify_integrity() def _verify_integrity(self) -> None: @@ -814,7 +815,7 @@ def iset(self, loc: int | slice | np.ndarray, value: ArrayLike): # TODO we receive a datetime/timedelta64 ndarray from DataFrame._iset_item # but we should avoid that and pass directly the proper array - value = ensure_wrapped_if_datetimelike(value) + value = maybe_coerce_values(value) assert isinstance(value, (np.ndarray, ExtensionArray)) assert value.ndim == 1 @@ -873,7 +874,7 @@ def insert(self, loc: int, item: Hashable, value: ArrayLike) -> None: raise ValueError( f"Expected a 1D array, got an array with shape {value.shape}" ) - value = ensure_wrapped_if_datetimelike(value) + value = maybe_coerce_values(value) # TODO self.arrays can be empty # assert len(value) == len(self.arrays[0]) @@ -1188,7 +1189,7 @@ def __init__( assert len(arrays) == 1 self._axes = [ensure_index(ax) for ax in self._axes] arr = arrays[0] - arr = ensure_wrapped_if_datetimelike(arr) + arr = maybe_coerce_values(arr) if isinstance(arr, ABCPandasArray): arr = arr.to_numpy() self.arrays = [arr] diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index bd4dfdb4ebad0..4f1b16e747394 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -1860,6 +1860,10 @@ def maybe_coerce_values(values) -> ArrayLike: if issubclass(values.dtype.type, str): values = np.array(values, dtype=object) + if isinstance(values, (DatetimeArray, TimedeltaArray)) and values.freq is not None: + # freq is only stored in DatetimeIndex/TimedeltaIndex, not in Series/DataFrame + values = values._with_freq(None) + return values diff --git a/pandas/core/series.py b/pandas/core/series.py index c8e9898f9462a..d0ff50cca5355 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -813,8 +813,8 @@ def __array__(self, dtype: NpDtype | None = None) -> np.ndarray: >>> tzser = pd.Series(pd.date_range('2000', periods=2, tz="CET")) >>> np.asarray(tzser, dtype="object") - array([Timestamp('2000-01-01 00:00:00+0100', tz='CET', freq='D'), - Timestamp('2000-01-02 00:00:00+0100', tz='CET', freq='D')], + array([Timestamp('2000-01-01 00:00:00+0100', tz='CET'), + Timestamp('2000-01-02 00:00:00+0100', tz='CET')], dtype=object) Or the values may be localized to UTC and the tzinfo discarded with diff --git a/pandas/tests/extension/test_datetime.py b/pandas/tests/extension/test_datetime.py index 33589027c0d0f..bb8347f0a0122 100644 --- a/pandas/tests/extension/test_datetime.py +++ b/pandas/tests/extension/test_datetime.py @@ -97,7 +97,10 @@ class TestDatetimeDtype(BaseDatetimeTests, base.BaseDtypeTests): class TestConstructors(BaseDatetimeTests, base.BaseConstructorsTests): - pass + def test_series_constructor(self, data): + # Series construction drops any .freq attr + data = data._with_freq(None) + super().test_series_constructor(data) class TestGetitem(BaseDatetimeTests, base.BaseGetitemTests): diff --git a/pandas/tests/frame/methods/test_set_index.py b/pandas/tests/frame/methods/test_set_index.py index 62dc400f8de9f..51f66128b1500 100644 --- a/pandas/tests/frame/methods/test_set_index.py +++ b/pandas/tests/frame/methods/test_set_index.py @@ -96,7 +96,7 @@ def test_set_index_cast_datetimeindex(self): idf = df.set_index("A") assert isinstance(idf.index, DatetimeIndex) - def test_set_index_dst(self, using_array_manager): + def test_set_index_dst(self): di = date_range("2006-10-29 00:00:00", periods=3, freq="H", tz="US/Pacific") df = DataFrame(data={"a": [0, 1, 2], "b": [3, 4, 5]}, index=di).reset_index() @@ -106,8 +106,7 @@ def test_set_index_dst(self, using_array_manager): data={"a": [0, 1, 2], "b": [3, 4, 5]}, index=Index(di, name="index"), ) - if not using_array_manager: - exp.index = exp.index._with_freq(None) + exp.index = exp.index._with_freq(None) tm.assert_frame_equal(res, exp) # GH#12920 diff --git a/pandas/tests/window/test_rolling.py b/pandas/tests/window/test_rolling.py index 28465e3a979a7..4846e15da039f 100644 --- a/pandas/tests/window/test_rolling.py +++ b/pandas/tests/window/test_rolling.py @@ -586,7 +586,7 @@ def test_rolling_datetime(axis_frame, tz_naive_fixture): ), ], ) -def test_rolling_window_as_string(center, expected_data, using_array_manager): +def test_rolling_window_as_string(center, expected_data): # see gh-22590 date_today = datetime.now() days = date_range(date_today, date_today + timedelta(365), freq="D") @@ -602,9 +602,7 @@ def test_rolling_window_as_string(center, expected_data, using_array_manager): ].agg("max") index = days.rename("DateCol") - if not using_array_manager: - # INFO(ArrayManager) preserves the frequence of the index - index = index._with_freq(None) + index = index._with_freq(None) expected = Series(expected_data, index=index, name="metric") tm.assert_series_equal(result, expected)