Repository: cupy/cupy

Issue:
Add nanmean() nanstd()
It would be great if you can add functions like nanmean(), nanstd() and nanvar() like numpy


Discussion:
I would like to work on this issue.
We welcome contributions for these functions.

Resolving patch:
diff --git a/cupy/__init__.py b/cupy/__init__.py
--- a/cupy/__init__.py
+++ b/cupy/__init__.py
@@ -634,6 +634,8 @@ def isscalar(num):
 from cupy.statistics.meanvar import mean  # NOQA
 from cupy.statistics.meanvar import std  # NOQA
 from cupy.statistics.meanvar import var  # NOQA
+from cupy.statistics.meanvar import nanstd  # NOQA
+from cupy.statistics.meanvar import nanvar  # NOQA
 
 from cupy.statistics.histogram import bincount  # NOQA
 from cupy.statistics.histogram import histogram  # NOQA
diff --git a/cupy/statistics/meanvar.py b/cupy/statistics/meanvar.py
--- a/cupy/statistics/meanvar.py
+++ b/cupy/statistics/meanvar.py
@@ -1,6 +1,7 @@
 import numpy
 
 import cupy
+from cupy.core import _routines_statistics as _statistics
 
 
 # TODO(okuta): Implement median
@@ -141,7 +142,55 @@ def std(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
 # TODO(okuta): Implement nanmean
 
 
-# TODO(okuta): Implement nanstd
+def nanvar(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
+    """Returns the variance along an axis ignoring NaN values.
+
+    Args:
+        a (cupy.ndarray): Array to compute variance.
+        axis (int): Along which axis to compute variance. The flattened array
+            is used by default.
+        dtype: Data type specifier.
+        out (cupy.ndarray): Output array.
+        keepdims (bool): If ``True``, the axis is remained as an axis of
+            size one.
+
+    Returns:
+        cupy.ndarray: The variance of the input array along the axis.
+
+    .. seealso:: :func:`numpy.nanvar`
+
+    """
+    if a.dtype.kind in 'biu':
+        return a.var(axis=axis, dtype=dtype, out=out, ddof=ddof,
+                     keepdims=keepdims)
+
+    # TODO(okuta): check type
+    return _statistics._ndarray_nanvar(a, axis=axis, dtype=dtype, out=out,
+                                       ddof=ddof, keepdims=keepdims)
 
 
-# TODO(okuta): Implement nanvar
+def nanstd(a, axis=None, dtype=None, out=None, ddof=0, keepdims=False):
+    """Returns the standard deviation along an axis ignoring NaN values.
+
+    Args:
+        a (cupy.ndarray): Array to compute standard deviation.
+        axis (int): Along which axis to compute standard deviation. The
+            flattened array is used by default.
+        dtype: Data type specifier.
+        out (cupy.ndarray): Output array.
+        keepdims (bool): If ``True``, the axis is remained as an axis of
+            size one.
+
+    Returns:
+        cupy.ndarray: The standard deviation of the input array along the axis.
+
+    .. seealso:: :func:`numpy.nanstd`
+
+    """
+    if a.dtype.kind in 'biu':
+        return a.std(axis=axis, dtype=dtype, out=out, ddof=ddof,
+                     keepdims=keepdims)
+
+    # TODO(okuta): check type
+    return _statistics._ndarray_nanstd(a, axis=axis, dtype=dtype, out=out,
+                                       ddof=ddof, keepdims=keepdims)


Test patch:
diff --git a/tests/cupy_tests/statics_tests/test_meanvar.py b/tests/cupy_tests/statics_tests/test_meanvar.py
--- a/tests/cupy_tests/statics_tests/test_meanvar.py
+++ b/tests/cupy_tests/statics_tests/test_meanvar.py
@@ -179,3 +179,96 @@ def test_std_axis_ddof(self, xp, dtype):
     def test_external_std_axis_ddof(self, xp, dtype):
         a = testing.shaped_arange((2, 3, 4), xp, dtype)
         return xp.std(a, axis=1, ddof=1)
+
+
+@testing.parameterize(
+    *testing.product({
+        'shape': [(3, 4), (4, 3, 5)],
+        'axis': [None, 0, 1],
+        'keepdims': [True, False],
+        'ddof': [0, 1]
+    }))
+@testing.gpu
+class TestNanVarStd(unittest.TestCase):
+
+    @testing.for_all_dtypes(no_float16=True, no_complex=True)
+    @testing.numpy_cupy_allclose(rtol=1e-6)
+    def test_nanvar(self, xp, dtype):
+        a = testing.shaped_random(self.shape, xp, dtype=dtype)
+        if a.dtype.kind not in 'biu':
+            a[0, :] = xp.nan
+        return xp.nanvar(
+            a, axis=self.axis, ddof=self.ddof, keepdims=self.keepdims)
+
+    @testing.for_all_dtypes(no_float16=True, no_complex=True)
+    @testing.numpy_cupy_allclose(rtol=1e-6)
+    def test_nanstd(self, xp, dtype):
+        a = testing.shaped_random(self.shape, xp, dtype=dtype)
+        if a.dtype.kind not in 'biu':
+            a[0, :] = xp.nan
+        return xp.nanstd(
+            a, axis=self.axis, ddof=self.ddof, keepdims=self.keepdims)
+
+
+@testing.gpu
+class TestNanVarStdAdditional(unittest.TestCase):
+
+    @testing.for_all_dtypes(no_float16=True, no_complex=True)
+    @testing.numpy_cupy_allclose(rtol=1e-6)
+    def test_nanvar_out(self, xp, dtype):
+        a = testing.shaped_random((10, 20, 30), xp, dtype)
+        z = xp.zeros((20, 30))
+
+        if a.dtype.kind not in 'biu':
+            a[1, :] = xp.nan
+            a[:, 3] = xp.nan
+
+        xp.nanvar(a, axis=0, out=z)
+        return z
+
+    @testing.slow
+    @testing.for_all_dtypes(no_float16=True, no_complex=True)
+    @testing.numpy_cupy_allclose(rtol=1e-6)
+    def test_nanvar_huge(self, xp, dtype):
+        a = testing.shaped_random((1024, 512), xp, dtype)
+
+        if a.dtype.kind not in 'biu':
+            a[:512, :256] = xp.nan
+
+        return xp.nanvar(a, axis=1)
+
+    @testing.numpy_cupy_allclose(rtol=1e-4)
+    def test_nanvar_float16(self, xp):
+        a = testing.shaped_arange((4, 5), xp, numpy.float16)
+        a[0][0] = xp.nan
+        return xp.nanvar(a, axis=0)
+
+    @testing.for_all_dtypes(no_float16=True, no_complex=True)
+    @testing.numpy_cupy_allclose(rtol=1e-6)
+    def test_nanstd_out(self, xp, dtype):
+        a = testing.shaped_random((10, 20, 30), xp, dtype)
+        z = xp.zeros((20, 30))
+
+        if a.dtype.kind not in 'biu':
+            a[1, :] = xp.nan
+            a[:, 3] = xp.nan
+
+        xp.nanstd(a, axis=0, out=z)
+        return z
+
+    @testing.slow
+    @testing.for_all_dtypes(no_float16=True, no_complex=True)
+    @testing.numpy_cupy_allclose(rtol=1e-6)
+    def test_nanstd_huge(self, xp, dtype):
+        a = testing.shaped_random((1024, 512), xp, dtype)
+
+        if a.dtype.kind not in 'biu':
+            a[:512, :256] = xp.nan
+
+        return xp.nanstd(a, axis=1)
+
+    @testing.numpy_cupy_allclose(rtol=1e-4)
+    def test_nanstd_float16(self, xp):
+        a = testing.shaped_arange((4, 5), xp, numpy.float16)
+        a[0][0] = xp.nan
+        return xp.nanstd(a, axis=1)