Repository: pandas-dev/pandas

Issue:
BUG: Assigning Panel item using .ix/.loc no longer works
I am updating a codebase from an OLD version of pandas (0.7.3) to current version (0.14.1).

```
pandas: 0.14.1
numpy: 1.8.0
```

The following code has stopped working correctly (using .ix[] or .loc[]):

``` python
import numpy as np
import pandas as ps

np.random.seed(0)

index=range(3)
columns = list('abc')

panel = ps.Panel({'A' : ps.DataFrame(np.random.randn(3, 3), index=index, columns=columns),
                  'B' : ps.DataFrame(np.random.randn(3, 3), index=index, columns=columns),
                  'C' : ps.DataFrame(np.random.randn(3, 3), index=index, columns=columns)
                  })

replace = ps.DataFrame(np.eye(3,3), index=range(3), columns=columns)

print panel['A']

for idx in list('ABC'):
    panel.loc[idx,:,:] = replace

print panel['A']
```

Output:

```
          a         b         c
0  1.764052  0.400157  0.978738
1  2.240893  1.867558 -0.977278
2  0.950088 -0.151357 -0.103219
    a   b   c
0 NaN NaN NaN
1 NaN NaN NaN
2 NaN NaN NaN
```

However, if I simply do this:

``` python
for idx in list('ABC'):
    panel[idx] = replace

print panel['A']
```

It behaves as expected:

```
   a  b  c
0  1  0  0
1  0  1  0
2  0  0  1
```

Also, if I assign more than just the item, it works fine:

``` python
panel.loc['A', 0, 'a'] = 1.0
print panel['A']
```

```
          a         b         c
0  1.000000  0.400157  0.978738
1  2.240893  1.867558 -0.977278
2  0.950088 -0.151357 -0.103219
```

Thanks.



Discussion:
hmm, must be an alignment issue. thanks, marking as a bug.

note that

`panel.loc['A'] = replace` works as well. Its when specifiying multiple indexers it
gets confused.

Any update on this? I have a situation where I need to replace the columns of a 'minor'-oriented panel, necessitating `p.loc[:, :, idx] = replace`

Any suggested workarounds for this?

that syntax should work
give an example is it does not

Investigation showed what's different about my use case. My issue is I am trying to add columns. For the example above, my panel was set up like so:

```
panel = ps.Panel.from_dict({
                  'A' : ps.DataFrame(np.random.randn(3, 3), index=index, columns=columns),
                  'B' : ps.DataFrame(np.random.randn(3, 3), index=index, columns=columns),
                  'C' : ps.DataFrame(np.random.randn(3, 3), index=index, columns=columns)
                  }, orient='minor')
```

 I'm trying to add a 'd' column to the panel:

`replace = ps.DataFrame(np.eye(4,4), index=range(4), columns=list('abcd'))`

such that only the 'A' label gets its values replaced. For B and C, the 'd' column should be NaN. The way I'd thought to do it was:

`panel.loc[:, :, 'A'] = replace`

But this doesn't add 'd'. I end up with:

`print panel.loc[:, :, 'A']`

```
   a  b  c
0  1  0  0
1  0  1  0
2  0  0  1
```

Is this how it ought to behave? Am I asking for what I want the wrong way?

@jreback yes. Example code: https://gist.github.com/spitz-dan-l/01a8e7788ffaeec87fd7

@spitz-dan-l 
the indicated behavior is correct; the panel will reindex the provide data to the same as the panel.

prob best to just reindex the axes to the union of the axes you want

e.g. something like

```
panel = panel.reindex(items=panel.items|replace.columns,major_axis=panel.major_axis|replace.index)
panel.loc[:,:,'A'] = replace
```

or you can use `concat` and `update`. `.loc` is not the right tool here because of the ambiguity. pandas normally aligns the input (special case on the 0th axis where it can enlarge).

thanks!


Resolving patch:
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -609,7 +609,13 @@ def _align_series(self, indexer, ser):
     def _align_frame(self, indexer, df):
         is_frame = self.obj.ndim == 2
         is_panel = self.obj.ndim >= 3
+
         if isinstance(indexer, tuple):
+
+            aligners = [not _is_null_slice(idx) for idx in indexer]
+            sum_aligners = sum(aligners)
+            single_aligner = sum_aligners == 1
+
             idx, cols = None, None
             sindexers = []
             for i, ix in enumerate(indexer):
