Repository: pandas-dev/pandas

Issue:
BUG: pivot_table aggfunc should accept string function-likes
Virtually everywhere we allow strings to substitue for functions in ``.agg``, ``.apply`` and such, straightforward to add here. Just need to call https://github.com/pandas-dev/pandas/blob/master/pandas/core/base.py#L627.

example from ``pd.pivot_table`` doc-string

```
In [11]: >>> df = pd.DataFrame({"A": ["foo", "foo", "foo", "foo", "foo",
    ...: ...                          "bar", "bar", "bar", "bar"],
    ...: ...                    "B": ["one", "one", "one", "two", "two",
    ...: ...                          "one", "one", "two", "two"],
    ...: ...                    "C": ["small", "large", "large", "small",
    ...: ...                          "small", "large", "small", "small",
    ...: ...                          "large"],
    ...: ...                    "D": [1, 2, 2, 3, 3, 4, 5, 6, 7]})
    ...: 

In [12]: df
Out[12]: 
     A    B      C  D
0  foo  one  small  1
1  foo  one  large  2
2  foo  one  large  2
3  foo  two  small  3
4  foo  two  small  3
5  bar  one  large  4
6  bar  one  small  5
7  bar  two  small  6
8  bar  two  large  7
```

works
```
In [13]: pd.pivot_table(df, values='D', index=['A', 'B'],
    ...:                      columns=['C'], aggfunc=[np.sum, np.mean])
 Out[13]: 
          sum        mean      
C       large small large small
A   B                          
bar one   4.0   5.0   4.0   5.0
    two   7.0   6.0   7.0   6.0
foo one   4.0   1.0   2.0   1.0
    two   NaN   6.0   NaN   3.0
```

fails
```
In [14]: pd.pivot_table(df, values='D', index=['A', 'B'],
    ...:                      columns=['C'], aggfunc=['sum', 'mean'])
AttributeError: 'str' object has no attribute '__name__'
```


Discussion:
Hi @jreback, I was looking at this today.  I believe the line throwing the error is https://github.com/pandas-dev/pandas/blob/52838e609c1b2a495069964dea862a39dd067b2b/pandas/core/reshape/pivot.py#L41

I would propose something like the following and would be happy to submit a PR.  LMK
         
```
if isinstance(func, compat.string_types):
    keys.append(func)
else:
    keys.append(func.__name__)
```

sure
you can also use

getattr(func, ‘__name__’, func)
Good deal, I like that better.  

Resolving patch:
diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py
--- a/pandas/core/reshape/pivot.py
+++ b/pandas/core/reshape/pivot.py
@@ -38,7 +38,8 @@ def pivot_table(data, values=None, index=None, columns=None, aggfunc='mean',
                                 fill_value=fill_value, aggfunc=func,
                                 margins=margins, margins_name=margins_name)
             pieces.append(table)
-            keys.append(func.__name__)
+            keys.append(getattr(func, '__name__', func))
+
         return concat(pieces, keys=keys, axis=1)
 
     keys = index + columns


Test patch:
diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py
--- a/pandas/tests/reshape/test_pivot.py
+++ b/pandas/tests/reshape/test_pivot.py
@@ -1109,6 +1109,51 @@ def test_pivot_margins_name_unicode(self):
         expected = pd.DataFrame(index=index)
         tm.assert_frame_equal(table, expected)
 
+    def test_pivot_string_as_func(self):
+        # GH #18713
+        # for correctness purposes
+        data = DataFrame({'A': ['foo', 'foo', 'foo', 'foo', 'bar', 'bar',
+                                'bar', 'bar', 'foo', 'foo', 'foo'],
+                          'B': ['one', 'one', 'one', 'two', 'one', 'one',
+                                'one', 'two', 'two', 'two', 'one'],
+                          'C': range(11)})
+
+        result = pivot_table(data, index='A', columns='B', aggfunc='sum')
+        mi = MultiIndex(levels=[['C'], ['one', 'two']],
+                        labels=[[0, 0], [0, 1]], names=[None, 'B'])
+        expected = DataFrame({('C', 'one'): {'bar': 15, 'foo': 13},
+                              ('C', 'two'): {'bar': 7, 'foo': 20}},
+                             columns=mi).rename_axis('A')
+        tm.assert_frame_equal(result, expected)
+
+        result = pivot_table(data, index='A', columns='B',
+                             aggfunc=['sum', 'mean'])
+        mi = MultiIndex(levels=[['sum', 'mean'], ['C'], ['one', 'two']],
+                        labels=[[0, 0, 1, 1], [0, 0, 0, 0], [0, 1, 0, 1]],
+                        names=[None, None, 'B'])
+        expected = DataFrame({('mean', 'C', 'one'): {'bar': 5.0, 'foo': 3.25},
+                              ('mean', 'C', 'two'): {'bar': 7.0,
+                                                     'foo': 6.666666666666667},
+                              ('sum', 'C', 'one'): {'bar': 15, 'foo': 13},
+                              ('sum', 'C', 'two'): {'bar': 7, 'foo': 20}},
+                             columns=mi).rename_axis('A')
+        tm.assert_frame_equal(result, expected)
+
+    @pytest.mark.parametrize('f, f_numpy',
+                             [('sum', np.sum),
+                              ('mean', np.mean),
+                              ('std', np.std),
+                              (['sum', 'mean'], [np.sum, np.mean]),
+                              (['sum', 'std'], [np.sum, np.std]),
+                              (['std', 'mean'], [np.std, np.mean])])
+    def test_pivot_string_func_vs_func(self, f, f_numpy):
+        # GH #18713
+        # for consistency purposes
+        result = pivot_table(self.data, index='A', columns='B', aggfunc=f)
+        expected = pivot_table(self.data, index='A', columns='B',
+                               aggfunc=f_numpy)
+        tm.assert_frame_equal(result, expected)
+
 
 class TestCrosstab(object):