8000 Merge pull request #5418 from shoyer/nanprod · numpy/numpy@c6b8109 · GitHub
[go: up one dir, main page]

Skip to content

Commit c6b8109

Browse files
committed
Merge pull request #5418 from shoyer/nanprod
ENH: add np.nanprod
2 parents c39a56a + 1b2d2e9 commit c6b8109

File tree

3 files changed

+158
-149
lines changed

3 files changed

+158
-149
lines changed

doc/release/1.10.0-notes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Highlights
1111
* Addition of *np.linalg.multi_dot*: compute the dot product of two or more
1212
arrays in a single function call, while automatically selecting the fastest
1313
evaluation order.
14+
* Addition of `nanprod` to the set of nanfunctions.
1415

1516

1617
Dropped Support

numpy/lib/nanfunctions.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
- `nanargmin` -- index of minimum non-NaN value
1010
- `nanargmax` -- index of maximum non-NaN value
1111
- `nansum` -- sum of non-NaN values
12+
- `nanprod` -- product of non-NaN values
1213
- `nanmean` -- mean of non-NaN values
1314
- `nanvar` -- variance of non-NaN values
1415
- `nanstd` -- standard deviation of non-NaN values
16+
- `nanmedian` -- median of non-NaN values
17+
- `nanpercentile` -- qth percentile of non-NaN values
1518
1619
"""
1720
from __future__ import division, absolute_import, print_function
@@ -22,7 +25,7 @@
2225

2326
__all__ = [
2427
'nansum', 'nanmax', 'nanmin', 'nanargmax', 'nanargmin', 'nanmean',
25-
'nanmedian', 'nanpercentile', 'nanvar', 'nanstd'
28+
'nanmedian', 'nanpercentile', 'nanvar', 'nanstd', 'nanprod',
2629
]
2730

2831

@@ -510,6 +513,76 @@ def nansum(a, axis=None, dtype=None, out=None, keepdims=0):
510513
return np.sum(a, axis=axis, dtype=dtype, out=out, keepdims=keepdims)
511514

512515

516+
def nanprod(a, axis=None, dtype=None, out=None, keepdims=0):
517+
"""
518+
Return the product of array elements over a given axis treating Not a
519+
Numbers (NaNs) as zero.
520+
521+
One is returned for slices that are all-NaN or empty.
522+
523+
.. versionadded:: 1.10.0
524+
525+
Parameters
526+
----------
527+
a : array_like
528+
Array containing numbers whose sum is desired. If `a` is not an
529+
array, a conversion is attempted.
530+
axis : int, optional
531+
Axis along which the product is computed. The default is to compute
532+
the product of the flattened array.
533+
dtype : data-type, optional
534+
The type of the returned array and of the accumulator in which the
535+
elements are summed. By default, the dtype of `a` is used. An
536+
exception is when `a` has an integer type with less precision than
537+
the platform (u)intp. In that case, the default will be either
538+
(u)int32 or (u)int64 depending on whether the platform is 32 or 64
539+
bits. For inexact inputs, dtype must be inexact.
540+
out : ndarray, optional
541+
Alternate output array in which to place the result. The default
542+
is ``None``. If provided, it must have the same shape as the
543+
expected output, but the type will be cast if necessary. See
544+
`doc.ufuncs` for details. The casting of NaN to integer can yield
545+
unexpected results.
546+
keepdims : bool, optional
547+
If True, the axes which are reduced are left in the result as
548+
dimensions with size one. With this option, the result will
549+
broadcast correctly against the original `arr`.
550+
551+
Returns
552+
-------
553+
y : ndarray or numpy scalar
554+
555+
See Also
556+
--------
557+
numpy.prod : Product across array propagating NaNs.
558+
isnan : Show which elements are NaN.
559+
560+
Notes
561+
-----
562+
Numpy integer arithmetic is modular. If the size of a product exceeds
563+
the size of an integer accumulator, its value will wrap around and the
564+
result will be incorrect. Specifying ``dtype=double`` can alleviate
565+
that problem.
566+
567+
Examples
568+
--------
569+
>>> np.nanprod(1)
570+
1
571+
>>> np.nanprod([1])
572+
1
573+
>>> np.nanprod([1, np.nan])
574+
1.0
575+
>>> a = np.array([[1, 2], [3, np.nan]])
576+
>>> np.nanprod(a)
577+
6.0
578+
>>> np.nanprod(a, axis=0)
579+
array([ 3., 2.])
580+
581+
"""
582+
a, mask = _replace_nan(a, 1)
583+
return np.prod(a, axis=axis, dtype=dtype, out=out, keepdims=keepdims)
584+
585+
513586
def nanmean(a, axis=None, dtype=None, out=None, keepdims=False):
514587
"""
515588
Compute the arithmetic mean along the specified axis, ignoring NaNs.
@@ -770,6 +843,8 @@ def nanpercentile(a, q, axis=None, out=None, overwrite_input=False,
770843
771844
Returns the qth percentile of the array elements.
772845
846+
.. versionadded:: 1.9.0
847+
773848
Parameters
774849
----------
775850
a : array_like

numpy/lib/tests/test_nanfunctions.py

Lines changed: 81 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,11 @@ def test_nansum(self):
236236
for mat in self.integer_arrays():
237237
assert_equal(np.nansum(mat), tgt)
238238

239+
def test_nanprod(self):
240+
tgt = np.prod(self.mat)
241+
for mat in self.integer_arrays():
242+
assert_equal(np.nanprod(mat), tgt)
243+
239244
def test_nanmean(self):
240245
tgt = np.mean(self.mat)
241246
for mat in self.integer_arrays():
@@ -260,137 +265,14 @@ def test_nanstd(self):
260265
assert_equal(np.nanstd(mat, ddof=1), tgt)
261266

262267

263-
class TestNanFunctions_Sum(TestCase):
264-
265-
def test_mutation(self):
266-
# Check that passed array is not modified.
267-
ndat = _ndat.copy()
268-
np.nansum(ndat)
269-
assert_equal(ndat, _ndat)
270-
271-
def test_keepdims(self):
272-
mat = np.eye(3)
273-
for axis in [None, 0, 1]:
274-
tgt = np.sum(mat, axis=axis, keepdims=True)
275-
res = np.nansum(mat, axis=axis, keepdims=True)
276-
assert_(res.ndim == tgt.ndim)
277-
278-
def test_out(self):
279-
mat = np.eye(3)
280-
resout = np.zeros(3)
281-
tgt = np.sum(mat, axis=1)
282-
res = np.nansum(mat, axis=1, out=resout)
283-
assert_almost_equal(res, resout)
284-
assert_almost_equal(res, tgt)
285-
286-
def test_dtype_from_dtype(self):
287-
mat = np.eye(3)
288-
codes = 'efdgFDG'
289-
for c in codes:
290-
tgt = np.sum(mat, dtype=np.dtype(c), axis=1).dtype.type
291-
res = np.nansum(mat, dtype=np.dtype(c), axis=1).dtype.type
292-
assert_(res is tgt)
293-
# scalar case
294-
tgt = np.sum(mat, dtype=np.dtype(c), axis=None).dtype.type
295-
res = np.nansum(mat, dtype=np.dtype(c), axis=None).dtype.type
296-
assert_(res is tgt)
297-
298-
def test_dtype_from_char(self):
299-
mat = np.eye(3)
300-
codes = 'efdgFDG'
301-
for c in codes:
302-
tgt = np.sum(mat, dtype=c, axis=1).dtype.type
303-
res = np.nansum(mat, dtype=c, axis=1).dtype.type
304-
assert_(res is tgt)
305-
# scalar case
306-
tgt = np.sum(mat, dtype=c, axis=None).dtype.type
307-
res = np.nansum(mat, dtype=c, axis=None).dtype.type
308-
assert_(res is tgt)
309-
310-
def test_dtype_from_input(self):
311-
codes = 'efdgFDG'
312-
for c in codes:
313-
mat = np.eye(3, dtype=c)
314-
tgt = np.sum(mat, axis=1).dtype.type
315-
res = np.nansum(mat, axis=1).dtype.type
316-
assert_(res is tgt)
317-
# scalar case
318-
tgt = np.sum(mat, axis=None).dtype.type
319-
res = np.nansum(mat, axis=None).dtype.type
320-
assert_(res is tgt)
321-
322-
def test_result_values(self):
323-
tgt = [np.sum(d) for d in _rdat]
324-
res = np.nansum(_ndat, axis=1)
325-
assert_almost_equal(res, tgt)
326-
327-
def test_allnan 1241 s(self):
328-
# Check for FutureWarning
329-
with warnings.catch_warnings(record=True) as w:
330-
warnings.simplefilter('always')
331-
res = np.nansum([np.nan]*3, axis=None)
332-
assert_(res == 0, 'result is not 0')
333-
assert_(len(w) == 0, 'warning raised')
334-
# Check scalar
335-
res = np.nansum(np.nan)
336-
assert_(res == 0, 'result is not 0')
337-
assert_(len(w) == 0, 'warning raised')
338-
# Check there is no warning for not all-nan
339-
np.nansum([0]*3, axis=None)
340-
assert_(len(w) == 0, 'unwanted warning raised')
341-
342-
def test_empty(self):
343-
mat = np.zeros((0, 3))
344-
tgt = [0]*3
345-
res = np.nansum(mat, axis=0)
346-
assert_equal(res, tgt)
347-
tgt = []
348-
res = np.nansum(mat, axis=1)
349-
assert_equal(res, tgt)
350-
tgt = 0
351-
res = np.nansum(mat, axis=None)
352-
assert_equal(res, tgt)
353-
354-
def test_scalar(self):
355-
assert_(np.nansum(0.) == 0.)
356-
357-
def test_matrices(self):
358-
# Check that it works and that type and
359-
# shape are preserved
360-
mat = np.matrix(np.eye(3))
361-
res = np.nansum(mat, axis=0)
362-
assert_(isinstance(res, np.matrix))
363-
assert_(res.shape == (1, 3))
364-
res = np.nansum(mat, axis=1)
365-
assert_(isinstance(res, np.matrix))
366-
assert_(res.shape == (3, 1))
367-
res = np.nansum(mat)
368-
assert_(np.isscalar(res))
369-
370-
371-
class TestNanFunctions_MeanVarStd(TestCase):
372-
373-
nanfuncs = [np.nanmean, np.nanvar, np.nanstd]
374-
stdfuncs = [np.mean, np.var, np.std]
375-
268+
class SharedNanFunctionsTestsMixin(object):
376269
def test_mutation(self):
377270
# Check that passed array is not modified.
378271
ndat = _ndat.copy()
379272
for f in self.nanfuncs:
380273
f(ndat)
381274
assert_equal(ndat, _ndat)
382275

383-
def test_dtype_error(self):
384-
for f in self.nanfuncs:
385-
for dtype in [np.bool_, np.int_, np.object]:
386-
assert_raises(TypeError, f, _ndat, axis=1, dtype=np.int)
387-
388-
def test_out_dtype_error(self):
389-
for f in self.nanfuncs:
390-
for dtype in [np.bool_, np.int_, np.object]:
391-
out = np.empty(_ndat.shape[0], dtype=dtype)
392-
assert_raises(TypeError, f, _ndat, axis=1, out=out)
393-
394276
def test_keepdims(self):
395277
mat = np.eye(3)
396278
for nf, rf in zip(self.nanfuncs, self.stdfuncs):
@@ -447,6 +329,81 @@ def test_dtype_from_input(self):
447329
res = nf(mat, axis=None).dtype.type
448330
assert_(res is tgt)
449331

332+
def test_result_values(self):
333+
for nf, rf in zip(self.nanfuncs, self.stdfuncs):
334+
tgt = [rf(d) for d in _rdat]
335+
res = nf(_ndat, axis=1)
336+
assert_almost_equal(res, tgt)
337+
338+
def test_scalar(self):
339+
for f in self.nanfuncs:
340+
assert_(f(0.) == 0.)
341+
342+
def test_matrices(self):
343+
# Check that it works and that type and
344+
# shape are preserved
345+
mat = np.matrix(np.eye(3))
346+
for f in self.nanfuncs:
347+
res = f(mat, axis=0)
348+
assert_(isinstance(res, np.matrix))
349+
assert_(res.shape == (1, 3))
350+
res = f(mat, axis=1)
351+
assert_(isinstance(res, np.matrix))
352+
assert_(res.shape == (3, 1))
353+
res = f(mat)
354+
assert_(np.isscalar(res))
355+
356+
357+
class TestNanFunctions_SumProd(TestCase, SharedNanFunctionsTestsMixin):
358+
359+
nanfuncs = [np.nansum, np.nanprod]
360+
stdfuncs = [np.sum, np.prod]
361+
362+
def test_allnans(self):
363+
# Check for FutureWarning
364+
with warnings.catch_warnings(record=True) as w:
365+
warnings.simplefilter('always')
366+
res = np.nansum([np.nan]*3, axis=None)
367+
assert_(res == 0, 'result is not 0')
368+
assert_(len(w) == 0, 'warning raised')
369+
# Check scalar
370+
res = np.nansum(np.nan)
371+
assert_(res == 0, 'result is not 0')
372+
assert_(len(w) == 0, 'warning raised')
373+
# Check there is no warning for not all-nan
374+
np.nansum([0]*3, axis=None)
375+
assert_(len(w) == 0, 'unwanted warning raised')
376+
377+
def test_empty(self):
378+
for f, tgt_value in zip([np.nansum, np.nanprod], [0, 1]):
379+
mat = np.zeros((0, 3))
380+
tgt = [tgt_value]*3
381+
res = f(mat, axis=0)
382+
assert_equal(res, tgt)
383+
tgt = []
384+
res = f(mat, axis=1)
385+
assert_equal(res, tgt)
386+
tgt = tgt_value
387+
res = f(mat, axis=None)
388+
assert_equal(res, tgt)
389+
390+
391+
class TestNanFunctions_MeanVarStd(TestCase, SharedNanFunctionsTestsMixin):
392+
393+
nanfuncs = [np.nanmean, np.nanvar, np.nanstd]
394+
stdfuncs = [np.mean, np.var, np.std]
395+
396+
def test_dtype_error(self):
397+
for f in self.nanfuncs:
398+
for dtype in [np.bool_, np.int_, np.object]:
399+
assert_raises(TypeError, f, _ndat, axis=1, dtype=np.int)
400+
401+
def test_out_dtype_error(self):
402+
for f in self.nanfuncs:
403+
for dtype in [np.bool_, np.int_, np.object]:
404+
out = np.empty(_ndat.shape[0], dtype=dtype)
405+
assert_raises(TypeError, f, _ndat, axis=1, out=out)
406+
450407
def test_ddof(self):
451408
nanfuncs = [np.nanvar, np.nanstd]
452409
stdfuncs = [np.var, np.std]
@@ -473,12 +430,6 @@ def test_ddof_too_big(self):
473430
else:
474431
assert_(len(w) == 0)
475432

476-
def test_result_values(self):
477-
for nf, rf in zip(self.nanfuncs, self.stdfuncs):
478-
tgt = [rf(d) for d in _rdat]
479-
res = nf(_ndat, axis=1)
480-
assert_almost_equal(res, tgt)
481-
482433
def test_allnans(self):
483434
mat = np.array([np.nan]*9).reshape(3, 3)
484435
for f in self.nanfuncs:
@@ -508,24 +459,6 @@ def test_empty(self):
508459
assert_equal(f(mat, axis=axis), np.zeros([]))
509460
assert_(len(w) == 0)
510461

511-
def test_scalar(self):
512-
for f in self.nanfuncs:
513-
assert_(f(0.) == 0.)
514-
515-
def test_matrices(self):
516-
# Check that it works and that type and
517-
# shape are preserved
518-
mat = np.matrix(np.eye(3))
519-
for f in self.nanfuncs:
520-
res = f(mat, axis=0)
521-
assert_(isinstance(res, np.matrix))
522-
assert_(res.shape == (1, 3))
523-
res = f(mat, axis=1)
524-
assert_(isinstance(res, np.matrix))
525-
assert_(res.shape == (3, 1))
526-
res = f(mat)
527-
assert_(np.isscalar(res))
528-
529462

530463
class TestNanFunctions_Median(TestCase):
531464

0 commit comments

Comments
 (0)
0