Repository: pandas-dev/pandas

Issue:
DataFrame.apply fails for string function arguments with additional positional or keyword arguments
#### Code sample

```python
import pandas as pd
df = pd.DataFrame([[1, 2, 3], [1, 2, 3]])
df.apply('sum', axis=1, min_count=1)
```

#### Problem description

When we use the ``DataFrame.apply`` method with a string function argument (e.g. 'sum') and provide additional positional or keyword arguments it fails with the following exception:

```python
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<cut>/pandas/core/frame.py", line 6173, in apply
    return op.get_result()
  File "<cut>/pandas/core/apply.py", line 151, in get_result
    return self.apply_standard()
  File "<cut>/pandas/core/apply.py", line 257, in apply_standard
    self.apply_series_generator()
  File "<cut>/pandas/core/apply.py", line 286, in apply_series_generator
    results[i] = self.f(v)
  File "<cut>/pandas-dev/pandas/core/apply.py", line 78, in f
    return func(x, *args, **kwds)
TypeError: ("'str' object is not callable", 'occurred at index 0')
```
but works just fine without additional arguments. The code above fails in master.

#### Expected Output

```python
>>> df.apply('sum', axis=1, min_count=1)
0    6
1    6
dtype: int64
```

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit: 70e6f7c3ce7aca9a0ee08bacb2fe0ad85db02d88
python: 3.6.6.final.0
python-bits: 64
OS: Linux
OS-release: 3.0.101-108.13.1.14249.0.PTF-default
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.24.0.dev0+469.g70e6f7c3c
pytest: 3.7.1
pip: 10.0.1
setuptools: 40.0.0
Cython: 0.28.5
numpy: 1.15.0
scipy: 1.1.0
pyarrow: 0.9.0
xarray: 0.10.8
IPython: 6.5.0
sphinx: 1.7.6
patsy: 0.5.0
dateutil: 2.7.3
pytz: 2018.5
blosc: None
bottleneck: 1.2.1
tables: 3.4.4
numexpr: 2.6.7
feather: 0.4.0
matplotlib: 2.2.3
openpyxl: 2.5.5
xlrd: 1.1.0
xlwt: 1.3.0
xlsxwriter: 1.0.5
lxml: 4.2.4
bs4: 4.6.3
html5lib: 1.0.1
sqlalchemy: 1.2.10
pymysql: 0.9.2
psycopg2: None
jinja2: 2.10
s3fs: 0.1.5
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: 0.1.1
</details>



Discussion:

Resolving patch:
diff --git a/pandas/core/apply.py b/pandas/core/apply.py
--- a/pandas/core/apply.py
+++ b/pandas/core/apply.py
@@ -71,7 +71,9 @@ def __init__(self, obj, func, broadcast, raw, reduce, result_type,
         self.result_type = result_type
 
         # curry if needed
-        if kwds or args and not isinstance(func, np.ufunc):
+        if ((kwds or args) and
+                not isinstance(func, (np.ufunc, compat.string_types))):
+
             def f(x):
                 return func(x, *args, **kwds)
         else:


Test patch:
diff --git a/pandas/tests/frame/test_apply.py b/pandas/tests/frame/test_apply.py
--- a/pandas/tests/frame/test_apply.py
+++ b/pandas/tests/frame/test_apply.py
@@ -120,14 +120,17 @@ def test_apply_standard_nonunique(self):
         rs = df.T.apply(lambda s: s[0], axis=0)
         assert_series_equal(rs, xp)
 
-    @pytest.mark.parametrize('arg', ['sum', 'mean', 'min', 'max', 'std'])
-    def test_with_string_args(self, arg):
-        result = self.frame.apply(arg)
-        expected = getattr(self.frame, arg)()
-        tm.assert_series_equal(result, expected)
-
-        result = self.frame.apply(arg, axis=1)
-        expected = getattr(self.frame, arg)(axis=1)
+    @pytest.mark.parametrize('func', ['sum', 'mean', 'min', 'max', 'std'])
+    @pytest.mark.parametrize('args,kwds', [
+        pytest.param([], {}, id='no_args_or_kwds'),
+        pytest.param([1], {}, id='axis_from_args'),
+        pytest.param([], {'axis': 1}, id='axis_from_kwds'),
+        pytest.param([], {'numeric_only': True}, id='optional_kwds'),
+        pytest.param([1, None], {'numeric_only': True}, id='args_and_kwds')
+    ])
+    def test_apply_with_string_funcs(self, func, args, kwds):
+        result = self.frame.apply(func, *args, **kwds)
+        expected = getattr(self.frame, func)(*args, **kwds)
         tm.assert_series_equal(result, expected)
 
     def test_apply_broadcast_deprecated(self):