Repository: numpy/numpy

Issue:
DOC: Document ma.filled behavior with non-scalar fill_value 
The ma.filled function and equivalent MaskedArray method are documented as follows:

```
filled(self, fill_value=None) ..

    Parameters
    ----------
    fill_value : scalar, optional
```

However, the current implementation (using np.copyto(.., where=mask)) allows any shape that can be broadcast to the shape of self for fill_value.

This is a useful property and should be documented. 



Discussion:
HI! I am an Outreachy applicant and I guess, this is the only unassigned issue :) Can I solve it?
@yesass yes, and please xref your pull request by putting `#4363` in the comments so others will know you have started working on this.
@mattip  Hi! Can you check it out? https://github.com/numpy/numpy/pull/13241
Lifting [a comment from #10504](https://github.com/numpy/numpy/pull/10504#issuecomment-362846574):

> I'm wary of documenting this if it's not deliberate or well-supported behavior.
> 
> Arrays with non-scalar fill values also behave poorly upon slicing:
> ```python
> >>> m = np.ma.array(np.zeros((3, 5)), mask=True, fill_value=np.arange(5))
> >>> m.filled()
> array([[0., 1., 2., 3., 4.],
>        [0., 1., 2., 3., 4.],
>        [0., 1., 2., 3., 4.]])
> >>> m[:, :2].filled()
> ValueError: could not broadcast input array from shape (5) into shape (3,2)
> ```
> 
> Fixing this requires copying parts of xarray, which seems super out of scope.
> 
> I think it would be better to just advise using
> ```
> np.where(m.mask, some_broadcasting_fillvalue, m.data)
> ```
> possibly even in a deprecation warning.
@eric-wieser Hello!
So as I understood, the doc should be this way:
```
        fill_value : {None, array_like}, optional
        If array_like, the resulting ndarray must be broadcastable
        over `a`.  Arrays with non-scalar fill values are deprecated. The default is None.
```

Also, there should be Deprecation Warning raised like:
```
warnings.warn(
                "Arrays with non-scalar fill values are deprecated. Use "
                "Arrays with scalar values instead".format(self.__name__),
                DeprecationWarning, stacklevel=2)
```
I think we should compromise on allowing `filled(...)` to take a broadcasting value, and writing tests for it (it's easy to get right), but deprecating using an non-scalar fill value for `fill_value` (because getting that right is hard).
The  deprecation warning should have a comment before it: `# 2019-04-02, 1.17.0, gh-4363`
Ok, I will edit the docstring and add deprecation warning. I have several questions:
Is it necessary to add examples in docstrings? 
And should I write a test? If yes, could you tell a specific file that contains tests in this method (to see the examples).
It is nice but not required to add examples in the docstrings. Some of them already have examples. If you do modify docstrings, make sure to run `python runtests.py --refguide-check` in addition to `python runtests.py` to make sure they pass tests.

You should always add tests for changed behaviour. Deprecation tests go into `/numpy/ma/tests/test_deprecations.py`, regulare masked array tests go in other files in that directory.

Resolving patch:
diff --git a/numpy/ma/core.py b/numpy/ma/core.py
--- a/numpy/ma/core.py
+++ b/numpy/ma/core.py
@@ -602,8 +602,10 @@ def filled(a, fill_value=None):
     ----------
     a : MaskedArray or array_like
         An input object.
-    fill_value : scalar, optional
-        Filling value. Default is None.
+    fill_value : array_like, optional.
+        Can be scalar or non-scalar. If non-scalar, the
+        resulting filled array should be broadcastable
+        over input array. Default is None.
 
     Returns
     -------
@@ -623,10 +625,19 @@ def filled(a, fill_value=None):
     array([[999999,      1,      2],
            [999999,      4,      5],
            [     6,      7,      8]])
+    >>> x.filled(fill_value=333)
+    array([[333,   1,   2],
+           [333,   4,   5],
+           [  6,   7,   8]])
+    >>> x.filled(fill_value=np.arange(3))
+    array([[0, 1, 2],
+           [0, 4, 5],
+           [6, 7, 8]])
 
     """
     if hasattr(a, 'filled'):
         return a.filled(fill_value)
+
     elif isinstance(a, ndarray):
         # Should we check for contiguity ? and a.flags['CONTIGUOUS']:
         return a
@@ -3653,6 +3664,14 @@ def fill_value(self):
     @fill_value.setter
     def fill_value(self, value=None):
         target = _check_fill_value(value, self.dtype)
+        if not target.ndim == 0:
+            # 2019-11-12, 1.18.0
+            warnings.warn(
+                "Non-scalar arrays for the fill value are deprecated. Use "
+                "arrays with scalar values instead. The filled function "
+                "still supports any array as `fill_value`.",
+                DeprecationWarning, stacklevel=2)
+
         _fill_value = self._fill_value
         if _fill_value is None:
             # Create the attribute if it was undefined
@@ -3673,9 +3692,11 @@ def filled(self, fill_value=None):
 
         Parameters
         ----------
-        fill_value : scalar, optional
-            The value to use for invalid entries (None by default).
-            If None, the `fill_value` attribute of the array is used instead.
+        fill_value : array_like, optional
+            The value to use for invalid entries. Can be scalar or non-scalar.
+            If non-scalar, the resulting ndarray must be broadcastable over
+            input array. Default is None, in which case, the `fill_value`
+            attribute of the array is used instead.
 
         Returns
         -------
@@ -3694,6 +3715,8 @@ def filled(self, fill_value=None):
         >>> x = np.ma.array([1,2,3,4,5], mask=[0,0,1,0,1], fill_value=-999)
         >>> x.filled()
         array([   1,    2, -999,    4, -999])
+        >>> x.filled(fill_value=1000)
+        array([   1,    2, 1000,    4, 1000])
         >>> type(x.filled())
         <class 'numpy.ndarray'>
 
@@ -6220,9 +6243,11 @@ def filled(self, fill_value=None):
 
         Parameters
         ----------
-        fill_value : scalar, optional
-            The value to use for invalid entries (None by default).
-            If None, the `fill_value` attribute is used instead.
+        fill_value : array_like, optional
+            The value to use for invalid entries. Can be scalar or
+            non-scalar. If latter is the case, the filled array should
+            be broadcastable over input array. Default is None, in
+            which case the `fill_value` attribute is used instead.
 
         Returns
         -------


Test patch: