Skip to content

Commit 43b6c3f

Browse files
committed
Merge pull request #9632 from manuelRiel/master
Return the right class, when subclassing a DataFrame with multi-level index
2 parents 3d769c4 + 5805889 commit 43b6c3f

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

doc/source/whatsnew/v0.16.1.txt

+1
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,4 @@ Bug Fixes
300300
- Bug in ``transform`` when groups are equal in number and dtype to the input index (:issue:`9700`)
301301
- Google BigQuery connector now imports dependencies on a per-method basis.(:issue:`9713`)
302302
- Updated BigQuery connector to no longer use deprecated ``oauth2client.tools.run()`` (:issue:`8327`)
303+
- Bug in subclassed ``DataFrame``. It may not return the correct class, when slicing or subsetting it. (:issue:`9632`)

pandas/core/frame.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1839,15 +1839,15 @@ def _getitem_multilevel(self, key):
18391839
result.columns = result_columns
18401840
else:
18411841
new_values = self.values[:, loc]
1842-
result = DataFrame(new_values, index=self.index,
1842+
result = self._constructor(new_values, index=self.index,
18431843
columns=result_columns).__finalize__(self)
18441844
if len(result.columns) == 1:
18451845
top = result.columns[0]
18461846
if ((type(top) == str and top == '') or
18471847
(type(top) == tuple and top[0] == '')):
18481848
result = result['']
18491849
if isinstance(result, Series):
1850-
result = Series(result, index=self.index, name=key)
1850+
result = self._constructor_sliced(result, index=self.index, name=key)
18511851

18521852
result._set_is_copy(self)
18531853
return result

pandas/tests/test_frame.py

+53
Original file line numberDiff line numberDiff line change
@@ -2791,6 +2791,59 @@ def test_insert_error_msmgs(self):
27912791
with assertRaisesRegexp(TypeError, msg):
27922792
df['gr'] = df.groupby(['b', 'c']).count()
27932793

2794+
def test_frame_subclassing_and_slicing(self):
2795+
# Subclass frame and ensure it returns the right class on slicing it
2796+
# In reference to PR 9632
2797+
2798+
class CustomSeries(Series):
2799+
@property
2800+
def _constructor(self):
2801+
return CustomSeries
2802+
2803+
def custom_series_function(self):
2804+
return 'OK'
2805+
2806+
class CustomDataFrame(DataFrame):
2807+
"Subclasses pandas DF, fills DF with simulation results, adds some custom plotting functions."
2808+
2809+
def __init__(self, *args, **kw):
2810+
super(CustomDataFrame, self).__init__(*args, **kw)
2811+
2812+
@property
2813+
def _constructor(self):
2814+
return CustomDataFrame
2815+
2816+
_constructor_sliced = CustomSeries
2817+
2818+
def custom_frame_function(self):
2819+
return 'OK'
2820+
2821+
data = {'col1': range(10),
2822+
'col2': range(10)}
2823+
cdf = CustomDataFrame(data)
2824+
2825+
# Did we get back our own DF class?
2826+
self.assertTrue(isinstance(cdf, CustomDataFrame))
2827+
2828+
# Do we get back our own Series class after selecting a column?
2829+
cdf_series = cdf.col1
2830+
self.assertTrue(isinstance(cdf_series, CustomSeries))
2831+
self.assertEqual(cdf_series.custom_series_function(), 'OK')
2832+
2833+
# Do we get back our own DF class after slicing row-wise?
2834+
cdf_rows = cdf[1:5]
2835+
self.assertTrue(isinstance(cdf_rows, CustomDataFrame))
2836+
self.assertEqual(cdf_rows.custom_frame_function(), 'OK')
2837+
2838+
# Make sure sliced part of multi-index frame is custom class
2839+
mcol = pd.MultiIndex.from_tuples([('A', 'A'), ('A', 'B')])
2840+
cdf_multi = CustomDataFrame([[0, 1], [2, 3]], columns=mcol)
2841+
self.assertTrue(isinstance(cdf_multi['A'], CustomDataFrame))
2842+
2843+
mcol = pd.MultiIndex.from_tuples([('A', ''), ('B', '')])
2844+
cdf_multi2 = CustomDataFrame([[0, 1], [2, 3]], columns=mcol)
2845+
self.assertTrue(isinstance(cdf_multi2['A'], CustomSeries))
2846+
27942847
def test_constructor_subclass_dict(self):
27952848
# Test for passing dict subclass to constructor
27962849
data = {'col1': tm.TestSubDict((x, 10.0 * x) for x in range(10)),

0 commit comments

Comments
 (0)