Repository: pandas-dev/pandas

Issue:
BUG: AttributeError when trying to assign a value to an element of a MultiIndex DataFrame
Code snipet producing the (unexpected) ValueError (versions of pandas and numpy printed after the traceback)

``` python
import numpy as np
import pandas as pd
print('pandas : %s' % pd.__version__)
print('numpy : %s' % np.__version__)
a = np.random.rand(10, 3)
df = pd.DataFrame(a, columns=['x', 'y', 'z'])
tuples = [(i, j) for i in range(5) for j in range(2)]
index = pd.MultiIndex.from_tuples(tuples)
df.index = index
df.loc[0]['z'].iloc[0] = 1.
```

output:

``` python
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-9-8152e4c5a53b> in <module>()
      8 index = pd.MultiIndex.from_tuples(tuples)
      9 df.index = index
---> 10 df.loc[0]['z'].iloc[0] = 1.

/usr/local/lib/python2.7/dist-packages/pandas-0.12.0_852_gb76b265-py2.7-linux-i686.egg/pandas/core/indexing.pyc in __setitem__(self, key, value)
     92             indexer = self._convert_to_indexer(key, is_setter=True)
     93 
---> 94         self._setitem_with_indexer(indexer, value)
     95 
     96     def _has_valid_type(self, k, axis):

/usr/local/lib/python2.7/dist-packages/pandas-0.12.0_852_gb76b265-py2.7-linux-i686.egg/pandas/core/indexing.pyc in _setitem_with_indexer(self, indexer, value)
    364 
    365             self.obj._data = self.obj._data.setitem(indexer,value)
--> 366             self.obj._maybe_update_cacher(clear=True)
    367 
    368     def _align_series(self, indexer, ser):

/usr/local/lib/python2.7/dist-packages/pandas-0.12.0_852_gb76b265-py2.7-linux-i686.egg/pandas/core/generic.pyc in _maybe_update_cacher(self, clear)
    944         cacher = getattr(self,'_cacher',None)
    945         if cacher is not None:
--> 946             cacher[1]()._maybe_cache_changed(cacher[0],self)
    947         if clear:
    948             self._clear_item_cache()

AttributeError: 'NoneType' object has no attribute '_maybe_cache_changed'

pandas : 0.12.0-852-gb76b265
numpy : 1.8.0.dev-d310678
```



Discussion:
```
In [40]: pd.__version__
Out[40]: '0.12.0-852-gb76b265'
```

That is an invalid assignment because its chained, see [here](http://pandas.pydata.org/pandas-docs/dev/indexing.html#indexing-view-versus-copy)

```
In [34]: df2
Out[34]: 
            x         y         z
0 0  0.286564  0.050018  0.111550
  1  0.964252  0.691074  0.336067
1 0  0.536750  0.368807  0.199002
  1  0.826987  0.316490  0.574972
2 0  0.547737  0.663395  0.312058
  1  0.925936  0.279883  0.926590
3 0  0.701851  0.320985  0.462495
  1  0.062629  0.471302  0.438546
4 0  0.156002  0.314738  0.756440
  1  0.763509  0.858044  0.403415

In [35]: df2.loc[(0,0),'z'] = 1

In [38]: df2.loc[0,'z'].iloc[1] = 2

In [39]: df2
Out[39]: 
            x         y         z
0 0  0.286564  0.050018  1.000000
  1  0.964252  0.691074  2.000000
1 0  0.536750  0.368807  0.199002
  1  0.826987  0.316490  0.574972
2 0  0.547737  0.663395  0.312058
  1  0.925936  0.279883  0.926590
3 0  0.701851  0.320985  0.462495
  1  0.062629  0.471302  0.438546
4 0  0.156002  0.314738  0.756440
  1  0.763509  0.858044  0.403415
```

35 is the way to do this guaranteed; 38 works, but is chained (though happens to work)

Separately I will looks at the exception....


Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -943,7 +943,13 @@ def _maybe_update_cacher(self, clear=False):
             if clear, then clear our cache """
         cacher = getattr(self,'_cacher',None)
         if cacher is not None:
-            cacher[1]()._maybe_cache_changed(cacher[0],self)
+            try:
+                cacher[1]()._maybe_cache_changed(cacher[0],self)
+            except:
+
+                # our referant is dead
+                del self._cacher
+
         if clear:
             self._clear_item_cache()
 


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
@@ -1557,6 +1557,24 @@ def test_cache_updating(self):
         self.assert_("A+1" in panel.ix[0].columns)
         self.assert_("A+1" in panel.ix[1].columns)
 
+        # 5216
+        # make sure that we don't try to set a dead cache
+        a = np.random.rand(10, 3)
+        df = DataFrame(a, columns=['x', 'y', 'z'])
+        tuples = [(i, j) for i in range(5) for j in range(2)]
+        index = MultiIndex.from_tuples(tuples)
+        df.index = index
+
+        # setting via chained assignment
+        df.loc[0]['z'].iloc[0] = 1.
+        result = df.loc[(0,0),'z']
+        self.assert_(result == 1)
+
+        # correct setting
+        df.loc[(0,0),'z'] = 2
+        result = df.loc[(0,0),'z']
+        self.assert_(result == 2)
+
     def test_floating_index_doc_example(self):
 
         index = Index([1.5, 2, 3, 4.5, 5])