Skip to content

Commit

Permalink
REGR: Restore _constructor_from_mgr to pass manager object to constru…
Browse files Browse the repository at this point in the history
…ctor (#54922)
  • Loading branch information
jorisvandenbossche committed Oct 26, 2023
1 parent aeb3644 commit f67d7d6
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 22 deletions.
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

0 comments on commit f67d7d6

Please sign in to comment.