Repository: pandas-dev/pandas

Issue:
DataFrame with MultiIndex columns implicitly assumes that stored data has an ndim attribute
I submit that this should be a valid pandas DataFrame, odd though it may appear

```
df = DataFrame([[np.mean, np.median],['mean','median']],
               columns=MultiIndex.from_tuples([('functs','mean'),
                                               ('functs','median')]), 
               index=['function', 'name'])
```

It looks like this:

```
In [45]: print df
                                functs                                
                                  mean                          median
function  <function mean at 0x33fb8c0>  <function median at 0x34fac80>
name                              mean                          median

```

However, it can't always be indexed using the `.ix` or `.loc` indexers.

This works as expected:

```
print df[('functs','mean')]['function']
```

As does this:

```
print df.loc['name'][('functs','mean')]
```

But this raises an AttributeError, because `np.mean` does not have an `ndim` attribute, but the indexing code assumes that it does:

```
print df.loc['function'][('functs','mean')]
```

The traceback is as follows:

```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-43-95d403db0b5a> in <module>()
----> 1 df.loc['function',('functs','mean')]

/usr/local/lib/python2.7/dist-packages/pandas/core/indexing.pyc in __getitem__(self, key)
   1140     def __getitem__(self, key):
   1141         if type(key) is tuple:
-> 1142             return self._getitem_tuple(key)
   1143         else:
   1144             return self._getitem_axis(key, axis=0)

/usr/local/lib/python2.7/dist-packages/pandas/core/indexing.pyc in _getitem_tuple(self, tup)
    653     def _getitem_tuple(self, tup):
    654         try:
--> 655             return self._getitem_lowerdim(tup)
    656         except IndexingError:
    657             pass

/usr/local/lib/python2.7/dist-packages/pandas/core/indexing.pyc in _getitem_lowerdim(self, tup)
    761         # we may have a nested tuples indexer here
    762         if self._is_nested_tuple_indexer(tup):
--> 763             return self._getitem_nested_tuple(tup)
    764 
    765         # we maybe be using a tuple to represent multiple dimensions here

/usr/local/lib/python2.7/dist-packages/pandas/core/indexing.pyc in _getitem_nested_tuple(self, tup)
    842             # has the dim of the obj changed?
    843             # GH 7199
--> 844             if obj.ndim < current_ndim:
    845 
    846                 # GH 7516

AttributeError: 'function' object has no attribute 'ndim'

```

This worked for me until I upgraded to 0.14.1. Either it is a bug or I am wrong that DataFrames are type-agnostic containers. (In the latter case, it seems that some kind of error-handling should be in place to check for disallowed datatypes.)



Discussion:
did you upgrade from < 0.14.0?

you should always use this form of access: `df.loc['function',('functs','mean')]`, FYI

this is a bug a `np.isscalar(np.mean)` is False (which IMHO is sort of weird). But I think I'll just check for a `.ndim` attribute. It does this to figure out whether to return the object or iterate thru additional dimensions.


Resolving patch:
diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py
--- a/pandas/core/indexing.py
+++ b/pandas/core/indexing.py
@@ -838,7 +838,7 @@ def _getitem_nested_tuple(self, tup):
             axis += 1
 
             # if we have a scalar, we are done
-            if np.isscalar(obj):
+            if np.isscalar(obj) or not hasattr(obj,'ndim'):
                 break
 
             # has the dim of the obj changed?


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
@@ -1974,6 +1974,15 @@ def f():
         result = s.loc[idx[:,['foo','bah']]]
         assert_series_equal(result,expected)
 
+        # regression from < 0.14.0
+        # GH 7914
+        df = DataFrame([[np.mean, np.median],['mean','median']],
+                       columns=MultiIndex.from_tuples([('functs','mean'),
+                                                       ('functs','median')]),
+                       index=['function', 'name'])
+        result = df.loc['function',('functs','mean')]
+        self.assertEqual(result,np.mean)
+
     def test_setitem_dtype_upcast(self):
 
         # GH3216