From e8a528d3a2c5d38ae231b96202977bcf23f9fe45 Mon Sep 17 00:00:00 2001 From: jreback Date: Wed, 7 Aug 2013 13:38:15 -0400 Subject: [PATCH 1/3] BUG: (GH4016) fix panel slicing issue that was returning an object that should not have be a reduction in ndim --- doc/source/release.rst | 2 ++ pandas/core/panel.py | 31 ++++++++++++++++++++----------- pandas/tests/test_indexing.py | 10 ++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index 9af04a8d703cf..3d63e74faf419 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -129,6 +129,8 @@ pandas 0.13 (:issue:`4486`) - Fixed an issue where cumsum and cumprod didn't work with bool dtypes (:issue:`4170`, :issue:`4440`) + - Fixed Panel slicing issued in ``xs`` that was returning an incorrect dimmed object + (:issue:`4016`) pandas 0.12 =========== diff --git a/pandas/core/panel.py b/pandas/core/panel.py index 75990e76c2b8f..d89121b1309b4 100644 --- a/pandas/core/panel.py +++ b/pandas/core/panel.py @@ -1048,7 +1048,7 @@ def xs(self, key, axis=1, copy=True): self._consolidate_inplace() axis_number = self._get_axis_number(axis) new_data = self._data.xs(key, axis=axis_number, copy=copy) - return self._constructor_sliced(new_data) + return self._construct_return_type(new_data) _xs = xs @@ -1263,24 +1263,33 @@ def _reduce(self, op, axis=0, skipna=True): if result.ndim == 2 and axis_name != self._info_axis: result = result.T - return self._constructor_sliced(result, + return self._construct_return_type(result, axes) + + def _construct_return_type(self, result, axes=None, **kwargs): + """ return the type for the ndim of the result """ + ndim = result.ndim + if self.ndim == ndim: + """ return the construction dictionary for these axes """ + if axes is None: + return self._constructor(result) + return self._constructor(result, **self._construct_axes_dict()) + + elif self.ndim == ndim + 1: + if axes is None: + return self._constructor_sliced(result) + return self._constructor_sliced(result, **self._extract_axes_for_slice(self, axes)) + raise PandasError("invalid _construct_return_type [self->%s] [result->%s]" % + (self.ndim, result.ndim)) + def _wrap_result(self, result, axis): axis = self._get_axis_name(axis) axes = self._get_plane_axes(axis) if result.ndim == 2 and axis != self._info_axis: result = result.T - # do we have reduced dimensionalility? - if self.ndim == result.ndim: - return self._constructor(result, **self._construct_axes_dict()) - elif self.ndim == result.ndim + 1: - return self._constructor_sliced(result, - **self._extract_axes_for_slice(self, axes)) - - raise PandasError("invalid _wrap_result [self->%s] [result->%s]" % - (self.ndim, result.ndim)) + return self._construct_return_type(result, axes) def count(self, axis='major'): """ diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py index f6a6bd1587a04..41dc6ba62419a 100644 --- a/pandas/tests/test_indexing.py +++ b/pandas/tests/test_indexing.py @@ -873,6 +873,16 @@ def test_iloc_panel_issue(self): self.assert_(p.iloc[1, :3, 1].shape == (3,)) self.assert_(p.iloc[:3, 1, 1].shape == (3,)) + def test_panel_getitem(self): + # GH4016, date selection returns a frame when a partial string selection + ind = date_range(start="2000", freq="D", periods=1000) + df = DataFrame(np.random.randn(len(ind), 5), index=ind, columns=list('ABCDE')) + panel = Panel({'frame_'+c:df for c in list('ABC')}) + + test2 = panel.ix[:, "2002":"2002-12-31"] + test1 = panel.ix[:, "2002"] + tm.assert_panel_equal(test1,test2) + def test_multi_assign(self): # GH 3626, an assignement of a sub-df to a df From 0f72731e5c49bf7704123295e1754e802ac74d65 Mon Sep 17 00:00:00 2001 From: jreback Date: Wed, 7 Aug 2013 15:04:26 -0400 Subject: [PATCH 2/3] BUG: (GH3830), panel assignment using loc with a transpose frame did not work --- doc/source/release.rst | 1 + pandas/core/indexing.py | 27 +++++++++++++++++++++++---- pandas/tests/test_panel.py | 25 +++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index 3d63e74faf419..c43fcf3f7686e 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -131,6 +131,7 @@ pandas 0.13 (:issue:`4170`, :issue:`4440`) - Fixed Panel slicing issued in ``xs`` that was returning an incorrect dimmed object (:issue:`4016`) + - Fixed Panel assignment with a transposed frame (:issue:`3830`) pandas 0.12 =========== diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 4d64b058a15d7..78567f871da68 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -208,12 +208,11 @@ def _align_series(self, indexer, ser): raise ValueError('Incompatible indexer with Series') def _align_frame(self, indexer, df): - from pandas import DataFrame - is_frame = isinstance(self.obj, DataFrame) - if not is_frame: - df = df.T + is_frame = self.obj.ndim == 2 + is_panel = self.obj.ndim >= 3 if isinstance(indexer, tuple): idx, cols = None, None + sindexers = [] for i, ix in enumerate(indexer): ax = self.obj.axes[i] if com._is_sequence(ix) or isinstance(ix, slice): @@ -223,6 +222,16 @@ def _align_frame(self, indexer, df): cols = ax[ix].ravel() else: break + else: + sindexers.append(i) + + # panel + if is_panel: + if len(sindexers) == 1 and idx is None and cols is None: + if sindexers[0] == 0: + df = df.T + return self.obj.conform(df,axis=sindexers[0]) + df = df.T if idx is not None and cols is not None: if df.index.equals(idx) and df.columns.equals(cols): @@ -244,8 +253,18 @@ def _align_frame(self, indexer, df): idx = self.obj.axes[1] cols = self.obj.axes[2] + # by definition we are indexing on the 0th axis + if is_panel: + df = df.T + if idx.equals(df.index) and cols.equals(df.columns): return df.copy().values + + # a passed in dataframe which is actually a transpose + # of what is needed + elif idx.equals(df.columns) and cols.equals(df.index): + return df.T.copy().values + return df.reindex(idx, columns=cols).values raise ValueError('Incompatible indexer with DataFrame') diff --git a/pandas/tests/test_panel.py b/pandas/tests/test_panel.py index 94afac7d9328f..3d2a5f2e58ded 100644 --- a/pandas/tests/test_panel.py +++ b/pandas/tests/test_panel.py @@ -655,6 +655,31 @@ def test_ix_frame_align(self): out = p.ix[0, [0, 1, 3, 5], -2:] assert_frame_equal(out, df.T.reindex([0, 1, 3, 5], p.minor_axis[-2:])) + # GH3830, panel assignent by values/frame + for dtype in ['float64','int64']: + + panel = Panel(np.arange(40).reshape((2,4,5)), items=['a1','a2'], dtype=dtype) + df1 = panel.iloc[0] + df2 = panel.iloc[1] + + tm.assert_frame_equal(panel.loc['a1'], df1) + tm.assert_frame_equal(panel.loc['a2'], df2) + + # Assignment by Value Passes for 'a2' + panel.loc['a2'] = df1.values + tm.assert_frame_equal(panel.loc['a1'], df1) + tm.assert_frame_equal(panel.loc['a2'], df1) + + # Assignment by DataFrame Ok w/o loc 'a2' + panel['a2'] = df2 + tm.assert_frame_equal(panel.loc['a1'], df1) + tm.assert_frame_equal(panel.loc['a2'], df2) + + # Assignment by DataFrame Fails for 'a2' + panel.loc['a2'] = df2 + tm.assert_frame_equal(panel.loc['a1'], df1) + tm.assert_frame_equal(panel.loc['a2'], df2) + def _check_view(self, indexer, comp): cp = self.panel.copy() obj = cp.ix[indexer] From f1aee70f8649fc2473fe3b2c04920bc449fff08b Mon Sep 17 00:00:00 2001 From: jreback Date: Wed, 7 Aug 2013 15:11:11 -0400 Subject: [PATCH 3/3] ER: GH3777, raise a NotImplementedError for Panel -> Panel setting with alignment --- doc/source/release.rst | 1 + pandas/core/indexing.py | 10 +++++++++- pandas/tests/test_indexing.py | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/doc/source/release.rst b/doc/source/release.rst index c43fcf3f7686e..00309b7a3dc0d 100644 --- a/doc/source/release.rst +++ b/doc/source/release.rst @@ -132,6 +132,7 @@ pandas 0.13 - Fixed Panel slicing issued in ``xs`` that was returning an incorrect dimmed object (:issue:`4016`) - Fixed Panel assignment with a transposed frame (:issue:`3830`) + - Raise on set indexing with a Panel and a Panel as a value which needs alignment (:issue:`3777`) pandas 0.12 =========== diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 78567f871da68..a4d2cffc3dd23 100644 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -100,7 +100,7 @@ def _convert_tuple(self, key): return tuple(keyidx) def _setitem_with_indexer(self, indexer, value): - from pandas.core.frame import DataFrame, Series + from pandas import Panel, DataFrame, Series # also has the side effect of consolidating in-place @@ -181,6 +181,9 @@ def setter(item, v): if isinstance(value, DataFrame): value = self._align_frame(indexer, value) + if isinstance(value, Panel): + value = self._align_panel(indexer, value) + # 2096 values = self.obj.values if np.prod(values.shape): @@ -269,6 +272,11 @@ def _align_frame(self, indexer, df): raise ValueError('Incompatible indexer with DataFrame') + def _align_panel(self, indexer, df): + is_frame = self.obj.ndim == 2 + is_panel = self.obj.ndim >= 3 + raise NotImplementedError("cannot set using an indexer with a Panel yet!") + def _getitem_tuple(self, tup): try: return self._getitem_lowerdim(tup) diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py index 41dc6ba62419a..385e762af424f 100644 --- a/pandas/tests/test_indexing.py +++ b/pandas/tests/test_indexing.py @@ -877,12 +877,27 @@ def test_panel_getitem(self): # GH4016, date selection returns a frame when a partial string selection ind = date_range(start="2000", freq="D", periods=1000) df = DataFrame(np.random.randn(len(ind), 5), index=ind, columns=list('ABCDE')) - panel = Panel({'frame_'+c:df for c in list('ABC')}) + panel = Panel(dict([ ('frame_'+c,df) for c in list('ABC') ])) test2 = panel.ix[:, "2002":"2002-12-31"] test1 = panel.ix[:, "2002"] tm.assert_panel_equal(test1,test2) + def test_panel_assignment(self): + + # GH3777 + wp = Panel(randn(2, 5, 4), items=['Item1', 'Item2'], major_axis=date_range('1/1/2000', periods=5), minor_axis=['A', 'B', 'C', 'D']) + wp2 = Panel(randn(2, 5, 4), items=['Item1', 'Item2'], major_axis=date_range('1/1/2000', periods=5), minor_axis=['A', 'B', 'C', 'D']) + expected = wp.loc[['Item1', 'Item2'], :, ['A', 'B']] + + def f(): + wp.loc[['Item1', 'Item2'], :, ['A', 'B']] = wp2.loc[['Item1', 'Item2'], :, ['A', 'B']] + self.assertRaises(NotImplementedError, f) + + #wp.loc[['Item1', 'Item2'], :, ['A', 'B']] = wp2.loc[['Item1', 'Item2'], :, ['A', 'B']] + #result = wp.loc[['Item1', 'Item2'], :, ['A', 'B']] + #tm.assert_panel_equal(result,expected) + def test_multi_assign(self): # GH 3626, an assignement of a sub-df to a df