diff --git a/doc/source/v0.15.0.txt b/doc/source/v0.15.0.txt index 1b7b05847a901..b1ae1bd72a424 100644 --- a/doc/source/v0.15.0.txt +++ b/doc/source/v0.15.0.txt @@ -1048,3 +1048,4 @@ Bug Fixes - Bug in ``NDFrame.loc`` indexing when row/column names were lost when target was a list/ndarray (:issue:`6552`) - Regression in ``NDFrame.loc`` indexing when rows/columns were converted to Float64Index if target was an empty list/ndarray (:issue:`7774`) - Bug in Series that allows it to be indexed by a DataFrame which has unexpected results. Such indexing is no longer permitted (:issue:`8444`) +- Bug in item assignment of a DataFrame with multi-index columns where right-hand-side columns were not aligned (:issue:`7655`) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 65f7d56f5aa8a..be98f977bf2bf 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -2194,6 +2194,15 @@ def reindexer(value): value = reindexer(value) elif isinstance(value, DataFrame): + # align right-hand-side columns if self.columns + # is multi-index and self[key] is a sub-frame + if isinstance(self.columns, MultiIndex) and key in self.columns: + loc = self.columns.get_loc(key) + if isinstance(loc, (slice, Series, np.ndarray, Index)): + cols = _maybe_droplevels(self.columns[loc], key) + if len(cols) and not cols.equals(value.columns): + value = value.reindex_axis(cols, axis=1) + # now align rows value = reindexer(value).T elif isinstance(value, Categorical): diff --git a/pandas/tests/test_frame.py b/pandas/tests/test_frame.py index 035a301807039..0dbca6f122b93 100644 --- a/pandas/tests/test_frame.py +++ b/pandas/tests/test_frame.py @@ -224,6 +224,31 @@ def test_setitem_list_of_tuples(self): expected = Series(tuples, index=self.frame.index) assert_series_equal(result, expected) + def test_setitem_mulit_index(self): + # GH7655, test that assigning to a sub-frame of a frame + # with multi-index columns aligns both rows and columns + it = ['jim', 'joe', 'jolie'], ['first', 'last'], \ + ['left', 'center', 'right'] + + cols = MultiIndex.from_product(it) + index = pd.date_range('20141006',periods=20) + vals = np.random.randint(1, 1000, (len(index), len(cols))) + df = pd.DataFrame(vals, columns=cols, index=index) + + i, j = df.index.values.copy(), it[-1][:] + + np.random.shuffle(i) + df['jim'] = df['jolie'].loc[i, ::-1] + assert_frame_equal(df['jim'], df['jolie']) + + np.random.shuffle(j) + df[('joe', 'first')] = df[('jolie', 'last')].loc[i, j] + assert_frame_equal(df[('joe', 'first')], df[('jolie', 'last')]) + + np.random.shuffle(j) + df[('joe', 'last')] = df[('jolie', 'first')].loc[i, j] + assert_frame_equal(df[('joe', 'last')], df[('jolie', 'first')]) + def test_getitem_boolean(self): # boolean indexing d = self.tsframe.index[10]