Skip to content

Commit f67d7d6

Browse files
REGR: Restore _constructor_from_mgr to pass manager object to constructor (#54922)
1 parent aeb3644 commit f67d7d6

File tree

11 files changed

+84
-22
lines changed

11 files changed

+84
-22
lines changed

doc/source/whatsnew/v2.1.2.rst

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Fixed regressions
2929
- Fixed performance regression with wide DataFrames, typically involving methods where all columns were accessed individually (:issue:`55256`, :issue:`55245`)
3030
- Fixed regression in :func:`merge_asof` raising ``TypeError`` for ``by`` with datetime and timedelta dtypes (:issue:`55453`)
3131
- Fixed regression in :meth:`DataFrame.to_sql` not roundtripping datetime columns correctly for sqlite when using ``detect_types`` (:issue:`55554`)
32+
- Fixed regression in construction of certain DataFrame or Series subclasses (:issue:`54922`)
3233

3334
.. ---------------------------------------------------------------------------
3435
.. _whatsnew_212.bug_fixes:

pandas/core/frame.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -643,27 +643,25 @@ def _constructor(self) -> Callable[..., DataFrame]:
643643
return DataFrame
644644

645645
def _constructor_from_mgr(self, mgr, axes):
646-
df = self._from_mgr(mgr, axes=axes)
647-
if type(self) is DataFrame:
648-
# fastpath avoiding constructor call
649-
return df
646+
if self._constructor is DataFrame:
647+
# we are pandas.DataFrame (or a subclass that doesn't override _constructor)
648+
return self._from_mgr(mgr, axes=axes)
650649
else:
651650
assert axes is mgr.axes
652-
return self._constructor(df, copy=False)
651+
return self._constructor(mgr)
653652

654653
_constructor_sliced: Callable[..., Series] = Series
655654

656655
def _sliced_from_mgr(self, mgr, axes) -> Series:
657656
return Series._from_mgr(mgr, axes)
658657

659658
def _constructor_sliced_from_mgr(self, mgr, axes):
660-
ser = self._sliced_from_mgr(mgr, axes=axes)
661-
ser._name = None # caller is responsible for setting real name
662-
if type(self) is DataFrame:
663-
# fastpath avoiding constructor call
659+
if self._constructor_sliced is Series:
660+
ser = self._sliced_from_mgr(mgr, axes)
661+
ser._name = None # caller is responsible for setting real name
664662
return ser
665663
assert axes is mgr.axes
666-
return self._constructor_sliced(ser, copy=False)
664+
return self._constructor_sliced(mgr)
667665

668666
# ----------------------------------------------------------------------
669667
# Constructors

pandas/core/series.py

+11-9
Original file line numberDiff line numberDiff line change
@@ -632,14 +632,14 @@ def _constructor(self) -> Callable[..., Series]:
632632
return Series
633633

634634
def _constructor_from_mgr(self, mgr, axes):
635-
ser = self._from_mgr(mgr, axes=axes)
636-
ser._name = None # caller is responsible for setting real name
637-
if type(self) is Series:
638-
# fastpath avoiding constructor call
635+
if self._constructor is Series:
636+
# we are pandas.Series (or a subclass that doesn't override _constructor)
637+
ser = self._from_mgr(mgr, axes=axes)
638+
ser._name = None # caller is responsible for setting real name
639639
return ser
640640
else:
641641
assert axes is mgr.axes
642-
return self._constructor(ser, copy=False)
642+
return self._constructor(mgr)
643643

644644
@property
645645
def _constructor_expanddim(self) -> Callable[..., DataFrame]:
@@ -657,10 +657,12 @@ def _expanddim_from_mgr(self, mgr, axes) -> DataFrame:
657657
return DataFrame._from_mgr(mgr, axes=mgr.axes)
658658

659659
def _constructor_expanddim_from_mgr(self, mgr, axes):
660-
df = self._expanddim_from_mgr(mgr, axes)
661-
if type(self) is Series:
662-
return df
663-
return self._constructor_expanddim(df, copy=False)
660+
from pandas.core.frame import DataFrame
661+
662+
if self._constructor_expanddim is DataFrame:
663+
return self._expanddim_from_mgr(mgr, axes)
664+
assert axes is mgr.axes
665+
return self._constructor_expanddim(mgr)
664666

665667
# types
666668
@property

pandas/tests/frame/test_arithmetic.py

+3
Original file line numberDiff line numberDiff line change
@@ -2083,6 +2083,9 @@ def test_frame_sub_nullable_int(any_int_ea_dtype):
20832083
tm.assert_frame_equal(result, expected)
20842084

20852085

2086+
@pytest.mark.filterwarnings(
2087+
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
2088+
)
20862089
def test_frame_op_subclass_nonclass_constructor():
20872090
# GH#43201 subclass._constructor is a function, not the subclass itself
20882091

pandas/tests/frame/test_subclass.py

+33-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
)
1111
import pandas._testing as tm
1212

13+
pytestmark = pytest.mark.filterwarnings(
14+
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
15+
)
16+
1317

1418
@pytest.fixture()
1519
def gpd_style_subclass_df():
@@ -734,8 +738,36 @@ def test_replace_list_method(self):
734738
# https://github.com/pandas-dev/pandas/pull/46018
735739
df = tm.SubclassedDataFrame({"A": [0, 1, 2]})
736740
msg = "The 'method' keyword in SubclassedDataFrame.replace is deprecated"
737-
with tm.assert_produces_warning(FutureWarning, match=msg):
741+
with tm.assert_produces_warning(
742+
FutureWarning, match=msg, raise_on_extra_warnings=False
743+
):
738744
result = df.replace([1, 2], method="ffill")
739745
expected = tm.SubclassedDataFrame({"A": [0, 0, 0]})
740746
assert isinstance(result, tm.SubclassedDataFrame)
741747
tm.assert_frame_equal(result, expected)
748+
749+
750+
class MySubclassWithMetadata(DataFrame):
751+
_metadata = ["my_metadata"]
752+
753+
def __init__(self, *args, **kwargs) -> None:
754+
super().__init__(*args, **kwargs)
755+
756+
my_metadata = kwargs.pop("my_metadata", None)
757+
if args and isinstance(args[0], MySubclassWithMetadata):
758+
my_metadata = args[0].my_metadata # type: ignore[has-type]
759+
self.my_metadata = my_metadata
760+
761+
@property
762+
def _constructor(self):
763+
return MySubclassWithMetadata
764+
765+
766+
def test_constructor_with_metadata():
767+
# https://github.com/pandas-dev/pandas/pull/54922
768+
# https://github.com/pandas-dev/pandas/issues/55120
769+
df = MySubclassWithMetadata(
770+
np.random.default_rng(2).random((5, 3)), columns=["A", "B", "C"]
771+
)
772+
subset = df[["A", "B"]]
773+
assert isinstance(subset, MySubclassWithMetadata)

pandas/tests/groupby/test_groupby_subclass.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
import pandas._testing as tm
1212
from pandas.tests.groupby import get_groupby_method_args
1313

14+
pytestmark = pytest.mark.filterwarnings(
15+
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
16+
)
17+
1418

1519
@pytest.mark.parametrize(
1620
"obj",
@@ -64,7 +68,9 @@ def func(group):
6468
return group.testattr
6569

6670
msg = "DataFrameGroupBy.apply operated on the grouping columns"
67-
with tm.assert_produces_warning(FutureWarning, match=msg):
71+
with tm.assert_produces_warning(
72+
FutureWarning, match=msg, raise_on_extra_warnings=False
73+
):
6874
result = custom_df.groupby("c").apply(func)
6975
expected = tm.SubclassedSeries(["hello"] * 3, index=Index([7, 8, 9], name="c"))
7076
tm.assert_series_equal(result, expected)
@@ -104,6 +110,8 @@ def test_groupby_resample_preserves_subclass(obj):
104110

105111
# Confirm groupby.resample() preserves dataframe type
106112
msg = "DataFrameGroupBy.resample operated on the grouping columns"
107-
with tm.assert_produces_warning(FutureWarning, match=msg):
113+
with tm.assert_produces_warning(
114+
FutureWarning, match=msg, raise_on_extra_warnings=False
115+
):
108116
result = df.groupby("Buyer").resample("5D").sum()
109117
assert isinstance(result, obj)

pandas/tests/reshape/concat/test_concat.py

+3
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,9 @@ def test_duplicate_keys_same_frame():
591591
tm.assert_frame_equal(result, expected)
592592

593593

594+
@pytest.mark.filterwarnings(
595+
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
596+
)
594597
@pytest.mark.parametrize(
595598
"obj",
596599
[

pandas/tests/reshape/merge/test_merge.py

+3
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,9 @@ def test_merge_nan_right2(self):
688688
)[["i1", "i2", "i1_", "i3"]]
689689
tm.assert_frame_equal(result, expected)
690690

691+
@pytest.mark.filterwarnings(
692+
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
693+
)
691694
def test_merge_type(self, df, df2):
692695
class NotADataFrame(DataFrame):
693696
@property

pandas/tests/reshape/merge/test_merge_ordered.py

+3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ def test_multigroup(self, left, right):
7070
result = merge_ordered(left, right, on="key", left_by="group")
7171
assert result["group"].notna().all()
7272

73+
@pytest.mark.filterwarnings(
74+
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
75+
)
7376
def test_merge_type(self, left, right):
7477
class NotADataFrame(DataFrame):
7578
@property

pandas/tests/series/methods/test_to_frame.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from pandas import (
24
DataFrame,
35
Index,
@@ -40,6 +42,9 @@ def test_to_frame(self, datetime_series):
4042
)
4143
tm.assert_frame_equal(rs, xp)
4244

45+
@pytest.mark.filterwarnings(
46+
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
47+
)
4348
def test_to_frame_expanddim(self):
4449
# GH#9762
4550

pandas/tests/series/test_subclass.py

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
import pandas as pd
55
import pandas._testing as tm
66

7+
pytestmark = pytest.mark.filterwarnings(
8+
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
9+
)
10+
711

812
class TestSeriesSubclassing:
913
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)