Repository: pandas-dev/pandas

Issue:
Pandas.ix[:, "2002"] returns a DataFrame
``` python
import pandas as pd
import numpy as np

ind = pd.date_range(start="2000", freq="D", periods=1000)
df = pd.DataFrame(np.random.randn(len(ind), 5), index=ind, columns=list('ABCDE'))
panel = pd.Panel({'frame_'+c:df for c in list('ABC')})

test1 = panel.ix[:, "2002"]
test1.ndim # 3
type(test1) # pandas.core.frame.DataFrame

test2 = panel.ix[:, "2002":"2002-12-31"]
test2.ndim # 3
type(test2) # pandas.core.panel.Panel

print pd.__version__
#0.11.1.dev-d7fe745
```

When trying to grab all the data for year 2002, I get back a DataFrame. If I use a range for the major axis, then it returns a Panel. 

http://nbviewer.ipython.org/5853887



Discussion:
yep....not tested real well here; nor is some of this implemented for >2 dim, marking as a bug

ha! beat u! sorry :P

wonder if we were looking at the notebook at the same time...

@jreback should be marked as 0.11.1?

you can do a PR if you want, but marked as 0.12; nothing more for 0.11.1 (unless its ready and tested and can be merged right away)....no new issues

excellent.


Resolving patch:
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
--- 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):
@@ -208,12 +211,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 +225,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,12 +256,27 @@ 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')
 
+    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/core/panel.py b/pandas/core/panel.py
--- 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'):
         """


Test patch:
diff --git a/pandas/tests/test_indexing.py b/pandas/tests/test_indexing.py
--- a/pandas/tests/test_indexing.py
+++ b/pandas/tests/test_indexing.py
@@ -873,6 +873,31 @@ 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(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
diff --git a/pandas/tests/test_panel.py b/pandas/tests/test_panel.py
--- 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]