diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 2d74be6f503a2..ccafcd443ee66 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -647,6 +647,7 @@ Categorical Datetimelike ^^^^^^^^^^^^ +- Bug in :attr:`Series.dt.date` returned datetime dtype on Series with all NaT values; now returns object dtype (:issue:`61188`) - Bug in :attr:`is_year_start` where a DateTimeIndex constructed via a date_range with frequency 'MS' wouldn't have the correct year or quarter start attributes (:issue:`57377`) - Bug in :class:`DataFrame` raising ``ValueError`` when ``dtype`` is ``timedelta64`` and ``data`` is a list containing ``None`` (:issue:`60064`) - Bug in :class:`Timestamp` constructor failing to raise when ``tz=None`` is explicitly specified in conjunction with timezone-aware ``tzinfo`` or data (:issue:`48688`) diff --git a/pandas/core/indexes/accessors.py b/pandas/core/indexes/accessors.py index c404323a1168c..dd2479c394277 100644 --- a/pandas/core/indexes/accessors.py +++ b/pandas/core/indexes/accessors.py @@ -108,7 +108,9 @@ def _delegate_property_get(self, name: str): else: index = self._parent.index # return the result as a Series - return Series(result, index=index, name=self.name).__finalize__(self._parent) + return Series( + result, index=index, name=self.name, dtype=result.dtype + ).__finalize__(self._parent) def _delegate_property_set(self, name: str, value, *args, **kwargs) -> NoReturn: raise ValueError( diff --git a/pandas/core/reshape/melt.py b/pandas/core/reshape/melt.py index f4cb82816bbcf..c631dca7ea819 100644 --- a/pandas/core/reshape/melt.py +++ b/pandas/core/reshape/melt.py @@ -616,6 +616,9 @@ def get_var_names(df, stub: str, sep: str, suffix: str): return df.columns[df.columns.str.match(regex)] def melt_stub(df, stub: str, i, j, value_vars, sep: str): + # Ensure value_name and var_name are different when passing to melt + j_original = j + j = f"{j}_1" if stub == j else j newdf = melt( df, id_vars=i, @@ -632,7 +635,10 @@ def melt_stub(df, stub: str, i, j, value_vars, sep: str): # TODO: anything else to catch? pass - return newdf.set_index(i + [j]) + newdf = newdf.set_index(i + [j]) + if j != j_original: + newdf.index = newdf.index.set_names(j_original, level=-1) + return newdf if not is_list_like(stubnames): stubnames = [stubnames] diff --git a/pandas/tests/reshape/test_melt.py b/pandas/tests/reshape/test_melt.py index 95aa5291cb45a..e82dd501f24c0 100644 --- a/pandas/tests/reshape/test_melt.py +++ b/pandas/tests/reshape/test_melt.py @@ -1241,6 +1241,33 @@ def test_missing_stubname(self, any_string_dtype): expected.index = expected.index.set_levels(new_level, level=0) tm.assert_frame_equal(result, expected) + @pytest.mark.parametrize("stubnames", ["year", ["year"]]) + def test_stubname_equal_suffix(self, stubnames): + # https://github.com/pandas-dev/pandas/issues/46939 + df = DataFrame( + { + "year1": {0: 4.5, 1: 1.7}, + "year2": {0: 2.5, 1: 1.2}, + "X": dict(zip(range(2), range(2, 4))), + } + ) + df["id"] = df.index + result = wide_to_long( + df, + stubnames=stubnames, + i="id", + j="year", + ) + expected = DataFrame( + [[2, 4.5], [3, 1.7], [2, 2.5], [3, 1.2]], + columns=["X", "year"], + index=pd.MultiIndex.from_arrays( + [[0, 1, 0, 1], [1, 1, 2, 2]], + names=["id", "year"], + ), + ) + tm.assert_frame_equal(result, expected) + def test_wide_to_long_string_columns(string_storage): # GH 57066 diff --git a/pandas/tests/series/indexing/test_datetime.py b/pandas/tests/series/indexing/test_datetime.py index 97cafc33611ed..5431c250fa9fe 100644 --- a/pandas/tests/series/indexing/test_datetime.py +++ b/pandas/tests/series/indexing/test_datetime.py @@ -491,3 +491,23 @@ def test_compare_datetime_with_all_none(): result = ser > ser2 expected = Series([False, False]) tm.assert_series_equal(result, expected) + + +def test_dt_date_dtype_all_nat_is_object(): + # Ensure .dt.date on all-NaT Series returns object dtype and not datetime64 + # GH#61188 + s = Series([pd.NaT, pd.NaT], dtype="datetime64[s]") + result = s.dt.date + + expected = Series([pd.NaT, pd.NaT], dtype=object) + + tm.assert_series_equal(result, expected) + + +def test_dt_date_all_nat_le_date(): + # All-NaT Series should not raise error when compared to a datetime.date + # GH#61188 + s = Series([pd.NaT, pd.NaT], dtype="datetime64[s]") + result = s.dt.date <= datetime.now().date() + expected = Series([False, False]) + tm.assert_series_equal(result, expected) diff --git a/web/pandas/static/img/books/pandas_cookbook_3.jpeg b/web/pandas/static/img/books/pandas_cookbook_3.jpeg new file mode 100644 index 0000000000000..cf1c27037de68 Binary files /dev/null and b/web/pandas/static/img/books/pandas_cookbook_3.jpeg differ