From 3ebbbb07285dec67cd89f410be4dd3b426c13f10 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Mon, 8 Aug 2016 23:38:07 -0700 Subject: ENH: Add ma.convolve and ma.correlate for #6458 --- numpy/ma/core.py | 81 ++++++++++++++++++++++++++++++++++++++++++--- numpy/ma/tests/test_core.py | 9 +++++ 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/numpy/ma/core.py b/numpy/ma/core.py index c4a54acb4..92fb5a3e2 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -56,10 +56,10 @@ __all__ = [ 'argmax', 'argmin', 'argsort', 'around', 'array', 'asanyarray', 'asarray', 'bitwise_and', 'bitwise_or', 'bitwise_xor', 'bool_', 'ceil', 'choose', 'clip', 'common_fill_value', 'compress', 'compressed', - 'concatenate', 'conjugate', 'copy', 'cos', 'cosh', 'count', 'cumprod', - 'cumsum', 'default_fill_value', 'diag', 'diagonal', 'diff', 'divide', - 'dump', 'dumps', 'empty', 'empty_like', 'equal', 'exp', 'expand_dims', - 'fabs', 'filled', 'fix_invalid', 'flatten_mask', + 'concatenate', 'conjugate', 'convolve', 'copy', 'correlate', 'cos', 'cosh', + 'count', 'cumprod', 'cumsum', 'default_fill_value', 'diag', 'diagonal', + 'diff', 'divide', 'dump', 'dumps', 'empty', 'empty_like', 'equal', 'exp', + 'expand_dims', 'fabs', 'filled', 'fix_invalid', 'flatten_mask', 'flatten_structured_array', 'floor', 'floor_divide', 'fmod', 'frombuffer', 'fromflex', 'fromfunction', 'getdata', 'getmask', 'getmaskarray', 'greater', 'greater_equal', 'harden_mask', 'hypot', @@ -7366,6 +7366,79 @@ outer.__doc__ = doc_note(np.outer.__doc__, outerproduct = outer +def _convolve_or_correlate(f, a, v, mode, contagious): + if contagious: + # results which are contributed to by either item in any pair being invalid + mask = ( + f(getmaskarray(a), np.ones(v.shape, dtype=np.bool), mode=mode) + | f(np.ones(a.shape, dtype=np.bool), getmaskarray(v), mode=mode) + ) + data = f(getdata(a), getdata(v), mode=mode) + else: + # results which are not contributed to by any pair of valid elements + mask = ~f(~getmaskarray(a), ~getmaskarray(v)) + data = f(filled(a, 0), filled(v, 0), mode=mode) + + return masked_array(data, mask=mask) + + +def correlate(a, v, mode='valid', contagious=True): + """ + Cross-correlation of two 1-dimensional sequences. + + Parameters + ---------- + a, v : array_like + Input sequences. + mode : {'valid', 'same', 'full'}, optional + Refer to the `np.convolve` docstring. Note that the default + is 'valid', unlike `convolve`, which uses 'full'. + contagious : bool + If True, then if any masked element is included in the sum for a result + element, then the result is masked. + If False, then the result element is only masked if no non-masked cells + contribute towards it + + Returns + ------- + out : ndarray + Discrete cross-correlation of `a` and `v`. + + See Also + -------- + numpy.correlate : Equivalent function in the top-level NumPy module. + """ + return _convolve_or_correlate(np.correlate, a, v, mode, contagious) + + +def convolve(a, v, mode='full', contagious=True): + """ + Returns the discrete, linear convolution of two one-dimensional sequences. + + Parameters + ---------- + a, v : array_like + Input sequences. + mode : {'valid', 'same', 'full'}, optional + Refer to the `np.convolve` docstring. + contagious : bool + If True, then if any masked element is included in the sum for a result + element, then the result is masked. + If False, then the result element is only masked if no non-masked cells + contribute towards it + + Returns + ------- + out : ndarray + Discrete, linear convolution of `a` and `v`. + + See Also + -------- + numpy.convolve : Equivalent function in the top-level NumPy module. + """ + return _convolve_or_correlate(np.convolve, a, v, mode, contagious) + + def allequal(a, b, fill_value=True): """ Return True if all entries of a and b are equal, using diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index a4887aaf0..822a00007 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -4086,6 +4086,15 @@ class TestMaskedArrayFunctions(TestCase): test = np.ma.compressed(M(shape=(0,1,2))) assert_equal(test, 42) + def test_convolve(self): + a = masked_equal(np.arange(5), 2) + b = np.array([1, 1]) + test = np.ma.convolve(a, b) + assert_equal(test, masked_equal([0, 1, -1, -1, 7, 4], -1)) + + test = np.ma.convolve(a, b, contagious=False) + assert_equal(test, masked_equal([0, 1, 1, 3, 7, 4], -1)) + class TestMaskedFields(TestCase): -- cgit v1.2.3 From cb52fd63627fa7547f5467324726d151af3aff84 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 19 Oct 2016 18:14:25 +0100 Subject: API: Rename contagious to propagate_mask As discussed in the mailing list --- numpy/ma/core.py | 21 ++++++++++----------- numpy/ma/tests/test_core.py | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/numpy/ma/core.py b/numpy/ma/core.py index 92fb5a3e2..0270e06ea 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -7366,8 +7366,8 @@ outer.__doc__ = doc_note(np.outer.__doc__, outerproduct = outer -def _convolve_or_correlate(f, a, v, mode, contagious): - if contagious: +def _convolve_or_correlate(f, a, v, mode, propagate_mask): + if propagate_mask: # results which are contributed to by either item in any pair being invalid mask = ( f(getmaskarray(a), np.ones(v.shape, dtype=np.bool), mode=mode) @@ -7382,7 +7382,7 @@ def _convolve_or_correlate(f, a, v, mode, contagious): return masked_array(data, mask=mask) -def correlate(a, v, mode='valid', contagious=True): +def correlate(a, v, mode='valid', propagate_mask=True): """ Cross-correlation of two 1-dimensional sequences. @@ -7393,10 +7393,9 @@ def correlate(a, v, mode='valid', contagious=True): mode : {'valid', 'same', 'full'}, optional Refer to the `np.convolve` docstring. Note that the default is 'valid', unlike `convolve`, which uses 'full'. - contagious : bool - If True, then if any masked element is included in the sum for a result - element, then the result is masked. - If False, then the result element is only masked if no non-masked cells + propagate_mask : bool + If True, then a result element is masked if any masked element contributes towards it. + If False, then a result element is only masked if no non-masked element contribute towards it Returns @@ -7408,10 +7407,10 @@ def correlate(a, v, mode='valid', contagious=True): -------- numpy.correlate : Equivalent function in the top-level NumPy module. """ - return _convolve_or_correlate(np.correlate, a, v, mode, contagious) + return _convolve_or_correlate(np.correlate, a, v, mode, propagate_mask) -def convolve(a, v, mode='full', contagious=True): +def convolve(a, v, mode='full', propagate_mask=True): """ Returns the discrete, linear convolution of two one-dimensional sequences. @@ -7421,7 +7420,7 @@ def convolve(a, v, mode='full', contagious=True): Input sequences. mode : {'valid', 'same', 'full'}, optional Refer to the `np.convolve` docstring. - contagious : bool + propagate_mask : bool If True, then if any masked element is included in the sum for a result element, then the result is masked. If False, then the result element is only masked if no non-masked cells @@ -7436,7 +7435,7 @@ def convolve(a, v, mode='full', contagious=True): -------- numpy.convolve : Equivalent function in the top-level NumPy module. """ - return _convolve_or_correlate(np.convolve, a, v, mode, contagious) + return _convolve_or_correlate(np.convolve, a, v, mode, propagate_mask) def allequal(a, b, fill_value=True): diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index 822a00007..14ea4dcf2 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -4092,7 +4092,7 @@ class TestMaskedArrayFunctions(TestCase): test = np.ma.convolve(a, b) assert_equal(test, masked_equal([0, 1, -1, -1, 7, 4], -1)) - test = np.ma.convolve(a, b, contagious=False) + test = np.ma.convolve(a, b, propagate_mask=False) assert_equal(test, masked_equal([0, 1, 1, 3, 7, 4], -1)) -- cgit v1.2.3 From bf3fb267a3b014adbf6a980684877374636234b3 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 19 Oct 2016 18:15:12 +0100 Subject: DOC: correct ma.convolve docstrings, and add the feature to the release notes --- doc/release/1.12.0-notes.rst | 7 +++++++ numpy/ma/core.py | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/release/1.12.0-notes.rst b/doc/release/1.12.0-notes.rst index 9544b6d02..f95dcc41a 100644 --- a/doc/release/1.12.0-notes.rst +++ b/doc/release/1.12.0-notes.rst @@ -249,6 +249,13 @@ context manager will work as expected. Additionally, it is possible to use the context manager as a decorator which can be useful when multiple tests give need to hide the same warning. +New masked array functions ``ma.convolve`` and ``ma.correlate`` added +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +These functions wrapped the non-masked versions, but propagate through masked +values. There are two different propagation modes. The default causes masked +values to contaminate the result with masks, but the other mode only outputs +masks if there is no alternative. + Improvements ============ diff --git a/numpy/ma/core.py b/numpy/ma/core.py index 0270e06ea..3996c76cf 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -7367,6 +7367,9 @@ outerproduct = outer def _convolve_or_correlate(f, a, v, mode, propagate_mask): + """ + Helper function for ma.correlate and ma.convolve + """ if propagate_mask: # results which are contributed to by either item in any pair being invalid mask = ( @@ -7400,7 +7403,7 @@ def correlate(a, v, mode='valid', propagate_mask=True): Returns ------- - out : ndarray + out : MaskedArray Discrete cross-correlation of `a` and `v`. See Also @@ -7428,7 +7431,7 @@ def convolve(a, v, mode='full', propagate_mask=True): Returns ------- - out : ndarray + out : MaskedArray Discrete, linear convolution of `a` and `v`. See Also -- cgit v1.2.3 From 4c2ad8a4a23524f422f476c1596c973c4308e5da Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Wed, 19 Oct 2016 19:56:44 +0100 Subject: BUG: Fix (and test) np.ma.convolve for raw lists --- numpy/ma/core.py | 4 ++-- numpy/ma/tests/test_core.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/numpy/ma/core.py b/numpy/ma/core.py index 3996c76cf..4466dc0af 100644 --- a/numpy/ma/core.py +++ b/numpy/ma/core.py @@ -7373,8 +7373,8 @@ def _convolve_or_correlate(f, a, v, mode, propagate_mask): if propagate_mask: # results which are contributed to by either item in any pair being invalid mask = ( - f(getmaskarray(a), np.ones(v.shape, dtype=np.bool), mode=mode) - | f(np.ones(a.shape, dtype=np.bool), getmaskarray(v), mode=mode) + f(getmaskarray(a), np.ones(np.shape(v), dtype=np.bool), mode=mode) + | f(np.ones(np.shape(a), dtype=np.bool), getmaskarray(v), mode=mode) ) data = f(getdata(a), getdata(v), mode=mode) else: diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py index 14ea4dcf2..9b65643ed 100644 --- a/numpy/ma/tests/test_core.py +++ b/numpy/ma/tests/test_core.py @@ -4095,6 +4095,16 @@ class TestMaskedArrayFunctions(TestCase): test = np.ma.convolve(a, b, propagate_mask=False) assert_equal(test, masked_equal([0, 1, 1, 3, 7, 4], -1)) + test = np.ma.convolve([1, 1], [1, 1, 1]) + assert_equal(test, masked_equal([1, 2, 2, 1], -1)) + + a = [1, 1] + b = masked_equal([1, -1, -1, 1], -1) + test = np.ma.convolve(a, b, propagate_mask=False) + assert_equal(test, masked_equal([1, 1, -1, 1, 1], -1)) + test = np.ma.convolve(a, b, propagate_mask=True) + assert_equal(test, masked_equal([-1, -1, -1, -1, -1], -1)) + class TestMaskedFields(TestCase): -- cgit v1.2.3