10000 ENH: add np.nanprod by shoyer · Pull Request #5418 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

ENH: add np.nanprod #5418

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 5, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to loa 10000 d files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/release/1.10.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Highlights
* Addition of *np.linalg.multi_dot*: compute the dot product of two or more
arrays in a single function call, while automatically selecting the fastest
evaluation order.
* Addition of `nanprod` to the set of nanfunctions.


Dropped Support
Expand Down
77 changes: 76 additions & 1 deletion numpy/lib/nanfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
- `nanargmin` -- index of minimum non-NaN value
- `nanargmax` -- index of maximum non-NaN value
- `nansum` -- sum of non-NaN values
- `nanprod` -- product of non-NaN values
- `nanmean` -- mean of non-NaN values
- `nanvar` -- variance of non-NaN values
- `nanstd` -- standard deviation of non-NaN values
- `nanmedian` -- median of non-NaN values
- `nanpercentile` -- qth percentile of non-NaN values

"""
from __future__ import division, absolute_import, print_function
Expand All @@ -22,7 +25,7 @@

__all__ = [
'nansum', 'nanmax', 'nanmin', 'nanargmax', 'nanargmin', 'nanmean',
'nanmedian', 'nanpercentile', 'nanvar', 'nanstd'
'nanmedian', 'nanpercentile', 'nanvar', 'nanstd', 'nanprod',
]


Expand Down Expand Up @@ -510,6 +513,76 @@ 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):
"""
Return the product of array elements over a given axis treating Not a
Numbers (NaNs) as zero.

One is returned for slices that are all-NaN or empty.

.. versionadded:: 1.10.0