@@ -626,13 +632,21 @@ def _align_frame(self, indexer, df):
 
             # 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
+
+                # need to conform to the convention
+                # as we are not selecting on the items axis
+                # and we have a single indexer
+                # GH 7763
+                if len(sindexers) == 1 and sindexers[0] != 0:
+                    df = df.T
+
+                if idx is None:
+                    idx = df.index
+                if cols is None:
+                    cols = df.columns
 
             if idx is not None and cols is not None:
+
                 if df.index.equals(idx) and df.columns.equals(cols):
                     val = df.copy().values
                 else:
@@ -655,21 +669,15 @@ def _align_frame(self, indexer, df):
                 val = df.reindex(index=ax).values
             return val
 
-        elif np.isscalar(indexer) and not is_frame:
+        elif np.isscalar(indexer) and is_panel:
             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
+            if idx.equals(df.index) and cols.equals(df.columns):
+                return df.copy().values
 
             return df.reindex(idx, columns=cols).values
 


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
@@ -2291,6 +2291,33 @@ def test_panel_getitem(self):
         test1 = panel.ix[:, "2002"]
         tm.assert_panel_equal(test1,test2)
 
+    def test_panel_setitem(self):
+
+        # GH 7763
+        # loc and setitem have setting differences
+        np.random.seed(0)
+        index=range(3)
+        columns = list('abc')
+
+        panel = Panel({'A' : DataFrame(np.random.randn(3, 3), index=index, columns=columns),
+                       'B' : DataFrame(np.random.randn(3, 3), index=index, columns=columns),
+                       'C' : DataFrame(np.random.randn(3, 3), index=index, columns=columns)
+                       })
+
+        replace = DataFrame(np.eye(3,3), index=range(3), columns=columns)
+        expected = Panel({ 'A' : replace, 'B' : replace, 'C' : replace })
+
+        p = panel.copy()
+        for idx in list('ABC'):
+            p[idx] = replace
+        tm.assert_panel_equal(p, expected)
+
+        p = panel.copy()
+        for idx in list('ABC'):
+            p.loc[idx,:,:] = replace
+        tm.assert_panel_equal(p, expected)
+
+
     def test_panel_assignment(self):
 
         # GH3777
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
@@ -668,24 +668,42 @@ def test_ix_align(self):
 
     def test_ix_frame_align(self):
         from pandas import DataFrame
-        df = DataFrame(np.random.randn(2, 10))
-        df.sort_index(inplace=True)
-        p_orig = Panel(np.random.randn(3, 10, 2))
+        p_orig = tm.makePanel()
+        df = p_orig.ix[0].copy()
+        assert_frame_equal(p_orig['ItemA'],df)
 
         p = p_orig.copy()
         p.ix[0, :, :] = df
-        out = p.ix[0, :, :].T.reindex(df.index, columns=df.columns)
-        assert_frame_equal(out, df)
+        assert_panel_equal(p, p_orig)
 
         p = p_orig.copy()
         p.ix[0] = df
-        out = p.ix[0].T.reindex(df.index, columns=df.columns)
-        assert_frame_equal(out, df)
+        assert_panel_equal(p, p_orig)
+
+        p = p_orig.copy()
+        p.iloc[0, :, :] = df
+        assert_panel_equal(p, p_orig)
+
+        p = p_orig.copy()
+        p.iloc[0] = df
+        assert_panel_equal(p, p_orig)
+
+        p = p_orig.copy()
+        p.loc['ItemA'] = df
+        assert_panel_equal(p, p_orig)
+
+        p = p_orig.copy()
+        p.loc['ItemA', :, :] = df
+        assert_panel_equal(p, p_orig)
+
+        p = p_orig.copy()
+        p['ItemA'] = df
+        assert_panel_equal(p, p_orig)
 
         p = p_orig.copy()
         p.ix[0, [0, 1, 3, 5], -2:] = df
         out = p.ix[0, [0, 1, 3, 5], -2:]
-        assert_frame_equal(out, df.T.reindex([0, 1, 3, 5], p.minor_axis[-2:]))
+        assert_frame_equal(out, df.iloc[[0,1,3,5],[2,3]])
 
         # GH3830, panel assignent by values/frame
         for dtype in ['float64','int64']: