Skip to content

Commit f1f7a75

Browse files
m-richardsnickleus27
authored andcommitted
DataFrame.convert_dtypes doesn't preserve subclasses (pandas-dev#44249)
1 parent 0010c6d commit f1f7a75

File tree

4 files changed

+27
-5
lines changed

4 files changed

+27
-5
lines changed

doc/source/whatsnew/v1.4.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,7 @@ Conversion
538538
- Bug in :class:`Series` constructor returning 0 for missing values with dtype ``int64`` and ``False`` for dtype ``bool`` (:issue:`43017`, :issue:`43018`)
539539
- Bug in :class:`IntegerDtype` not allowing coercion from string dtype (:issue:`25472`)
540540
- Bug in :func:`to_datetime` with ``arg:xr.DataArray`` and ``unit="ns"`` specified raises TypeError (:issue:`44053`)
541+
- Bug in :meth:`DataFrame.convert_dtypes` not returning the correct type when a subclass does not overload :meth:`_constructor_sliced` (:issue:`43201`)
541542
-
542543

543544
Strings

pandas/core/generic.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
Literal,
1919
Mapping,
2020
Sequence,
21+
Type,
2122
cast,
2223
final,
2324
overload,
@@ -6219,8 +6220,12 @@ def convert_dtypes(
62196220
for col_name, col in self.items()
62206221
]
62216222
if len(results) > 0:
6223+
result = concat(results, axis=1, copy=False)
6224+
cons = cast(Type["DataFrame"], self._constructor)
6225+
result = cons(result)
6226+
result = result.__finalize__(self, method="convert_dtypes")
62226227
# https://github.com/python/mypy/issues/8354
6223-
return cast(NDFrameT, concat(results, axis=1, copy=False))
6228+
return cast(NDFrameT, result)
62246229
else:
62256230
return self.copy()
62266231

pandas/tests/frame/test_subclass.py

+19
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@
1313
import pandas._testing as tm
1414

1515

16+
@pytest.fixture()
17+
def gpd_style_subclass_df():
18+
class SubclassedDataFrame(DataFrame):
19+
@property
20+
def _constructor(self):
21+
return SubclassedDataFrame
22+
23+
return SubclassedDataFrame({"a": [1, 2, 3]})
24+
25+
1626
class TestDataFrameSubclassing:
1727
def test_frame_subclassing_and_slicing(self):
1828
# Subclass frame and ensure it returns the right class on slicing it
@@ -704,6 +714,15 @@ def test_idxmax_preserves_subclass(self):
704714
result = df.idxmax()
705715
assert isinstance(result, tm.SubclassedSeries)
706716

717+
def test_convert_dtypes_preserves_subclass(self, gpd_style_subclass_df):
718+
# GH 43668
719+
df = tm.SubclassedDataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
720+
result = df.convert_dtypes()
721+
assert isinstance(result, tm.SubclassedDataFrame)
722+
723+
result = gpd_style_subclass_df.convert_dtypes()
724+
assert isinstance(result, type(gpd_style_subclass_df))
725+
707726
def test_equals_subclass(self):
708727
# https://github.com/pandas-dev/pandas/pull/34402
709728
# allow subclass in both directions

pandas/tests/generic/test_finalize.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -347,10 +347,7 @@
347347
operator.methodcaller("infer_objects"),
348348
),
349349
(pd.Series, ([1, 2],), operator.methodcaller("convert_dtypes")),
350-
pytest.param(
351-
(pd.DataFrame, frame_data, operator.methodcaller("convert_dtypes")),
352-
marks=not_implemented_mark,
353-
),
350+
(pd.DataFrame, frame_data, operator.methodcaller("convert_dtypes")),
354351
(pd.Series, ([1, None, 3],), operator.methodcaller("interpolate")),
355352
(pd.DataFrame, ({"A": [1, None, 3]},), operator.methodcaller("interpolate")),
356353
(pd.Series, ([1, 2],), operator.methodcaller("clip", lower=1)),

0 commit comments

Comments
 (0)