diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 0ab95dd260a9c..4bff94fd8476b 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -473,7 +473,7 @@ Other - Bug in :meth:`DataFrame.replace` and :meth:`Series.replace` incorrectly raising ``AssertionError`` instead of ``ValueError`` when invalid parameter combinations are passed (:issue:`36045`) - Bug in :meth:`DataFrame.replace` and :meth:`Series.replace` with numeric values and string ``to_replace`` (:issue:`34789`) -- Fixed metadata propagation in the :class:`Series.dt` accessor (:issue:`28283`) +- Fixed metadata propagation in the :class:`Series.dt` and :class:`Series.str` accessors (:issue:`28283`) - Bug in :meth:`Index.union` behaving differently depending on whether operand is a :class:`Index` or other list-like (:issue:`36384`) .. --------------------------------------------------------------------------- diff --git a/pandas/core/strings/accessor.py b/pandas/core/strings/accessor.py index cae8cc1baf1df..12096034b54e4 100644 --- a/pandas/core/strings/accessor.py +++ b/pandas/core/strings/accessor.py @@ -247,6 +247,8 @@ def _wrap_result( from pandas import Index, MultiIndex if not hasattr(result, "ndim") or not hasattr(result, "dtype"): + if isinstance(result, ABCDataFrame): + result = result.__finalize__(self._orig, name="str") return result assert result.ndim < 3 @@ -324,6 +326,11 @@ def cons_row(x): # Must be a Series cons = self._orig._constructor result = cons(result, name=name, index=index) + result = result.__finalize__(self._orig, method="str") + if name is not None and result.ndim == 1: + # __finalize__ might copy over the original name, but we may + # want the new name (e.g. str.extract). + result.name = name return result def _get_series_list(self, others): @@ -597,6 +604,7 @@ def cat(self, others=None, sep=None, na_rep=None, join="left"): else: dtype = self._orig.dtype result = Series(result, dtype=dtype, index=data.index, name=self._orig.name) + result = result.__finalize__(self._orig, method="str_cat") return result _shared_docs[ @@ -3034,7 +3042,8 @@ def str_extract(arr, pat, flags=0, expand=True): if not isinstance(expand, bool): raise ValueError("expand must be True or False") if expand: - return _str_extract_frame(arr._orig, pat, flags=flags) + result = _str_extract_frame(arr._orig, pat, flags=flags) + return result.__finalize__(arr._orig, method="str_extract") else: result, name = _str_extract_noexpand(arr._orig, pat, flags=flags) return arr._wrap_result(result, name=name, expand=expand) diff --git a/pandas/tests/generic/test_finalize.py b/pandas/tests/generic/test_finalize.py index 6692102bc9008..25c926b1de4c6 100644 --- a/pandas/tests/generic/test_finalize.py +++ b/pandas/tests/generic/test_finalize.py @@ -598,22 +598,13 @@ def test_binops(args, annotate, all_arithmetic_functions): [ operator.methodcaller("capitalize"), operator.methodcaller("casefold"), - pytest.param( - operator.methodcaller("cat", ["a"]), - marks=pytest.mark.xfail(reason="finalize not called."), - ), + operator.methodcaller("cat", ["a"]), operator.methodcaller("contains", "a"), operator.methodcaller("count", "a"), operator.methodcaller("encode", "utf-8"), operator.methodcaller("endswith", "a"), - pytest.param( - operator.methodcaller("extract", r"(\w)(\d)"), - marks=pytest.mark.xfail(reason="finalize not called."), - ), - pytest.param( - operator.methodcaller("extract", r"(\w)(\d)"), - marks=pytest.mark.xfail(reason="finalize not called."), - ), + operator.methodcaller("extract", r"(\w)(\d)"), + operator.methodcaller("extract", r"(\w)(\d)", expand=False), operator.methodcaller("find", "a"), operator.methodcaller("findall", "a"), operator.methodcaller("get", 0), @@ -655,7 +646,6 @@ def test_binops(args, annotate, all_arithmetic_functions): ], ids=idfn, ) -@not_implemented_mark def test_string_method(method): s = pd.Series(["a1"]) s.attrs = {"a": 1}