Skip to content
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

REGR: Restore _constructor_from_mgr to pass manager object to constructor #54922

Merged
Merged
1 change: 1 addition & 0 deletions doc/source/whatsnew/v2.1.2.rst
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
18 changes: 8 additions & 10 deletions pandas/core/frame.py
Expand Up @@ -643,27 +643,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: 11 additions & 9 deletions pandas/core/series.py
Expand Up @@ -632,14 +632,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 @@ -657,10 +657,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:
return df
return self._constructor_expanddim(df, copy=False)
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(mgr)

# types
@property
Expand Down
3 changes: 3 additions & 0 deletions pandas/tests/frame/test_arithmetic.py
Expand Up @@ -2083,6 +2083,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
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)
12 changes: 10 additions & 2 deletions pandas/tests/groupby/test_groupby_subclass.py
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 Expand Up @@ -64,7 +68,9 @@ def func(group):
return group.testattr

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

# Confirm groupby.resample() preserves dataframe type
msg = "DataFrameGroupBy.resample operated on the grouping columns"
with tm.assert_produces_warning(FutureWarning, match=msg):
with tm.assert_produces_warning(
FutureWarning, match=msg, raise_on_extra_warnings=False
):
result = df.groupby("Buyer").resample("5D").sum()
assert isinstance(result, obj)
3 changes: 3 additions & 0 deletions pandas/tests/reshape/concat/test_concat.py
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
Expand Up @@ -688,6 +688,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
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
@@ -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
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