Parameters
----------
a : array_like
Array containing numbers whose sum is desired. If `a` is not an
array, a conversion is attempted.
axis : int, optional
Axis along which the product is computed. The default is to compute
the product of the flattened array.
dtype : data-type, optional
The type of the returned array and of the accumulator in which the
elements are summed. By default, the dtype of `a` is used. An
exception is when `a` has an integer type with less precision than
the platform (u)intp. In that case, the default will be either
(u)int32 or (u)int64 depending on whether the platform is 32 or 64
bits. For inexact inputs, dtype must be inexact.
out : ndarray, optional
Alternate output array in which to place the result. The default
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. The casting of NaN to integer can yield
unexpected results.
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`.

Returns
-------
y : ndarray or numpy scalar

See Also
--------
numpy.prod : Product across array propagating NaNs.
isnan : Show which elements are NaN.

Notes
-----
Numpy integer arithmetic is modular. If the size of a product exceeds
the size of an integer accumulator, its value will wrap around and the
result will be incorrect. Specifying ``dtype=double`` can alleviate
that problem.

Examples
--------
>>> np.nanprod(1)
1
>>> np.nanprod([1])
1
>>> np.nanprod([1, np.nan])
1.0
>>> a = np.array([[1, 2], [3, np.nan]])
>>> np.nanprod(a)
6.0
>>> np.nanprod(a, axis=0)
array([ 3., 2.])

"""
a, mask = _replace_nan(a, 1)
return np.prod(a, axis=axis, dtype=dtype, out=out, keepdims=keepdims)


def nanmean(a, axis=None, dtype=None, out=None, keepdims=False):
"""
Compute the arithmetic mean along the specified axis, ignoring NaNs.
Expand Down Expand Up @@ -770,6 +843,8 @@ def nanpercentile(a, q, axis=None, out=None, overwrite_input=False,

Returns the qth percentile of the array elements.

.. versionadded:: 1.9.0

Parameters
----------
a : array_like
Expand Down
229 changes: 81 additions & 148 deletions numpy/lib/tests/test_nanfunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ def test_nansum(self):
for mat in self.integer_arrays():
assert_equal(np.nansum(mat), tgt)

def test_nanprod(self):
tgt = np.prod(self.mat)
for mat in self.integer_arrays():
assert_equal(np.nanprod(mat), tgt)

def test_nanmean(self):
tgt = np.mean(self.mat)
for mat in self.integer_arrays():
Expand All @@ -260,137 +265,14 @@ def test_nanstd(self):
assert_equal(np.nanstd(mat, ddof=1), tgt)


class TestNanFunctions_Sum(TestCase):

def test_mutation(self):
# Check that passed array is not modified.
ndat = _ndat.copy()
np.nansum(ndat)
assert_equal(ndat, _ndat)

def test_keepdims(self):
mat = np.eye(3)
for axis in [None, 0, 1]:
tgt = np.sum(mat, axis=axis, keepdims=True)
res = np.nansum(mat, axis=axis, keepdims=True)
assert_(res.ndim == tgt.ndim)

def test_out(self):
mat = np.eye(3)
resout = np.zeros(3)
tgt = np.sum(mat, axis=1)
res = np.nansum(mat, axis=1, out=resout)
assert_almost_equal(res, resout)
assert_almost_equal(res, tgt)

def test_dtype_from_dtype(self):
mat = np.eye(3)
codes = 'efdgFDG'
for c in codes:
tgt = np.sum(mat, dtype=np.dtype(c), axis=1).dtype.type
res = np.nansum(mat, dtype=np.dtype(c), axis=1).dtype.type
assert_(res is tgt)
# scalar case
tgt = np.sum(mat, dtype=np.dtype(c), axis=None).dtype.type
res = np.nansum(mat, dtype=np.dtype(c), axis=None).dtype.type
assert_(res is tgt)

def test_dtype_from_char(self):
mat = np.eye(3)
codes = 'efdgFDG'
for c in codes:
tgt = np.sum(mat, dtype=c, axis=1).dtype.type
res = np.nansum(mat, dtype=c, axis=1).dtype.type
assert_(res is tgt)
# scalar case
tgt = np.sum(mat, dtype=c, axis=None).dtype.type
res = np.nansum(mat, dtype=c, axis=None).dtype.type
assert_(res is tgt)

def test_dtype_from_input(self):
codes = 'efdgFDG'
for c in codes:
mat = np.eye(3, dtype=c)
tgt = np.sum(mat, axis=1).dtype.type
res = np.nansum(mat, axis=1).dtype.type
assert_(res is tgt)
# scalar case
tgt = np.sum(mat, axis=None).dtype.type
res = np.nansum(mat, axis=None).dtype.type
assert_(res is tgt)

def test_result_values(self):
tgt = [np.sum(d) for d in _rdat]
res = np.nansum(_ndat, axis=1)
assert_almost_equal(res, tgt)

def test_allnans(self):
# Check for FutureWarning
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
res = np.nansum([np.nan]*3, axis=None)
assert_(res == 0, 'result is not 0')
assert_(len(w) == 0, 'warning raised')
# Check scalar
res = np.nansum(np.nan)
assert_(res == 0, 'result is not 0')
assert_(len(w) == 0, 'warning raised')
# Check there is no warning for not all-nan
np.nansum([0]*3, axis=None)
assert_(len(w) == 0, 'unwanted warning raised')

def test_empty(self):
mat = np.zeros((0, 3))
tgt = [0]*3
res = np.nansum(mat, axis=0)
assert_equal(res, tgt)
tgt = []
res = np.nansum(mat, axis=1)
assert_equal(res, tgt)
tgt = 0
res = np.nansum(mat, axis=None)
assert_equal(res, tgt)

def test_scalar(self):
assert_(np.nansum(0.) == 0.)

def test_matrices(self):
# Check that it works and that type and
# shape are preserved
mat = np.matrix(np.eye(3))
res = np.nansum(mat, axis=0)
assert_(isinstance(res, np.matrix))
assert_(res.shape == (1, 3))
res = np.nansum(mat, axis=1)
assert_(isinstance(res, np.matrix))
assert_(res.shape == (3, 1))
res = np.nansum(mat)
assert_(np.isscalar(res))


class TestNanFunctions_MeanVarStd(TestCase):

nanfuncs = [np.nanmean, np.nanvar, np.nanstd]
stdfuncs = [np.mean, np.var, np.std]

class SharedNanFunctionsTestsMixin(object):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting approach.

def test_mutation(self):
# Check that passed array is not modified.
ndat = _ndat.copy()
for f in self.nanfuncs:
f(ndat)
assert_equal(ndat, _ndat)

def test_dtype_error(self):
for f in self.nanfuncs:
for dtype in [np.bool_, np.int_, np.object]:
assert_raises(TypeError, f, _ndat, axis=1, dtype=np.int)

def test_out_dtype_error(self):
for f in self.nanfuncs:
for dtype in [np.bool_, np.int_, np.object]:
out = np.empty(_ndat.shape[0], dtype=dtype)
assert_raises(TypeError, f, _ndat, axis=1, out=out)

def test_keepdims(self):
mat = np.eye(3)
for nf, rf in zip(self.nanfuncs, self.stdfuncs):
Expand Down Expand Up @@ -447,6 +329,81 @@ def test_dtype_from_input(self):
res = nf(mat, axis=None).dtype.type
assert_(res is tgt)

def test_result_values(self):
for nf, rf in zip(self.nanfuncs, self.stdfuncs):
tgt = [rf(d) for d in _rdat]
res = nf(_ndat, axis=1)
assert_almost_equal(res, tgt)

def test_scalar(self):
for f in self.nanfuncs:
assert_(f(0.) == 0.)

def test_matrices(self):
# Check that it works and that type and
# shape are preserved
mat = np.matrix(np.eye(3))
for f in self.nanfuncs:
res = f(mat, axis=0)
assert_(isinstance(res, np.matrix))
assert_(res.shape == (1, 3))
res = f(mat, axis=1)
assert_(isinstance(res, np.matrix))
assert_(res.shape == (3, 1))
res = f(mat)
assert_(np.isscalar(res))


class TestNanFunctions_SumProd(TestCase, SharedNanFunctionsTestsMixin):

nanfuncs = [np.nansum, np.nanprod]
stdfuncs = [np.sum, np.prod]

def test_allnans(self):
# Check for FutureWarning
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always')
res = np.nansum([np.nan]*3, axis=None)
assert_(res == 0, 'result is not 0')
assert_(len(w) == 0, 'warning raised')
# Check scalar
res = np.nansum(np.nan)
assert_(res == 0, 'result is not 0')
assert_(len(w) == 0, 'warning raised')
# Check there is no warning for not all-nan
np.nansum([0]*3, axis=None)
assert_(len(w) == 0, 'unwanted warning raised')

def test_empty(self):
for f, tgt_value in zip([np.nansum, np.nanprod], [0, 1]):
mat = np.zeros((0, 3))
tgt = [tgt_value]*3
res = f(mat, axis=0)
assert_equal(res, tgt)
tgt = []
res = f(mat, axis=1)
assert_equal(res, tgt)
tgt = tgt_value
res = f(mat, axis=None)
assert_equal(res, tgt)


class TestNanFunctions_MeanVarStd(TestCase, SharedNanFunctionsTestsMixin):

nanfuncs = [np.nanmean, np.nanvar, np.nanstd]
stdfuncs = [np.mean, np.var, np.std]

def test_dtype_error(self):
for f in self.nanfuncs:
for dtype in [np.bool_, np.int_, np.object]:
assert_raises(TypeError, f, _ndat, axis=1, dtype=np.int)

def test_out_dtype_error(self):
for f in self.nanfuncs:
for dtype in [np.bool_, np.int_, np.object]:
out = np.empty(_ndat.shape[0], dtype=dtype)
assert_raises(TypeError, f, _ndat, axis=1, out=out)

def test_ddof(self):
nanfuncs = [np.nanvar, np.nanstd]
stdfuncs = [np.var, np.std]
Expand All @@ -473,12 +430,6 @@ def test_ddof_too_big(self):
else:
assert_(len(w) == 0)

def test_result_values(self):
for nf, rf in zip(self.nanfuncs, self.stdfuncs):
tgt = [rf(d) for d in _rdat]
res = nf(_ndat, axis=1)
assert_almost_equal(res, tgt)

def test_allnans(self):
mat = np.array([np.nan]*9).reshape(3, 3)
for f in self.nanfuncs:
Expand Down Expand Up @@ -508,24 +459,6 @@ def test_empty(self):
assert_equal(f(mat, axis=axis), np.zeros([]))
assert_(len(w) == 0)

def test_scalar(self):
for f in self.nanfuncs:
assert_(f(0.) == 0.)

def test_matrices(self):
# Check that it works and that type and
# shape are preserved
mat = np.matrix(np.eye(3))
for f in self.nanfuncs:
res = f(mat, axis=0)
assert_(isinstance(res, np.matrix))
assert_(res.shape == (1, 3))
res = f(mat, axis=1)
assert_(isinstance(res, np.matrix))
assert_(res.shape == (3, 1))
res = f(mat)
assert_(np.isscalar(res))


class TestNanFunctions_Median(TestCase):

Expand Down
0