From 75ae820c74e927679af82ef634a8f3ef0f97503f Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Mon, 14 Apr 2014 08:43:20 -0400 Subject: [PATCH 1/5] BUG: many functions silently drop `keepdims` kwarg change test from `type(a) is not mu.ndarray` to `not isinstance(a, mu.ndarray)` Because every sub-class of ndarray is not guaranteed to implement `keepdims` as a kwarg, when wrapping these methods care must be taken. The previous behavior was to silently eat the kwarg when dealing with a sub-class of ndarray. Now, if `keepdims=np._NoValue` (the new default) it is not passed through to the underlying function call (so the default value of `keepdims` is now controlled by the sub-class). If `keepdims` is not `np._NoValue` then it is passed through and will raise an exception if the sub-class does not support the kwarg. A special case in nanvar was required to deal with `matrix` that previously relied on `fromnumeric` silently dropping `keepdims`. --- numpy/core/fromnumeric.py | 195 +++++++++++++++++++++---------- numpy/core/tests/test_numeric.py | 12 ++ numpy/lib/nanfunctions.py | 145 ++++++++++++++++------- 3 files changed, 254 insertions(+), 98 deletions(-) diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index 4faeb557a6d3..bb89adbe1761 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -17,7 +17,6 @@ _dt_ = nt.sctype2char - # functions that are methods __all__ = [ 'alen', 'all', 'alltrue', 'amax', 'amin', 'any', 'argmax', @@ -1380,6 +1379,7 @@ def trace(a, offset=0, axis1=0, axis2=1, dtype=None, out=None): return asanyarray(a).trace(offset, axis1, axis2, dtype, out) + def ravel(a, order='C'): """Return a contiguous flattened array. @@ -1740,7 +1740,7 @@ def clip(a, a_min, a_max, out=None): return clip(a_min, a_max, out) -def sum(a, axis=None, dtype=None, out=None, keepdims=False): +def sum(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): """ Sum of array elements over a given axis. @@ -1770,9 +1770,15 @@ def sum(a, axis=None, dtype=None, out=None, keepdims=False): the same shape as the expected output, but the type of the output values will be cast if necessary. keepdims : bool, optional - If this is set to True, the axes which are reduced are left in the - result as dimensions with size one. With this option, the result - will broadcast correctly against the input array. + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `arr`. + + If the default value is passed, then `keepdims` will not be + passed through to the `sum` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. Returns ------- @@ -1821,6 +1827,9 @@ def sum(a, axis=None, dtype=None, out=None, keepdims=False): -128 """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims if isinstance(a, _gentype): res = _sum_(a) if out is not None: @@ -1832,15 +1841,14 @@ def sum(a, axis=None, dtype=None, out=None, keepdims=False): sum = a.sum except AttributeError: return _methods._sum(a, axis=axis, dtype=dtype, - out=out, keepdims=keepdims) - # NOTE: Dropping the keepdims parameters here... - return sum(axis=axis, dtype=dtype, out=out) + out=out, **kwargs) + return sum(axis=axis, dtype=dtype, out=out, **kwargs) else: return _methods._sum(a, axis=axis, dtype=dtype, - out=out, keepdims=keepdims) + out=out, **kwargs) -def product(a, axis=None, dtype=None, out=None, keepdims=False): +def product(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): """ Return the product of array elements over a given axis. @@ -1849,11 +1857,13 @@ def product(a, axis=None, dtype=None, out=None, keepdims=False): prod : equivalent function; see for details. """ - return um.multiply.reduce(a, axis=axis, dtype=dtype, - out=out, keepdims=keepdims) + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims + return um.multiply.reduce(a, axis=axis, dtype=dtype, out=out, **kwargs) -def sometrue(a, axis=None, out=None, keepdims=False): +def sometrue(a, axis=None, out=None, keepdims=np._NoValue): """ Check whether some values are true. @@ -1865,14 +1875,13 @@ def sometrue(a, axis=None, out=None, keepdims=False): """ arr = asanyarray(a) - - try: - return arr.any(axis=axis, out=out, keepdims=keepdims) - except TypeError: - return arr.any(axis=axis, out=out) + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims + return arr.any(axis=axis, out=out, **kwargs) -def alltrue(a, axis=None, out=None, keepdims=False): +def alltrue(a, axis=None, out=None, keepdims=np._NoValue): """ Check if all elements of input array are true. @@ -1882,14 +1891,13 @@ def alltrue(a, axis=None, out=None, keepdims=False): """ arr = asanyarray(a) + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims + return arr.all(axis=axis, out=out, **kwargs) - try: - return arr.all(axis=axis, out=out, keepdims=keepdims) - except TypeError: - return arr.all(axis=axis, out=out) - -def any(a, axis=None, out=None, keepdims=False): +def any(a, axis=None, out=None, keepdims=np._NoValue): """ Test whether any array element along a given axis evaluates to True. @@ -1915,11 +1923,18 @@ def any(a, axis=None, out=None, keepdims=False): (e.g., if it is of type float, then it will remain so, returning 1.0 for True and 0.0 for False, regardless of the type of `a`). See `doc.ufuncs` (Section "Output arguments") for details. + keepdims : bool, optional If this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, the result will broadcast correctly against the original `arr`. + If the default value is passed, then `keepdims` will not be + passed through to the `any` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + Returns ------- any : bool or ndarray @@ -1963,14 +1978,13 @@ def any(a, axis=None, out=None, keepdims=False): """ arr = asanyarray(a) - - try: - return arr.any(axis=axis, out=out, keepdims=keepdims) - except TypeError: - return arr.any(axis=axis, out=out) + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims + return arr.any(axis=axis, out=out, **kwargs) -def all(a, axis=None, out=None, keepdims=False): +def all(a, axis=None, out=None, keepdims=np._NoValue): """ Test whether all array elements along a given axis evaluate to True. @@ -1994,11 +2008,18 @@ def all(a, axis=None, out=None, keepdims=False): type is preserved (e.g., if ``dtype(out)`` is float, the result will consist of 0.0's and 1.0's). See `doc.ufuncs` (Section "Output arguments") for more details. + keepdims : bool, optional If this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, the result will broadcast correctly against the original `arr`. + If the default value is passed, then `keepdims` will not be + passed through to the `all` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + Returns ------- all : ndarray, bool @@ -2037,11 +2058,10 @@ def all(a, axis=None, out=None, keepdims=False): """ arr = asanyarray(a) - - try: - return arr.all(axis=axis, out=out, keepdims=keepdims) - except TypeError: - return arr.all(axis=axis, out=out) + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims + return arr.all(axis=axis, out=out, **kwargs) def cumsum(a, axis=None, dtype=None, out=None): @@ -2177,7 +2197,7 @@ def ptp(a, axis=None, out=None): return ptp(axis, out) -def amax(a, axis=None, out=None, keepdims=False): +def amax(a, axis=None, out=None, keepdims=np._NoValue): """ Return the maximum of an array or maximum along an axis. @@ -2197,11 +2217,18 @@ def amax(a, axis=None, out=None, keepdims=False): Alternative output array in which to place the result. Must be of the same shape and buffer length as the expected output. See `doc.ufuncs` (Section "Output arguments") for more details. + keepdims : bool, optional If this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, the result will broadcast correctly against the original `arr`. + If the default value is passed, then `keepdims` will not be + passed through to the `amax` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + Returns ------- amax : ndarray or scalar @@ -2255,20 +2282,22 @@ def amax(a, axis=None, out=None, keepdims=False): 4.0 """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims if type(a) is not mu.ndarray: try: amax = a.max except AttributeError: return _methods._amax(a, axis=axis, - out=out, keepdims=keepdims) - # NOTE: Dropping the keepdims parameter - return amax(axis=axis, out=out) + out=out, **kwargs) + return amax(axis=axis, out=out, **kwargs) else: return _methods._amax(a, axis=axis, - out=out, keepdims=keepdims) + out=out, **kwargs) -def amin(a, axis=None, out=None, keepdims=False): +def amin(a, axis=None, out=None, keepdims=np._NoValue): """ Return the minimum of an array or minimum along an axis. @@ -2288,11 +2317,18 @@ def amin(a, axis=None, out=None, keepdims=False): Alternative output array in which to place the result. Must be of the same shape and buffer length as the expected output. See `doc.ufuncs` (Section "Output arguments") for more details. + keepdims : bool, optional If this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, the result will broadcast correctly against the original `arr`. + If the default value is passed, then `keepdims` will not be + passed through to the `amin` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + Returns ------- amin : ndarray or scalar @@ -2346,17 +2382,19 @@ def amin(a, axis=None, out=None, keepdims=False): 0.0 """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims if type(a) is not mu.ndarray: try: amin = a.min except AttributeError: return _methods._amin(a, axis=axis, - out=out, keepdims=keepdims) - # NOTE: Dropping the keepdims parameter - return amin(axis=axis, out=out) + out=out, **kwargs) + return amin(axis=axis, out=out, **kwargs) else: return _methods._amin(a, axis=axis, - out=out, keepdims=keepdims) + out=out, **kwargs) def alen(a): @@ -2392,7 +2430,7 @@ def alen(a): return len(array(a, ndmin=1)) -def prod(a, axis=None, dtype=None, out=None, keepdims=False): +def prod(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): """ Return the product of array elements over a given axis. @@ -2427,6 +2465,12 @@ def prod(a, axis=None, dtype=None, out=None, keepdims=False): result as dimensions with size one. With this option, the result will broadcast correctly against the input array. + If the default value is passed, then `keepdims` will not be + passed through to the `prod` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + Returns ------- product_along_axis : ndarray, see `dtype` parameter above. @@ -2484,16 +2528,19 @@ def prod(a, axis=None, dtype=None, out=None, keepdims=False): True """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims if type(a) is not mu.ndarray: try: prod = a.prod except AttributeError: return _methods._prod(a, axis=axis, dtype=dtype, - out=out, keepdims=keepdims) - return prod(axis=axis, dtype=dtype, out=out) + out=out, **kwargs) + return prod(axis=axis, dtype=dtype, out=out, **kwargs) else: return _methods._prod(a, axis=axis, dtype=dtype, - out=out, keepdims=keepdims) + out=out, **kwargs) def cumprod(a, axis=None, dtype=None, out=None): @@ -2793,7 +2840,7 @@ def round_(a, decimals=0, out=None): return round(decimals, out) -def mean(a, axis=None, dtype=None, out=None, keepdims=False): +def mean(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): """ Compute the arithmetic mean along the specified axis. @@ -2823,11 +2870,18 @@ def mean(a, axis=None, dtype=None, out=None, keepdims=False): is ``None``; if provided, it must have the same shape as the expected output, but the type will be cast if necessary. See `doc.ufuncs` for details. + keepdims : bool, optional If this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, the result will broadcast correctly against the original `arr`. + If the default value is passed, then `keepdims` will not be + passed through to the `mean` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + Returns ------- m : ndarray, see dtype parameter above @@ -2874,18 +2928,21 @@ def mean(a, axis=None, dtype=None, out=None, keepdims=False): 0.55000000074505806 """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims if type(a) is not mu.ndarray: try: mean = a.mean - return mean(axis=axis, dtype=dtype, out=out) + return mean(axis=axis, dtype=dtype, out=out, **kwargs) except AttributeError: pass return _methods._mean(a, axis=axis, dtype=dtype, - out=out, keepdims=keepdims) + out=out, **kwargs) -def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): +def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): """ Compute the standard deviation along the specified axis. @@ -2922,6 +2979,12 @@ def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): in the result as dimensions with size one. With this option, the result will broadcast correctly against the original `arr`. + If the default value is passed, then `keepdims` will not be + passed through to the `std` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + Returns ------- standard_deviation : ndarray, see dtype parameter above. @@ -2981,19 +3044,23 @@ def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): 0.44999999925494177 """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims + if type(a) is not mu.ndarray: try: std = a.std - return std(axis=axis, dtype=dtype, out=out, ddof=ddof) + return std(axis=axis, dtype=dtype, out=out, ddof=ddof, **kwargs) except AttributeError: pass return _methods._std(a, axis=axis, dtype=dtype, out=out, ddof=ddof, - keepdims=keepdims) + **kwargs) def var(a, axis=None, dtype=None, out=None, ddof=0, - keepdims=False): + keepdims=np._NoValue): """ Compute the variance along the specified axis. @@ -3031,6 +3098,12 @@ def var(a, axis=None, dtype=None, out=None, ddof=0, in the result as dimensions with size one. With this option, the result will broadcast correctly against the original `arr`. + If the default value is passed, then `keepdims` will not be + passed through to the `var` method of sub-classes of + `ndarray`, however any non-default value will be. If the + sub-classes `sum` method does not implement `keepdims` any + exceptions will be raised. + Returns ------- variance : ndarray, see dtype parameter above @@ -3089,12 +3162,16 @@ def var(a, axis=None, dtype=None, out=None, ddof=0, 0.2025 """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims + if type(a) is not mu.ndarray: try: var = a.var - return var(axis=axis, dtype=dtype, out=out, ddof=ddof) + return var(axis=axis, dtype=dtype, out=out, ddof=ddof, **kwargs) except AttributeError: pass return _methods._var(a, axis=axis, dtype=dtype, out=out, ddof=ddof, - keepdims=keepdims) + **kwargs) diff --git a/numpy/core/tests/test_numeric.py b/numpy/core/tests/test_numeric.py index 7309cf2249c0..e22a5e193434 100644 --- a/numpy/core/tests/test_numeric.py +++ b/numpy/core/tests/test_numeric.py @@ -2472,5 +2472,17 @@ def test_number_of_arguments(self): assert_equal(mit.numiter, j) +class TestKeepdims(TestCase): + + class sub_array(np.ndarray): + def sum(self, axis=None, dtype=None, out=None): + return np.ndarray.sum(self, axis, dtype, out, keepdims=True) + + def test_raise(self): + sub_class = self.sub_array + x = np.arange(30).view(sub_class) + assert_raises(TypeError, np.sum, x, keepdims=True) + + if __name__ == "__main__": run_module_suite() diff --git a/numpy/lib/nanfunctions.py b/numpy/lib/nanfunctions.py index 8fe7afd46fb5..56f0010afedb 100644 --- a/numpy/lib/nanfunctions.py +++ b/numpy/lib/nanfunctions.py @@ -23,6 +23,7 @@ import numpy as np from numpy.lib.function_base import _ureduce as _ureduce + __all__ = [ 'nansum', 'nanmax', 'nanmin', 'nanargmax', 'nanargmin', 'nanmean', 'nanmedian', 'nanpercentile', 'nanvar', 'nanstd', 'nanprod', @@ -141,7 +142,7 @@ def _divide_by_count(a, b, out=None): return np.divide(a, b, out=out, casting='unsafe') -def nanmin(a, axis=None, out=None, keepdims=False): +def nanmin(a, axis=None, out=None, keepdims=np._NoValue): """ Return minimum of an array or minimum along an axis, ignoring any NaNs. When all-NaN slices are encountered a ``RuntimeWarning`` is raised and @@ -163,9 +164,14 @@ def nanmin(a, axis=None, out=None, keepdims=False): .. versionadded:: 1.8.0 keepdims : bool, optional - If this is set to True, the axes which are reduced are left in the - result as dimensions with size one. With this option, the result - will broadcast correctly against the original `a`. + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `a`. + + If the value is anything but the default, then + `keepdims` will be passed through to the `min` method + of sub-classes of `ndarray`. If the sub-classes methods + does not implement `keepdims` any exceptions will be raised. .. versionadded:: 1.8.0 @@ -220,27 +226,30 @@ def nanmin(a, axis=None, out=None, keepdims=False): -inf """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims if not isinstance(a, np.ndarray) or type(a) is np.ndarray: # Fast, but not safe for subclasses of ndarray - res = np.fmin.reduce(a, axis=axis, out=out, keepdims=keepdims) + res = np.fmin.reduce(a, axis=axis, out=out, **kwargs) if np.isnan(res).any(): warnings.warn("All-NaN axis encountered", RuntimeWarning) else: # Slow, but safe for subclasses of ndarray a, mask = _replace_nan(a, +np.inf) - res = np.amin(a, axis=axis, out=out, keepdims=keepdims) + res = np.amin(a, axis=axis, out=out, **kwargs) if mask is None: return res # Check for all-NaN axis - mask = np.all(mask, axis=axis, keepdims=keepdims) + mask = np.all(mask, axis=axis, **kwargs) if np.any(mask): res = _copyto(res, np.nan, mask) warnings.warn("All-NaN axis encountered", RuntimeWarning) return res -def nanmax(a, axis=None, out=None, keepdims=False): +def nanmax(a, axis=None, out=None, keepdims=np._NoValue): """ Return the maximum of an array or maximum along an axis, ignoring any NaNs. When all-NaN slices are encountered a ``RuntimeWarning`` is @@ -262,9 +271,14 @@ def nanmax(a, axis=None, out=None, keepdims=False): .. versionadded:: 1.8.0 keepdims : bool, optional - If this is set to True, the axes which are reduced are left in the - result as dimensions with size one. With this option, the result - will broadcast correctly against the original `a`. + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `a`. + + If the value is anything but the default, then + `keepdims` will be passed through to the `max` method + of sub-classes of `ndarray`. If the sub-classes methods + does not implement `keepdims` any exceptions will be raised. .. versionadded:: 1.8.0 @@ -319,20 +333,23 @@ def nanmax(a, axis=None, out=None, keepdims=False): inf """ + kwargs = {} + if keepdims is not np._NoValue: + kwargs['keepdims'] = keepdims if not isinstance(a, np.ndarray) or type(a) is np.ndarray: # Fast, but not safe for subclasses of ndarray - res = np.fmax.reduce(a, axis=axis, out=out, keepdims=keepdims) + res = np.fmax.reduce(a, axis=axis, out=out, **kwargs) if np.isnan(res).any(): warnings.warn("All-NaN slice encountered", RuntimeWarning) else: # Slow, but safe for subclasses of ndarray a, mask = _replace_nan(a, -np.inf) - res = np.amax(a, axis=axis, out=out, keepdims=keepdims) + res = np.amax(a, axis=axis, out=out, **kwargs) if mask is None: return res # Check for all-NaN axis - mask = np.all(mask, axis=axis, keepdims=keepdims) + mask = np.all(mask, axis=axis, **kwargs) if np.any(mask): res = _copyto(res, np.nan, mask) warnings.warn("All-NaN axis encountered", RuntimeWarning) @@ -428,7 +445,7 @@ def nanargmax(a, axis=None): return res -def nansum(a, axis=None, dtype=None, out=None, keepdims=0): +def nansum(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): """ Return the sum of array elements over a given axis treating Not a Numbers (NaNs) as zero. @@ -462,9 +479,15 @@ def nansum(a, axis=None, dtype=None, out=None, keepdims=0): .. versionadded:: 1.8.0 keepdims : bool, optional - If True, the axes which are reduced are left in the result as - dimensions with size one. With this option, the result will - broadcast correctly against the original `arr`. + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `a`. + + + If the value is anything but the default, then + `keepdims` will be passed through to the `mean` or `sum` methods + of sub-classes of `ndarray`. If the sub-classes methods + does not implement `keepdims` any exceptions will be raised. .. versionadded:: 1.8.0 @@ -513,7 +536,7 @@ def nansum(a, axis=None, dtype=None, out=None, keepdims=0): return np.sum(a, axis=axis, dtype=dtype, out=out, keepdims=keepdims) -def nanprod(a, axis=None, dtype=None, out=None, keepdims=0): +def nanprod(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): """ Return the product of array elements over a given axis treating Not a Numbers (NaNs) as zero. @@ -583,7 +606,7 @@ def nanprod(a, axis=None, dtype=None, out=None, keepdims=0): return np.prod(a, axis=axis, dtype=dtype, out=out, keepdims=keepdims) -def nanmean(a, axis=None, dtype=None, out=None, keepdims=False): +def nanmean(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): """ Compute the arithmetic mean along the specified axis, ignoring NaNs. @@ -613,9 +636,14 @@ def nanmean(a, axis=None, dtype=None, out=None, keepdims=False): expected output, but the type will be cast if necessary. See `doc.ufuncs` for details. keepdims : bool, optional - If this is set to True, the axes which are reduced are left in the - result as dimensions with size one. With this option, the result - will broadcast correctly against the original `arr`. + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `a`. + + If the value is anything but the default, then + `keepdims` will be passed through to the `mean` or `sum` methods + of sub-classes of `ndarray`. If the sub-classes methods + does not implement `keepdims` any exceptions will be raised. Returns ------- @@ -727,6 +755,7 @@ def _nanmedian(a, axis=None, out=None, overwrite_input=False): out[...] = result return result + def _nanmedian_small(a, axis=None, out=None, overwrite_input=False): """ sort + indexing median, faster for small medians along multiple @@ -743,7 +772,8 @@ def _nanmedian_small(a, axis=None, out=None, overwrite_input=False): return out return m.filled(np.nan) -def nanmedian(a, axis=None, out=None, overwrite_input=False, keepdims=False): + +def nanmedian(a, axis=None, out=None, overwrite_input=False, keepdims=np._NoValue): """ Compute the median along the specified axis, while ignoring NaNs. @@ -772,9 +802,15 @@ def nanmedian(a, axis=None, out=None, overwrite_input=False, keepdims=False): False. If `overwrite_input` is ``True`` and `a` is not already an `ndarray`, an error will be raised. keepdims : bool, optional - If this is set to True, the axes which are reduced are left in - the result as dimensions with size one. With this option, the - result will broadcast correctly against the original `arr`. + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `a`. + + If this is anything but the default value it will be passed + through (in the special case of an empty array) to the + `mean` function of the underlying array. If the array is + a sub-class and `mean` does not have the kwarg `keepdims` this + will raise a RuntimeError. Returns ------- @@ -829,14 +865,14 @@ def nanmedian(a, axis=None, out=None, overwrite_input=False, keepdims=False): r, k = _ureduce(a, func=_nanmedian, axis=axis, out=out, overwrite_input=overwrite_input) - if keepdims: + if keepdims and keepdims is not np._NoValue: return r.reshape(k) else: return r def nanpercentile(a, q, axis=None, out=None, overwrite_input=False, - interpolation='linear', keepdims=False): + interpolation='linear', keepdims=np._NoValue): """ Compute the qth percentile of the data along the specified axis, while ignoring nan values. @@ -883,9 +919,21 @@ def nanpercentile(a, q, axis=None, out=None, overwrite_input=False, * nearest: ``i`` or ``j``, whichever is nearest. * midpoint: ``(i + j) / 2``. keepdims : bool, optional +<<<<<<< 35b5f5be1ffffada84c8be207e7b8b196a58f786 If this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, the result will broadcast correctly against the original array `a`. +======= + If this is set to True, the axes which are reduced are left + in the result as dimensions with size one. With this option, + the result will broadcast correctly against the original `a`. + + If this is anything but the default value it will be passed + through (in the special case of an empty array) to the + `mean` function of the underlying array. If the array is + a sub-class and `mean` does not have the kwarg `keepdims` this + will raise a RuntimeError. +>>>>>>> BUG: many functions silently drop `keepdims` kwarg Returns ------- @@ -893,7 +941,7 @@ def nanpercentile(a, q, axis=None, out=None, overwrite_input=False, If `q` is a single percentile and `axis=None`, then the result is a scalar. If multiple percentiles are given, first axis of the result corresponds to the percentiles. The other axes are - the axes that remain after the reduction of `a`. If the input + the axes that remain after the reduction of `a`. If the input contains integers or floats smaller than ``float64``, the output data-type is ``float64``. Otherwise, the output data-type is the same as that of the input. If `out` is specified, that array is @@ -954,7 +1002,7 @@ def nanpercentile(a, q, axis=None, out=None, overwrite_input=False, r, k = _ureduce(a, func=_nanpercentile, q=q, axis=axis, out=out, overwrite_input=overwrite_input, interpolation=interpolation) - if keepdims: + if keepdims and keepdims is not np._NoValue: if q.ndim == 0: return r.reshape(k) else: @@ -964,7 +1012,7 @@ def nanpercentile(a, q, axis=None, out=None, overwrite_input=False, def _nanpercentile(a, q, axis=None, out=None, overwrite_input=False, - interpolation='linear', keepdims=False): + interpolation='linear'): """ Private function that doesn't support extended axis or keepdims. These methods are extended to this function using _ureduce @@ -981,7 +1029,7 @@ def _nanpercentile(a, q, axis=None, out=None, overwrite_input=False, # Move that axis to the beginning to match percentile's # convention. if q.ndim != 0: - result = np.rollaxis(result, axis) + result = np.rollaxis(result, axis) if out is not None: out[...] = result @@ -1020,7 +1068,7 @@ def _nanpercentile1d(arr1d, q, overwrite_input=False, interpolation='linear'): interpolation=interpolation) -def nanvar(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): +def nanvar(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): """ Compute the variance along the specified axis, while ignoring NaNs. @@ -1056,7 +1104,8 @@ def nanvar(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): keepdims : bool, optional If this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, - the result will broadcast correctly against the original `arr`. + the result will broadcast correctly against the original `a`. + Returns ------- @@ -1095,6 +1144,9 @@ def nanvar(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): below). Specifying a higher-accuracy accumulator using the ``dtype`` keyword can alleviate this issue. + For this function to work on sub-classes of ndarray, they must define + `sum` with the kwarg `keepdims` + Examples -------- >>> a = np.array([[1, np.nan], [3, 4]]) @@ -1122,8 +1174,17 @@ def nanvar(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): warnings.simplefilter('ignore') # Compute mean - cnt = np.sum(~mask, axis=axis, dtype=np.intp, keepdims=True) - avg = np.sum(arr, axis=axis, dtype=dtype, keepdims=True) + if type(arr) is np.matrix: + _keepdims = np._NoValue + else: + _keepdims = True + # we need to special case matrix for reverse compatibility + # in order for this to work, these sums need to be called with + # keepdims=True, however matrix now raises an error in this case, but + # the reason that it drops the keepdims kwarg is to force keepdims=True + # so this used to work by serendipity. + cnt = np.sum(~mask, axis=axis, dtype=np.intp, keepdims=_keepdims) + avg = np.sum(arr, axis=axis, dtype=dtype, keepdims=_keepdims) avg = _divide_by_count(avg, cnt) # Compute squared deviation from mean. @@ -1151,7 +1212,7 @@ def nanvar(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): return var -def nanstd(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): +def nanstd(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): """ Compute the standard deviation along the specified axis, while ignoring NaNs. @@ -1185,10 +1246,16 @@ def nanstd(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False): Means Delta Degrees of Freedom. The divisor used in calculations is ``N - ddof``, where ``N`` represents the number of non-NaN elements. By default `ddof` is zero. + keepdims : bool, optional If this is set to True, the axes which are reduced are left in the result as dimensions with size one. With this option, - the result will broadcast correctly against the original `arr`. + the result will broadcast correctly against the original `a`. + + If this value is anything but the default it is passed through + as-is to the relevant functions of the sub-classes. If these + functions do not have a `keepdims` kwarg, a RuntimeError will + be raised. Returns ------- From 010d17ee8167196ea90c24c57b4ea34badfc11ae Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 5 Feb 2016 12:09:29 -0500 Subject: [PATCH 2/5] STY: pep8 only --- numpy/core/fromnumeric.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index bb89adbe1761..1c97b7c4ff1c 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -3059,8 +3059,7 @@ def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): **kwargs) -def var(a, axis=None, dtype=None, out=None, ddof=0, - keepdims=np._NoValue): +def var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): """ Compute the variance along the specified axis. @@ -3174,4 +3173,4 @@ def var(a, axis=None, dtype=None, out=None, ddof=0, pass return _methods._var(a, axis=axis, dtype=dtype, out=out, ddof=ddof, - **kwargs) + **kwargs) From 08fb5807118423e314f324b9bcafdbaab9316f4d Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 5 Feb 2016 12:10:26 -0500 Subject: [PATCH 3/5] MNT: move std, var, mean calls out of try block Move the calls to user-provided versions of std, var, and mean on non mu.ndarray objects out of the `try` block so that numpy will not mask AttributeError raised during the execution of the function rather than due to the object not having the required method. --- numpy/core/fromnumeric.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index 1c97b7c4ff1c..00b2dbae0eb4 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -2934,12 +2934,13 @@ def mean(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): if type(a) is not mu.ndarray: try: mean = a.mean - return mean(axis=axis, dtype=dtype, out=out, **kwargs) except AttributeError: pass + else: + return mean(axis=axis, dtype=dtype, out=out, **kwargs) return _methods._mean(a, axis=axis, dtype=dtype, - out=out, **kwargs) + out=out, **kwargs) def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): @@ -3051,12 +3052,13 @@ def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): if type(a) is not mu.ndarray: try: std = a.std - return std(axis=axis, dtype=dtype, out=out, ddof=ddof, **kwargs) except AttributeError: pass + else: + return std(axis=axis, dtype=dtype, out=out, ddof=ddof, **kwargs) return _methods._std(a, axis=axis, dtype=dtype, out=out, ddof=ddof, - **kwargs) + **kwargs) def var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): @@ -3168,9 +3170,11 @@ def var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=np._NoValue): if type(a) is not mu.ndarray: try: var = a.var - return var(axis=axis, dtype=dtype, out=out, ddof=ddof, **kwargs) + except AttributeError: pass + else: + return var(axis=axis, dtype=dtype, out=out, ddof=ddof, **kwargs) return _methods._var(a, axis=axis, dtype=dtype, out=out, ddof=ddof, **kwargs) From f473fe4418a98e2e0edb357f23b74964b09d6a7a Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 5 Feb 2016 12:15:50 -0500 Subject: [PATCH 4/5] MNT: reduce number of return statements In sum, amax, amin, and prod simplify the logic to remove an identical return statement / call to `_methods._xxx`. This removes several elif/else pairs and reduces the number of exit points from the functions but makes the code path a bit more complicated to trace. --- numpy/core/fromnumeric.py | 50 +++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/numpy/core/fromnumeric.py b/numpy/core/fromnumeric.py index 00b2dbae0eb4..52a15e30d7d1 100644 --- a/numpy/core/fromnumeric.py +++ b/numpy/core/fromnumeric.py @@ -1836,16 +1836,15 @@ def sum(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): out[...] = res return out return res - elif type(a) is not mu.ndarray: + if type(a) is not mu.ndarray: try: sum = a.sum except AttributeError: - return _methods._sum(a, axis=axis, dtype=dtype, - out=out, **kwargs) - return sum(axis=axis, dtype=dtype, out=out, **kwargs) - else: - return _methods._sum(a, axis=axis, dtype=dtype, - out=out, **kwargs) + pass + else: + return sum(axis=axis, dtype=dtype, out=out, **kwargs) + return _methods._sum(a, axis=axis, dtype=dtype, + out=out, **kwargs) def product(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): @@ -2285,16 +2284,17 @@ def amax(a, axis=None, out=None, keepdims=np._NoValue): kwargs = {} if keepdims is not np._NoValue: kwargs['keepdims'] = keepdims + if type(a) is not mu.ndarray: try: amax = a.max except AttributeError: - return _methods._amax(a, axis=axis, - out=out, **kwargs) - return amax(axis=axis, out=out, **kwargs) - else: - return _methods._amax(a, axis=axis, - out=out, **kwargs) + pass + else: + return amax(axis=axis, out=out, **kwargs) + + return _methods._amax(a, axis=axis, + out=out, **kwargs) def amin(a, axis=None, out=None, keepdims=np._NoValue): @@ -2389,12 +2389,12 @@ def amin(a, axis=None, out=None, keepdims=np._NoValue): try: amin = a.min except AttributeError: - return _methods._amin(a, axis=axis, - out=out, **kwargs) - return amin(axis=axis, out=out, **kwargs) - else: - return _methods._amin(a, axis=axis, - out=out, **kwargs) + pass + else: + return amin(axis=axis, out=out, **kwargs) + + return _methods._amin(a, axis=axis, + out=out, **kwargs) def alen(a): @@ -2535,12 +2535,12 @@ def prod(a, axis=None, dtype=None, out=None, keepdims=np._NoValue): try: prod = a.prod except AttributeError: - return _methods._prod(a, axis=axis, dtype=dtype, - out=out, **kwargs) - return prod(axis=axis, dtype=dtype, out=out, **kwargs) - else: - return _methods._prod(a, axis=axis, dtype=dtype, - out=out, **kwargs) + pass + else: + return prod(axis=axis, dtype=dtype, out=out, **kwargs) + + return _methods._prod(a, axis=axis, dtype=dtype, + out=out, **kwargs) def cumprod(a, axis=None, dtype=None, out=None): From e1621a9d2a5acec84028a0dec2cea5c40d5a4067 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Fri, 5 Feb 2016 13:37:04 -0500 Subject: [PATCH 5/5] DOC: add release notes --- doc/release/1.11.0-notes.rst | 1 + doc/release/1.12.0-notes.rst | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/doc/release/1.11.0-notes.rst b/doc/release/1.11.0-notes.rst index e95225a7c0d5..aa11cdf0789f 100644 --- a/doc/release/1.11.0-notes.rst +++ b/doc/release/1.11.0-notes.rst @@ -142,6 +142,7 @@ FutureWarning to changed behavior due to a bug, sometimes no warning was raised and the dimensions were already preserved. + C API ~~~~~ diff --git a/doc/release/1.12.0-notes.rst b/doc/release/1.12.0-notes.rst index d093e0542c86..ce606e5b5366 100644 --- a/doc/release/1.12.0-notes.rst +++ b/doc/release/1.12.0-notes.rst @@ -41,6 +41,30 @@ XXX the two coincide. Previous behavior of 'lower' + 0.5 is fixed. +``keepdims`` kwarg is passed through to user-class methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +numpy functions that take a ``keepdims`` kwarg now pass the value +through to the corresponding methods on ndarray sub-classes. Previously the +``keepdims`` keyword would be silently dropped. These functions now have +the following behavior: + +1. If user does not provide ``keepdims``, no keyword is passed to the underlying + method. +2. Any user-provided value of ``keepdims`` is passed through as a keyword + argument to the method. + +This will raise in the case where the method does not support a +``keepdims`` kwarg and the user explicitly passes in ``keepdims``. + + +The following functions are changed: ``sum``, ``product``, +``sometrue``, ``alltrue``, ``any``, ``all``, ``amax``, ``amin``, +``prod``, ``mean``, ``std``, ``var``, ``nanmin``, ``nanmax``, +``nansum``, ``nanprod``, ``nanmean``, ``nanmedian``, ``nanvar``, +``nanstd`` + + DeprecationWarning to error ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -73,4 +97,3 @@ Changes Deprecations ============ -