Skip to content

Backport PR #54922 on branch 2.1.x (REGR: Restore _constructor_from_mgr to pass manager object to constructor) #55704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.1.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Fixed regressions
- Fixed performance regression with wide DataFrames, typically involving methods where all columns were accessed individually (:issue:`55256`, :issue:`55245`)
- Fixed regression in :func:`merge_asof` raising ``TypeError`` for ``by`` with datetime and timedelta dtypes (:issue:`55453`)
- Fixed regression in :meth:`DataFrame.to_sql` not roundtripping datetime columns correctly for sqlite when using ``detect_types`` (:issue:`55554`)
- Fixed regression in construction of certain DataFrame or Series subclasses (:issue:`54922`)

.. ---------------------------------------------------------------------------
.. _whatsnew_212.bug_fixes:
Expand Down
19 changes: 8 additions & 11 deletions pandas/core/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,28 +639,25 @@ def _constructor(self) -> Callable[..., DataFrame]:
return DataFrame

def _constructor_from_mgr(self, mgr, axes):
df = self._from_mgr(mgr, axes=axes)

if type(self) is DataFrame:
# fastpath avoiding constructor call
return df
if self._constructor is DataFrame:
# we are pandas.DataFrame (or a subclass that doesn't override _constructor)
return self._from_mgr(mgr, axes=axes)
else:
assert axes is mgr.axes
return self._constructor(df, copy=False)
return self._constructor(mgr)

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

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

def _constructor_sliced_from_mgr(self, mgr, axes):
ser = self._sliced_from_mgr(mgr, axes=axes)
ser._name = None # caller is responsible for setting real name
if type(self) is DataFrame:
# fastpath avoiding constructor call
if self._constructor_sliced is Series:
ser = self._sliced_from_mgr(mgr, axes)
ser._name = None # caller is responsible for setting real name
return ser
assert axes is mgr.axes
return self._constructor_sliced(ser, copy=False)
return self._constructor_sliced(mgr)

# ----------------------------------------------------------------------
# Constructors
Expand Down
20 changes: 10 additions & 10 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,14 +579,14 @@ def _constructor(self) -> Callable[..., Series]:
return Series

def _constructor_from_mgr(self, mgr, axes):
ser = self._from_mgr(mgr, axes=axes)
ser._name = None # caller is responsible for setting real name
if type(self) is Series:
# fastpath avoiding constructor call
if self._constructor is Series:
# we are pandas.Series (or a subclass that doesn't override _constructor)
ser = self._from_mgr(mgr, axes=axes)
ser._name = None # caller is responsible for setting real name
return ser
else:
assert axes is mgr.axes
return self._constructor(ser, copy=False)
return self._constructor(mgr)

@property
def _constructor_expanddim(self) -> Callable[..., DataFrame]:
Expand All @@ -610,12 +610,12 @@ def _expanddim_from_mgr(self, mgr, axes) -> DataFrame:
return DataFrame._from_mgr(mgr, axes=mgr.axes)

def _constructor_expanddim_from_mgr(self, mgr, axes):
df = self._expanddim_from_mgr(mgr, axes)
if type(self) is Series:
# fastpath avoiding constructor
return df
from pandas.core.frame import DataFrame

if self._constructor_expanddim is DataFrame:
return self._expanddim_from_mgr(mgr, axes)
assert axes is mgr.axes
return self._constructor_expanddim(df, copy=False)
return self._constructor_expanddim(mgr)

# types
@property
Expand Down
3 changes: 3 additions & 0 deletions pandas/tests/frame/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2078,6 +2078,9 @@ def test_frame_sub_nullable_int(any_int_ea_dtype):
tm.assert_frame_equal(result, expected)


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

Expand Down
34 changes: 33 additions & 1 deletion pandas/tests/frame/test_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
)
import pandas._testing as tm

pytestmark = pytest.mark.filterwarnings(
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
)


@pytest.fixture()
def gpd_style_subclass_df():
Expand Down Expand Up @@ -734,8 +738,36 @@ def test_replace_list_method(self):
# https://github.com/pandas-dev/pandas/pull/46018
df = tm.SubclassedDataFrame({"A": [0, 1, 2]})
msg = "The 'method' keyword in SubclassedDataFrame.replace is deprecated"
with tm.assert_produces_warning(FutureWarning, match=msg):
with tm.assert_produces_warning(
FutureWarning, match=msg, raise_on_extra_warnings=False
):
result = df.replace([1, 2], method="ffill")
expected = tm.SubclassedDataFrame({"A": [0, 0, 0]})
assert isinstance(result, tm.SubclassedDataFrame)
tm.assert_frame_equal(result, expected)


class MySubclassWithMetadata(DataFrame):
_metadata = ["my_metadata"]

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

my_metadata = kwargs.pop("my_metadata", None)
if args and isinstance(args[0], MySubclassWithMetadata):
my_metadata = args[0].my_metadata # type: ignore[has-type]
self.my_metadata = my_metadata

@property
def _constructor(self):
return MySubclassWithMetadata


def test_constructor_with_metadata():
# https://github.com/pandas-dev/pandas/pull/54922
# https://github.com/pandas-dev/pandas/issues/55120
df = MySubclassWithMetadata(
np.random.default_rng(2).random((5, 3)), columns=["A", "B", "C"]
)
subset = df[["A", "B"]]
assert isinstance(subset, MySubclassWithMetadata)
4 changes: 4 additions & 0 deletions pandas/tests/groupby/test_groupby_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
import pandas._testing as tm
from pandas.tests.groupby import get_groupby_method_args

pytestmark = pytest.mark.filterwarnings(
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
)


@pytest.mark.parametrize(
"obj",
Expand Down
3 changes: 3 additions & 0 deletions pandas/tests/reshape/concat/test_concat.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,9 @@ def test_duplicate_keys_same_frame():
tm.assert_frame_equal(result, expected)


@pytest.mark.filterwarnings(
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
)
@pytest.mark.parametrize(
"obj",
[
Expand Down
3 changes: 3 additions & 0 deletions pandas/tests/reshape/merge/test_merge.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,9 @@ def test_merge_nan_right2(self):
)[["i1", "i2", "i1_", "i3"]]
tm.assert_frame_equal(result, expected)

@pytest.mark.filterwarnings(
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
)
def test_merge_type(self, df, df2):
class NotADataFrame(DataFrame):
@property
Expand Down
3 changes: 3 additions & 0 deletions pandas/tests/reshape/merge/test_merge_ordered.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ def test_multigroup(self, left, right):
result = merge_ordered(left, right, on="key", left_by="group")
assert result["group"].notna().all()

@pytest.mark.filterwarnings(
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
)
def test_merge_type(self, left, right):
class NotADataFrame(DataFrame):
@property
Expand Down
5 changes: 5 additions & 0 deletions pandas/tests/series/methods/test_to_frame.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import pytest

from pandas import (
DataFrame,
Index,
Expand Down Expand Up @@ -40,6 +42,9 @@ def test_to_frame(self, datetime_series):
)
tm.assert_frame_equal(rs, xp)

@pytest.mark.filterwarnings(
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
)
def test_to_frame_expanddim(self):
# GH#9762

Expand Down
4 changes: 4 additions & 0 deletions pandas/tests/series/test_subclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import pandas as pd
import pandas._testing as tm

pytestmark = pytest.mark.filterwarnings(
"ignore:Passing a BlockManager|Passing a SingleBlockManager:DeprecationWarning"
)


class TestSeriesSubclassing:
@pytest.mark.parametrize(
Expand Down