diff --git a/doc/source/whatsnew/v0.23.0.txt b/doc/source/whatsnew/v0.23.0.txt index 43e384b01ad2c..1b7cc3d5b235e 100644 --- a/doc/source/whatsnew/v0.23.0.txt +++ b/doc/source/whatsnew/v0.23.0.txt @@ -1098,6 +1098,7 @@ Numeric - Bug in :class:`DataFrame` flex arithmetic (e.g. ``df.add(other, fill_value=foo)``) with a ``fill_value`` other than ``None`` failed to raise ``NotImplementedError`` in corner cases where either the frame or ``other`` has length zero (:issue:`19522`) - Multiplication and division of numeric-dtyped :class:`Index` objects with timedelta-like scalars returns ``TimedeltaIndex`` instead of raising ``TypeError`` (:issue:`19333`) - Bug where ``NaN`` was returned instead of 0 by :func:`Series.pct_change` and :func:`DataFrame.pct_change` when ``fill_method`` is not ``None`` (:issue:`19873`) +- :meth:`~DataFrame.agg` now correctly handles numpy NaN-aware methods like :meth:`numpy.nansum` (:issue:`19629`) Strings ^^^^^^^ diff --git a/pandas/core/base.py b/pandas/core/base.py index 0d55fa8b97aae..ed3bdb8dc45fb 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -22,7 +22,8 @@ from pandas.core import common as com, algorithms import pandas.core.nanops as nanops import pandas._libs.lib as lib -from pandas.compat.numpy import function as nv +from pandas.compat.numpy import (function as nv, _np_version_under1p10, + _np_version_under1p12) from pandas.compat import PYPY from pandas.util._decorators import (Appender, cache_readonly, deprecate_kwarg, Substitution) @@ -191,17 +192,31 @@ class SelectionMixin(object): np.all: 'all', np.any: 'any', np.sum: 'sum', + np.nansum: 'sum', np.mean: 'mean', + np.nanmean: 'mean', np.prod: 'prod', np.std: 'std', + np.nanstd: 'std', np.var: 'var', + np.nanvar: 'var', np.median: 'median', + np.nanmedian: 'median', np.max: 'max', + np.nanmax: 'max', np.min: 'min', + np.nanmin: 'min', np.cumprod: 'cumprod', - np.cumsum: 'cumsum' + np.cumsum: 'cumsum', } + if not _np_version_under1p10: + _cython_table[np.nanprod] = 'prod' + + if not _np_version_under1p12: + _cython_table[np.nancumprod] = 'cumprod' + _cython_table[np.nancumsum] = 'cumsum' + @property def _selection_name(self): """ diff --git a/pandas/tests/test_nanops.py b/pandas/tests/test_nanops.py index a70ee80aee180..5d90a67ca7e3b 100644 --- a/pandas/tests/test_nanops.py +++ b/pandas/tests/test_nanops.py @@ -994,6 +994,49 @@ def prng(self): return np.random.RandomState(1234) +@pytest.fixture(params=[ + pd.Series([1, 2, 3, 4, 5, 6]), + pd.DataFrame([[1, 2, 3], [4, 5, 6]]) +]) +def nan_test_object(request): + return request.param + + +@pytest.mark.parametrize("standard, nan_method", [ + (np.sum, np.nansum), + (np.mean, np.nanmean), + (np.std, np.nanstd), + (np.var, np.nanvar), + (np.median, np.nanmedian), + (np.max, np.nanmax), + (np.min, np.nanmin) +]) +def test_np_nan_functions(standard, nan_method, nan_test_object): + tm.assert_almost_equal(nan_test_object.agg(standard), + nan_test_object.agg(nan_method), + check_exact=True) + + +@td.skip_if_no("numpy", min_version="1.10.0") +def test_np_nanprod(nan_test_object): + tm.assert_almost_equal(nan_test_object.agg(np.prod), + nan_test_object.agg(np.nanprod), + check_exact=True) + + +@td.skip_if_no("numpy", min_version="1.12.0") +def test_np_nancumprod(nan_test_object): + # Not using pytest params for methods as will fail at build time + methods = [ + (np.cumprod, np.nancumprod), + (np.cumsum, np.nancumsum) + ] + for standard, nan_method in methods: + tm.assert_almost_equal(nan_test_object.agg(standard), + nan_test_object.agg(nan_method), + check_exact=True) + + def test_use_bottleneck(): if nanops._BOTTLENECK_INSTALLED: