about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/numpy/ma
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/numpy/ma
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/numpy/ma')
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/API_CHANGES.txt135
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/LICENSE24
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/README.rst236
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/__init__.py54
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/__init__.pyi234
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/core.py8565
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/core.pyi471
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/extras.py2133
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/extras.pyi85
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/mrecords.py783
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/mrecords.pyi90
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/setup.py12
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/tests/__init__.py0
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/tests/test_core.py5687
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/tests/test_deprecations.py84
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/tests/test_extras.py1870
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/tests/test_mrecords.py493
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/tests/test_old_ma.py874
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/tests/test_regression.py97
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/tests/test_subclassing.py460
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/testutils.py288
-rw-r--r--.venv/lib/python3.12/site-packages/numpy/ma/timer_comparison.py443
22 files changed, 23118 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/API_CHANGES.txt b/.venv/lib/python3.12/site-packages/numpy/ma/API_CHANGES.txt
new file mode 100644
index 00000000..a3d792a1
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/API_CHANGES.txt
@@ -0,0 +1,135 @@
+.. -*- rest -*-
+
+==================================================
+API changes in the new masked array implementation
+==================================================
+
+Masked arrays are subclasses of ndarray
+---------------------------------------
+
+Contrary to the original implementation, masked arrays are now regular
+ndarrays::
+
+  >>> x = masked_array([1,2,3],mask=[0,0,1])
+  >>> print isinstance(x, numpy.ndarray)
+  True
+
+
+``_data`` returns a view of the masked array
+--------------------------------------------
+
+Masked arrays are composed of a ``_data`` part and a ``_mask``. Accessing the
+``_data`` part will return a regular ndarray or any of its subclass, depending
+on the initial data::
+
+  >>> x = masked_array(numpy.matrix([[1,2],[3,4]]),mask=[[0,0],[0,1]])
+  >>> print x._data
+  [[1 2]
+   [3 4]]
+  >>> print type(x._data)
+  <class 'numpy.matrixlib.defmatrix.matrix'>
+
+
+In practice, ``_data`` is implemented as a property, not as an attribute.
+Therefore, you cannot access it directly, and some simple tests such as the
+following one will fail::
+
+  >>>x._data is x._data
+  False
+
+
+``filled(x)`` can return a subclass of ndarray
+----------------------------------------------
+The function ``filled(a)`` returns an array of the same type as ``a._data``::
+
+  >>> x = masked_array(numpy.matrix([[1,2],[3,4]]),mask=[[0,0],[0,1]])
+  >>> y = filled(x)
+  >>> print type(y)
+  <class 'numpy.matrixlib.defmatrix.matrix'>
+  >>> print y
+  matrix([[     1,      2],
+          [     3, 999999]])
+
+
+``put``, ``putmask`` behave like their ndarray counterparts
+-----------------------------------------------------------
+
+Previously, ``putmask`` was used like this::
+
+  mask = [False,True,True]
+  x = array([1,4,7],mask=mask)
+  putmask(x,mask,[3])
+
+which translated to::
+
+  x[~mask] = [3]
+
+(Note that a ``True``-value in a mask suppresses a value.)
+
+In other words, the mask had the same length as ``x``, whereas
+``values`` had ``sum(~mask)`` elements.
+
+Now, the behaviour is similar to that of ``ndarray.putmask``, where
+the mask and the values are both the same length as ``x``, i.e.
+
+::
+
+  putmask(x,mask,[3,0,0])
+
+
+``fill_value`` is a property
+----------------------------
+
+``fill_value`` is no longer a method, but a property::
+
+  >>> print x.fill_value
+  999999
+
+``cumsum`` and ``cumprod`` ignore missing values
+------------------------------------------------
+
+Missing values are assumed to be the identity element, i.e. 0 for
+``cumsum`` and 1 for ``cumprod``::
+
+  >>> x = N.ma.array([1,2,3,4],mask=[False,True,False,False])
+  >>> print x
+  [1 -- 3 4]
+  >>> print x.cumsum()
+  [1 -- 4 8]
+  >> print x.cumprod()
+  [1 -- 3 12]
+
+``bool(x)`` raises a ValueError
+-------------------------------
+
+Masked arrays now behave like regular ``ndarrays``, in that they cannot be
+converted to booleans:
+
+::
+
+  >>> x = N.ma.array([1,2,3])
+  >>> bool(x)
+  Traceback (most recent call last):
+    File "<stdin>", line 1, in <module>
+  ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
+
+
+==================================
+New features (non exhaustive list)
+==================================
+
+``mr_``
+-------
+
+``mr_`` mimics the behavior of ``r_`` for masked arrays::
+
+  >>> np.ma.mr_[3,4,5]
+  masked_array(data = [3 4 5],
+        mask = False,
+        fill_value=999999)
+
+
+``anom``
+--------
+
+The ``anom`` method returns the deviations from the average (anomalies).
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/LICENSE b/.venv/lib/python3.12/site-packages/numpy/ma/LICENSE
new file mode 100644
index 00000000..b41aae0c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/LICENSE
@@ -0,0 +1,24 @@
+* Copyright (c) 2006, University of Georgia and Pierre G.F. Gerard-Marchant
+* All rights reserved.
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright
+*       notice, this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright
+*       notice, this list of conditions and the following disclaimer in the
+*       documentation and/or other materials provided with the distribution.
+*     * Neither the name of the University of Georgia nor the
+*       names of its contributors may be used to endorse or promote products
+*       derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/README.rst b/.venv/lib/python3.12/site-packages/numpy/ma/README.rst
new file mode 100644
index 00000000..47f20d64
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/README.rst
@@ -0,0 +1,236 @@
+==================================
+A Guide to Masked Arrays in NumPy
+==================================
+
+.. Contents::
+
+See http://www.scipy.org/scipy/numpy/wiki/MaskedArray (dead link)
+for updates of this document.
+
+
+History
+-------
+
+As a regular user of MaskedArray, I (Pierre G.F. Gerard-Marchant) became
+increasingly frustrated with the subclassing of masked arrays (even if
+I can only blame my inexperience). I needed to develop a class of arrays
+that could store some additional information along with numerical values,
+while keeping the possibility for missing data (picture storing a series
+of dates along with measurements, what would later become the `TimeSeries
+Scikit <http://projects.scipy.org/scipy/scikits/wiki/TimeSeries>`__
+(dead link).
+
+I started to implement such a class, but then quickly realized that
+any additional information disappeared when processing these subarrays
+(for example, adding a constant value to a subarray would erase its
+dates). I ended up writing the equivalent of *numpy.core.ma* for my
+particular class, ufuncs included. Everything went fine until I needed to
+subclass my new class, when more problems showed up: some attributes of
+the new subclass were lost during processing. I identified the culprit as
+MaskedArray, which returns masked ndarrays when I expected masked
+arrays of my class. I was preparing myself to rewrite *numpy.core.ma*
+when I forced myself to learn how to subclass ndarrays. As I became more
+familiar with the *__new__* and *__array_finalize__* methods,
+I started to wonder why masked arrays were objects, and not ndarrays,
+and whether it wouldn't be more convenient for subclassing if they did
+behave like regular ndarrays.
+
+The new *maskedarray* is what I eventually come up with. The
+main differences with the initial *numpy.core.ma* package are
+that MaskedArray is now a subclass of *ndarray* and that the
+*_data* section can now be any subclass of *ndarray*. Apart from a
+couple of issues listed below, the behavior of the new MaskedArray
+class reproduces the old one. Initially the *maskedarray*
+implementation was marginally slower than *numpy.ma* in some areas,
+but work is underway to speed it up; the expectation is that it can be
+made substantially faster than the present *numpy.ma*.
+
+
+Note that if the subclass has some special methods and
+attributes, they are not propagated to the masked version:
+this would require a modification of the *__getattribute__*
+method (first trying *ndarray.__getattribute__*, then trying
+*self._data.__getattribute__* if an exception is raised in the first
+place), which really slows things down.
+
+Main differences
+----------------
+
+ * The *_data* part of the masked array can be any subclass of ndarray (but not recarray, cf below).
+ * *fill_value* is now a property, not a function.
+ * in the majority of cases, the mask is forced to *nomask* when no value is actually masked. A notable exception is when a masked array (with no masked values) has just been unpickled.
+ * I got rid of the *share_mask* flag, I never understood its purpose.
+ * *put*, *putmask* and *take* now mimic the ndarray methods, to avoid unpleasant surprises. Moreover, *put* and *putmask* both update the mask when needed.  * if *a* is a masked array, *bool(a)* raises a *ValueError*, as it does with ndarrays.
+ * in the same way, the comparison of two masked arrays is a masked array, not a boolean
+ * *filled(a)* returns an array of the same subclass as *a._data*, and no test is performed on whether it is contiguous or not.
+ * the mask is always printed, even if it's *nomask*, which makes things easy (for me at least) to remember that a masked array is used.
+ * *cumsum* works as if the *_data* array was filled with 0. The mask is preserved, but not updated.
+ * *cumprod* works as if the *_data* array was filled with 1. The mask is preserved, but not updated.
+
+New features
+------------
+
+This list is non-exhaustive...
+
+ * the *mr_* function mimics *r_* for masked arrays.
+ * the *anom* method returns the anomalies (deviations from the average)
+
+Using the new package with numpy.core.ma
+----------------------------------------
+
+I tried to make sure that the new package can understand old masked
+arrays. Unfortunately, there's no upward compatibility.
+
+For example:
+
+>>> import numpy.core.ma as old_ma
+>>> import maskedarray as new_ma
+>>> x = old_ma.array([1,2,3,4,5], mask=[0,0,1,0,0])
+>>> x
+array(data =
+ [     1      2 999999      4      5],
+      mask =
+ [False False True False False],
+      fill_value=999999)
+>>> y = new_ma.array([1,2,3,4,5], mask=[0,0,1,0,0])
+>>> y
+array(data = [1 2 -- 4 5],
+      mask = [False False True False False],
+      fill_value=999999)
+>>> x==y
+array(data =
+ [True True True True True],
+      mask =
+ [False False True False False],
+      fill_value=?)
+>>> old_ma.getmask(x) == new_ma.getmask(x)
+array([True, True, True, True, True])
+>>> old_ma.getmask(y) == new_ma.getmask(y)
+array([True, True, False, True, True])
+>>> old_ma.getmask(y)
+False
+
+
+Using maskedarray with matplotlib
+---------------------------------
+
+Starting with matplotlib 0.91.2, the masked array importing will work with
+the maskedarray branch) as well as with earlier versions.
+
+By default matplotlib still uses numpy.ma, but there is an rcParams setting
+that you can use to select maskedarray instead.  In the matplotlibrc file
+you will find::
+
+  #maskedarray : False       # True to use external maskedarray module
+                             # instead of numpy.ma; this is a temporary #
+                             setting for testing maskedarray.
+
+
+Uncomment and set to True to select maskedarray everywhere.
+Alternatively, you can test a script with maskedarray by using a
+command-line option, e.g.::
+
+  python simple_plot.py --maskedarray
+
+
+Masked records
+--------------
+
+Like *numpy.core.ma*, the *ndarray*-based implementation
+of MaskedArray is limited when working with records: you can
+mask any record of the array, but not a field in a record. If you
+need this feature, you may want to give the *mrecords* package
+a try (available in the *maskedarray* directory in the scipy
+sandbox). This module defines a new class, *MaskedRecord*. An
+instance of this class accepts a *recarray* as data, and uses two
+masks: the *fieldmask* has as many entries as records in the array,
+each entry with the same fields as a record, but of boolean types:
+they indicate whether the field is masked or not; a record entry
+is flagged as masked in the *mask* array if all the fields are
+masked. A few examples in the file should give you an idea of what
+can be done. Note that *mrecords* is still experimental...
+
+Optimizing maskedarray
+----------------------
+
+Should masked arrays be filled before processing or not?
+--------------------------------------------------------
+
+In the current implementation, most operations on masked arrays involve
+the following steps:
+
+ * the input arrays are filled
+ * the operation is performed on the filled arrays
+ * the mask is set for the results, from the combination of the input masks and the mask corresponding to the domain of the operation.
+
+For example, consider the division of two masked arrays::
+
+  import numpy
+  import maskedarray as ma
+  x = ma.array([1,2,3,4],mask=[1,0,0,0], dtype=numpy.float_)
+  y = ma.array([-1,0,1,2], mask=[0,0,0,1], dtype=numpy.float_)
+
+The division of x by y is then computed as::
+
+  d1 = x.filled(0) # d1 = array([0., 2., 3., 4.])
+  d2 = y.filled(1) # array([-1.,  0.,  1.,  1.])
+  m = ma.mask_or(ma.getmask(x), ma.getmask(y)) # m =
+  array([True,False,False,True])
+  dm = ma.divide.domain(d1,d2) # array([False,  True, False, False])
+  result = (d1/d2).view(MaskedArray) # masked_array([-0. inf, 3., 4.])
+  result._mask = logical_or(m, dm)
+
+Note that a division by zero takes place. To avoid it, we can consider
+to fill the input arrays, taking the domain mask into account, so that::
+
+  d1 = x._data.copy() # d1 = array([1., 2., 3., 4.])
+  d2 = y._data.copy() # array([-1.,  0.,  1.,  2.])
+  dm = ma.divide.domain(d1,d2) # array([False,  True, False, False])
+  numpy.putmask(d2, dm, 1) # d2 = array([-1.,  1.,  1.,  2.])
+  m = ma.mask_or(ma.getmask(x), ma.getmask(y)) # m =
+  array([True,False,False,True])
+  result = (d1/d2).view(MaskedArray) # masked_array([-1. 0., 3., 2.])
+  result._mask = logical_or(m, dm)
+
+Note that the *.copy()* is required to avoid updating the inputs with
+*putmask*.  The *.filled()* method also involves a *.copy()*.
+
+A third possibility consists in avoid filling the arrays::
+
+  d1 = x._data # d1 = array([1., 2., 3., 4.])
+  d2 = y._data # array([-1.,  0.,  1.,  2.])
+  dm = ma.divide.domain(d1,d2) # array([False,  True, False, False])
+  m = ma.mask_or(ma.getmask(x), ma.getmask(y)) # m =
+  array([True,False,False,True])
+  result = (d1/d2).view(MaskedArray) # masked_array([-1. inf, 3., 2.])
+  result._mask = logical_or(m, dm)
+
+Note that here again the division by zero takes place.
+
+A quick benchmark gives the following results:
+
+ * *numpy.ma.divide*  : 2.69 ms per loop
+ * classical division     : 2.21 ms per loop
+ * division w/ prefilling : 2.34 ms per loop
+ * division w/o filling   : 1.55 ms per loop
+
+So, is it worth filling the arrays beforehand ? Yes, if we are interested
+in avoiding floating-point exceptions that may fill the result with infs
+and nans. No, if we are only interested into speed...
+
+
+Thanks
+------
+
+I'd like to thank Paul Dubois, Travis Oliphant and Sasha for the
+original masked array package: without you, I would never have started
+that (it might be argued that I shouldn't have anyway, but that's
+another story...).  I also wish to extend these thanks to Reggie Dugard
+and Eric Firing for their suggestions and numerous improvements.
+
+
+Revision notes
+--------------
+
+  * 08/25/2007 : Creation of this page
+  * 01/23/2007 : The package has been moved to the SciPy sandbox, and is regularly updated: please check out your SVN version!
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/__init__.py b/.venv/lib/python3.12/site-packages/numpy/ma/__init__.py
new file mode 100644
index 00000000..870cc4ef
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/__init__.py
@@ -0,0 +1,54 @@
+"""
+=============
+Masked Arrays
+=============
+
+Arrays sometimes contain invalid or missing data.  When doing operations
+on such arrays, we wish to suppress invalid values, which is the purpose masked
+arrays fulfill (an example of typical use is given below).
+
+For example, examine the following array:
+
+>>> x = np.array([2, 1, 3, np.nan, 5, 2, 3, np.nan])
+
+When we try to calculate the mean of the data, the result is undetermined:
+
+>>> np.mean(x)
+nan
+
+The mean is calculated using roughly ``np.sum(x)/len(x)``, but since
+any number added to ``NaN`` [1]_ produces ``NaN``, this doesn't work.  Enter
+masked arrays:
+
+>>> m = np.ma.masked_array(x, np.isnan(x))
+>>> m
+masked_array(data = [2.0 1.0 3.0 -- 5.0 2.0 3.0 --],
+      mask = [False False False  True False False False  True],
+      fill_value=1e+20)
+
+Here, we construct a masked array that suppress all ``NaN`` values.  We
+may now proceed to calculate the mean of the other values:
+
+>>> np.mean(m)
+2.6666666666666665
+
+.. [1] Not-a-Number, a floating point value that is the result of an
+       invalid operation.
+
+.. moduleauthor:: Pierre Gerard-Marchant
+.. moduleauthor:: Jarrod Millman
+
+"""
+from . import core
+from .core import *
+
+from . import extras
+from .extras import *
+
+__all__ = ['core', 'extras']
+__all__ += core.__all__
+__all__ += extras.__all__
+
+from numpy._pytesttester import PytestTester
+test = PytestTester(__name__)
+del PytestTester
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/__init__.pyi b/.venv/lib/python3.12/site-packages/numpy/ma/__init__.pyi
new file mode 100644
index 00000000..ce72383e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/__init__.pyi
@@ -0,0 +1,234 @@
+from numpy._pytesttester import PytestTester
+
+from numpy.ma import extras as extras
+
+from numpy.ma.core import (
+    MAError as MAError,
+    MaskError as MaskError,
+    MaskType as MaskType,
+    MaskedArray as MaskedArray,
+    abs as abs,
+    absolute as absolute,
+    add as add,
+    all as all,
+    allclose as allclose,
+    allequal as allequal,
+    alltrue as alltrue,
+    amax as amax,
+    amin as amin,
+    angle as angle,
+    anom as anom,
+    anomalies as anomalies,
+    any as any,
+    append as append,
+    arange as arange,
+    arccos as arccos,
+    arccosh as arccosh,
+    arcsin as arcsin,
+    arcsinh as arcsinh,
+    arctan as arctan,
+    arctan2 as arctan2,
+    arctanh as arctanh,
+    argmax as argmax,
+    argmin as argmin,
+    argsort as argsort,
+    around as around,
+    array as array,
+    asanyarray as asanyarray,
+    asarray as asarray,
+    bitwise_and as bitwise_and,
+    bitwise_or as bitwise_or,
+    bitwise_xor as bitwise_xor,
+    bool_ as bool_,
+    ceil as ceil,
+    choose as choose,
+    clip as clip,
+    common_fill_value as common_fill_value,
+    compress as compress,
+    compressed as compressed,
+    concatenate as concatenate,
+    conjugate as conjugate,
+    convolve as convolve,
+    copy as copy,
+    correlate as correlate,
+    cos as cos,
+    cosh as cosh,
+    count as count,
+    cumprod as cumprod,
+    cumsum as cumsum,
+    default_fill_value as default_fill_value,
+    diag as diag,
+    diagonal as diagonal,
+    diff as diff,
+    divide as divide,
+    empty as empty,
+    empty_like as empty_like,
+    equal as equal,
+    exp as exp,
+    expand_dims as expand_dims,
+    fabs as fabs,
+    filled as filled,
+    fix_invalid as fix_invalid,
+    flatten_mask as flatten_mask,
+    flatten_structured_array as flatten_structured_array,
+    floor as floor,
+    floor_divide as floor_divide,
+    fmod as fmod,
+    frombuffer as frombuffer,
+    fromflex as fromflex,
+    fromfunction as fromfunction,
+    getdata as getdata,
+    getmask as getmask,
+    getmaskarray as getmaskarray,
+    greater as greater,
+    greater_equal as greater_equal,
+    harden_mask as harden_mask,
+    hypot as hypot,
+    identity as identity,
+    ids as ids,
+    indices as indices,
+    inner as inner,
+    innerproduct as innerproduct,
+    isMA as isMA,
+    isMaskedArray as isMaskedArray,
+    is_mask as is_mask,
+    is_masked as is_masked,
+    isarray as isarray,
+    left_shift as left_shift,
+    less as less,
+    less_equal as less_equal,
+    log as log,
+    log10 as log10,
+    log2 as log2,
+    logical_and as logical_and,
+    logical_not as logical_not,
+    logical_or as logical_or,
+    logical_xor as logical_xor,
+    make_mask as make_mask,
+    make_mask_descr as make_mask_descr,
+    make_mask_none as make_mask_none,
+    mask_or as mask_or,
+    masked as masked,
+    masked_array as masked_array,
+    masked_equal as masked_equal,
+    masked_greater as masked_greater,
+    masked_greater_equal as masked_greater_equal,
+    masked_inside as masked_inside,
+    masked_invalid as masked_invalid,
+    masked_less as masked_less,
+    masked_less_equal as masked_less_equal,
+    masked_not_equal as masked_not_equal,
+    masked_object as masked_object,
+    masked_outside as masked_outside,
+    masked_print_option as masked_print_option,
+    masked_singleton as masked_singleton,
+    masked_values as masked_values,
+    masked_where as masked_where,
+    max as max,
+    maximum as maximum,
+    maximum_fill_value as maximum_fill_value,
+    mean as mean,
+    min as min,
+    minimum as minimum,
+    minimum_fill_value as minimum_fill_value,
+    mod as mod,
+    multiply as multiply,
+    mvoid as mvoid,
+    ndim as ndim,
+    negative as negative,
+    nomask as nomask,
+    nonzero as nonzero,
+    not_equal as not_equal,
+    ones as ones,
+    outer as outer,
+    outerproduct as outerproduct,
+    power as power,
+    prod as prod,
+    product as product,
+    ptp as ptp,
+    put as put,
+    putmask as putmask,
+    ravel as ravel,
+    remainder as remainder,
+    repeat as repeat,
+    reshape as reshape,
+    resize as resize,
+    right_shift as right_shift,
+    round as round,
+    set_fill_value as set_fill_value,
+    shape as shape,
+    sin as sin,
+    sinh as sinh,
+    size as size,
+    soften_mask as soften_mask,
+    sometrue as sometrue,
+    sort as sort,
+    sqrt as sqrt,
+    squeeze as squeeze,
+    std as std,
+    subtract as subtract,
+    sum as sum,
+    swapaxes as swapaxes,
+    take as take,
+    tan as tan,
+    tanh as tanh,
+    trace as trace,
+    transpose as transpose,
+    true_divide as true_divide,
+    var as var,
+    where as where,
+    zeros as zeros,
+)
+
+from numpy.ma.extras import (
+    apply_along_axis as apply_along_axis,
+    apply_over_axes as apply_over_axes,
+    atleast_1d as atleast_1d,
+    atleast_2d as atleast_2d,
+    atleast_3d as atleast_3d,
+    average as average,
+    clump_masked as clump_masked,
+    clump_unmasked as clump_unmasked,
+    column_stack as column_stack,
+    compress_cols as compress_cols,
+    compress_nd as compress_nd,
+    compress_rowcols as compress_rowcols,
+    compress_rows as compress_rows,
+    count_masked as count_masked,
+    corrcoef as corrcoef,
+    cov as cov,
+    diagflat as diagflat,
+    dot as dot,
+    dstack as dstack,
+    ediff1d as ediff1d,
+    flatnotmasked_contiguous as flatnotmasked_contiguous,
+    flatnotmasked_edges as flatnotmasked_edges,
+    hsplit as hsplit,
+    hstack as hstack,
+    isin as isin,
+    in1d as in1d,
+    intersect1d as intersect1d,
+    mask_cols as mask_cols,
+    mask_rowcols as mask_rowcols,
+    mask_rows as mask_rows,
+    masked_all as masked_all,
+    masked_all_like as masked_all_like,
+    median as median,
+    mr_ as mr_,
+    ndenumerate as ndenumerate,
+    notmasked_contiguous as notmasked_contiguous,
+    notmasked_edges as notmasked_edges,
+    polyfit as polyfit,
+    row_stack as row_stack,
+    setdiff1d as setdiff1d,
+    setxor1d as setxor1d,
+    stack as stack,
+    unique as unique,
+    union1d as union1d,
+    vander as vander,
+    vstack as vstack,
+)
+
+__all__: list[str]
+__path__: list[str]
+test: PytestTester
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/core.py b/.venv/lib/python3.12/site-packages/numpy/ma/core.py
new file mode 100644
index 00000000..16f74e89
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/core.py
@@ -0,0 +1,8565 @@
+"""
+numpy.ma : a package to handle missing or invalid values.
+
+This package was initially written for numarray by Paul F. Dubois
+at Lawrence Livermore National Laboratory.
+In 2006, the package was completely rewritten by Pierre Gerard-Marchant
+(University of Georgia) to make the MaskedArray class a subclass of ndarray,
+and to improve support of structured arrays.
+
+
+Copyright 1999, 2000, 2001 Regents of the University of California.
+Released for unlimited redistribution.
+
+* Adapted for numpy_core 2005 by Travis Oliphant and (mainly) Paul Dubois.
+* Subclassing of the base `ndarray` 2006 by Pierre Gerard-Marchant
+  (pgmdevlist_AT_gmail_DOT_com)
+* Improvements suggested by Reggie Dugard (reggie_AT_merfinllc_DOT_com)
+
+.. moduleauthor:: Pierre Gerard-Marchant
+
+"""
+# pylint: disable-msg=E1002
+import builtins
+import inspect
+import operator
+import warnings
+import textwrap
+import re
+from functools import reduce
+
+import numpy as np
+import numpy.core.umath as umath
+import numpy.core.numerictypes as ntypes
+from numpy.core import multiarray as mu
+from numpy import ndarray, amax, amin, iscomplexobj, bool_, _NoValue
+from numpy import array as narray
+from numpy.lib.function_base import angle
+from numpy.compat import (
+    getargspec, formatargspec, long, unicode, bytes
+    )
+from numpy import expand_dims
+from numpy.core.numeric import normalize_axis_tuple
+
+
+__all__ = [
+    'MAError', 'MaskError', 'MaskType', 'MaskedArray', 'abs', 'absolute',
+    'add', 'all', 'allclose', 'allequal', 'alltrue', 'amax', 'amin',
+    'angle', 'anom', 'anomalies', 'any', 'append', 'arange', 'arccos',
+    'arccosh', 'arcsin', 'arcsinh', 'arctan', 'arctan2', 'arctanh',
+    'argmax', 'argmin', 'argsort', 'around', 'array', 'asanyarray',
+    'asarray', 'bitwise_and', 'bitwise_or', 'bitwise_xor', 'bool_', 'ceil',
+    'choose', 'clip', 'common_fill_value', 'compress', 'compressed',
+    'concatenate', 'conjugate', 'convolve', 'copy', 'correlate', 'cos', 'cosh',
+    'count', 'cumprod', 'cumsum', 'default_fill_value', 'diag', 'diagonal',
+    'diff', 'divide', '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',
+    'identity', 'ids', 'indices', 'inner', 'innerproduct', 'isMA',
+    'isMaskedArray', 'is_mask', 'is_masked', 'isarray', 'left_shift',
+    'less', 'less_equal', 'log', 'log10', 'log2',
+    'logical_and', 'logical_not', 'logical_or', 'logical_xor', 'make_mask',
+    'make_mask_descr', 'make_mask_none', 'mask_or', 'masked',
+    'masked_array', 'masked_equal', 'masked_greater',
+    'masked_greater_equal', 'masked_inside', 'masked_invalid',
+    'masked_less', 'masked_less_equal', 'masked_not_equal',
+    'masked_object', 'masked_outside', 'masked_print_option',
+    'masked_singleton', 'masked_values', 'masked_where', 'max', 'maximum',
+    'maximum_fill_value', 'mean', 'min', 'minimum', 'minimum_fill_value',
+    'mod', 'multiply', 'mvoid', 'ndim', 'negative', 'nomask', 'nonzero',
+    'not_equal', 'ones', 'ones_like', 'outer', 'outerproduct', 'power', 'prod',
+    'product', 'ptp', 'put', 'putmask', 'ravel', 'remainder',
+    'repeat', 'reshape', 'resize', 'right_shift', 'round', 'round_',
+    'set_fill_value', 'shape', 'sin', 'sinh', 'size', 'soften_mask',
+    'sometrue', 'sort', 'sqrt', 'squeeze', 'std', 'subtract', 'sum',
+    'swapaxes', 'take', 'tan', 'tanh', 'trace', 'transpose', 'true_divide',
+    'var', 'where', 'zeros', 'zeros_like',
+    ]
+
+MaskType = np.bool_
+nomask = MaskType(0)
+
+class MaskedArrayFutureWarning(FutureWarning):
+    pass
+
+def _deprecate_argsort_axis(arr):
+    """
+    Adjust the axis passed to argsort, warning if necessary
+
+    Parameters
+    ----------
+    arr
+        The array which argsort was called on
+
+    np.ma.argsort has a long-term bug where the default of the axis argument
+    is wrong (gh-8701), which now must be kept for backwards compatibility.
+    Thankfully, this only makes a difference when arrays are 2- or more-
+    dimensional, so we only need a warning then.
+    """
+    if arr.ndim <= 1:
+        # no warning needed - but switch to -1 anyway, to avoid surprising
+        # subclasses, which are more likely to implement scalar axes.
+        return -1
+    else:
+        # 2017-04-11, Numpy 1.13.0, gh-8701: warn on axis default
+        warnings.warn(
+            "In the future the default for argsort will be axis=-1, not the "
+            "current None, to match its documentation and np.argsort. "
+            "Explicitly pass -1 or None to silence this warning.",
+            MaskedArrayFutureWarning, stacklevel=3)
+        return None
+
+
+def doc_note(initialdoc, note):
+    """
+    Adds a Notes section to an existing docstring.
+
+    """
+    if initialdoc is None:
+        return
+    if note is None:
+        return initialdoc
+
+    notesplit = re.split(r'\n\s*?Notes\n\s*?-----', inspect.cleandoc(initialdoc))
+    notedoc = "\n\nNotes\n-----\n%s\n" % inspect.cleandoc(note)
+
+    return ''.join(notesplit[:1] + [notedoc] + notesplit[1:])
+
+
+def get_object_signature(obj):
+    """
+    Get the signature from obj
+
+    """
+    try:
+        sig = formatargspec(*getargspec(obj))
+    except TypeError:
+        sig = ''
+    return sig
+
+
+###############################################################################
+#                              Exceptions                                     #
+###############################################################################
+
+
+class MAError(Exception):
+    """
+    Class for masked array related errors.
+
+    """
+    pass
+
+
+class MaskError(MAError):
+    """
+    Class for mask related errors.
+
+    """
+    pass
+
+
+###############################################################################
+#                           Filling options                                   #
+###############################################################################
+
+
+# b: boolean - c: complex - f: floats - i: integer - O: object - S: string
+default_filler = {'b': True,
+                  'c': 1.e20 + 0.0j,
+                  'f': 1.e20,
+                  'i': 999999,
+                  'O': '?',
+                  'S': b'N/A',
+                  'u': 999999,
+                  'V': b'???',
+                  'U': 'N/A'
+                  }
+
+# Add datetime64 and timedelta64 types
+for v in ["Y", "M", "W", "D", "h", "m", "s", "ms", "us", "ns", "ps",
+          "fs", "as"]:
+    default_filler["M8[" + v + "]"] = np.datetime64("NaT", v)
+    default_filler["m8[" + v + "]"] = np.timedelta64("NaT", v)
+
+float_types_list = [np.half, np.single, np.double, np.longdouble,
+                    np.csingle, np.cdouble, np.clongdouble]
+max_filler = ntypes._minvals
+max_filler.update([(k, -np.inf) for k in float_types_list[:4]])
+max_filler.update([(k, complex(-np.inf, -np.inf)) for k in float_types_list[-3:]])
+
+min_filler = ntypes._maxvals
+min_filler.update([(k,  +np.inf) for k in float_types_list[:4]])
+min_filler.update([(k, complex(+np.inf, +np.inf)) for k in float_types_list[-3:]])
+
+del float_types_list
+
+def _recursive_fill_value(dtype, f):
+    """
+    Recursively produce a fill value for `dtype`, calling f on scalar dtypes
+    """
+    if dtype.names is not None:
+        # We wrap into `array` here, which ensures we use NumPy cast rules
+        # for integer casts, this allows the use of 99999 as a fill value
+        # for int8.
+        # TODO: This is probably a mess, but should best preserve behavior?
+        vals = tuple(
+                np.array(_recursive_fill_value(dtype[name], f))
+                for name in dtype.names)
+        return np.array(vals, dtype=dtype)[()]  # decay to void scalar from 0d
+    elif dtype.subdtype:
+        subtype, shape = dtype.subdtype
+        subval = _recursive_fill_value(subtype, f)
+        return np.full(shape, subval)
+    else:
+        return f(dtype)
+
+
+def _get_dtype_of(obj):
+    """ Convert the argument for *_fill_value into a dtype """
+    if isinstance(obj, np.dtype):
+        return obj
+    elif hasattr(obj, 'dtype'):
+        return obj.dtype
+    else:
+        return np.asanyarray(obj).dtype
+
+
+def default_fill_value(obj):
+    """
+    Return the default fill value for the argument object.
+
+    The default filling value depends on the datatype of the input
+    array or the type of the input scalar:
+
+       ========  ========
+       datatype  default
+       ========  ========
+       bool      True
+       int       999999
+       float     1.e20
+       complex   1.e20+0j
+       object    '?'
+       string    'N/A'
+       ========  ========
+
+    For structured types, a structured scalar is returned, with each field the
+    default fill value for its type.
+
+    For subarray types, the fill value is an array of the same size containing
+    the default scalar fill value.
+
+    Parameters
+    ----------
+    obj : ndarray, dtype or scalar
+        The array data-type or scalar for which the default fill value
+        is returned.
+
+    Returns
+    -------
+    fill_value : scalar
+        The default fill value.
+
+    Examples
+    --------
+    >>> np.ma.default_fill_value(1)
+    999999
+    >>> np.ma.default_fill_value(np.array([1.1, 2., np.pi]))
+    1e+20
+    >>> np.ma.default_fill_value(np.dtype(complex))
+    (1e+20+0j)
+
+    """
+    def _scalar_fill_value(dtype):
+        if dtype.kind in 'Mm':
+            return default_filler.get(dtype.str[1:], '?')
+        else:
+            return default_filler.get(dtype.kind, '?')
+
+    dtype = _get_dtype_of(obj)
+    return _recursive_fill_value(dtype, _scalar_fill_value)
+
+
+def _extremum_fill_value(obj, extremum, extremum_name):
+
+    def _scalar_fill_value(dtype):
+        try:
+            return extremum[dtype]
+        except KeyError as e:
+            raise TypeError(
+                f"Unsuitable type {dtype} for calculating {extremum_name}."
+            ) from None
+
+    dtype = _get_dtype_of(obj)
+    return _recursive_fill_value(dtype, _scalar_fill_value)
+
+
+def minimum_fill_value(obj):
+    """
+    Return the maximum value that can be represented by the dtype of an object.
+
+    This function is useful for calculating a fill value suitable for
+    taking the minimum of an array with a given dtype.
+
+    Parameters
+    ----------
+    obj : ndarray, dtype or scalar
+        An object that can be queried for it's numeric type.
+
+    Returns
+    -------
+    val : scalar
+        The maximum representable value.
+
+    Raises
+    ------
+    TypeError
+        If `obj` isn't a suitable numeric type.
+
+    See Also
+    --------
+    maximum_fill_value : The inverse function.
+    set_fill_value : Set the filling value of a masked array.
+    MaskedArray.fill_value : Return current fill value.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.int8()
+    >>> ma.minimum_fill_value(a)
+    127
+    >>> a = np.int32()
+    >>> ma.minimum_fill_value(a)
+    2147483647
+
+    An array of numeric data can also be passed.
+
+    >>> a = np.array([1, 2, 3], dtype=np.int8)
+    >>> ma.minimum_fill_value(a)
+    127
+    >>> a = np.array([1, 2, 3], dtype=np.float32)
+    >>> ma.minimum_fill_value(a)
+    inf
+
+    """
+    return _extremum_fill_value(obj, min_filler, "minimum")
+
+
+def maximum_fill_value(obj):
+    """
+    Return the minimum value that can be represented by the dtype of an object.
+
+    This function is useful for calculating a fill value suitable for
+    taking the maximum of an array with a given dtype.
+
+    Parameters
+    ----------
+    obj : ndarray, dtype or scalar
+        An object that can be queried for it's numeric type.
+
+    Returns
+    -------
+    val : scalar
+        The minimum representable value.
+
+    Raises
+    ------
+    TypeError
+        If `obj` isn't a suitable numeric type.
+
+    See Also
+    --------
+    minimum_fill_value : The inverse function.
+    set_fill_value : Set the filling value of a masked array.
+    MaskedArray.fill_value : Return current fill value.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.int8()
+    >>> ma.maximum_fill_value(a)
+    -128
+    >>> a = np.int32()
+    >>> ma.maximum_fill_value(a)
+    -2147483648
+
+    An array of numeric data can also be passed.
+
+    >>> a = np.array([1, 2, 3], dtype=np.int8)
+    >>> ma.maximum_fill_value(a)
+    -128
+    >>> a = np.array([1, 2, 3], dtype=np.float32)
+    >>> ma.maximum_fill_value(a)
+    -inf
+
+    """
+    return _extremum_fill_value(obj, max_filler, "maximum")
+
+
+def _recursive_set_fill_value(fillvalue, dt):
+    """
+    Create a fill value for a structured dtype.
+
+    Parameters
+    ----------
+    fillvalue : scalar or array_like
+        Scalar or array representing the fill value. If it is of shorter
+        length than the number of fields in dt, it will be resized.
+    dt : dtype
+        The structured dtype for which to create the fill value.
+
+    Returns
+    -------
+    val : tuple
+        A tuple of values corresponding to the structured fill value.
+
+    """
+    fillvalue = np.resize(fillvalue, len(dt.names))
+    output_value = []
+    for (fval, name) in zip(fillvalue, dt.names):
+        cdtype = dt[name]
+        if cdtype.subdtype:
+            cdtype = cdtype.subdtype[0]
+
+        if cdtype.names is not None:
+            output_value.append(tuple(_recursive_set_fill_value(fval, cdtype)))
+        else:
+            output_value.append(np.array(fval, dtype=cdtype).item())
+    return tuple(output_value)
+
+
+def _check_fill_value(fill_value, ndtype):
+    """
+    Private function validating the given `fill_value` for the given dtype.
+
+    If fill_value is None, it is set to the default corresponding to the dtype.
+
+    If fill_value is not None, its value is forced to the given dtype.
+
+    The result is always a 0d array.
+
+    """
+    ndtype = np.dtype(ndtype)
+    if fill_value is None:
+        fill_value = default_fill_value(ndtype)
+    elif ndtype.names is not None:
+        if isinstance(fill_value, (ndarray, np.void)):
+            try:
+                fill_value = np.array(fill_value, copy=False, dtype=ndtype)
+            except ValueError as e:
+                err_msg = "Unable to transform %s to dtype %s"
+                raise ValueError(err_msg % (fill_value, ndtype)) from e
+        else:
+            fill_value = np.asarray(fill_value, dtype=object)
+            fill_value = np.array(_recursive_set_fill_value(fill_value, ndtype),
+                                  dtype=ndtype)
+    else:
+        if isinstance(fill_value, str) and (ndtype.char not in 'OSVU'):
+            # Note this check doesn't work if fill_value is not a scalar
+            err_msg = "Cannot set fill value of string with array of dtype %s"
+            raise TypeError(err_msg % ndtype)
+        else:
+            # In case we want to convert 1e20 to int.
+            # Also in case of converting string arrays.
+            try:
+                fill_value = np.array(fill_value, copy=False, dtype=ndtype)
+            except (OverflowError, ValueError) as e:
+                # Raise TypeError instead of OverflowError or ValueError.
+                # OverflowError is seldom used, and the real problem here is
+                # that the passed fill_value is not compatible with the ndtype.
+                err_msg = "Cannot convert fill_value %s to dtype %s"
+                raise TypeError(err_msg % (fill_value, ndtype)) from e
+    return np.array(fill_value)
+
+
+def set_fill_value(a, fill_value):
+    """
+    Set the filling value of a, if a is a masked array.
+
+    This function changes the fill value of the masked array `a` in place.
+    If `a` is not a masked array, the function returns silently, without
+    doing anything.
+
+    Parameters
+    ----------
+    a : array_like
+        Input array.
+    fill_value : dtype
+        Filling value. A consistency test is performed to make sure
+        the value is compatible with the dtype of `a`.
+
+    Returns
+    -------
+    None
+        Nothing returned by this function.
+
+    See Also
+    --------
+    maximum_fill_value : Return the default fill value for a dtype.
+    MaskedArray.fill_value : Return current fill value.
+    MaskedArray.set_fill_value : Equivalent method.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(5)
+    >>> a
+    array([0, 1, 2, 3, 4])
+    >>> a = ma.masked_where(a < 3, a)
+    >>> a
+    masked_array(data=[--, --, --, 3, 4],
+                 mask=[ True,  True,  True, False, False],
+           fill_value=999999)
+    >>> ma.set_fill_value(a, -999)
+    >>> a
+    masked_array(data=[--, --, --, 3, 4],
+                 mask=[ True,  True,  True, False, False],
+           fill_value=-999)
+
+    Nothing happens if `a` is not a masked array.
+
+    >>> a = list(range(5))
+    >>> a
+    [0, 1, 2, 3, 4]
+    >>> ma.set_fill_value(a, 100)
+    >>> a
+    [0, 1, 2, 3, 4]
+    >>> a = np.arange(5)
+    >>> a
+    array([0, 1, 2, 3, 4])
+    >>> ma.set_fill_value(a, 100)
+    >>> a
+    array([0, 1, 2, 3, 4])
+
+    """
+    if isinstance(a, MaskedArray):
+        a.set_fill_value(fill_value)
+    return
+
+
+def get_fill_value(a):
+    """
+    Return the filling value of a, if any.  Otherwise, returns the
+    default filling value for that type.
+
+    """
+    if isinstance(a, MaskedArray):
+        result = a.fill_value
+    else:
+        result = default_fill_value(a)
+    return result
+
+
+def common_fill_value(a, b):
+    """
+    Return the common filling value of two masked arrays, if any.
+
+    If ``a.fill_value == b.fill_value``, return the fill value,
+    otherwise return None.
+
+    Parameters
+    ----------
+    a, b : MaskedArray
+        The masked arrays for which to compare fill values.
+
+    Returns
+    -------
+    fill_value : scalar or None
+        The common fill value, or None.
+
+    Examples
+    --------
+    >>> x = np.ma.array([0, 1.], fill_value=3)
+    >>> y = np.ma.array([0, 1.], fill_value=3)
+    >>> np.ma.common_fill_value(x, y)
+    3.0
+
+    """
+    t1 = get_fill_value(a)
+    t2 = get_fill_value(b)
+    if t1 == t2:
+        return t1
+    return None
+
+
+def filled(a, fill_value=None):
+    """
+    Return input as an array with masked data replaced by a fill value.
+
+    If `a` is not a `MaskedArray`, `a` itself is returned.
+    If `a` is a `MaskedArray` and `fill_value` is None, `fill_value` is set to
+    ``a.fill_value``.
+
+    Parameters
+    ----------
+    a : MaskedArray or array_like
+        An input object.
+    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
+    -------
+    a : ndarray
+        The filled array.
+
+    See Also
+    --------
+    compressed
+
+    Examples
+    --------
+    >>> x = np.ma.array(np.arange(9).reshape(3, 3), mask=[[1, 0, 0],
+    ...                                                   [1, 0, 0],
+    ...                                                   [0, 0, 0]])
+    >>> x.filled()
+    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
+    elif isinstance(a, dict):
+        return np.array(a, 'O')
+    else:
+        return np.array(a)
+
+
+def get_masked_subclass(*arrays):
+    """
+    Return the youngest subclass of MaskedArray from a list of (masked) arrays.
+
+    In case of siblings, the first listed takes over.
+
+    """
+    if len(arrays) == 1:
+        arr = arrays[0]
+        if isinstance(arr, MaskedArray):
+            rcls = type(arr)
+        else:
+            rcls = MaskedArray
+    else:
+        arrcls = [type(a) for a in arrays]
+        rcls = arrcls[0]
+        if not issubclass(rcls, MaskedArray):
+            rcls = MaskedArray
+        for cls in arrcls[1:]:
+            if issubclass(cls, rcls):
+                rcls = cls
+    # Don't return MaskedConstant as result: revert to MaskedArray
+    if rcls.__name__ == 'MaskedConstant':
+        return MaskedArray
+    return rcls
+
+
+def getdata(a, subok=True):
+    """
+    Return the data of a masked array as an ndarray.
+
+    Return the data of `a` (if any) as an ndarray if `a` is a ``MaskedArray``,
+    else return `a` as a ndarray or subclass (depending on `subok`) if not.
+
+    Parameters
+    ----------
+    a : array_like
+        Input ``MaskedArray``, alternatively a ndarray or a subclass thereof.
+    subok : bool
+        Whether to force the output to be a `pure` ndarray (False) or to
+        return a subclass of ndarray if appropriate (True, default).
+
+    See Also
+    --------
+    getmask : Return the mask of a masked array, or nomask.
+    getmaskarray : Return the mask of a masked array, or full array of False.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = ma.masked_equal([[1,2],[3,4]], 2)
+    >>> a
+    masked_array(
+      data=[[1, --],
+            [3, 4]],
+      mask=[[False,  True],
+            [False, False]],
+      fill_value=2)
+    >>> ma.getdata(a)
+    array([[1, 2],
+           [3, 4]])
+
+    Equivalently use the ``MaskedArray`` `data` attribute.
+
+    >>> a.data
+    array([[1, 2],
+           [3, 4]])
+
+    """
+    try:
+        data = a._data
+    except AttributeError:
+        data = np.array(a, copy=False, subok=subok)
+    if not subok:
+        return data.view(ndarray)
+    return data
+
+
+get_data = getdata
+
+
+def fix_invalid(a, mask=nomask, copy=True, fill_value=None):
+    """
+    Return input with invalid data masked and replaced by a fill value.
+
+    Invalid data means values of `nan`, `inf`, etc.
+
+    Parameters
+    ----------
+    a : array_like
+        Input array, a (subclass of) ndarray.
+    mask : sequence, optional
+        Mask. Must be convertible to an array of booleans with the same
+        shape as `data`. True indicates a masked (i.e. invalid) data.
+    copy : bool, optional
+        Whether to use a copy of `a` (True) or to fix `a` in place (False).
+        Default is True.
+    fill_value : scalar, optional
+        Value used for fixing invalid data. Default is None, in which case
+        the ``a.fill_value`` is used.
+
+    Returns
+    -------
+    b : MaskedArray
+        The input array with invalid entries fixed.
+
+    Notes
+    -----
+    A copy is performed by default.
+
+    Examples
+    --------
+    >>> x = np.ma.array([1., -1, np.nan, np.inf], mask=[1] + [0]*3)
+    >>> x
+    masked_array(data=[--, -1.0, nan, inf],
+                 mask=[ True, False, False, False],
+           fill_value=1e+20)
+    >>> np.ma.fix_invalid(x)
+    masked_array(data=[--, -1.0, --, --],
+                 mask=[ True, False,  True,  True],
+           fill_value=1e+20)
+
+    >>> fixed = np.ma.fix_invalid(x)
+    >>> fixed.data
+    array([ 1.e+00, -1.e+00,  1.e+20,  1.e+20])
+    >>> x.data
+    array([ 1., -1., nan, inf])
+
+    """
+    a = masked_array(a, copy=copy, mask=mask, subok=True)
+    invalid = np.logical_not(np.isfinite(a._data))
+    if not invalid.any():
+        return a
+    a._mask |= invalid
+    if fill_value is None:
+        fill_value = a.fill_value
+    a._data[invalid] = fill_value
+    return a
+
+def is_string_or_list_of_strings(val):
+    return (isinstance(val, str) or
+            (isinstance(val, list) and val and
+             builtins.all(isinstance(s, str) for s in val)))
+
+###############################################################################
+#                                  Ufuncs                                     #
+###############################################################################
+
+
+ufunc_domain = {}
+ufunc_fills = {}
+
+
+class _DomainCheckInterval:
+    """
+    Define a valid interval, so that :
+
+    ``domain_check_interval(a,b)(x) == True`` where
+    ``x < a`` or ``x > b``.
+
+    """
+
+    def __init__(self, a, b):
+        "domain_check_interval(a,b)(x) = true where x < a or y > b"
+        if a > b:
+            (a, b) = (b, a)
+        self.a = a
+        self.b = b
+
+    def __call__(self, x):
+        "Execute the call behavior."
+        # nans at masked positions cause RuntimeWarnings, even though
+        # they are masked. To avoid this we suppress warnings.
+        with np.errstate(invalid='ignore'):
+            return umath.logical_or(umath.greater(x, self.b),
+                                    umath.less(x, self.a))
+
+
+class _DomainTan:
+    """
+    Define a valid interval for the `tan` function, so that:
+
+    ``domain_tan(eps) = True`` where ``abs(cos(x)) < eps``
+
+    """
+
+    def __init__(self, eps):
+        "domain_tan(eps) = true where abs(cos(x)) < eps)"
+        self.eps = eps
+
+    def __call__(self, x):
+        "Executes the call behavior."
+        with np.errstate(invalid='ignore'):
+            return umath.less(umath.absolute(umath.cos(x)), self.eps)
+
+
+class _DomainSafeDivide:
+    """
+    Define a domain for safe division.
+
+    """
+
+    def __init__(self, tolerance=None):
+        self.tolerance = tolerance
+
+    def __call__(self, a, b):
+        # Delay the selection of the tolerance to here in order to reduce numpy
+        # import times. The calculation of these parameters is a substantial
+        # component of numpy's import time.
+        if self.tolerance is None:
+            self.tolerance = np.finfo(float).tiny
+        # don't call ma ufuncs from __array_wrap__ which would fail for scalars
+        a, b = np.asarray(a), np.asarray(b)
+        with np.errstate(invalid='ignore'):
+            return umath.absolute(a) * self.tolerance >= umath.absolute(b)
+
+
+class _DomainGreater:
+    """
+    DomainGreater(v)(x) is True where x <= v.
+
+    """
+
+    def __init__(self, critical_value):
+        "DomainGreater(v)(x) = true where x <= v"
+        self.critical_value = critical_value
+
+    def __call__(self, x):
+        "Executes the call behavior."
+        with np.errstate(invalid='ignore'):
+            return umath.less_equal(x, self.critical_value)
+
+
+class _DomainGreaterEqual:
+    """
+    DomainGreaterEqual(v)(x) is True where x < v.
+
+    """
+
+    def __init__(self, critical_value):
+        "DomainGreaterEqual(v)(x) = true where x < v"
+        self.critical_value = critical_value
+
+    def __call__(self, x):
+        "Executes the call behavior."
+        with np.errstate(invalid='ignore'):
+            return umath.less(x, self.critical_value)
+
+
+class _MaskedUFunc:
+    def __init__(self, ufunc):
+        self.f = ufunc
+        self.__doc__ = ufunc.__doc__
+        self.__name__ = ufunc.__name__
+
+    def __str__(self):
+        return f"Masked version of {self.f}"
+
+
+class _MaskedUnaryOperation(_MaskedUFunc):
+    """
+    Defines masked version of unary operations, where invalid values are
+    pre-masked.
+
+    Parameters
+    ----------
+    mufunc : callable
+        The function for which to define a masked version. Made available
+        as ``_MaskedUnaryOperation.f``.
+    fill : scalar, optional
+        Filling value, default is 0.
+    domain : class instance
+        Domain for the function. Should be one of the ``_Domain*``
+        classes. Default is None.
+
+    """
+
+    def __init__(self, mufunc, fill=0, domain=None):
+        super().__init__(mufunc)
+        self.fill = fill
+        self.domain = domain
+        ufunc_domain[mufunc] = domain
+        ufunc_fills[mufunc] = fill
+
+    def __call__(self, a, *args, **kwargs):
+        """
+        Execute the call behavior.
+
+        """
+        d = getdata(a)
+        # Deal with domain
+        if self.domain is not None:
+            # Case 1.1. : Domained function
+            # nans at masked positions cause RuntimeWarnings, even though
+            # they are masked. To avoid this we suppress warnings.
+            with np.errstate(divide='ignore', invalid='ignore'):
+                result = self.f(d, *args, **kwargs)
+            # Make a mask
+            m = ~umath.isfinite(result)
+            m |= self.domain(d)
+            m |= getmask(a)
+        else:
+            # Case 1.2. : Function without a domain
+            # Get the result and the mask
+            with np.errstate(divide='ignore', invalid='ignore'):
+                result = self.f(d, *args, **kwargs)
+            m = getmask(a)
+
+        if not result.ndim:
+            # Case 2.1. : The result is scalarscalar
+            if m:
+                return masked
+            return result
+
+        if m is not nomask:
+            # Case 2.2. The result is an array
+            # We need to fill the invalid data back w/ the input Now,
+            # that's plain silly: in C, we would just skip the element and
+            # keep the original, but we do have to do it that way in Python
+
+            # In case result has a lower dtype than the inputs (as in
+            # equal)
+            try:
+                np.copyto(result, d, where=m)
+            except TypeError:
+                pass
+        # Transform to
+        masked_result = result.view(get_masked_subclass(a))
+        masked_result._mask = m
+        masked_result._update_from(a)
+        return masked_result
+
+
+class _MaskedBinaryOperation(_MaskedUFunc):
+    """
+    Define masked version of binary operations, where invalid
+    values are pre-masked.
+
+    Parameters
+    ----------
+    mbfunc : function
+        The function for which to define a masked version. Made available
+        as ``_MaskedBinaryOperation.f``.
+    domain : class instance
+        Default domain for the function. Should be one of the ``_Domain*``
+        classes. Default is None.
+    fillx : scalar, optional
+        Filling value for the first argument, default is 0.
+    filly : scalar, optional
+        Filling value for the second argument, default is 0.
+
+    """
+
+    def __init__(self, mbfunc, fillx=0, filly=0):
+        """
+        abfunc(fillx, filly) must be defined.
+
+        abfunc(x, filly) = x for all x to enable reduce.
+
+        """
+        super().__init__(mbfunc)
+        self.fillx = fillx
+        self.filly = filly
+        ufunc_domain[mbfunc] = None
+        ufunc_fills[mbfunc] = (fillx, filly)
+
+    def __call__(self, a, b, *args, **kwargs):
+        """
+        Execute the call behavior.
+
+        """
+        # Get the data, as ndarray
+        (da, db) = (getdata(a), getdata(b))
+        # Get the result
+        with np.errstate():
+            np.seterr(divide='ignore', invalid='ignore')
+            result = self.f(da, db, *args, **kwargs)
+        # Get the mask for the result
+        (ma, mb) = (getmask(a), getmask(b))
+        if ma is nomask:
+            if mb is nomask:
+                m = nomask
+            else:
+                m = umath.logical_or(getmaskarray(a), mb)
+        elif mb is nomask:
+            m = umath.logical_or(ma, getmaskarray(b))
+        else:
+            m = umath.logical_or(ma, mb)
+
+        # Case 1. : scalar
+        if not result.ndim:
+            if m:
+                return masked
+            return result
+
+        # Case 2. : array
+        # Revert result to da where masked
+        if m is not nomask and m.any():
+            # any errors, just abort; impossible to guarantee masked values
+            try:
+                np.copyto(result, da, casting='unsafe', where=m)
+            except Exception:
+                pass
+
+        # Transforms to a (subclass of) MaskedArray
+        masked_result = result.view(get_masked_subclass(a, b))
+        masked_result._mask = m
+        if isinstance(a, MaskedArray):
+            masked_result._update_from(a)
+        elif isinstance(b, MaskedArray):
+            masked_result._update_from(b)
+        return masked_result
+
+    def reduce(self, target, axis=0, dtype=None):
+        """
+        Reduce `target` along the given `axis`.
+
+        """
+        tclass = get_masked_subclass(target)
+        m = getmask(target)
+        t = filled(target, self.filly)
+        if t.shape == ():
+            t = t.reshape(1)
+            if m is not nomask:
+                m = make_mask(m, copy=True)
+                m.shape = (1,)
+
+        if m is nomask:
+            tr = self.f.reduce(t, axis)
+            mr = nomask
+        else:
+            tr = self.f.reduce(t, axis, dtype=dtype)
+            mr = umath.logical_and.reduce(m, axis)
+
+        if not tr.shape:
+            if mr:
+                return masked
+            else:
+                return tr
+        masked_tr = tr.view(tclass)
+        masked_tr._mask = mr
+        return masked_tr
+
+    def outer(self, a, b):
+        """
+        Return the function applied to the outer product of a and b.
+
+        """
+        (da, db) = (getdata(a), getdata(b))
+        d = self.f.outer(da, db)
+        ma = getmask(a)
+        mb = getmask(b)
+        if ma is nomask and mb is nomask:
+            m = nomask
+        else:
+            ma = getmaskarray(a)
+            mb = getmaskarray(b)
+            m = umath.logical_or.outer(ma, mb)
+        if (not m.ndim) and m:
+            return masked
+        if m is not nomask:
+            np.copyto(d, da, where=m)
+        if not d.shape:
+            return d
+        masked_d = d.view(get_masked_subclass(a, b))
+        masked_d._mask = m
+        return masked_d
+
+    def accumulate(self, target, axis=0):
+        """Accumulate `target` along `axis` after filling with y fill
+        value.
+
+        """
+        tclass = get_masked_subclass(target)
+        t = filled(target, self.filly)
+        result = self.f.accumulate(t, axis)
+        masked_result = result.view(tclass)
+        return masked_result
+
+
+
+class _DomainedBinaryOperation(_MaskedUFunc):
+    """
+    Define binary operations that have a domain, like divide.
+
+    They have no reduce, outer or accumulate.
+
+    Parameters
+    ----------
+    mbfunc : function
+        The function for which to define a masked version. Made available
+        as ``_DomainedBinaryOperation.f``.
+    domain : class instance
+        Default domain for the function. Should be one of the ``_Domain*``
+        classes.
+    fillx : scalar, optional
+        Filling value for the first argument, default is 0.
+    filly : scalar, optional
+        Filling value for the second argument, default is 0.
+
+    """
+
+    def __init__(self, dbfunc, domain, fillx=0, filly=0):
+        """abfunc(fillx, filly) must be defined.
+           abfunc(x, filly) = x for all x to enable reduce.
+        """
+        super().__init__(dbfunc)
+        self.domain = domain
+        self.fillx = fillx
+        self.filly = filly
+        ufunc_domain[dbfunc] = domain
+        ufunc_fills[dbfunc] = (fillx, filly)
+
+    def __call__(self, a, b, *args, **kwargs):
+        "Execute the call behavior."
+        # Get the data
+        (da, db) = (getdata(a), getdata(b))
+        # Get the result
+        with np.errstate(divide='ignore', invalid='ignore'):
+            result = self.f(da, db, *args, **kwargs)
+        # Get the mask as a combination of the source masks and invalid
+        m = ~umath.isfinite(result)
+        m |= getmask(a)
+        m |= getmask(b)
+        # Apply the domain
+        domain = ufunc_domain.get(self.f, None)
+        if domain is not None:
+            m |= domain(da, db)
+        # Take care of the scalar case first
+        if not m.ndim:
+            if m:
+                return masked
+            else:
+                return result
+        # When the mask is True, put back da if possible
+        # any errors, just abort; impossible to guarantee masked values
+        try:
+            np.copyto(result, 0, casting='unsafe', where=m)
+            # avoid using "*" since this may be overlaid
+            masked_da = umath.multiply(m, da)
+            # only add back if it can be cast safely
+            if np.can_cast(masked_da.dtype, result.dtype, casting='safe'):
+                result += masked_da
+        except Exception:
+            pass
+
+        # Transforms to a (subclass of) MaskedArray
+        masked_result = result.view(get_masked_subclass(a, b))
+        masked_result._mask = m
+        if isinstance(a, MaskedArray):
+            masked_result._update_from(a)
+        elif isinstance(b, MaskedArray):
+            masked_result._update_from(b)
+        return masked_result
+
+
+# Unary ufuncs
+exp = _MaskedUnaryOperation(umath.exp)
+conjugate = _MaskedUnaryOperation(umath.conjugate)
+sin = _MaskedUnaryOperation(umath.sin)
+cos = _MaskedUnaryOperation(umath.cos)
+arctan = _MaskedUnaryOperation(umath.arctan)
+arcsinh = _MaskedUnaryOperation(umath.arcsinh)
+sinh = _MaskedUnaryOperation(umath.sinh)
+cosh = _MaskedUnaryOperation(umath.cosh)
+tanh = _MaskedUnaryOperation(umath.tanh)
+abs = absolute = _MaskedUnaryOperation(umath.absolute)
+angle = _MaskedUnaryOperation(angle)  # from numpy.lib.function_base
+fabs = _MaskedUnaryOperation(umath.fabs)
+negative = _MaskedUnaryOperation(umath.negative)
+floor = _MaskedUnaryOperation(umath.floor)
+ceil = _MaskedUnaryOperation(umath.ceil)
+around = _MaskedUnaryOperation(np.round_)
+logical_not = _MaskedUnaryOperation(umath.logical_not)
+
+# Domained unary ufuncs
+sqrt = _MaskedUnaryOperation(umath.sqrt, 0.0,
+                             _DomainGreaterEqual(0.0))
+log = _MaskedUnaryOperation(umath.log, 1.0,
+                            _DomainGreater(0.0))
+log2 = _MaskedUnaryOperation(umath.log2, 1.0,
+                             _DomainGreater(0.0))
+log10 = _MaskedUnaryOperation(umath.log10, 1.0,
+                              _DomainGreater(0.0))
+tan = _MaskedUnaryOperation(umath.tan, 0.0,
+                            _DomainTan(1e-35))
+arcsin = _MaskedUnaryOperation(umath.arcsin, 0.0,
+                               _DomainCheckInterval(-1.0, 1.0))
+arccos = _MaskedUnaryOperation(umath.arccos, 0.0,
+                               _DomainCheckInterval(-1.0, 1.0))
+arccosh = _MaskedUnaryOperation(umath.arccosh, 1.0,
+                                _DomainGreaterEqual(1.0))
+arctanh = _MaskedUnaryOperation(umath.arctanh, 0.0,
+                                _DomainCheckInterval(-1.0 + 1e-15, 1.0 - 1e-15))
+
+# Binary ufuncs
+add = _MaskedBinaryOperation(umath.add)
+subtract = _MaskedBinaryOperation(umath.subtract)
+multiply = _MaskedBinaryOperation(umath.multiply, 1, 1)
+arctan2 = _MaskedBinaryOperation(umath.arctan2, 0.0, 1.0)
+equal = _MaskedBinaryOperation(umath.equal)
+equal.reduce = None
+not_equal = _MaskedBinaryOperation(umath.not_equal)
+not_equal.reduce = None
+less_equal = _MaskedBinaryOperation(umath.less_equal)
+less_equal.reduce = None
+greater_equal = _MaskedBinaryOperation(umath.greater_equal)
+greater_equal.reduce = None
+less = _MaskedBinaryOperation(umath.less)
+less.reduce = None
+greater = _MaskedBinaryOperation(umath.greater)
+greater.reduce = None
+logical_and = _MaskedBinaryOperation(umath.logical_and)
+alltrue = _MaskedBinaryOperation(umath.logical_and, 1, 1).reduce
+logical_or = _MaskedBinaryOperation(umath.logical_or)
+sometrue = logical_or.reduce
+logical_xor = _MaskedBinaryOperation(umath.logical_xor)
+bitwise_and = _MaskedBinaryOperation(umath.bitwise_and)
+bitwise_or = _MaskedBinaryOperation(umath.bitwise_or)
+bitwise_xor = _MaskedBinaryOperation(umath.bitwise_xor)
+hypot = _MaskedBinaryOperation(umath.hypot)
+
+# Domained binary ufuncs
+divide = _DomainedBinaryOperation(umath.divide, _DomainSafeDivide(), 0, 1)
+true_divide = _DomainedBinaryOperation(umath.true_divide,
+                                       _DomainSafeDivide(), 0, 1)
+floor_divide = _DomainedBinaryOperation(umath.floor_divide,
+                                        _DomainSafeDivide(), 0, 1)
+remainder = _DomainedBinaryOperation(umath.remainder,
+                                     _DomainSafeDivide(), 0, 1)
+fmod = _DomainedBinaryOperation(umath.fmod, _DomainSafeDivide(), 0, 1)
+mod = _DomainedBinaryOperation(umath.mod, _DomainSafeDivide(), 0, 1)
+
+
+###############################################################################
+#                        Mask creation functions                              #
+###############################################################################
+
+
+def _replace_dtype_fields_recursive(dtype, primitive_dtype):
+    "Private function allowing recursion in _replace_dtype_fields."
+    _recurse = _replace_dtype_fields_recursive
+
+    # Do we have some name fields ?
+    if dtype.names is not None:
+        descr = []
+        for name in dtype.names:
+            field = dtype.fields[name]
+            if len(field) == 3:
+                # Prepend the title to the name
+                name = (field[-1], name)
+            descr.append((name, _recurse(field[0], primitive_dtype)))
+        new_dtype = np.dtype(descr)
+
+    # Is this some kind of composite a la (float,2)
+    elif dtype.subdtype:
+        descr = list(dtype.subdtype)
+        descr[0] = _recurse(dtype.subdtype[0], primitive_dtype)
+        new_dtype = np.dtype(tuple(descr))
+
+    # this is a primitive type, so do a direct replacement
+    else:
+        new_dtype = primitive_dtype
+
+    # preserve identity of dtypes
+    if new_dtype == dtype:
+        new_dtype = dtype
+
+    return new_dtype
+
+
+def _replace_dtype_fields(dtype, primitive_dtype):
+    """
+    Construct a dtype description list from a given dtype.
+
+    Returns a new dtype object, with all fields and subtypes in the given type
+    recursively replaced with `primitive_dtype`.
+
+    Arguments are coerced to dtypes first.
+    """
+    dtype = np.dtype(dtype)
+    primitive_dtype = np.dtype(primitive_dtype)
+    return _replace_dtype_fields_recursive(dtype, primitive_dtype)
+
+
+def make_mask_descr(ndtype):
+    """
+    Construct a dtype description list from a given dtype.
+
+    Returns a new dtype object, with the type of all fields in `ndtype` to a
+    boolean type. Field names are not altered.
+
+    Parameters
+    ----------
+    ndtype : dtype
+        The dtype to convert.
+
+    Returns
+    -------
+    result : dtype
+        A dtype that looks like `ndtype`, the type of all fields is boolean.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> dtype = np.dtype({'names':['foo', 'bar'],
+    ...                   'formats':[np.float32, np.int64]})
+    >>> dtype
+    dtype([('foo', '<f4'), ('bar', '<i8')])
+    >>> ma.make_mask_descr(dtype)
+    dtype([('foo', '|b1'), ('bar', '|b1')])
+    >>> ma.make_mask_descr(np.float32)
+    dtype('bool')
+
+    """
+    return _replace_dtype_fields(ndtype, MaskType)
+
+
+def getmask(a):
+    """
+    Return the mask of a masked array, or nomask.
+
+    Return the mask of `a` as an ndarray if `a` is a `MaskedArray` and the
+    mask is not `nomask`, else return `nomask`. To guarantee a full array
+    of booleans of the same shape as a, use `getmaskarray`.
+
+    Parameters
+    ----------
+    a : array_like
+        Input `MaskedArray` for which the mask is required.
+
+    See Also
+    --------
+    getdata : Return the data of a masked array as an ndarray.
+    getmaskarray : Return the mask of a masked array, or full array of False.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = ma.masked_equal([[1,2],[3,4]], 2)
+    >>> a
+    masked_array(
+      data=[[1, --],
+            [3, 4]],
+      mask=[[False,  True],
+            [False, False]],
+      fill_value=2)
+    >>> ma.getmask(a)
+    array([[False,  True],
+           [False, False]])
+
+    Equivalently use the `MaskedArray` `mask` attribute.
+
+    >>> a.mask
+    array([[False,  True],
+           [False, False]])
+
+    Result when mask == `nomask`
+
+    >>> b = ma.masked_array([[1,2],[3,4]])
+    >>> b
+    masked_array(
+      data=[[1, 2],
+            [3, 4]],
+      mask=False,
+      fill_value=999999)
+    >>> ma.nomask
+    False
+    >>> ma.getmask(b) == ma.nomask
+    True
+    >>> b.mask == ma.nomask
+    True
+
+    """
+    return getattr(a, '_mask', nomask)
+
+
+get_mask = getmask
+
+
+def getmaskarray(arr):
+    """
+    Return the mask of a masked array, or full boolean array of False.
+
+    Return the mask of `arr` as an ndarray if `arr` is a `MaskedArray` and
+    the mask is not `nomask`, else return a full boolean array of False of
+    the same shape as `arr`.
+
+    Parameters
+    ----------
+    arr : array_like
+        Input `MaskedArray` for which the mask is required.
+
+    See Also
+    --------
+    getmask : Return the mask of a masked array, or nomask.
+    getdata : Return the data of a masked array as an ndarray.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = ma.masked_equal([[1,2],[3,4]], 2)
+    >>> a
+    masked_array(
+      data=[[1, --],
+            [3, 4]],
+      mask=[[False,  True],
+            [False, False]],
+      fill_value=2)
+    >>> ma.getmaskarray(a)
+    array([[False,  True],
+           [False, False]])
+
+    Result when mask == ``nomask``
+
+    >>> b = ma.masked_array([[1,2],[3,4]])
+    >>> b
+    masked_array(
+      data=[[1, 2],
+            [3, 4]],
+      mask=False,
+      fill_value=999999)
+    >>> ma.getmaskarray(b)
+    array([[False, False],
+           [False, False]])
+
+    """
+    mask = getmask(arr)
+    if mask is nomask:
+        mask = make_mask_none(np.shape(arr), getattr(arr, 'dtype', None))
+    return mask
+
+
+def is_mask(m):
+    """
+    Return True if m is a valid, standard mask.
+
+    This function does not check the contents of the input, only that the
+    type is MaskType. In particular, this function returns False if the
+    mask has a flexible dtype.
+
+    Parameters
+    ----------
+    m : array_like
+        Array to test.
+
+    Returns
+    -------
+    result : bool
+        True if `m.dtype.type` is MaskType, False otherwise.
+
+    See Also
+    --------
+    ma.isMaskedArray : Test whether input is an instance of MaskedArray.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> m = ma.masked_equal([0, 1, 0, 2, 3], 0)
+    >>> m
+    masked_array(data=[--, 1, --, 2, 3],
+                 mask=[ True, False,  True, False, False],
+           fill_value=0)
+    >>> ma.is_mask(m)
+    False
+    >>> ma.is_mask(m.mask)
+    True
+
+    Input must be an ndarray (or have similar attributes)
+    for it to be considered a valid mask.
+
+    >>> m = [False, True, False]
+    >>> ma.is_mask(m)
+    False
+    >>> m = np.array([False, True, False])
+    >>> m
+    array([False,  True, False])
+    >>> ma.is_mask(m)
+    True
+
+    Arrays with complex dtypes don't return True.
+
+    >>> dtype = np.dtype({'names':['monty', 'pithon'],
+    ...                   'formats':[bool, bool]})
+    >>> dtype
+    dtype([('monty', '|b1'), ('pithon', '|b1')])
+    >>> m = np.array([(True, False), (False, True), (True, False)],
+    ...              dtype=dtype)
+    >>> m
+    array([( True, False), (False,  True), ( True, False)],
+          dtype=[('monty', '?'), ('pithon', '?')])
+    >>> ma.is_mask(m)
+    False
+
+    """
+    try:
+        return m.dtype.type is MaskType
+    except AttributeError:
+        return False
+
+
+def _shrink_mask(m):
+    """
+    Shrink a mask to nomask if possible
+    """
+    if m.dtype.names is None and not m.any():
+        return nomask
+    else:
+        return m
+
+
+def make_mask(m, copy=False, shrink=True, dtype=MaskType):
+    """
+    Create a boolean mask from an array.
+
+    Return `m` as a boolean mask, creating a copy if necessary or requested.
+    The function can accept any sequence that is convertible to integers,
+    or ``nomask``.  Does not require that contents must be 0s and 1s, values
+    of 0 are interpreted as False, everything else as True.
+
+    Parameters
+    ----------
+    m : array_like
+        Potential mask.
+    copy : bool, optional
+        Whether to return a copy of `m` (True) or `m` itself (False).
+    shrink : bool, optional
+        Whether to shrink `m` to ``nomask`` if all its values are False.
+    dtype : dtype, optional
+        Data-type of the output mask. By default, the output mask has a
+        dtype of MaskType (bool). If the dtype is flexible, each field has
+        a boolean dtype. This is ignored when `m` is ``nomask``, in which
+        case ``nomask`` is always returned.
+
+    Returns
+    -------
+    result : ndarray
+        A boolean mask derived from `m`.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> m = [True, False, True, True]
+    >>> ma.make_mask(m)
+    array([ True, False,  True,  True])
+    >>> m = [1, 0, 1, 1]
+    >>> ma.make_mask(m)
+    array([ True, False,  True,  True])
+    >>> m = [1, 0, 2, -3]
+    >>> ma.make_mask(m)
+    array([ True, False,  True,  True])
+
+    Effect of the `shrink` parameter.
+
+    >>> m = np.zeros(4)
+    >>> m
+    array([0., 0., 0., 0.])
+    >>> ma.make_mask(m)
+    False
+    >>> ma.make_mask(m, shrink=False)
+    array([False, False, False, False])
+
+    Using a flexible `dtype`.
+
+    >>> m = [1, 0, 1, 1]
+    >>> n = [0, 1, 0, 0]
+    >>> arr = []
+    >>> for man, mouse in zip(m, n):
+    ...     arr.append((man, mouse))
+    >>> arr
+    [(1, 0), (0, 1), (1, 0), (1, 0)]
+    >>> dtype = np.dtype({'names':['man', 'mouse'],
+    ...                   'formats':[np.int64, np.int64]})
+    >>> arr = np.array(arr, dtype=dtype)
+    >>> arr
+    array([(1, 0), (0, 1), (1, 0), (1, 0)],
+          dtype=[('man', '<i8'), ('mouse', '<i8')])
+    >>> ma.make_mask(arr, dtype=dtype)
+    array([(True, False), (False, True), (True, False), (True, False)],
+          dtype=[('man', '|b1'), ('mouse', '|b1')])
+
+    """
+    if m is nomask:
+        return nomask
+
+    # Make sure the input dtype is valid.
+    dtype = make_mask_descr(dtype)
+
+    # legacy boolean special case: "existence of fields implies true"
+    if isinstance(m, ndarray) and m.dtype.fields and dtype == np.bool_:
+        return np.ones(m.shape, dtype=dtype)
+
+    # Fill the mask in case there are missing data; turn it into an ndarray.
+    result = np.array(filled(m, True), copy=copy, dtype=dtype, subok=True)
+    # Bas les masques !
+    if shrink:
+        result = _shrink_mask(result)
+    return result
+
+
+def make_mask_none(newshape, dtype=None):
+    """
+    Return a boolean mask of the given shape, filled with False.
+
+    This function returns a boolean ndarray with all entries False, that can
+    be used in common mask manipulations. If a complex dtype is specified, the
+    type of each field is converted to a boolean type.
+
+    Parameters
+    ----------
+    newshape : tuple
+        A tuple indicating the shape of the mask.
+    dtype : {None, dtype}, optional
+        If None, use a MaskType instance. Otherwise, use a new datatype with
+        the same fields as `dtype`, converted to boolean types.
+
+    Returns
+    -------
+    result : ndarray
+        An ndarray of appropriate shape and dtype, filled with False.
+
+    See Also
+    --------
+    make_mask : Create a boolean mask from an array.
+    make_mask_descr : Construct a dtype description list from a given dtype.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> ma.make_mask_none((3,))
+    array([False, False, False])
+
+    Defining a more complex dtype.
+
+    >>> dtype = np.dtype({'names':['foo', 'bar'],
+    ...                   'formats':[np.float32, np.int64]})
+    >>> dtype
+    dtype([('foo', '<f4'), ('bar', '<i8')])
+    >>> ma.make_mask_none((3,), dtype=dtype)
+    array([(False, False), (False, False), (False, False)],
+          dtype=[('foo', '|b1'), ('bar', '|b1')])
+
+    """
+    if dtype is None:
+        result = np.zeros(newshape, dtype=MaskType)
+    else:
+        result = np.zeros(newshape, dtype=make_mask_descr(dtype))
+    return result
+
+
+def _recursive_mask_or(m1, m2, newmask):
+    names = m1.dtype.names
+    for name in names:
+        current1 = m1[name]
+        if current1.dtype.names is not None:
+            _recursive_mask_or(current1, m2[name], newmask[name])
+        else:
+            umath.logical_or(current1, m2[name], newmask[name])
+
+
+def mask_or(m1, m2, copy=False, shrink=True):
+    """
+    Combine two masks with the ``logical_or`` operator.
+
+    The result may be a view on `m1` or `m2` if the other is `nomask`
+    (i.e. False).
+
+    Parameters
+    ----------
+    m1, m2 : array_like
+        Input masks.
+    copy : bool, optional
+        If copy is False and one of the inputs is `nomask`, return a view
+        of the other input mask. Defaults to False.
+    shrink : bool, optional
+        Whether to shrink the output to `nomask` if all its values are
+        False. Defaults to True.
+
+    Returns
+    -------
+    mask : output mask
+        The result masks values that are masked in either `m1` or `m2`.
+
+    Raises
+    ------
+    ValueError
+        If `m1` and `m2` have different flexible dtypes.
+
+    Examples
+    --------
+    >>> m1 = np.ma.make_mask([0, 1, 1, 0])
+    >>> m2 = np.ma.make_mask([1, 0, 0, 0])
+    >>> np.ma.mask_or(m1, m2)
+    array([ True,  True,  True, False])
+
+    """
+
+    if (m1 is nomask) or (m1 is False):
+        dtype = getattr(m2, 'dtype', MaskType)
+        return make_mask(m2, copy=copy, shrink=shrink, dtype=dtype)
+    if (m2 is nomask) or (m2 is False):
+        dtype = getattr(m1, 'dtype', MaskType)
+        return make_mask(m1, copy=copy, shrink=shrink, dtype=dtype)
+    if m1 is m2 and is_mask(m1):
+        return m1
+    (dtype1, dtype2) = (getattr(m1, 'dtype', None), getattr(m2, 'dtype', None))
+    if dtype1 != dtype2:
+        raise ValueError("Incompatible dtypes '%s'<>'%s'" % (dtype1, dtype2))
+    if dtype1.names is not None:
+        # Allocate an output mask array with the properly broadcast shape.
+        newmask = np.empty(np.broadcast(m1, m2).shape, dtype1)
+        _recursive_mask_or(m1, m2, newmask)
+        return newmask
+    return make_mask(umath.logical_or(m1, m2), copy=copy, shrink=shrink)
+
+
+def flatten_mask(mask):
+    """
+    Returns a completely flattened version of the mask, where nested fields
+    are collapsed.
+
+    Parameters
+    ----------
+    mask : array_like
+        Input array, which will be interpreted as booleans.
+
+    Returns
+    -------
+    flattened_mask : ndarray of bools
+        The flattened input.
+
+    Examples
+    --------
+    >>> mask = np.array([0, 0, 1])
+    >>> np.ma.flatten_mask(mask)
+    array([False, False,  True])
+
+    >>> mask = np.array([(0, 0), (0, 1)], dtype=[('a', bool), ('b', bool)])
+    >>> np.ma.flatten_mask(mask)
+    array([False, False, False,  True])
+
+    >>> mdtype = [('a', bool), ('b', [('ba', bool), ('bb', bool)])]
+    >>> mask = np.array([(0, (0, 0)), (0, (0, 1))], dtype=mdtype)
+    >>> np.ma.flatten_mask(mask)
+    array([False, False, False, False, False,  True])
+
+    """
+
+    def _flatmask(mask):
+        "Flatten the mask and returns a (maybe nested) sequence of booleans."
+        mnames = mask.dtype.names
+        if mnames is not None:
+            return [flatten_mask(mask[name]) for name in mnames]
+        else:
+            return mask
+
+    def _flatsequence(sequence):
+        "Generates a flattened version of the sequence."
+        try:
+            for element in sequence:
+                if hasattr(element, '__iter__'):
+                    yield from _flatsequence(element)
+                else:
+                    yield element
+        except TypeError:
+            yield sequence
+
+    mask = np.asarray(mask)
+    flattened = _flatsequence(_flatmask(mask))
+    return np.array([_ for _ in flattened], dtype=bool)
+
+
+def _check_mask_axis(mask, axis, keepdims=np._NoValue):
+    "Check whether there are masked values along the given axis"
+    kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+    if mask is not nomask:
+        return mask.all(axis=axis, **kwargs)
+    return nomask
+
+
+###############################################################################
+#                             Masking functions                               #
+###############################################################################
+
+def masked_where(condition, a, copy=True):
+    """
+    Mask an array where a condition is met.
+
+    Return `a` as an array masked where `condition` is True.
+    Any masked values of `a` or `condition` are also masked in the output.
+
+    Parameters
+    ----------
+    condition : array_like
+        Masking condition.  When `condition` tests floating point values for
+        equality, consider using ``masked_values`` instead.
+    a : array_like
+        Array to mask.
+    copy : bool
+        If True (default) make a copy of `a` in the result.  If False modify
+        `a` in place and return a view.
+
+    Returns
+    -------
+    result : MaskedArray
+        The result of masking `a` where `condition` is True.
+
+    See Also
+    --------
+    masked_values : Mask using floating point equality.
+    masked_equal : Mask where equal to a given value.
+    masked_not_equal : Mask where `not` equal to a given value.
+    masked_less_equal : Mask where less than or equal to a given value.
+    masked_greater_equal : Mask where greater than or equal to a given value.
+    masked_less : Mask where less than a given value.
+    masked_greater : Mask where greater than a given value.
+    masked_inside : Mask inside a given interval.
+    masked_outside : Mask outside a given interval.
+    masked_invalid : Mask invalid values (NaNs or infs).
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(4)
+    >>> a
+    array([0, 1, 2, 3])
+    >>> ma.masked_where(a <= 2, a)
+    masked_array(data=[--, --, --, 3],
+                 mask=[ True,  True,  True, False],
+           fill_value=999999)
+
+    Mask array `b` conditional on `a`.
+
+    >>> b = ['a', 'b', 'c', 'd']
+    >>> ma.masked_where(a == 2, b)
+    masked_array(data=['a', 'b', --, 'd'],
+                 mask=[False, False,  True, False],
+           fill_value='N/A',
+                dtype='<U1')
+
+    Effect of the `copy` argument.
+
+    >>> c = ma.masked_where(a <= 2, a)
+    >>> c
+    masked_array(data=[--, --, --, 3],
+                 mask=[ True,  True,  True, False],
+           fill_value=999999)
+    >>> c[0] = 99
+    >>> c
+    masked_array(data=[99, --, --, 3],
+                 mask=[False,  True,  True, False],
+           fill_value=999999)
+    >>> a
+    array([0, 1, 2, 3])
+    >>> c = ma.masked_where(a <= 2, a, copy=False)
+    >>> c[0] = 99
+    >>> c
+    masked_array(data=[99, --, --, 3],
+                 mask=[False,  True,  True, False],
+           fill_value=999999)
+    >>> a
+    array([99,  1,  2,  3])
+
+    When `condition` or `a` contain masked values.
+
+    >>> a = np.arange(4)
+    >>> a = ma.masked_where(a == 2, a)
+    >>> a
+    masked_array(data=[0, 1, --, 3],
+                 mask=[False, False,  True, False],
+           fill_value=999999)
+    >>> b = np.arange(4)
+    >>> b = ma.masked_where(b == 0, b)
+    >>> b
+    masked_array(data=[--, 1, 2, 3],
+                 mask=[ True, False, False, False],
+           fill_value=999999)
+    >>> ma.masked_where(a == 3, b)
+    masked_array(data=[--, 1, --, --],
+                 mask=[ True, False,  True,  True],
+           fill_value=999999)
+
+    """
+    # Make sure that condition is a valid standard-type mask.
+    cond = make_mask(condition, shrink=False)
+    a = np.array(a, copy=copy, subok=True)
+
+    (cshape, ashape) = (cond.shape, a.shape)
+    if cshape and cshape != ashape:
+        raise IndexError("Inconsistent shape between the condition and the input"
+                         " (got %s and %s)" % (cshape, ashape))
+    if hasattr(a, '_mask'):
+        cond = mask_or(cond, a._mask)
+        cls = type(a)
+    else:
+        cls = MaskedArray
+    result = a.view(cls)
+    # Assign to *.mask so that structured masks are handled correctly.
+    result.mask = _shrink_mask(cond)
+    # There is no view of a boolean so when 'a' is a MaskedArray with nomask
+    # the update to the result's mask has no effect.
+    if not copy and hasattr(a, '_mask') and getmask(a) is nomask:
+        a._mask = result._mask.view()
+    return result
+
+
+def masked_greater(x, value, copy=True):
+    """
+    Mask an array where greater than a given value.
+
+    This function is a shortcut to ``masked_where``, with
+    `condition` = (x > value).
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(4)
+    >>> a
+    array([0, 1, 2, 3])
+    >>> ma.masked_greater(a, 2)
+    masked_array(data=[0, 1, 2, --],
+                 mask=[False, False, False,  True],
+           fill_value=999999)
+
+    """
+    return masked_where(greater(x, value), x, copy=copy)
+
+
+def masked_greater_equal(x, value, copy=True):
+    """
+    Mask an array where greater than or equal to a given value.
+
+    This function is a shortcut to ``masked_where``, with
+    `condition` = (x >= value).
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(4)
+    >>> a
+    array([0, 1, 2, 3])
+    >>> ma.masked_greater_equal(a, 2)
+    masked_array(data=[0, 1, --, --],
+                 mask=[False, False,  True,  True],
+           fill_value=999999)
+
+    """
+    return masked_where(greater_equal(x, value), x, copy=copy)
+
+
+def masked_less(x, value, copy=True):
+    """
+    Mask an array where less than a given value.
+
+    This function is a shortcut to ``masked_where``, with
+    `condition` = (x < value).
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(4)
+    >>> a
+    array([0, 1, 2, 3])
+    >>> ma.masked_less(a, 2)
+    masked_array(data=[--, --, 2, 3],
+                 mask=[ True,  True, False, False],
+           fill_value=999999)
+
+    """
+    return masked_where(less(x, value), x, copy=copy)
+
+
+def masked_less_equal(x, value, copy=True):
+    """
+    Mask an array where less than or equal to a given value.
+
+    This function is a shortcut to ``masked_where``, with
+    `condition` = (x <= value).
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(4)
+    >>> a
+    array([0, 1, 2, 3])
+    >>> ma.masked_less_equal(a, 2)
+    masked_array(data=[--, --, --, 3],
+                 mask=[ True,  True,  True, False],
+           fill_value=999999)
+
+    """
+    return masked_where(less_equal(x, value), x, copy=copy)
+
+
+def masked_not_equal(x, value, copy=True):
+    """
+    Mask an array where `not` equal to a given value.
+
+    This function is a shortcut to ``masked_where``, with
+    `condition` = (x != value).
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(4)
+    >>> a
+    array([0, 1, 2, 3])
+    >>> ma.masked_not_equal(a, 2)
+    masked_array(data=[--, --, 2, --],
+                 mask=[ True,  True, False,  True],
+           fill_value=999999)
+
+    """
+    return masked_where(not_equal(x, value), x, copy=copy)
+
+
+def masked_equal(x, value, copy=True):
+    """
+    Mask an array where equal to a given value.
+
+    Return a MaskedArray, masked where the data in array `x` are
+    equal to `value`. The fill_value of the returned MaskedArray
+    is set to `value`.
+
+    For floating point arrays, consider using ``masked_values(x, value)``.
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+    masked_values : Mask using floating point equality.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(4)
+    >>> a
+    array([0, 1, 2, 3])
+    >>> ma.masked_equal(a, 2)
+    masked_array(data=[0, 1, --, 3],
+                 mask=[False, False,  True, False],
+           fill_value=2)
+
+    """
+    output = masked_where(equal(x, value), x, copy=copy)
+    output.fill_value = value
+    return output
+
+
+def masked_inside(x, v1, v2, copy=True):
+    """
+    Mask an array inside a given interval.
+
+    Shortcut to ``masked_where``, where `condition` is True for `x` inside
+    the interval [v1,v2] (v1 <= x <= v2).  The boundaries `v1` and `v2`
+    can be given in either order.
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+
+    Notes
+    -----
+    The array `x` is prefilled with its filling value.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = [0.31, 1.2, 0.01, 0.2, -0.4, -1.1]
+    >>> ma.masked_inside(x, -0.3, 0.3)
+    masked_array(data=[0.31, 1.2, --, --, -0.4, -1.1],
+                 mask=[False, False,  True,  True, False, False],
+           fill_value=1e+20)
+
+    The order of `v1` and `v2` doesn't matter.
+
+    >>> ma.masked_inside(x, 0.3, -0.3)
+    masked_array(data=[0.31, 1.2, --, --, -0.4, -1.1],
+                 mask=[False, False,  True,  True, False, False],
+           fill_value=1e+20)
+
+    """
+    if v2 < v1:
+        (v1, v2) = (v2, v1)
+    xf = filled(x)
+    condition = (xf >= v1) & (xf <= v2)
+    return masked_where(condition, x, copy=copy)
+
+
+def masked_outside(x, v1, v2, copy=True):
+    """
+    Mask an array outside a given interval.
+
+    Shortcut to ``masked_where``, where `condition` is True for `x` outside
+    the interval [v1,v2] (x < v1)|(x > v2).
+    The boundaries `v1` and `v2` can be given in either order.
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+
+    Notes
+    -----
+    The array `x` is prefilled with its filling value.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = [0.31, 1.2, 0.01, 0.2, -0.4, -1.1]
+    >>> ma.masked_outside(x, -0.3, 0.3)
+    masked_array(data=[--, --, 0.01, 0.2, --, --],
+                 mask=[ True,  True, False, False,  True,  True],
+           fill_value=1e+20)
+
+    The order of `v1` and `v2` doesn't matter.
+
+    >>> ma.masked_outside(x, 0.3, -0.3)
+    masked_array(data=[--, --, 0.01, 0.2, --, --],
+                 mask=[ True,  True, False, False,  True,  True],
+           fill_value=1e+20)
+
+    """
+    if v2 < v1:
+        (v1, v2) = (v2, v1)
+    xf = filled(x)
+    condition = (xf < v1) | (xf > v2)
+    return masked_where(condition, x, copy=copy)
+
+
+def masked_object(x, value, copy=True, shrink=True):
+    """
+    Mask the array `x` where the data are exactly equal to value.
+
+    This function is similar to `masked_values`, but only suitable
+    for object arrays: for floating point, use `masked_values` instead.
+
+    Parameters
+    ----------
+    x : array_like
+        Array to mask
+    value : object
+        Comparison value
+    copy : {True, False}, optional
+        Whether to return a copy of `x`.
+    shrink : {True, False}, optional
+        Whether to collapse a mask full of False to nomask
+
+    Returns
+    -------
+    result : MaskedArray
+        The result of masking `x` where equal to `value`.
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+    masked_equal : Mask where equal to a given value (integers).
+    masked_values : Mask using floating point equality.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> food = np.array(['green_eggs', 'ham'], dtype=object)
+    >>> # don't eat spoiled food
+    >>> eat = ma.masked_object(food, 'green_eggs')
+    >>> eat
+    masked_array(data=[--, 'ham'],
+                 mask=[ True, False],
+           fill_value='green_eggs',
+                dtype=object)
+    >>> # plain ol` ham is boring
+    >>> fresh_food = np.array(['cheese', 'ham', 'pineapple'], dtype=object)
+    >>> eat = ma.masked_object(fresh_food, 'green_eggs')
+    >>> eat
+    masked_array(data=['cheese', 'ham', 'pineapple'],
+                 mask=False,
+           fill_value='green_eggs',
+                dtype=object)
+
+    Note that `mask` is set to ``nomask`` if possible.
+
+    >>> eat
+    masked_array(data=['cheese', 'ham', 'pineapple'],
+                 mask=False,
+           fill_value='green_eggs',
+                dtype=object)
+
+    """
+    if isMaskedArray(x):
+        condition = umath.equal(x._data, value)
+        mask = x._mask
+    else:
+        condition = umath.equal(np.asarray(x), value)
+        mask = nomask
+    mask = mask_or(mask, make_mask(condition, shrink=shrink))
+    return masked_array(x, mask=mask, copy=copy, fill_value=value)
+
+
+def masked_values(x, value, rtol=1e-5, atol=1e-8, copy=True, shrink=True):
+    """
+    Mask using floating point equality.
+
+    Return a MaskedArray, masked where the data in array `x` are approximately
+    equal to `value`, determined using `isclose`. The default tolerances for
+    `masked_values` are the same as those for `isclose`.
+
+    For integer types, exact equality is used, in the same way as
+    `masked_equal`.
+
+    The fill_value is set to `value` and the mask is set to ``nomask`` if
+    possible.
+
+    Parameters
+    ----------
+    x : array_like
+        Array to mask.
+    value : float
+        Masking value.
+    rtol, atol : float, optional
+        Tolerance parameters passed on to `isclose`
+    copy : bool, optional
+        Whether to return a copy of `x`.
+    shrink : bool, optional
+        Whether to collapse a mask full of False to ``nomask``.
+
+    Returns
+    -------
+    result : MaskedArray
+        The result of masking `x` where approximately equal to `value`.
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+    masked_equal : Mask where equal to a given value (integers).
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = np.array([1, 1.1, 2, 1.1, 3])
+    >>> ma.masked_values(x, 1.1)
+    masked_array(data=[1.0, --, 2.0, --, 3.0],
+                 mask=[False,  True, False,  True, False],
+           fill_value=1.1)
+
+    Note that `mask` is set to ``nomask`` if possible.
+
+    >>> ma.masked_values(x, 2.1)
+    masked_array(data=[1. , 1.1, 2. , 1.1, 3. ],
+                 mask=False,
+           fill_value=2.1)
+
+    Unlike `masked_equal`, `masked_values` can perform approximate equalities.
+
+    >>> ma.masked_values(x, 2.1, atol=1e-1)
+    masked_array(data=[1.0, 1.1, --, 1.1, 3.0],
+                 mask=[False, False,  True, False, False],
+           fill_value=2.1)
+
+    """
+    xnew = filled(x, value)
+    if np.issubdtype(xnew.dtype, np.floating):
+        mask = np.isclose(xnew, value, atol=atol, rtol=rtol)
+    else:
+        mask = umath.equal(xnew, value)
+    ret = masked_array(xnew, mask=mask, copy=copy, fill_value=value)
+    if shrink:
+        ret.shrink_mask()
+    return ret
+
+
+def masked_invalid(a, copy=True):
+    """
+    Mask an array where invalid values occur (NaNs or infs).
+
+    This function is a shortcut to ``masked_where``, with
+    `condition` = ~(np.isfinite(a)). Any pre-existing mask is conserved.
+    Only applies to arrays with a dtype where NaNs or infs make sense
+    (i.e. floating point types), but accepts any array_like object.
+
+    See Also
+    --------
+    masked_where : Mask where a condition is met.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(5, dtype=float)
+    >>> a[2] = np.NaN
+    >>> a[3] = np.PINF
+    >>> a
+    array([ 0.,  1., nan, inf,  4.])
+    >>> ma.masked_invalid(a)
+    masked_array(data=[0.0, 1.0, --, --, 4.0],
+                 mask=[False, False,  True,  True, False],
+           fill_value=1e+20)
+
+    """
+    a = np.array(a, copy=False, subok=True)
+    res = masked_where(~(np.isfinite(a)), a, copy=copy)
+    # masked_invalid previously never returned nomask as a mask and doing so
+    # threw off matplotlib (gh-22842).  So use shrink=False:
+    if res._mask is nomask:
+        res._mask = make_mask_none(res.shape, res.dtype)
+    return res
+
+###############################################################################
+#                            Printing options                                 #
+###############################################################################
+
+
+class _MaskedPrintOption:
+    """
+    Handle the string used to represent missing data in a masked array.
+
+    """
+
+    def __init__(self, display):
+        """
+        Create the masked_print_option object.
+
+        """
+        self._display = display
+        self._enabled = True
+
+    def display(self):
+        """
+        Display the string to print for masked values.
+
+        """
+        return self._display
+
+    def set_display(self, s):
+        """
+        Set the string to print for masked values.
+
+        """
+        self._display = s
+
+    def enabled(self):
+        """
+        Is the use of the display value enabled?
+
+        """
+        return self._enabled
+
+    def enable(self, shrink=1):
+        """
+        Set the enabling shrink to `shrink`.
+
+        """
+        self._enabled = shrink
+
+    def __str__(self):
+        return str(self._display)
+
+    __repr__ = __str__
+
+# if you single index into a masked location you get this object.
+masked_print_option = _MaskedPrintOption('--')
+
+
+def _recursive_printoption(result, mask, printopt):
+    """
+    Puts printoptions in result where mask is True.
+
+    Private function allowing for recursion
+
+    """
+    names = result.dtype.names
+    if names is not None:
+        for name in names:
+            curdata = result[name]
+            curmask = mask[name]
+            _recursive_printoption(curdata, curmask, printopt)
+    else:
+        np.copyto(result, printopt, where=mask)
+    return
+
+# For better or worse, these end in a newline
+_legacy_print_templates = dict(
+    long_std=textwrap.dedent("""\
+        masked_%(name)s(data =
+         %(data)s,
+        %(nlen)s        mask =
+         %(mask)s,
+        %(nlen)s  fill_value = %(fill)s)
+        """),
+    long_flx=textwrap.dedent("""\
+        masked_%(name)s(data =
+         %(data)s,
+        %(nlen)s        mask =
+         %(mask)s,
+        %(nlen)s  fill_value = %(fill)s,
+        %(nlen)s       dtype = %(dtype)s)
+        """),
+    short_std=textwrap.dedent("""\
+        masked_%(name)s(data = %(data)s,
+        %(nlen)s        mask = %(mask)s,
+        %(nlen)s  fill_value = %(fill)s)
+        """),
+    short_flx=textwrap.dedent("""\
+        masked_%(name)s(data = %(data)s,
+        %(nlen)s        mask = %(mask)s,
+        %(nlen)s  fill_value = %(fill)s,
+        %(nlen)s       dtype = %(dtype)s)
+        """)
+)
+
+###############################################################################
+#                          MaskedArray class                                  #
+###############################################################################
+
+
+def _recursive_filled(a, mask, fill_value):
+    """
+    Recursively fill `a` with `fill_value`.
+
+    """
+    names = a.dtype.names
+    for name in names:
+        current = a[name]
+        if current.dtype.names is not None:
+            _recursive_filled(current, mask[name], fill_value[name])
+        else:
+            np.copyto(current, fill_value[name], where=mask[name])
+
+
+def flatten_structured_array(a):
+    """
+    Flatten a structured array.
+
+    The data type of the output is chosen such that it can represent all of the
+    (nested) fields.
+
+    Parameters
+    ----------
+    a : structured array
+
+    Returns
+    -------
+    output : masked array or ndarray
+        A flattened masked array if the input is a masked array, otherwise a
+        standard ndarray.
+
+    Examples
+    --------
+    >>> ndtype = [('a', int), ('b', float)]
+    >>> a = np.array([(1, 1), (2, 2)], dtype=ndtype)
+    >>> np.ma.flatten_structured_array(a)
+    array([[1., 1.],
+           [2., 2.]])
+
+    """
+
+    def flatten_sequence(iterable):
+        """
+        Flattens a compound of nested iterables.
+
+        """
+        for elm in iter(iterable):
+            if hasattr(elm, '__iter__'):
+                yield from flatten_sequence(elm)
+            else:
+                yield elm
+
+    a = np.asanyarray(a)
+    inishape = a.shape
+    a = a.ravel()
+    if isinstance(a, MaskedArray):
+        out = np.array([tuple(flatten_sequence(d.item())) for d in a._data])
+        out = out.view(MaskedArray)
+        out._mask = np.array([tuple(flatten_sequence(d.item()))
+                              for d in getmaskarray(a)])
+    else:
+        out = np.array([tuple(flatten_sequence(d.item())) for d in a])
+    if len(inishape) > 1:
+        newshape = list(out.shape)
+        newshape[0] = inishape
+        out.shape = tuple(flatten_sequence(newshape))
+    return out
+
+
+def _arraymethod(funcname, onmask=True):
+    """
+    Return a class method wrapper around a basic array method.
+
+    Creates a class method which returns a masked array, where the new
+    ``_data`` array is the output of the corresponding basic method called
+    on the original ``_data``.
+
+    If `onmask` is True, the new mask is the output of the method called
+    on the initial mask. Otherwise, the new mask is just a reference
+    to the initial mask.
+
+    Parameters
+    ----------
+    funcname : str
+        Name of the function to apply on data.
+    onmask : bool
+        Whether the mask must be processed also (True) or left
+        alone (False). Default is True. Make available as `_onmask`
+        attribute.
+
+    Returns
+    -------
+    method : instancemethod
+        Class method wrapper of the specified basic array method.
+
+    """
+    def wrapped_method(self, *args, **params):
+        result = getattr(self._data, funcname)(*args, **params)
+        result = result.view(type(self))
+        result._update_from(self)
+        mask = self._mask
+        if not onmask:
+            result.__setmask__(mask)
+        elif mask is not nomask:
+            # __setmask__ makes a copy, which we don't want
+            result._mask = getattr(mask, funcname)(*args, **params)
+        return result
+    methdoc = getattr(ndarray, funcname, None) or getattr(np, funcname, None)
+    if methdoc is not None:
+        wrapped_method.__doc__ = methdoc.__doc__
+    wrapped_method.__name__ = funcname
+    return wrapped_method
+
+
+class MaskedIterator:
+    """
+    Flat iterator object to iterate over masked arrays.
+
+    A `MaskedIterator` iterator is returned by ``x.flat`` for any masked array
+    `x`. It allows iterating over the array as if it were a 1-D array,
+    either in a for-loop or by calling its `next` method.
+
+    Iteration is done in C-contiguous style, with the last index varying the
+    fastest. The iterator can also be indexed using basic slicing or
+    advanced indexing.
+
+    See Also
+    --------
+    MaskedArray.flat : Return a flat iterator over an array.
+    MaskedArray.flatten : Returns a flattened copy of an array.
+
+    Notes
+    -----
+    `MaskedIterator` is not exported by the `ma` module. Instead of
+    instantiating a `MaskedIterator` directly, use `MaskedArray.flat`.
+
+    Examples
+    --------
+    >>> x = np.ma.array(arange(6).reshape(2, 3))
+    >>> fl = x.flat
+    >>> type(fl)
+    <class 'numpy.ma.core.MaskedIterator'>
+    >>> for item in fl:
+    ...     print(item)
+    ...
+    0
+    1
+    2
+    3
+    4
+    5
+
+    Extracting more than a single element b indexing the `MaskedIterator`
+    returns a masked array:
+
+    >>> fl[2:4]
+    masked_array(data = [2 3],
+                 mask = False,
+           fill_value = 999999)
+
+    """
+
+    def __init__(self, ma):
+        self.ma = ma
+        self.dataiter = ma._data.flat
+
+        if ma._mask is nomask:
+            self.maskiter = None
+        else:
+            self.maskiter = ma._mask.flat
+
+    def __iter__(self):
+        return self
+
+    def __getitem__(self, indx):
+        result = self.dataiter.__getitem__(indx).view(type(self.ma))
+        if self.maskiter is not None:
+            _mask = self.maskiter.__getitem__(indx)
+            if isinstance(_mask, ndarray):
+                # set shape to match that of data; this is needed for matrices
+                _mask.shape = result.shape
+                result._mask = _mask
+            elif isinstance(_mask, np.void):
+                return mvoid(result, mask=_mask, hardmask=self.ma._hardmask)
+            elif _mask:  # Just a scalar, masked
+                return masked
+        return result
+
+    # This won't work if ravel makes a copy
+    def __setitem__(self, index, value):
+        self.dataiter[index] = getdata(value)
+        if self.maskiter is not None:
+            self.maskiter[index] = getmaskarray(value)
+
+    def __next__(self):
+        """
+        Return the next value, or raise StopIteration.
+
+        Examples
+        --------
+        >>> x = np.ma.array([3, 2], mask=[0, 1])
+        >>> fl = x.flat
+        >>> next(fl)
+        3
+        >>> next(fl)
+        masked
+        >>> next(fl)
+        Traceback (most recent call last):
+          ...
+        StopIteration
+
+        """
+        d = next(self.dataiter)
+        if self.maskiter is not None:
+            m = next(self.maskiter)
+            if isinstance(m, np.void):
+                return mvoid(d, mask=m, hardmask=self.ma._hardmask)
+            elif m:  # Just a scalar, masked
+                return masked
+        return d
+
+
+class MaskedArray(ndarray):
+    """
+    An array class with possibly masked values.
+
+    Masked values of True exclude the corresponding element from any
+    computation.
+
+    Construction::
+
+      x = MaskedArray(data, mask=nomask, dtype=None, copy=False, subok=True,
+                      ndmin=0, fill_value=None, keep_mask=True, hard_mask=None,
+                      shrink=True, order=None)
+
+    Parameters
+    ----------
+    data : array_like
+        Input data.
+    mask : sequence, optional
+        Mask. Must be convertible to an array of booleans with the same
+        shape as `data`. True indicates a masked (i.e. invalid) data.
+    dtype : dtype, optional
+        Data type of the output.
+        If `dtype` is None, the type of the data argument (``data.dtype``)
+        is used. If `dtype` is not None and different from ``data.dtype``,
+        a copy is performed.
+    copy : bool, optional
+        Whether to copy the input data (True), or to use a reference instead.
+        Default is False.
+    subok : bool, optional
+        Whether to return a subclass of `MaskedArray` if possible (True) or a
+        plain `MaskedArray`. Default is True.
+    ndmin : int, optional
+        Minimum number of dimensions. Default is 0.
+    fill_value : scalar, optional
+        Value used to fill in the masked values when necessary.
+        If None, a default based on the data-type is used.
+    keep_mask : bool, optional
+        Whether to combine `mask` with the mask of the input data, if any
+        (True), or to use only `mask` for the output (False). Default is True.
+    hard_mask : bool, optional
+        Whether to use a hard mask or not. With a hard mask, masked values
+        cannot be unmasked. Default is False.
+    shrink : bool, optional
+        Whether to force compression of an empty mask. Default is True.
+    order : {'C', 'F', 'A'}, optional
+        Specify the order of the array.  If order is 'C', then the array
+        will be in C-contiguous order (last-index varies the fastest).
+        If order is 'F', then the returned array will be in
+        Fortran-contiguous order (first-index varies the fastest).
+        If order is 'A' (default), then the returned array may be
+        in any order (either C-, Fortran-contiguous, or even discontiguous),
+        unless a copy is required, in which case it will be C-contiguous.
+
+    Examples
+    --------
+
+    The ``mask`` can be initialized with an array of boolean values
+    with the same shape as ``data``.
+
+    >>> data = np.arange(6).reshape((2, 3))
+    >>> np.ma.MaskedArray(data, mask=[[False, True, False],
+    ...                               [False, False, True]])
+    masked_array(
+      data=[[0, --, 2],
+            [3, 4, --]],
+      mask=[[False,  True, False],
+            [False, False,  True]],
+      fill_value=999999)
+
+    Alternatively, the ``mask`` can be initialized to homogeneous boolean
+    array with the same shape as ``data`` by passing in a scalar
+    boolean value:
+
+    >>> np.ma.MaskedArray(data, mask=False)
+    masked_array(
+      data=[[0, 1, 2],
+            [3, 4, 5]],
+      mask=[[False, False, False],
+            [False, False, False]],
+      fill_value=999999)
+
+    >>> np.ma.MaskedArray(data, mask=True)
+    masked_array(
+      data=[[--, --, --],
+            [--, --, --]],
+      mask=[[ True,  True,  True],
+            [ True,  True,  True]],
+      fill_value=999999,
+      dtype=int64)
+
+    .. note::
+        The recommended practice for initializing ``mask`` with a scalar
+        boolean value is to use ``True``/``False`` rather than
+        ``np.True_``/``np.False_``. The reason is :attr:`nomask`
+        is represented internally as ``np.False_``.
+
+        >>> np.False_ is np.ma.nomask
+        True
+
+    """
+
+    __array_priority__ = 15
+    _defaultmask = nomask
+    _defaulthardmask = False
+    _baseclass = ndarray
+
+    # Maximum number of elements per axis used when printing an array. The
+    # 1d case is handled separately because we need more values in this case.
+    _print_width = 100
+    _print_width_1d = 1500
+
+    def __new__(cls, data=None, mask=nomask, dtype=None, copy=False,
+                subok=True, ndmin=0, fill_value=None, keep_mask=True,
+                hard_mask=None, shrink=True, order=None):
+        """
+        Create a new masked array from scratch.
+
+        Notes
+        -----
+        A masked array can also be created by taking a .view(MaskedArray).
+
+        """
+        # Process data.
+        _data = np.array(data, dtype=dtype, copy=copy,
+                         order=order, subok=True, ndmin=ndmin)
+        _baseclass = getattr(data, '_baseclass', type(_data))
+        # Check that we're not erasing the mask.
+        if isinstance(data, MaskedArray) and (data.shape != _data.shape):
+            copy = True
+
+        # Here, we copy the _view_, so that we can attach new properties to it
+        # we must never do .view(MaskedConstant), as that would create a new
+        # instance of np.ma.masked, which make identity comparison fail
+        if isinstance(data, cls) and subok and not isinstance(data, MaskedConstant):
+            _data = ndarray.view(_data, type(data))
+        else:
+            _data = ndarray.view(_data, cls)
+
+        # Handle the case where data is not a subclass of ndarray, but
+        # still has the _mask attribute like MaskedArrays
+        if hasattr(data, '_mask') and not isinstance(data, ndarray):
+            _data._mask = data._mask
+            # FIXME: should we set `_data._sharedmask = True`?
+        # Process mask.
+        # Type of the mask
+        mdtype = make_mask_descr(_data.dtype)
+        if mask is nomask:
+            # Case 1. : no mask in input.
+            # Erase the current mask ?
+            if not keep_mask:
+                # With a reduced version
+                if shrink:
+                    _data._mask = nomask
+                # With full version
+                else:
+                    _data._mask = np.zeros(_data.shape, dtype=mdtype)
+            # Check whether we missed something
+            elif isinstance(data, (tuple, list)):
+                try:
+                    # If data is a sequence of masked array
+                    mask = np.array(
+                        [getmaskarray(np.asanyarray(m, dtype=_data.dtype))
+                         for m in data], dtype=mdtype)
+                except (ValueError, TypeError):
+                    # If data is nested
+                    mask = nomask
+                # Force shrinking of the mask if needed (and possible)
+                if (mdtype == MaskType) and mask.any():
+                    _data._mask = mask
+                    _data._sharedmask = False
+            else:
+                _data._sharedmask = not copy
+                if copy:
+                    _data._mask = _data._mask.copy()
+                    # Reset the shape of the original mask
+                    if getmask(data) is not nomask:
+                        # gh-21022 encounters an issue here
+                        # because data._mask.shape is not writeable, but
+                        # the op was also pointless in that case, because
+                        # the shapes were the same, so we can at least
+                        # avoid that path
+                        if data._mask.shape != data.shape:
+                            data._mask.shape = data.shape
+        else:
+            # Case 2. : With a mask in input.
+            # If mask is boolean, create an array of True or False
+
+            # if users pass `mask=None` be forgiving here and cast it False
+            # for speed; although the default is `mask=nomask` and can differ.
+            if mask is None:
+                mask = False
+
+            if mask is True and mdtype == MaskType:
+                mask = np.ones(_data.shape, dtype=mdtype)
+            elif mask is False and mdtype == MaskType:
+                mask = np.zeros(_data.shape, dtype=mdtype)
+            else:
+                # Read the mask with the current mdtype
+                try:
+                    mask = np.array(mask, copy=copy, dtype=mdtype)
+                # Or assume it's a sequence of bool/int
+                except TypeError:
+                    mask = np.array([tuple([m] * len(mdtype)) for m in mask],
+                                    dtype=mdtype)
+            # Make sure the mask and the data have the same shape
+            if mask.shape != _data.shape:
+                (nd, nm) = (_data.size, mask.size)
+                if nm == 1:
+                    mask = np.resize(mask, _data.shape)
+                elif nm == nd:
+                    mask = np.reshape(mask, _data.shape)
+                else:
+                    msg = "Mask and data not compatible: data size is %i, " + \
+                          "mask size is %i."
+                    raise MaskError(msg % (nd, nm))
+                copy = True
+            # Set the mask to the new value
+            if _data._mask is nomask:
+                _data._mask = mask
+                _data._sharedmask = not copy
+            else:
+                if not keep_mask:
+                    _data._mask = mask
+                    _data._sharedmask = not copy
+                else:
+                    if _data.dtype.names is not None:
+                        def _recursive_or(a, b):
+                            "do a|=b on each field of a, recursively"
+                            for name in a.dtype.names:
+                                (af, bf) = (a[name], b[name])
+                                if af.dtype.names is not None:
+                                    _recursive_or(af, bf)
+                                else:
+                                    af |= bf
+
+                        _recursive_or(_data._mask, mask)
+                    else:
+                        _data._mask = np.logical_or(mask, _data._mask)
+                    _data._sharedmask = False
+
+        # Update fill_value.
+        if fill_value is None:
+            fill_value = getattr(data, '_fill_value', None)
+        # But don't run the check unless we have something to check.
+        if fill_value is not None:
+            _data._fill_value = _check_fill_value(fill_value, _data.dtype)
+        # Process extra options ..
+        if hard_mask is None:
+            _data._hardmask = getattr(data, '_hardmask', False)
+        else:
+            _data._hardmask = hard_mask
+        _data._baseclass = _baseclass
+        return _data
+
+
+    def _update_from(self, obj):
+        """
+        Copies some attributes of obj to self.
+
+        """
+        if isinstance(obj, ndarray):
+            _baseclass = type(obj)
+        else:
+            _baseclass = ndarray
+        # We need to copy the _basedict to avoid backward propagation
+        _optinfo = {}
+        _optinfo.update(getattr(obj, '_optinfo', {}))
+        _optinfo.update(getattr(obj, '_basedict', {}))
+        if not isinstance(obj, MaskedArray):
+            _optinfo.update(getattr(obj, '__dict__', {}))
+        _dict = dict(_fill_value=getattr(obj, '_fill_value', None),
+                     _hardmask=getattr(obj, '_hardmask', False),
+                     _sharedmask=getattr(obj, '_sharedmask', False),
+                     _isfield=getattr(obj, '_isfield', False),
+                     _baseclass=getattr(obj, '_baseclass', _baseclass),
+                     _optinfo=_optinfo,
+                     _basedict=_optinfo)
+        self.__dict__.update(_dict)
+        self.__dict__.update(_optinfo)
+        return
+
+    def __array_finalize__(self, obj):
+        """
+        Finalizes the masked array.
+
+        """
+        # Get main attributes.
+        self._update_from(obj)
+
+        # We have to decide how to initialize self.mask, based on
+        # obj.mask. This is very difficult.  There might be some
+        # correspondence between the elements in the array we are being
+        # created from (= obj) and us. Or there might not. This method can
+        # be called in all kinds of places for all kinds of reasons -- could
+        # be empty_like, could be slicing, could be a ufunc, could be a view.
+        # The numpy subclassing interface simply doesn't give us any way
+        # to know, which means that at best this method will be based on
+        # guesswork and heuristics. To make things worse, there isn't even any
+        # clear consensus about what the desired behavior is. For instance,
+        # most users think that np.empty_like(marr) -- which goes via this
+        # method -- should return a masked array with an empty mask (see
+        # gh-3404 and linked discussions), but others disagree, and they have
+        # existing code which depends on empty_like returning an array that
+        # matches the input mask.
+        #
+        # Historically our algorithm was: if the template object mask had the
+        # same *number of elements* as us, then we used *it's mask object
+        # itself* as our mask, so that writes to us would also write to the
+        # original array. This is horribly broken in multiple ways.
+        #
+        # Now what we do instead is, if the template object mask has the same
+        # number of elements as us, and we do not have the same base pointer
+        # as the template object (b/c views like arr[...] should keep the same
+        # mask), then we make a copy of the template object mask and use
+        # that. This is also horribly broken but somewhat less so. Maybe.
+        if isinstance(obj, ndarray):
+            # XX: This looks like a bug -- shouldn't it check self.dtype
+            # instead?
+            if obj.dtype.names is not None:
+                _mask = getmaskarray(obj)
+            else:
+                _mask = getmask(obj)
+
+            # If self and obj point to exactly the same data, then probably
+            # self is a simple view of obj (e.g., self = obj[...]), so they
+            # should share the same mask. (This isn't 100% reliable, e.g. self
+            # could be the first row of obj, or have strange strides, but as a
+            # heuristic it's not bad.) In all other cases, we make a copy of
+            # the mask, so that future modifications to 'self' do not end up
+            # side-effecting 'obj' as well.
+            if (_mask is not nomask and obj.__array_interface__["data"][0]
+                    != self.__array_interface__["data"][0]):
+                # We should make a copy. But we could get here via astype,
+                # in which case the mask might need a new dtype as well
+                # (e.g., changing to or from a structured dtype), and the
+                # order could have changed. So, change the mask type if
+                # needed and use astype instead of copy.
+                if self.dtype == obj.dtype:
+                    _mask_dtype = _mask.dtype
+                else:
+                    _mask_dtype = make_mask_descr(self.dtype)
+
+                if self.flags.c_contiguous:
+                    order = "C"
+                elif self.flags.f_contiguous:
+                    order = "F"
+                else:
+                    order = "K"
+
+                _mask = _mask.astype(_mask_dtype, order)
+            else:
+                # Take a view so shape changes, etc., do not propagate back.
+                _mask = _mask.view()
+        else:
+            _mask = nomask
+
+        self._mask = _mask
+        # Finalize the mask
+        if self._mask is not nomask:
+            try:
+                self._mask.shape = self.shape
+            except ValueError:
+                self._mask = nomask
+            except (TypeError, AttributeError):
+                # When _mask.shape is not writable (because it's a void)
+                pass
+
+        # Finalize the fill_value
+        if self._fill_value is not None:
+            self._fill_value = _check_fill_value(self._fill_value, self.dtype)
+        elif self.dtype.names is not None:
+            # Finalize the default fill_value for structured arrays
+            self._fill_value = _check_fill_value(None, self.dtype)
+
+    def __array_wrap__(self, obj, context=None):
+        """
+        Special hook for ufuncs.
+
+        Wraps the numpy array and sets the mask according to context.
+
+        """
+        if obj is self:  # for in-place operations
+            result = obj
+        else:
+            result = obj.view(type(self))
+            result._update_from(self)
+
+        if context is not None:
+            result._mask = result._mask.copy()
+            func, args, out_i = context
+            # args sometimes contains outputs (gh-10459), which we don't want
+            input_args = args[:func.nin]
+            m = reduce(mask_or, [getmaskarray(arg) for arg in input_args])
+            # Get the domain mask
+            domain = ufunc_domain.get(func, None)
+            if domain is not None:
+                # Take the domain, and make sure it's a ndarray
+                with np.errstate(divide='ignore', invalid='ignore'):
+                    d = filled(domain(*input_args), True)
+
+                if d.any():
+                    # Fill the result where the domain is wrong
+                    try:
+                        # Binary domain: take the last value
+                        fill_value = ufunc_fills[func][-1]
+                    except TypeError:
+                        # Unary domain: just use this one
+                        fill_value = ufunc_fills[func]
+                    except KeyError:
+                        # Domain not recognized, use fill_value instead
+                        fill_value = self.fill_value
+
+                    np.copyto(result, fill_value, where=d)
+
+                    # Update the mask
+                    if m is nomask:
+                        m = d
+                    else:
+                        # Don't modify inplace, we risk back-propagation
+                        m = (m | d)
+
+            # Make sure the mask has the proper size
+            if result is not self and result.shape == () and m:
+                return masked
+            else:
+                result._mask = m
+                result._sharedmask = False
+
+        return result
+
+    def view(self, dtype=None, type=None, fill_value=None):
+        """
+        Return a view of the MaskedArray data.
+
+        Parameters
+        ----------
+        dtype : data-type or ndarray sub-class, optional
+            Data-type descriptor of the returned view, e.g., float32 or int16.
+            The default, None, results in the view having the same data-type
+            as `a`. As with ``ndarray.view``, dtype can also be specified as
+            an ndarray sub-class, which then specifies the type of the
+            returned object (this is equivalent to setting the ``type``
+            parameter).
+        type : Python type, optional
+            Type of the returned view, either ndarray or a subclass.  The
+            default None results in type preservation.
+        fill_value : scalar, optional
+            The value to use for invalid entries (None by default).
+            If None, then this argument is inferred from the passed `dtype`, or
+            in its absence the original array, as discussed in the notes below.
+
+        See Also
+        --------
+        numpy.ndarray.view : Equivalent method on ndarray object.
+
+        Notes
+        -----
+
+        ``a.view()`` is used two different ways:
+
+        ``a.view(some_dtype)`` or ``a.view(dtype=some_dtype)`` constructs a view
+        of the array's memory with a different data-type.  This can cause a
+        reinterpretation of the bytes of memory.
+
+        ``a.view(ndarray_subclass)`` or ``a.view(type=ndarray_subclass)`` just
+        returns an instance of `ndarray_subclass` that looks at the same array
+        (same shape, dtype, etc.)  This does not cause a reinterpretation of the
+        memory.
+
+        If `fill_value` is not specified, but `dtype` is specified (and is not
+        an ndarray sub-class), the `fill_value` of the MaskedArray will be
+        reset. If neither `fill_value` nor `dtype` are specified (or if
+        `dtype` is an ndarray sub-class), then the fill value is preserved.
+        Finally, if `fill_value` is specified, but `dtype` is not, the fill
+        value is set to the specified value.
+
+        For ``a.view(some_dtype)``, if ``some_dtype`` has a different number of
+        bytes per entry than the previous dtype (for example, converting a
+        regular array to a structured array), then the behavior of the view
+        cannot be predicted just from the superficial appearance of ``a`` (shown
+        by ``print(a)``). It also depends on exactly how ``a`` is stored in
+        memory. Therefore if ``a`` is C-ordered versus fortran-ordered, versus
+        defined as a slice or transpose, etc., the view may give different
+        results.
+        """
+
+        if dtype is None:
+            if type is None:
+                output = ndarray.view(self)
+            else:
+                output = ndarray.view(self, type)
+        elif type is None:
+            try:
+                if issubclass(dtype, ndarray):
+                    output = ndarray.view(self, dtype)
+                    dtype = None
+                else:
+                    output = ndarray.view(self, dtype)
+            except TypeError:
+                output = ndarray.view(self, dtype)
+        else:
+            output = ndarray.view(self, dtype, type)
+
+        # also make the mask be a view (so attr changes to the view's
+        # mask do no affect original object's mask)
+        # (especially important to avoid affecting np.masked singleton)
+        if getmask(output) is not nomask:
+            output._mask = output._mask.view()
+
+        # Make sure to reset the _fill_value if needed
+        if getattr(output, '_fill_value', None) is not None:
+            if fill_value is None:
+                if dtype is None:
+                    pass  # leave _fill_value as is
+                else:
+                    output._fill_value = None
+            else:
+                output.fill_value = fill_value
+        return output
+
+    def __getitem__(self, indx):
+        """
+        x.__getitem__(y) <==> x[y]
+
+        Return the item described by i, as a masked array.
+
+        """
+        # We could directly use ndarray.__getitem__ on self.
+        # But then we would have to modify __array_finalize__ to prevent the
+        # mask of being reshaped if it hasn't been set up properly yet
+        # So it's easier to stick to the current version
+        dout = self.data[indx]
+        _mask = self._mask
+
+        def _is_scalar(m):
+            return not isinstance(m, np.ndarray)
+
+        def _scalar_heuristic(arr, elem):
+            """
+            Return whether `elem` is a scalar result of indexing `arr`, or None
+            if undecidable without promoting nomask to a full mask
+            """
+            # obviously a scalar
+            if not isinstance(elem, np.ndarray):
+                return True
+
+            # object array scalar indexing can return anything
+            elif arr.dtype.type is np.object_:
+                if arr.dtype is not elem.dtype:
+                    # elem is an array, but dtypes do not match, so must be
+                    # an element
+                    return True
+
+            # well-behaved subclass that only returns 0d arrays when
+            # expected - this is not a scalar
+            elif type(arr).__getitem__ == ndarray.__getitem__:
+                return False
+
+            return None
+
+        if _mask is not nomask:
+            # _mask cannot be a subclass, so it tells us whether we should
+            # expect a scalar. It also cannot be of dtype object.
+            mout = _mask[indx]
+            scalar_expected = _is_scalar(mout)
+
+        else:
+            # attempt to apply the heuristic to avoid constructing a full mask
+            mout = nomask
+            scalar_expected = _scalar_heuristic(self.data, dout)
+            if scalar_expected is None:
+                # heuristics have failed
+                # construct a full array, so we can be certain. This is costly.
+                # we could also fall back on ndarray.__getitem__(self.data, indx)
+                scalar_expected = _is_scalar(getmaskarray(self)[indx])
+
+        # Did we extract a single item?
+        if scalar_expected:
+            # A record
+            if isinstance(dout, np.void):
+                # We should always re-cast to mvoid, otherwise users can
+                # change masks on rows that already have masked values, but not
+                # on rows that have no masked values, which is inconsistent.
+                return mvoid(dout, mask=mout, hardmask=self._hardmask)
+
+            # special case introduced in gh-5962
+            elif (self.dtype.type is np.object_ and
+                  isinstance(dout, np.ndarray) and
+                  dout is not masked):
+                # If masked, turn into a MaskedArray, with everything masked.
+                if mout:
+                    return MaskedArray(dout, mask=True)
+                else:
+                    return dout
+
+            # Just a scalar
+            else:
+                if mout:
+                    return masked
+                else:
+                    return dout
+        else:
+            # Force dout to MA
+            dout = dout.view(type(self))
+            # Inherit attributes from self
+            dout._update_from(self)
+            # Check the fill_value
+            if is_string_or_list_of_strings(indx):
+                if self._fill_value is not None:
+                    dout._fill_value = self._fill_value[indx]
+
+                    # Something like gh-15895 has happened if this check fails.
+                    # _fill_value should always be an ndarray.
+                    if not isinstance(dout._fill_value, np.ndarray):
+                        raise RuntimeError('Internal NumPy error.')
+                    # If we're indexing a multidimensional field in a
+                    # structured array (such as dtype("(2,)i2,(2,)i1")),
+                    # dimensionality goes up (M[field].ndim == M.ndim +
+                    # M.dtype[field].ndim).  That's fine for
+                    # M[field] but problematic for M[field].fill_value
+                    # which should have shape () to avoid breaking several
+                    # methods. There is no great way out, so set to
+                    # first element. See issue #6723.
+                    if dout._fill_value.ndim > 0:
+                        if not (dout._fill_value ==
+                                dout._fill_value.flat[0]).all():
+                            warnings.warn(
+                                "Upon accessing multidimensional field "
+                                f"{indx!s}, need to keep dimensionality "
+                                "of fill_value at 0. Discarding "
+                                "heterogeneous fill_value and setting "
+                                f"all to {dout._fill_value[0]!s}.",
+                                stacklevel=2)
+                        # Need to use `.flat[0:1].squeeze(...)` instead of just
+                        # `.flat[0]` to ensure the result is a 0d array and not
+                        # a scalar.
+                        dout._fill_value = dout._fill_value.flat[0:1].squeeze(axis=0)
+                dout._isfield = True
+            # Update the mask if needed
+            if mout is not nomask:
+                # set shape to match that of data; this is needed for matrices
+                dout._mask = reshape(mout, dout.shape)
+                dout._sharedmask = True
+                # Note: Don't try to check for m.any(), that'll take too long
+        return dout
+
+    # setitem may put NaNs into integer arrays or occasionally overflow a
+    # float.  But this may happen in masked values, so avoid otherwise
+    # correct warnings (as is typical also in masked calculations).
+    @np.errstate(over='ignore', invalid='ignore')
+    def __setitem__(self, indx, value):
+        """
+        x.__setitem__(i, y) <==> x[i]=y
+
+        Set item described by index. If value is masked, masks those
+        locations.
+
+        """
+        if self is masked:
+            raise MaskError('Cannot alter the masked element.')
+        _data = self._data
+        _mask = self._mask
+        if isinstance(indx, str):
+            _data[indx] = value
+            if _mask is nomask:
+                self._mask = _mask = make_mask_none(self.shape, self.dtype)
+            _mask[indx] = getmask(value)
+            return
+
+        _dtype = _data.dtype
+
+        if value is masked:
+            # The mask wasn't set: create a full version.
+            if _mask is nomask:
+                _mask = self._mask = make_mask_none(self.shape, _dtype)
+            # Now, set the mask to its value.
+            if _dtype.names is not None:
+                _mask[indx] = tuple([True] * len(_dtype.names))
+            else:
+                _mask[indx] = True
+            return
+
+        # Get the _data part of the new value
+        dval = getattr(value, '_data', value)
+        # Get the _mask part of the new value
+        mval = getmask(value)
+        if _dtype.names is not None and mval is nomask:
+            mval = tuple([False] * len(_dtype.names))
+        if _mask is nomask:
+            # Set the data, then the mask
+            _data[indx] = dval
+            if mval is not nomask:
+                _mask = self._mask = make_mask_none(self.shape, _dtype)
+                _mask[indx] = mval
+        elif not self._hardmask:
+            # Set the data, then the mask
+            if (isinstance(indx, masked_array) and
+                    not isinstance(value, masked_array)):
+                _data[indx.data] = dval
+            else:
+                _data[indx] = dval
+                _mask[indx] = mval
+        elif hasattr(indx, 'dtype') and (indx.dtype == MaskType):
+            indx = indx * umath.logical_not(_mask)
+            _data[indx] = dval
+        else:
+            if _dtype.names is not None:
+                err_msg = "Flexible 'hard' masks are not yet supported."
+                raise NotImplementedError(err_msg)
+            mindx = mask_or(_mask[indx], mval, copy=True)
+            dindx = self._data[indx]
+            if dindx.size > 1:
+                np.copyto(dindx, dval, where=~mindx)
+            elif mindx is nomask:
+                dindx = dval
+            _data[indx] = dindx
+            _mask[indx] = mindx
+        return
+
+    # Define so that we can overwrite the setter.
+    @property
+    def dtype(self):
+        return super().dtype
+
+    @dtype.setter
+    def dtype(self, dtype):
+        super(MaskedArray, type(self)).dtype.__set__(self, dtype)
+        if self._mask is not nomask:
+            self._mask = self._mask.view(make_mask_descr(dtype), ndarray)
+            # Try to reset the shape of the mask (if we don't have a void).
+            # This raises a ValueError if the dtype change won't work.
+            try:
+                self._mask.shape = self.shape
+            except (AttributeError, TypeError):
+                pass
+
+    @property
+    def shape(self):
+        return super().shape
+
+    @shape.setter
+    def shape(self, shape):
+        super(MaskedArray, type(self)).shape.__set__(self, shape)
+        # Cannot use self._mask, since it may not (yet) exist when a
+        # masked matrix sets the shape.
+        if getmask(self) is not nomask:
+            self._mask.shape = self.shape
+
+    def __setmask__(self, mask, copy=False):
+        """
+        Set the mask.
+
+        """
+        idtype = self.dtype
+        current_mask = self._mask
+        if mask is masked:
+            mask = True
+
+        if current_mask is nomask:
+            # Make sure the mask is set
+            # Just don't do anything if there's nothing to do.
+            if mask is nomask:
+                return
+            current_mask = self._mask = make_mask_none(self.shape, idtype)
+
+        if idtype.names is None:
+            # No named fields.
+            # Hardmask: don't unmask the data
+            if self._hardmask:
+                current_mask |= mask
+            # Softmask: set everything to False
+            # If it's obviously a compatible scalar, use a quick update
+            # method.
+            elif isinstance(mask, (int, float, np.bool_, np.number)):
+                current_mask[...] = mask
+            # Otherwise fall back to the slower, general purpose way.
+            else:
+                current_mask.flat = mask
+        else:
+            # Named fields w/
+            mdtype = current_mask.dtype
+            mask = np.array(mask, copy=False)
+            # Mask is a singleton
+            if not mask.ndim:
+                # It's a boolean : make a record
+                if mask.dtype.kind == 'b':
+                    mask = np.array(tuple([mask.item()] * len(mdtype)),
+                                    dtype=mdtype)
+                # It's a record: make sure the dtype is correct
+                else:
+                    mask = mask.astype(mdtype)
+            # Mask is a sequence
+            else:
+                # Make sure the new mask is a ndarray with the proper dtype
+                try:
+                    mask = np.array(mask, copy=copy, dtype=mdtype)
+                # Or assume it's a sequence of bool/int
+                except TypeError:
+                    mask = np.array([tuple([m] * len(mdtype)) for m in mask],
+                                    dtype=mdtype)
+            # Hardmask: don't unmask the data
+            if self._hardmask:
+                for n in idtype.names:
+                    current_mask[n] |= mask[n]
+            # Softmask: set everything to False
+            # If it's obviously a compatible scalar, use a quick update
+            # method.
+            elif isinstance(mask, (int, float, np.bool_, np.number)):
+                current_mask[...] = mask
+            # Otherwise fall back to the slower, general purpose way.
+            else:
+                current_mask.flat = mask
+        # Reshape if needed
+        if current_mask.shape:
+            current_mask.shape = self.shape
+        return
+
+    _set_mask = __setmask__
+
+    @property
+    def mask(self):
+        """ Current mask. """
+
+        # We could try to force a reshape, but that wouldn't work in some
+        # cases.
+        # Return a view so that the dtype and shape cannot be changed in place
+        # This still preserves nomask by identity
+        return self._mask.view()
+
+    @mask.setter
+    def mask(self, value):
+        self.__setmask__(value)
+
+    @property
+    def recordmask(self):
+        """
+        Get or set the mask of the array if it has no named fields. For
+        structured arrays, returns a ndarray of booleans where entries are
+        ``True`` if **all** the fields are masked, ``False`` otherwise:
+
+        >>> x = np.ma.array([(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)],
+        ...         mask=[(0, 0), (1, 0), (1, 1), (0, 1), (0, 0)],
+        ...        dtype=[('a', int), ('b', int)])
+        >>> x.recordmask
+        array([False, False,  True, False, False])
+        """
+
+        _mask = self._mask.view(ndarray)
+        if _mask.dtype.names is None:
+            return _mask
+        return np.all(flatten_structured_array(_mask), axis=-1)
+
+    @recordmask.setter
+    def recordmask(self, mask):
+        raise NotImplementedError("Coming soon: setting the mask per records!")
+
+    def harden_mask(self):
+        """
+        Force the mask to hard, preventing unmasking by assignment.
+
+        Whether the mask of a masked array is hard or soft is determined by
+        its `~ma.MaskedArray.hardmask` property. `harden_mask` sets
+        `~ma.MaskedArray.hardmask` to ``True`` (and returns the modified
+        self).
+
+        See Also
+        --------
+        ma.MaskedArray.hardmask
+        ma.MaskedArray.soften_mask
+
+        """
+        self._hardmask = True
+        return self
+
+    def soften_mask(self):
+        """
+        Force the mask to soft (default), allowing unmasking by assignment.
+
+        Whether the mask of a masked array is hard or soft is determined by
+        its `~ma.MaskedArray.hardmask` property. `soften_mask` sets
+        `~ma.MaskedArray.hardmask` to ``False`` (and returns the modified
+        self).
+
+        See Also
+        --------
+        ma.MaskedArray.hardmask
+        ma.MaskedArray.harden_mask
+
+        """
+        self._hardmask = False
+        return self
+
+    @property
+    def hardmask(self):
+        """
+        Specifies whether values can be unmasked through assignments.
+
+        By default, assigning definite values to masked array entries will
+        unmask them.  When `hardmask` is ``True``, the mask will not change
+        through assignments.
+
+        See Also
+        --------
+        ma.MaskedArray.harden_mask
+        ma.MaskedArray.soften_mask
+
+        Examples
+        --------
+        >>> x = np.arange(10)
+        >>> m = np.ma.masked_array(x, x>5)
+        >>> assert not m.hardmask
+
+        Since `m` has a soft mask, assigning an element value unmasks that
+        element:
+
+        >>> m[8] = 42
+        >>> m
+        masked_array(data=[0, 1, 2, 3, 4, 5, --, --, 42, --],
+                     mask=[False, False, False, False, False, False,
+                           True, True, False, True],
+               fill_value=999999)
+
+        After hardening, the mask is not affected by assignments:
+
+        >>> hardened = np.ma.harden_mask(m)
+        >>> assert m.hardmask and hardened is m
+        >>> m[:] = 23
+        >>> m
+        masked_array(data=[23, 23, 23, 23, 23, 23, --, --, 23, --],
+                     mask=[False, False, False, False, False, False,
+                           True, True, False, True],
+               fill_value=999999)
+
+        """
+        return self._hardmask
+
+    def unshare_mask(self):
+        """
+        Copy the mask and set the `sharedmask` flag to ``False``.
+
+        Whether the mask is shared between masked arrays can be seen from
+        the `sharedmask` property. `unshare_mask` ensures the mask is not
+        shared. A copy of the mask is only made if it was shared.
+
+        See Also
+        --------
+        sharedmask
+
+        """
+        if self._sharedmask:
+            self._mask = self._mask.copy()
+            self._sharedmask = False
+        return self
+
+    @property
+    def sharedmask(self):
+        """ Share status of the mask (read-only). """
+        return self._sharedmask
+
+    def shrink_mask(self):
+        """
+        Reduce a mask to nomask when possible.
+
+        Parameters
+        ----------
+        None
+
+        Returns
+        -------
+        None
+
+        Examples
+        --------
+        >>> x = np.ma.array([[1,2 ], [3, 4]], mask=[0]*4)
+        >>> x.mask
+        array([[False, False],
+               [False, False]])
+        >>> x.shrink_mask()
+        masked_array(
+          data=[[1, 2],
+                [3, 4]],
+          mask=False,
+          fill_value=999999)
+        >>> x.mask
+        False
+
+        """
+        self._mask = _shrink_mask(self._mask)
+        return self
+
+    @property
+    def baseclass(self):
+        """ Class of the underlying data (read-only). """
+        return self._baseclass
+
+    def _get_data(self):
+        """
+        Returns the underlying data, as a view of the masked array.
+
+        If the underlying data is a subclass of :class:`numpy.ndarray`, it is
+        returned as such.
+
+        >>> x = np.ma.array(np.matrix([[1, 2], [3, 4]]), mask=[[0, 1], [1, 0]])
+        >>> x.data
+        matrix([[1, 2],
+                [3, 4]])
+
+        The type of the data can be accessed through the :attr:`baseclass`
+        attribute.
+        """
+        return ndarray.view(self, self._baseclass)
+
+    _data = property(fget=_get_data)
+    data = property(fget=_get_data)
+
+    @property
+    def flat(self):
+        """ Return a flat iterator, or set a flattened version of self to value. """
+        return MaskedIterator(self)
+
+    @flat.setter
+    def flat(self, value):
+        y = self.ravel()
+        y[:] = value
+
+    @property
+    def fill_value(self):
+        """
+        The filling value of the masked array is a scalar. When setting, None
+        will set to a default based on the data type.
+
+        Examples
+        --------
+        >>> for dt in [np.int32, np.int64, np.float64, np.complex128]:
+        ...     np.ma.array([0, 1], dtype=dt).get_fill_value()
+        ...
+        999999
+        999999
+        1e+20
+        (1e+20+0j)
+
+        >>> x = np.ma.array([0, 1.], fill_value=-np.inf)
+        >>> x.fill_value
+        -inf
+        >>> x.fill_value = np.pi
+        >>> x.fill_value
+        3.1415926535897931 # may vary
+
+        Reset to default:
+
+        >>> x.fill_value = None
+        >>> x.fill_value
+        1e+20
+
+        """
+        if self._fill_value is None:
+            self._fill_value = _check_fill_value(None, self.dtype)
+
+        # Temporary workaround to account for the fact that str and bytes
+        # scalars cannot be indexed with (), whereas all other numpy
+        # scalars can. See issues #7259 and #7267.
+        # The if-block can be removed after #7267 has been fixed.
+        if isinstance(self._fill_value, ndarray):
+            return self._fill_value[()]
+        return self._fill_value
+
+    @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
+            self._fill_value = target
+        else:
+            # Don't overwrite the attribute, just fill it (for propagation)
+            _fill_value[()] = target
+
+    # kept for compatibility
+    get_fill_value = fill_value.fget
+    set_fill_value = fill_value.fset
+
+    def filled(self, fill_value=None):
+        """
+        Return a copy of self, with masked values filled with a given value.
+        **However**, if there are no masked values to fill, self will be
+        returned instead as an ndarray.
+
+        Parameters
+        ----------
+        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
+        -------
+        filled_array : ndarray
+            A copy of ``self`` with invalid entries replaced by *fill_value*
+            (be it the function argument or the attribute of ``self``), or
+            ``self`` itself as an ndarray if there are no invalid entries to
+            be replaced.
+
+        Notes
+        -----
+        The result is **not** a MaskedArray!
+
+        Examples
+        --------
+        >>> 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'>
+
+        Subclassing is preserved. This means that if, e.g., the data part of
+        the masked array is a recarray, `filled` returns a recarray:
+
+        >>> x = np.array([(-1, 2), (-3, 4)], dtype='i8,i8').view(np.recarray)
+        >>> m = np.ma.array(x, mask=[(True, False), (False, True)])
+        >>> m.filled()
+        rec.array([(999999,      2), (    -3, 999999)],
+                  dtype=[('f0', '<i8'), ('f1', '<i8')])
+        """
+        m = self._mask
+        if m is nomask:
+            return self._data
+
+        if fill_value is None:
+            fill_value = self.fill_value
+        else:
+            fill_value = _check_fill_value(fill_value, self.dtype)
+
+        if self is masked_singleton:
+            return np.asanyarray(fill_value)
+
+        if m.dtype.names is not None:
+            result = self._data.copy('K')
+            _recursive_filled(result, self._mask, fill_value)
+        elif not m.any():
+            return self._data
+        else:
+            result = self._data.copy('K')
+            try:
+                np.copyto(result, fill_value, where=m)
+            except (TypeError, AttributeError):
+                fill_value = narray(fill_value, dtype=object)
+                d = result.astype(object)
+                result = np.choose(m, (d, fill_value))
+            except IndexError:
+                # ok, if scalar
+                if self._data.shape:
+                    raise
+                elif m:
+                    result = np.array(fill_value, dtype=self.dtype)
+                else:
+                    result = self._data
+        return result
+
+    def compressed(self):
+        """
+        Return all the non-masked data as a 1-D array.
+
+        Returns
+        -------
+        data : ndarray
+            A new `ndarray` holding the non-masked data is returned.
+
+        Notes
+        -----
+        The result is **not** a MaskedArray!
+
+        Examples
+        --------
+        >>> x = np.ma.array(np.arange(5), mask=[0]*2 + [1]*3)
+        >>> x.compressed()
+        array([0, 1])
+        >>> type(x.compressed())
+        <class 'numpy.ndarray'>
+
+        """
+        data = ndarray.ravel(self._data)
+        if self._mask is not nomask:
+            data = data.compress(np.logical_not(ndarray.ravel(self._mask)))
+        return data
+
+    def compress(self, condition, axis=None, out=None):
+        """
+        Return `a` where condition is ``True``.
+
+        If condition is a `~ma.MaskedArray`, missing values are considered
+        as ``False``.
+
+        Parameters
+        ----------
+        condition : var
+            Boolean 1-d array selecting which entries to return. If len(condition)
+            is less than the size of a along the axis, then output is truncated
+            to length of condition array.
+        axis : {None, int}, optional
+            Axis along which the operation must be performed.
+        out : {None, ndarray}, optional
+            Alternative output array in which to place the result. It must have
+            the same shape as the expected output but the type will be cast if
+            necessary.
+
+        Returns
+        -------
+        result : MaskedArray
+            A :class:`~ma.MaskedArray` object.
+
+        Notes
+        -----
+        Please note the difference with :meth:`compressed` !
+        The output of :meth:`compress` has a mask, the output of
+        :meth:`compressed` does not.
+
+        Examples
+        --------
+        >>> x = np.ma.array([[1,2,3],[4,5,6],[7,8,9]], mask=[0] + [1,0]*4)
+        >>> x
+        masked_array(
+          data=[[1, --, 3],
+                [--, 5, --],
+                [7, --, 9]],
+          mask=[[False,  True, False],
+                [ True, False,  True],
+                [False,  True, False]],
+          fill_value=999999)
+        >>> x.compress([1, 0, 1])
+        masked_array(data=[1, 3],
+                     mask=[False, False],
+               fill_value=999999)
+
+        >>> x.compress([1, 0, 1], axis=1)
+        masked_array(
+          data=[[1, 3],
+                [--, --],
+                [7, 9]],
+          mask=[[False, False],
+                [ True,  True],
+                [False, False]],
+          fill_value=999999)
+
+        """
+        # Get the basic components
+        (_data, _mask) = (self._data, self._mask)
+
+        # Force the condition to a regular ndarray and forget the missing
+        # values.
+        condition = np.asarray(condition)
+
+        _new = _data.compress(condition, axis=axis, out=out).view(type(self))
+        _new._update_from(self)
+        if _mask is not nomask:
+            _new._mask = _mask.compress(condition, axis=axis)
+        return _new
+
+    def _insert_masked_print(self):
+        """
+        Replace masked values with masked_print_option, casting all innermost
+        dtypes to object.
+        """
+        if masked_print_option.enabled():
+            mask = self._mask
+            if mask is nomask:
+                res = self._data
+            else:
+                # convert to object array to make filled work
+                data = self._data
+                # For big arrays, to avoid a costly conversion to the
+                # object dtype, extract the corners before the conversion.
+                print_width = (self._print_width if self.ndim > 1
+                               else self._print_width_1d)
+                for axis in range(self.ndim):
+                    if data.shape[axis] > print_width:
+                        ind = print_width // 2
+                        arr = np.split(data, (ind, -ind), axis=axis)
+                        data = np.concatenate((arr[0], arr[2]), axis=axis)
+                        arr = np.split(mask, (ind, -ind), axis=axis)
+                        mask = np.concatenate((arr[0], arr[2]), axis=axis)
+
+                rdtype = _replace_dtype_fields(self.dtype, "O")
+                res = data.astype(rdtype)
+                _recursive_printoption(res, mask, masked_print_option)
+        else:
+            res = self.filled(self.fill_value)
+        return res
+
+    def __str__(self):
+        return str(self._insert_masked_print())
+
+    def __repr__(self):
+        """
+        Literal string representation.
+
+        """
+        if self._baseclass is np.ndarray:
+            name = 'array'
+        else:
+            name = self._baseclass.__name__
+
+
+        # 2016-11-19: Demoted to legacy format
+        if np.core.arrayprint._get_legacy_print_mode() <= 113:
+            is_long = self.ndim > 1
+            parameters = dict(
+                name=name,
+                nlen=" " * len(name),
+                data=str(self),
+                mask=str(self._mask),
+                fill=str(self.fill_value),
+                dtype=str(self.dtype)
+            )
+            is_structured = bool(self.dtype.names)
+            key = '{}_{}'.format(
+                'long' if is_long else 'short',
+                'flx' if is_structured else 'std'
+            )
+            return _legacy_print_templates[key] % parameters
+
+        prefix = f"masked_{name}("
+
+        dtype_needed = (
+            not np.core.arrayprint.dtype_is_implied(self.dtype) or
+            np.all(self.mask) or
+            self.size == 0
+        )
+
+        # determine which keyword args need to be shown
+        keys = ['data', 'mask', 'fill_value']
+        if dtype_needed:
+            keys.append('dtype')
+
+        # array has only one row (non-column)
+        is_one_row = builtins.all(dim == 1 for dim in self.shape[:-1])
+
+        # choose what to indent each keyword with
+        min_indent = 2
+        if is_one_row:
+            # first key on the same line as the type, remaining keys
+            # aligned by equals
+            indents = {}
+            indents[keys[0]] = prefix
+            for k in keys[1:]:
+                n = builtins.max(min_indent, len(prefix + keys[0]) - len(k))
+                indents[k] = ' ' * n
+            prefix = ''  # absorbed into the first indent
+        else:
+            # each key on its own line, indented by two spaces
+            indents = {k: ' ' * min_indent for k in keys}
+            prefix = prefix + '\n'  # first key on the next line
+
+        # format the field values
+        reprs = {}
+        reprs['data'] = np.array2string(
+            self._insert_masked_print(),
+            separator=", ",
+            prefix=indents['data'] + 'data=',
+            suffix=',')
+        reprs['mask'] = np.array2string(
+            self._mask,
+            separator=", ",
+            prefix=indents['mask'] + 'mask=',
+            suffix=',')
+        reprs['fill_value'] = repr(self.fill_value)
+        if dtype_needed:
+            reprs['dtype'] = np.core.arrayprint.dtype_short_repr(self.dtype)
+
+        # join keys with values and indentations
+        result = ',\n'.join(
+            '{}{}={}'.format(indents[k], k, reprs[k])
+            for k in keys
+        )
+        return prefix + result + ')'
+
+    def _delegate_binop(self, other):
+        # This emulates the logic in
+        #     private/binop_override.h:forward_binop_should_defer
+        if isinstance(other, type(self)):
+            return False
+        array_ufunc = getattr(other, "__array_ufunc__", False)
+        if array_ufunc is False:
+            other_priority = getattr(other, "__array_priority__", -1000000)
+            return self.__array_priority__ < other_priority
+        else:
+            # If array_ufunc is not None, it will be called inside the ufunc;
+            # None explicitly tells us to not call the ufunc, i.e., defer.
+            return array_ufunc is None
+
+    def _comparison(self, other, compare):
+        """Compare self with other using operator.eq or operator.ne.
+
+        When either of the elements is masked, the result is masked as well,
+        but the underlying boolean data are still set, with self and other
+        considered equal if both are masked, and unequal otherwise.
+
+        For structured arrays, all fields are combined, with masked values
+        ignored. The result is masked if all fields were masked, with self
+        and other considered equal only if both were fully masked.
+        """
+        omask = getmask(other)
+        smask = self.mask
+        mask = mask_or(smask, omask, copy=True)
+
+        odata = getdata(other)
+        if mask.dtype.names is not None:
+            # only == and != are reasonably defined for structured dtypes,
+            # so give up early for all other comparisons:
+            if compare not in (operator.eq, operator.ne):
+                return NotImplemented
+            # For possibly masked structured arrays we need to be careful,
+            # since the standard structured array comparison will use all
+            # fields, masked or not. To avoid masked fields influencing the
+            # outcome, we set all masked fields in self to other, so they'll
+            # count as equal.  To prepare, we ensure we have the right shape.
+            broadcast_shape = np.broadcast(self, odata).shape
+            sbroadcast = np.broadcast_to(self, broadcast_shape, subok=True)
+            sbroadcast._mask = mask
+            sdata = sbroadcast.filled(odata)
+            # Now take care of the mask; the merged mask should have an item
+            # masked if all fields were masked (in one and/or other).
+            mask = (mask == np.ones((), mask.dtype))
+            # Ensure we can compare masks below if other was not masked.
+            if omask is np.False_:
+                omask = np.zeros((), smask.dtype)
+
+        else:
+            # For regular arrays, just use the data as they come.
+            sdata = self.data
+
+        check = compare(sdata, odata)
+
+        if isinstance(check, (np.bool_, bool)):
+            return masked if mask else check
+
+        if mask is not nomask:
+            if compare in (operator.eq, operator.ne):
+                # Adjust elements that were masked, which should be treated
+                # as equal if masked in both, unequal if masked in one.
+                # Note that this works automatically for structured arrays too.
+                # Ignore this for operations other than `==` and `!=`
+                check = np.where(mask, compare(smask, omask), check)
+
+            if mask.shape != check.shape:
+                # Guarantee consistency of the shape, making a copy since the
+                # the mask may need to get written to later.
+                mask = np.broadcast_to(mask, check.shape).copy()
+
+        check = check.view(type(self))
+        check._update_from(self)
+        check._mask = mask
+
+        # Cast fill value to bool_ if needed. If it cannot be cast, the
+        # default boolean fill value is used.
+        if check._fill_value is not None:
+            try:
+                fill = _check_fill_value(check._fill_value, np.bool_)
+            except (TypeError, ValueError):
+                fill = _check_fill_value(None, np.bool_)
+            check._fill_value = fill
+
+        return check
+
+    def __eq__(self, other):
+        """Check whether other equals self elementwise.
+
+        When either of the elements is masked, the result is masked as well,
+        but the underlying boolean data are still set, with self and other
+        considered equal if both are masked, and unequal otherwise.
+
+        For structured arrays, all fields are combined, with masked values
+        ignored. The result is masked if all fields were masked, with self
+        and other considered equal only if both were fully masked.
+        """
+        return self._comparison(other, operator.eq)
+
+    def __ne__(self, other):
+        """Check whether other does not equal self elementwise.
+
+        When either of the elements is masked, the result is masked as well,
+        but the underlying boolean data are still set, with self and other
+        considered equal if both are masked, and unequal otherwise.
+
+        For structured arrays, all fields are combined, with masked values
+        ignored. The result is masked if all fields were masked, with self
+        and other considered equal only if both were fully masked.
+        """
+        return self._comparison(other, operator.ne)
+
+    # All other comparisons:
+    def __le__(self, other):
+        return self._comparison(other, operator.le)
+
+    def __lt__(self, other):
+        return self._comparison(other, operator.lt)
+
+    def __ge__(self, other):
+        return self._comparison(other, operator.ge)
+
+    def __gt__(self, other):
+        return self._comparison(other, operator.gt)
+
+    def __add__(self, other):
+        """
+        Add self to other, and return a new masked array.
+
+        """
+        if self._delegate_binop(other):
+            return NotImplemented
+        return add(self, other)
+
+    def __radd__(self, other):
+        """
+        Add other to self, and return a new masked array.
+
+        """
+        # In analogy with __rsub__ and __rdiv__, use original order:
+        # we get here from `other + self`.
+        return add(other, self)
+
+    def __sub__(self, other):
+        """
+        Subtract other from self, and return a new masked array.
+
+        """
+        if self._delegate_binop(other):
+            return NotImplemented
+        return subtract(self, other)
+
+    def __rsub__(self, other):
+        """
+        Subtract self from other, and return a new masked array.
+
+        """
+        return subtract(other, self)
+
+    def __mul__(self, other):
+        "Multiply self by other, and return a new masked array."
+        if self._delegate_binop(other):
+            return NotImplemented
+        return multiply(self, other)
+
+    def __rmul__(self, other):
+        """
+        Multiply other by self, and return a new masked array.
+
+        """
+        # In analogy with __rsub__ and __rdiv__, use original order:
+        # we get here from `other * self`.
+        return multiply(other, self)
+
+    def __div__(self, other):
+        """
+        Divide other into self, and return a new masked array.
+
+        """
+        if self._delegate_binop(other):
+            return NotImplemented
+        return divide(self, other)
+
+    def __truediv__(self, other):
+        """
+        Divide other into self, and return a new masked array.
+
+        """
+        if self._delegate_binop(other):
+            return NotImplemented
+        return true_divide(self, other)
+
+    def __rtruediv__(self, other):
+        """
+        Divide self into other, and return a new masked array.
+
+        """
+        return true_divide(other, self)
+
+    def __floordiv__(self, other):
+        """
+        Divide other into self, and return a new masked array.
+
+        """
+        if self._delegate_binop(other):
+            return NotImplemented
+        return floor_divide(self, other)
+
+    def __rfloordiv__(self, other):
+        """
+        Divide self into other, and return a new masked array.
+
+        """
+        return floor_divide(other, self)
+
+    def __pow__(self, other):
+        """
+        Raise self to the power other, masking the potential NaNs/Infs
+
+        """
+        if self._delegate_binop(other):
+            return NotImplemented
+        return power(self, other)
+
+    def __rpow__(self, other):
+        """
+        Raise other to the power self, masking the potential NaNs/Infs
+
+        """
+        return power(other, self)
+
+    def __iadd__(self, other):
+        """
+        Add other to self in-place.
+
+        """
+        m = getmask(other)
+        if self._mask is nomask:
+            if m is not nomask and m.any():
+                self._mask = make_mask_none(self.shape, self.dtype)
+                self._mask += m
+        else:
+            if m is not nomask:
+                self._mask += m
+        other_data = getdata(other)
+        other_data = np.where(self._mask, other_data.dtype.type(0), other_data)
+        self._data.__iadd__(other_data)
+        return self
+
+    def __isub__(self, other):
+        """
+        Subtract other from self in-place.
+
+        """
+        m = getmask(other)
+        if self._mask is nomask:
+            if m is not nomask and m.any():
+                self._mask = make_mask_none(self.shape, self.dtype)
+                self._mask += m
+        elif m is not nomask:
+            self._mask += m
+        other_data = getdata(other)
+        other_data = np.where(self._mask, other_data.dtype.type(0), other_data)
+        self._data.__isub__(other_data)
+        return self
+
+    def __imul__(self, other):
+        """
+        Multiply self by other in-place.
+
+        """
+        m = getmask(other)
+        if self._mask is nomask:
+            if m is not nomask and m.any():
+                self._mask = make_mask_none(self.shape, self.dtype)
+                self._mask += m
+        elif m is not nomask:
+            self._mask += m
+        other_data = getdata(other)
+        other_data = np.where(self._mask, other_data.dtype.type(1), other_data)
+        self._data.__imul__(other_data)
+        return self
+
+    def __idiv__(self, other):
+        """
+        Divide self by other in-place.
+
+        """
+        other_data = getdata(other)
+        dom_mask = _DomainSafeDivide().__call__(self._data, other_data)
+        other_mask = getmask(other)
+        new_mask = mask_or(other_mask, dom_mask)
+        # The following 4 lines control the domain filling
+        if dom_mask.any():
+            (_, fval) = ufunc_fills[np.divide]
+            other_data = np.where(
+                    dom_mask, other_data.dtype.type(fval), other_data)
+        self._mask |= new_mask
+        other_data = np.where(self._mask, other_data.dtype.type(1), other_data)
+        self._data.__idiv__(other_data)
+        return self
+
+    def __ifloordiv__(self, other):
+        """
+        Floor divide self by other in-place.
+
+        """
+        other_data = getdata(other)
+        dom_mask = _DomainSafeDivide().__call__(self._data, other_data)
+        other_mask = getmask(other)
+        new_mask = mask_or(other_mask, dom_mask)
+        # The following 3 lines control the domain filling
+        if dom_mask.any():
+            (_, fval) = ufunc_fills[np.floor_divide]
+            other_data = np.where(
+                    dom_mask, other_data.dtype.type(fval), other_data)
+        self._mask |= new_mask
+        other_data = np.where(self._mask, other_data.dtype.type(1), other_data)
+        self._data.__ifloordiv__(other_data)
+        return self
+
+    def __itruediv__(self, other):
+        """
+        True divide self by other in-place.
+
+        """
+        other_data = getdata(other)
+        dom_mask = _DomainSafeDivide().__call__(self._data, other_data)
+        other_mask = getmask(other)
+        new_mask = mask_or(other_mask, dom_mask)
+        # The following 3 lines control the domain filling
+        if dom_mask.any():
+            (_, fval) = ufunc_fills[np.true_divide]
+            other_data = np.where(
+                    dom_mask, other_data.dtype.type(fval), other_data)
+        self._mask |= new_mask
+        other_data = np.where(self._mask, other_data.dtype.type(1), other_data)
+        self._data.__itruediv__(other_data)
+        return self
+
+    def __ipow__(self, other):
+        """
+        Raise self to the power other, in place.
+
+        """
+        other_data = getdata(other)
+        other_data = np.where(self._mask, other_data.dtype.type(1), other_data)
+        other_mask = getmask(other)
+        with np.errstate(divide='ignore', invalid='ignore'):
+            self._data.__ipow__(other_data)
+        invalid = np.logical_not(np.isfinite(self._data))
+        if invalid.any():
+            if self._mask is not nomask:
+                self._mask |= invalid
+            else:
+                self._mask = invalid
+            np.copyto(self._data, self.fill_value, where=invalid)
+        new_mask = mask_or(other_mask, invalid)
+        self._mask = mask_or(self._mask, new_mask)
+        return self
+
+    def __float__(self):
+        """
+        Convert to float.
+
+        """
+        if self.size > 1:
+            raise TypeError("Only length-1 arrays can be converted "
+                            "to Python scalars")
+        elif self._mask:
+            warnings.warn("Warning: converting a masked element to nan.", stacklevel=2)
+            return np.nan
+        return float(self.item())
+
+    def __int__(self):
+        """
+        Convert to int.
+
+        """
+        if self.size > 1:
+            raise TypeError("Only length-1 arrays can be converted "
+                            "to Python scalars")
+        elif self._mask:
+            raise MaskError('Cannot convert masked element to a Python int.')
+        return int(self.item())
+
+    @property
+    def imag(self):
+        """
+        The imaginary part of the masked array.
+
+        This property is a view on the imaginary part of this `MaskedArray`.
+
+        See Also
+        --------
+        real
+
+        Examples
+        --------
+        >>> x = np.ma.array([1+1.j, -2j, 3.45+1.6j], mask=[False, True, False])
+        >>> x.imag
+        masked_array(data=[1.0, --, 1.6],
+                     mask=[False,  True, False],
+               fill_value=1e+20)
+
+        """
+        result = self._data.imag.view(type(self))
+        result.__setmask__(self._mask)
+        return result
+
+    # kept for compatibility
+    get_imag = imag.fget
+
+    @property
+    def real(self):
+        """
+        The real part of the masked array.
+
+        This property is a view on the real part of this `MaskedArray`.
+
+        See Also
+        --------
+        imag
+
+        Examples
+        --------
+        >>> x = np.ma.array([1+1.j, -2j, 3.45+1.6j], mask=[False, True, False])
+        >>> x.real
+        masked_array(data=[1.0, --, 3.45],
+                     mask=[False,  True, False],
+               fill_value=1e+20)
+
+        """
+        result = self._data.real.view(type(self))
+        result.__setmask__(self._mask)
+        return result
+
+    # kept for compatibility
+    get_real = real.fget
+
+    def count(self, axis=None, keepdims=np._NoValue):
+        """
+        Count the non-masked elements of the array along the given axis.
+
+        Parameters
+        ----------
+        axis : None or int or tuple of ints, optional
+            Axis or axes along which the count is performed.
+            The default, None, performs the count over all
+            the dimensions of the input array. `axis` may be negative, in
+            which case it counts from the last to the first axis.
+
+            .. versionadded:: 1.10.0
+
+            If this is a tuple of ints, the count is performed on multiple
+            axes, instead of a single axis or all the axes as before.
+        keepdims : bool, optional
+            If this is set to 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 array.
+
+        Returns
+        -------
+        result : ndarray or scalar
+            An array with the same shape as the input array, with the specified
+            axis removed. If the array is a 0-d array, or if `axis` is None, a
+            scalar is returned.
+
+        See Also
+        --------
+        ma.count_masked : Count masked elements in array or along a given axis.
+
+        Examples
+        --------
+        >>> import numpy.ma as ma
+        >>> a = ma.arange(6).reshape((2, 3))
+        >>> a[1, :] = ma.masked
+        >>> a
+        masked_array(
+          data=[[0, 1, 2],
+                [--, --, --]],
+          mask=[[False, False, False],
+                [ True,  True,  True]],
+          fill_value=999999)
+        >>> a.count()
+        3
+
+        When the `axis` keyword is specified an array of appropriate size is
+        returned.
+
+        >>> a.count(axis=0)
+        array([1, 1, 1])
+        >>> a.count(axis=1)
+        array([3, 0])
+
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        m = self._mask
+        # special case for matrices (we assume no other subclasses modify
+        # their dimensions)
+        if isinstance(self.data, np.matrix):
+            if m is nomask:
+                m = np.zeros(self.shape, dtype=np.bool_)
+            m = m.view(type(self.data))
+
+        if m is nomask:
+            # compare to _count_reduce_items in _methods.py
+
+            if self.shape == ():
+                if axis not in (None, 0):
+                    raise np.AxisError(axis=axis, ndim=self.ndim)
+                return 1
+            elif axis is None:
+                if kwargs.get('keepdims', False):
+                    return np.array(self.size, dtype=np.intp, ndmin=self.ndim)
+                return self.size
+
+            axes = normalize_axis_tuple(axis, self.ndim)
+            items = 1
+            for ax in axes:
+                items *= self.shape[ax]
+
+            if kwargs.get('keepdims', False):
+                out_dims = list(self.shape)
+                for a in axes:
+                    out_dims[a] = 1
+            else:
+                out_dims = [d for n, d in enumerate(self.shape)
+                            if n not in axes]
+            # make sure to return a 0-d array if axis is supplied
+            return np.full(out_dims, items, dtype=np.intp)
+
+        # take care of the masked singleton
+        if self is masked:
+            return 0
+
+        return (~m).sum(axis=axis, dtype=np.intp, **kwargs)
+
+    def ravel(self, order='C'):
+        """
+        Returns a 1D version of self, as a view.
+
+        Parameters
+        ----------
+        order : {'C', 'F', 'A', 'K'}, optional
+            The elements of `a` are read using this index order. 'C' means to
+            index the elements in C-like order, with the last axis index
+            changing fastest, back to the first axis index changing slowest.
+            'F' means to index the elements in Fortran-like index order, with
+            the first index changing fastest, and the last index changing
+            slowest. Note that the 'C' and 'F' options take no account of the
+            memory layout of the underlying array, and only refer to the order
+            of axis indexing.  'A' means to read the elements in Fortran-like
+            index order if `m` is Fortran *contiguous* in memory, C-like order
+            otherwise.  'K' means to read the elements in the order they occur
+            in memory, except for reversing the data when strides are negative.
+            By default, 'C' index order is used.
+            (Masked arrays currently use 'A' on the data when 'K' is passed.)
+
+        Returns
+        -------
+        MaskedArray
+            Output view is of shape ``(self.size,)`` (or
+            ``(np.ma.product(self.shape),)``).
+
+        Examples
+        --------
+        >>> x = np.ma.array([[1,2,3],[4,5,6],[7,8,9]], mask=[0] + [1,0]*4)
+        >>> x
+        masked_array(
+          data=[[1, --, 3],
+                [--, 5, --],
+                [7, --, 9]],
+          mask=[[False,  True, False],
+                [ True, False,  True],
+                [False,  True, False]],
+          fill_value=999999)
+        >>> x.ravel()
+        masked_array(data=[1, --, 3, --, 5, --, 7, --, 9],
+                     mask=[False,  True, False,  True, False,  True, False,  True,
+                           False],
+               fill_value=999999)
+
+        """
+        # The order of _data and _mask could be different (it shouldn't be
+        # normally).  Passing order `K` or `A` would be incorrect.
+        # So we ignore the mask memory order.
+        # TODO: We don't actually support K, so use A instead.  We could
+        #       try to guess this correct by sorting strides or deprecate.
+        if order in "kKaA":
+            order = "F" if self._data.flags.fnc else "C"
+        r = ndarray.ravel(self._data, order=order).view(type(self))
+        r._update_from(self)
+        if self._mask is not nomask:
+            r._mask = ndarray.ravel(self._mask, order=order).reshape(r.shape)
+        else:
+            r._mask = nomask
+        return r
+
+
+    def reshape(self, *s, **kwargs):
+        """
+        Give a new shape to the array without changing its data.
+
+        Returns a masked array containing the same data, but with a new shape.
+        The result is a view on the original array; if this is not possible, a
+        ValueError is raised.
+
+        Parameters
+        ----------
+        shape : int or tuple of ints
+            The new shape should be compatible with the original shape. If an
+            integer is supplied, then the result will be a 1-D array of that
+            length.
+        order : {'C', 'F'}, optional
+            Determines whether the array data should be viewed as in C
+            (row-major) or FORTRAN (column-major) order.
+
+        Returns
+        -------
+        reshaped_array : array
+            A new view on the array.
+
+        See Also
+        --------
+        reshape : Equivalent function in the masked array module.
+        numpy.ndarray.reshape : Equivalent method on ndarray object.
+        numpy.reshape : Equivalent function in the NumPy module.
+
+        Notes
+        -----
+        The reshaping operation cannot guarantee that a copy will not be made,
+        to modify the shape in place, use ``a.shape = s``
+
+        Examples
+        --------
+        >>> x = np.ma.array([[1,2],[3,4]], mask=[1,0,0,1])
+        >>> x
+        masked_array(
+          data=[[--, 2],
+                [3, --]],
+          mask=[[ True, False],
+                [False,  True]],
+          fill_value=999999)
+        >>> x = x.reshape((4,1))
+        >>> x
+        masked_array(
+          data=[[--],
+                [2],
+                [3],
+                [--]],
+          mask=[[ True],
+                [False],
+                [False],
+                [ True]],
+          fill_value=999999)
+
+        """
+        kwargs.update(order=kwargs.get('order', 'C'))
+        result = self._data.reshape(*s, **kwargs).view(type(self))
+        result._update_from(self)
+        mask = self._mask
+        if mask is not nomask:
+            result._mask = mask.reshape(*s, **kwargs)
+        return result
+
+    def resize(self, newshape, refcheck=True, order=False):
+        """
+        .. warning::
+
+            This method does nothing, except raise a ValueError exception. A
+            masked array does not own its data and therefore cannot safely be
+            resized in place. Use the `numpy.ma.resize` function instead.
+
+        This method is difficult to implement safely and may be deprecated in
+        future releases of NumPy.
+
+        """
+        # Note : the 'order' keyword looks broken, let's just drop it
+        errmsg = "A masked array does not own its data "\
+                 "and therefore cannot be resized.\n" \
+                 "Use the numpy.ma.resize function instead."
+        raise ValueError(errmsg)
+
+    def put(self, indices, values, mode='raise'):
+        """
+        Set storage-indexed locations to corresponding values.
+
+        Sets self._data.flat[n] = values[n] for each n in indices.
+        If `values` is shorter than `indices` then it will repeat.
+        If `values` has some masked values, the initial mask is updated
+        in consequence, else the corresponding values are unmasked.
+
+        Parameters
+        ----------
+        indices : 1-D array_like
+            Target indices, interpreted as integers.
+        values : array_like
+            Values to place in self._data copy at target indices.
+        mode : {'raise', 'wrap', 'clip'}, optional
+            Specifies how out-of-bounds indices will behave.
+            'raise' : raise an error.
+            'wrap' : wrap around.
+            'clip' : clip to the range.
+
+        Notes
+        -----
+        `values` can be a scalar or length 1 array.
+
+        Examples
+        --------
+        >>> x = np.ma.array([[1,2,3],[4,5,6],[7,8,9]], mask=[0] + [1,0]*4)
+        >>> x
+        masked_array(
+          data=[[1, --, 3],
+                [--, 5, --],
+                [7, --, 9]],
+          mask=[[False,  True, False],
+                [ True, False,  True],
+                [False,  True, False]],
+          fill_value=999999)
+        >>> x.put([0,4,8],[10,20,30])
+        >>> x
+        masked_array(
+          data=[[10, --, 3],
+                [--, 20, --],
+                [7, --, 30]],
+          mask=[[False,  True, False],
+                [ True, False,  True],
+                [False,  True, False]],
+          fill_value=999999)
+
+        >>> x.put(4,999)
+        >>> x
+        masked_array(
+          data=[[10, --, 3],
+                [--, 999, --],
+                [7, --, 30]],
+          mask=[[False,  True, False],
+                [ True, False,  True],
+                [False,  True, False]],
+          fill_value=999999)
+
+        """
+        # Hard mask: Get rid of the values/indices that fall on masked data
+        if self._hardmask and self._mask is not nomask:
+            mask = self._mask[indices]
+            indices = narray(indices, copy=False)
+            values = narray(values, copy=False, subok=True)
+            values.resize(indices.shape)
+            indices = indices[~mask]
+            values = values[~mask]
+
+        self._data.put(indices, values, mode=mode)
+
+        # short circuit if neither self nor values are masked
+        if self._mask is nomask and getmask(values) is nomask:
+            return
+
+        m = getmaskarray(self)
+
+        if getmask(values) is nomask:
+            m.put(indices, False, mode=mode)
+        else:
+            m.put(indices, values._mask, mode=mode)
+        m = make_mask(m, copy=False, shrink=True)
+        self._mask = m
+        return
+
+    def ids(self):
+        """
+        Return the addresses of the data and mask areas.
+
+        Parameters
+        ----------
+        None
+
+        Examples
+        --------
+        >>> x = np.ma.array([1, 2, 3], mask=[0, 1, 1])
+        >>> x.ids()
+        (166670640, 166659832) # may vary
+
+        If the array has no mask, the address of `nomask` is returned. This address
+        is typically not close to the data in memory:
+
+        >>> x = np.ma.array([1, 2, 3])
+        >>> x.ids()
+        (166691080, 3083169284) # may vary
+
+        """
+        if self._mask is nomask:
+            return (self.ctypes.data, id(nomask))
+        return (self.ctypes.data, self._mask.ctypes.data)
+
+    def iscontiguous(self):
+        """
+        Return a boolean indicating whether the data is contiguous.
+
+        Parameters
+        ----------
+        None
+
+        Examples
+        --------
+        >>> x = np.ma.array([1, 2, 3])
+        >>> x.iscontiguous()
+        True
+
+        `iscontiguous` returns one of the flags of the masked array:
+
+        >>> x.flags
+          C_CONTIGUOUS : True
+          F_CONTIGUOUS : True
+          OWNDATA : False
+          WRITEABLE : True
+          ALIGNED : True
+          WRITEBACKIFCOPY : False
+
+        """
+        return self.flags['CONTIGUOUS']
+
+    def all(self, axis=None, out=None, keepdims=np._NoValue):
+        """
+        Returns True if all elements evaluate to True.
+
+        The output array is masked where all the values along the given axis
+        are masked: if the output would have been a scalar and that all the
+        values are masked, then the output is `masked`.
+
+        Refer to `numpy.all` for full documentation.
+
+        See Also
+        --------
+        numpy.ndarray.all : corresponding function for ndarrays
+        numpy.all : equivalent function
+
+        Examples
+        --------
+        >>> np.ma.array([1,2,3]).all()
+        True
+        >>> a = np.ma.array([1,2,3], mask=True)
+        >>> (a.all() is np.ma.masked)
+        True
+
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        mask = _check_mask_axis(self._mask, axis, **kwargs)
+        if out is None:
+            d = self.filled(True).all(axis=axis, **kwargs).view(type(self))
+            if d.ndim:
+                d.__setmask__(mask)
+            elif mask:
+                return masked
+            return d
+        self.filled(True).all(axis=axis, out=out, **kwargs)
+        if isinstance(out, MaskedArray):
+            if out.ndim or mask:
+                out.__setmask__(mask)
+        return out
+
+    def any(self, axis=None, out=None, keepdims=np._NoValue):
+        """
+        Returns True if any of the elements of `a` evaluate to True.
+
+        Masked values are considered as False during computation.
+
+        Refer to `numpy.any` for full documentation.
+
+        See Also
+        --------
+        numpy.ndarray.any : corresponding function for ndarrays
+        numpy.any : equivalent function
+
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        mask = _check_mask_axis(self._mask, axis, **kwargs)
+        if out is None:
+            d = self.filled(False).any(axis=axis, **kwargs).view(type(self))
+            if d.ndim:
+                d.__setmask__(mask)
+            elif mask:
+                d = masked
+            return d
+        self.filled(False).any(axis=axis, out=out, **kwargs)
+        if isinstance(out, MaskedArray):
+            if out.ndim or mask:
+                out.__setmask__(mask)
+        return out
+
+    def nonzero(self):
+        """
+        Return the indices of unmasked elements that are not zero.
+
+        Returns a tuple of arrays, one for each dimension, containing the
+        indices of the non-zero elements in that dimension. The corresponding
+        non-zero values can be obtained with::
+
+            a[a.nonzero()]
+
+        To group the indices by element, rather than dimension, use
+        instead::
+
+            np.transpose(a.nonzero())
+
+        The result of this is always a 2d array, with a row for each non-zero
+        element.
+
+        Parameters
+        ----------
+        None
+
+        Returns
+        -------
+        tuple_of_arrays : tuple
+            Indices of elements that are non-zero.
+
+        See Also
+        --------
+        numpy.nonzero :
+            Function operating on ndarrays.
+        flatnonzero :
+            Return indices that are non-zero in the flattened version of the input
+            array.
+        numpy.ndarray.nonzero :
+            Equivalent ndarray method.
+        count_nonzero :
+            Counts the number of non-zero elements in the input array.
+
+        Examples
+        --------
+        >>> import numpy.ma as ma
+        >>> x = ma.array(np.eye(3))
+        >>> x
+        masked_array(
+          data=[[1., 0., 0.],
+                [0., 1., 0.],
+                [0., 0., 1.]],
+          mask=False,
+          fill_value=1e+20)
+        >>> x.nonzero()
+        (array([0, 1, 2]), array([0, 1, 2]))
+
+        Masked elements are ignored.
+
+        >>> x[1, 1] = ma.masked
+        >>> x
+        masked_array(
+          data=[[1.0, 0.0, 0.0],
+                [0.0, --, 0.0],
+                [0.0, 0.0, 1.0]],
+          mask=[[False, False, False],
+                [False,  True, False],
+                [False, False, False]],
+          fill_value=1e+20)
+        >>> x.nonzero()
+        (array([0, 2]), array([0, 2]))
+
+        Indices can also be grouped by element.
+
+        >>> np.transpose(x.nonzero())
+        array([[0, 0],
+               [2, 2]])
+
+        A common use for ``nonzero`` is to find the indices of an array, where
+        a condition is True.  Given an array `a`, the condition `a` > 3 is a
+        boolean array and since False is interpreted as 0, ma.nonzero(a > 3)
+        yields the indices of the `a` where the condition is true.
+
+        >>> a = ma.array([[1,2,3],[4,5,6],[7,8,9]])
+        >>> a > 3
+        masked_array(
+          data=[[False, False, False],
+                [ True,  True,  True],
+                [ True,  True,  True]],
+          mask=False,
+          fill_value=True)
+        >>> ma.nonzero(a > 3)
+        (array([1, 1, 1, 2, 2, 2]), array([0, 1, 2, 0, 1, 2]))
+
+        The ``nonzero`` method of the condition array can also be called.
+
+        >>> (a > 3).nonzero()
+        (array([1, 1, 1, 2, 2, 2]), array([0, 1, 2, 0, 1, 2]))
+
+        """
+        return narray(self.filled(0), copy=False).nonzero()
+
+    def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None):
+        """
+        (this docstring should be overwritten)
+        """
+        #!!!: implement out + test!
+        m = self._mask
+        if m is nomask:
+            result = super().trace(offset=offset, axis1=axis1, axis2=axis2,
+                                   out=out)
+            return result.astype(dtype)
+        else:
+            D = self.diagonal(offset=offset, axis1=axis1, axis2=axis2)
+            return D.astype(dtype).filled(0).sum(axis=-1, out=out)
+    trace.__doc__ = ndarray.trace.__doc__
+
+    def dot(self, b, out=None, strict=False):
+        """
+        a.dot(b, out=None)
+
+        Masked dot product of two arrays. Note that `out` and `strict` are
+        located in different positions than in `ma.dot`. In order to
+        maintain compatibility with the functional version, it is
+        recommended that the optional arguments be treated as keyword only.
+        At some point that may be mandatory.
+
+        .. versionadded:: 1.10.0
+
+        Parameters
+        ----------
+        b : masked_array_like
+            Inputs array.
+        out : masked_array, optional
+            Output argument. This must have the exact kind that would be
+            returned if it was not used. In particular, it must have the
+            right type, must be C-contiguous, and its dtype must be the
+            dtype that would be returned for `ma.dot(a,b)`. This is a
+            performance feature. Therefore, if these conditions are not
+            met, an exception is raised, instead of attempting to be
+            flexible.
+        strict : bool, optional
+            Whether masked data are propagated (True) or set to 0 (False)
+            for the computation. Default is False.  Propagating the mask
+            means that if a masked value appears in a row or column, the
+            whole row or column is considered masked.
+
+            .. versionadded:: 1.10.2
+
+        See Also
+        --------
+        numpy.ma.dot : equivalent function
+
+        """
+        return dot(self, b, out=out, strict=strict)
+
+    def sum(self, axis=None, dtype=None, out=None, keepdims=np._NoValue):
+        """
+        Return the sum of the array elements over the given axis.
+
+        Masked elements are set to 0 internally.
+
+        Refer to `numpy.sum` for full documentation.
+
+        See Also
+        --------
+        numpy.ndarray.sum : corresponding function for ndarrays
+        numpy.sum : equivalent function
+
+        Examples
+        --------
+        >>> x = np.ma.array([[1,2,3],[4,5,6],[7,8,9]], mask=[0] + [1,0]*4)
+        >>> x
+        masked_array(
+          data=[[1, --, 3],
+                [--, 5, --],
+                [7, --, 9]],
+          mask=[[False,  True, False],
+                [ True, False,  True],
+                [False,  True, False]],
+          fill_value=999999)
+        >>> x.sum()
+        25
+        >>> x.sum(axis=1)
+        masked_array(data=[4, 5, 16],
+                     mask=[False, False, False],
+               fill_value=999999)
+        >>> x.sum(axis=0)
+        masked_array(data=[8, 5, 12],
+                     mask=[False, False, False],
+               fill_value=999999)
+        >>> print(type(x.sum(axis=0, dtype=np.int64)[0]))
+        <class 'numpy.int64'>
+
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        _mask = self._mask
+        newmask = _check_mask_axis(_mask, axis, **kwargs)
+        # No explicit output
+        if out is None:
+            result = self.filled(0).sum(axis, dtype=dtype, **kwargs)
+            rndim = getattr(result, 'ndim', 0)
+            if rndim:
+                result = result.view(type(self))
+                result.__setmask__(newmask)
+            elif newmask:
+                result = masked
+            return result
+        # Explicit output
+        result = self.filled(0).sum(axis, dtype=dtype, out=out, **kwargs)
+        if isinstance(out, MaskedArray):
+            outmask = getmask(out)
+            if outmask is nomask:
+                outmask = out._mask = make_mask_none(out.shape)
+            outmask.flat = newmask
+        return out
+
+    def cumsum(self, axis=None, dtype=None, out=None):
+        """
+        Return the cumulative sum of the array elements over the given axis.
+
+        Masked values are set to 0 internally during the computation.
+        However, their position is saved, and the result will be masked at
+        the same locations.
+
+        Refer to `numpy.cumsum` for full documentation.
+
+        Notes
+        -----
+        The mask is lost if `out` is not a valid :class:`ma.MaskedArray` !
+
+        Arithmetic is modular when using integer types, and no error is
+        raised on overflow.
+
+        See Also
+        --------
+        numpy.ndarray.cumsum : corresponding function for ndarrays
+        numpy.cumsum : equivalent function
+
+        Examples
+        --------
+        >>> marr = np.ma.array(np.arange(10), mask=[0,0,0,1,1,1,0,0,0,0])
+        >>> marr.cumsum()
+        masked_array(data=[0, 1, 3, --, --, --, 9, 16, 24, 33],
+                     mask=[False, False, False,  True,  True,  True, False, False,
+                           False, False],
+               fill_value=999999)
+
+        """
+        result = self.filled(0).cumsum(axis=axis, dtype=dtype, out=out)
+        if out is not None:
+            if isinstance(out, MaskedArray):
+                out.__setmask__(self.mask)
+            return out
+        result = result.view(type(self))
+        result.__setmask__(self._mask)
+        return result
+
+    def prod(self, axis=None, dtype=None, out=None, keepdims=np._NoValue):
+        """
+        Return the product of the array elements over the given axis.
+
+        Masked elements are set to 1 internally for computation.
+
+        Refer to `numpy.prod` for full documentation.
+
+        Notes
+        -----
+        Arithmetic is modular when using integer types, and no error is raised
+        on overflow.
+
+        See Also
+        --------
+        numpy.ndarray.prod : corresponding function for ndarrays
+        numpy.prod : equivalent function
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        _mask = self._mask
+        newmask = _check_mask_axis(_mask, axis, **kwargs)
+        # No explicit output
+        if out is None:
+            result = self.filled(1).prod(axis, dtype=dtype, **kwargs)
+            rndim = getattr(result, 'ndim', 0)
+            if rndim:
+                result = result.view(type(self))
+                result.__setmask__(newmask)
+            elif newmask:
+                result = masked
+            return result
+        # Explicit output
+        result = self.filled(1).prod(axis, dtype=dtype, out=out, **kwargs)
+        if isinstance(out, MaskedArray):
+            outmask = getmask(out)
+            if outmask is nomask:
+                outmask = out._mask = make_mask_none(out.shape)
+            outmask.flat = newmask
+        return out
+    product = prod
+
+    def cumprod(self, axis=None, dtype=None, out=None):
+        """
+        Return the cumulative product of the array elements over the given axis.
+
+        Masked values are set to 1 internally during the computation.
+        However, their position is saved, and the result will be masked at
+        the same locations.
+
+        Refer to `numpy.cumprod` for full documentation.
+
+        Notes
+        -----
+        The mask is lost if `out` is not a valid MaskedArray !
+
+        Arithmetic is modular when using integer types, and no error is
+        raised on overflow.
+
+        See Also
+        --------
+        numpy.ndarray.cumprod : corresponding function for ndarrays
+        numpy.cumprod : equivalent function
+        """
+        result = self.filled(1).cumprod(axis=axis, dtype=dtype, out=out)
+        if out is not None:
+            if isinstance(out, MaskedArray):
+                out.__setmask__(self._mask)
+            return out
+        result = result.view(type(self))
+        result.__setmask__(self._mask)
+        return result
+
+    def mean(self, axis=None, dtype=None, out=None, keepdims=np._NoValue):
+        """
+        Returns the average of the array elements along given axis.
+
+        Masked entries are ignored, and result elements which are not
+        finite will be masked.
+
+        Refer to `numpy.mean` for full documentation.
+
+        See Also
+        --------
+        numpy.ndarray.mean : corresponding function for ndarrays
+        numpy.mean : Equivalent function
+        numpy.ma.average : Weighted average.
+
+        Examples
+        --------
+        >>> a = np.ma.array([1,2,3], mask=[False, False, True])
+        >>> a
+        masked_array(data=[1, 2, --],
+                     mask=[False, False,  True],
+               fill_value=999999)
+        >>> a.mean()
+        1.5
+
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+        if self._mask is nomask:
+            result = super().mean(axis=axis, dtype=dtype, **kwargs)[()]
+        else:
+            is_float16_result = False
+            if dtype is None:
+                if issubclass(self.dtype.type, (ntypes.integer, ntypes.bool_)):
+                    dtype = mu.dtype('f8')
+                elif issubclass(self.dtype.type, ntypes.float16):
+                    dtype = mu.dtype('f4')
+                    is_float16_result = True
+            dsum = self.sum(axis=axis, dtype=dtype, **kwargs)
+            cnt = self.count(axis=axis, **kwargs)
+            if cnt.shape == () and (cnt == 0):
+                result = masked
+            elif is_float16_result:
+                result = self.dtype.type(dsum * 1. / cnt)
+            else:
+                result = dsum * 1. / cnt
+        if out is not None:
+            out.flat = result
+            if isinstance(out, MaskedArray):
+                outmask = getmask(out)
+                if outmask is nomask:
+                    outmask = out._mask = make_mask_none(out.shape)
+                outmask.flat = getmask(result)
+            return out
+        return result
+
+    def anom(self, axis=None, dtype=None):
+        """
+        Compute the anomalies (deviations from the arithmetic mean)
+        along the given axis.
+
+        Returns an array of anomalies, with the same shape as the input and
+        where the arithmetic mean is computed along the given axis.
+
+        Parameters
+        ----------
+        axis : int, optional
+            Axis over which the anomalies are taken.
+            The default is to use the mean of the flattened array as reference.
+        dtype : dtype, optional
+            Type to use in computing the variance. For arrays of integer type
+             the default is float32; for arrays of float types it is the same as
+             the array type.
+
+        See Also
+        --------
+        mean : Compute the mean of the array.
+
+        Examples
+        --------
+        >>> a = np.ma.array([1,2,3])
+        >>> a.anom()
+        masked_array(data=[-1.,  0.,  1.],
+                     mask=False,
+               fill_value=1e+20)
+
+        """
+        m = self.mean(axis, dtype)
+        if not axis:
+            return self - m
+        else:
+            return self - expand_dims(m, axis)
+
+    def var(self, axis=None, dtype=None, out=None, ddof=0,
+            keepdims=np._NoValue):
+        """
+        Returns the variance of the array elements along given axis.
+
+        Masked entries are ignored, and result elements which are not
+        finite will be masked.
+
+        Refer to `numpy.var` for full documentation.
+
+        See Also
+        --------
+        numpy.ndarray.var : corresponding function for ndarrays
+        numpy.var : Equivalent function
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        # Easy case: nomask, business as usual
+        if self._mask is nomask:
+            ret = super().var(axis=axis, dtype=dtype, out=out, ddof=ddof,
+                              **kwargs)[()]
+            if out is not None:
+                if isinstance(out, MaskedArray):
+                    out.__setmask__(nomask)
+                return out
+            return ret
+
+        # Some data are masked, yay!
+        cnt = self.count(axis=axis, **kwargs) - ddof
+        danom = self - self.mean(axis, dtype, keepdims=True)
+        if iscomplexobj(self):
+            danom = umath.absolute(danom) ** 2
+        else:
+            danom *= danom
+        dvar = divide(danom.sum(axis, **kwargs), cnt).view(type(self))
+        # Apply the mask if it's not a scalar
+        if dvar.ndim:
+            dvar._mask = mask_or(self._mask.all(axis, **kwargs), (cnt <= 0))
+            dvar._update_from(self)
+        elif getmask(dvar):
+            # Make sure that masked is returned when the scalar is masked.
+            dvar = masked
+            if out is not None:
+                if isinstance(out, MaskedArray):
+                    out.flat = 0
+                    out.__setmask__(True)
+                elif out.dtype.kind in 'biu':
+                    errmsg = "Masked data information would be lost in one or "\
+                             "more location."
+                    raise MaskError(errmsg)
+                else:
+                    out.flat = np.nan
+                return out
+        # In case with have an explicit output
+        if out is not None:
+            # Set the data
+            out.flat = dvar
+            # Set the mask if needed
+            if isinstance(out, MaskedArray):
+                out.__setmask__(dvar.mask)
+            return out
+        return dvar
+    var.__doc__ = np.var.__doc__
+
+    def std(self, axis=None, dtype=None, out=None, ddof=0,
+            keepdims=np._NoValue):
+        """
+        Returns the standard deviation of the array elements along given axis.
+
+        Masked entries are ignored.
+
+        Refer to `numpy.std` for full documentation.
+
+        See Also
+        --------
+        numpy.ndarray.std : corresponding function for ndarrays
+        numpy.std : Equivalent function
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        dvar = self.var(axis, dtype, out, ddof, **kwargs)
+        if dvar is not masked:
+            if out is not None:
+                np.power(out, 0.5, out=out, casting='unsafe')
+                return out
+            dvar = sqrt(dvar)
+        return dvar
+
+    def round(self, decimals=0, out=None):
+        """
+        Return each element rounded to the given number of decimals.
+
+        Refer to `numpy.around` for full documentation.
+
+        See Also
+        --------
+        numpy.ndarray.round : corresponding function for ndarrays
+        numpy.around : equivalent function
+        """
+        result = self._data.round(decimals=decimals, out=out).view(type(self))
+        if result.ndim > 0:
+            result._mask = self._mask
+            result._update_from(self)
+        elif self._mask:
+            # Return masked when the scalar is masked
+            result = masked
+        # No explicit output: we're done
+        if out is None:
+            return result
+        if isinstance(out, MaskedArray):
+            out.__setmask__(self._mask)
+        return out
+
+    def argsort(self, axis=np._NoValue, kind=None, order=None,
+                endwith=True, fill_value=None):
+        """
+        Return an ndarray of indices that sort the array along the
+        specified axis.  Masked values are filled beforehand to
+        `fill_value`.
+
+        Parameters
+        ----------
+        axis : int, optional
+            Axis along which to sort. If None, the default, the flattened array
+            is used.
+
+            ..  versionchanged:: 1.13.0
+                Previously, the default was documented to be -1, but that was
+                in error. At some future date, the default will change to -1, as
+                originally intended.
+                Until then, the axis should be given explicitly when
+                ``arr.ndim > 1``, to avoid a FutureWarning.
+        kind : {'quicksort', 'mergesort', 'heapsort', 'stable'}, optional
+            The sorting algorithm used.
+        order : list, optional
+            When `a` is an array with fields defined, this argument specifies
+            which fields to compare first, second, etc.  Not all fields need be
+            specified.
+        endwith : {True, False}, optional
+            Whether missing values (if any) should be treated as the largest values
+            (True) or the smallest values (False)
+            When the array contains unmasked values at the same extremes of the
+            datatype, the ordering of these values and the masked values is
+            undefined.
+        fill_value : scalar or None, optional
+            Value used internally for the masked values.
+            If ``fill_value`` is not None, it supersedes ``endwith``.
+
+        Returns
+        -------
+        index_array : ndarray, int
+            Array of indices that sort `a` along the specified axis.
+            In other words, ``a[index_array]`` yields a sorted `a`.
+
+        See Also
+        --------
+        ma.MaskedArray.sort : Describes sorting algorithms used.
+        lexsort : Indirect stable sort with multiple keys.
+        numpy.ndarray.sort : Inplace sort.
+
+        Notes
+        -----
+        See `sort` for notes on the different sorting algorithms.
+
+        Examples
+        --------
+        >>> a = np.ma.array([3,2,1], mask=[False, False, True])
+        >>> a
+        masked_array(data=[3, 2, --],
+                     mask=[False, False,  True],
+               fill_value=999999)
+        >>> a.argsort()
+        array([1, 0, 2])
+
+        """
+
+        # 2017-04-11, Numpy 1.13.0, gh-8701: warn on axis default
+        if axis is np._NoValue:
+            axis = _deprecate_argsort_axis(self)
+
+        if fill_value is None:
+            if endwith:
+                # nan > inf
+                if np.issubdtype(self.dtype, np.floating):
+                    fill_value = np.nan
+                else:
+                    fill_value = minimum_fill_value(self)
+            else:
+                fill_value = maximum_fill_value(self)
+
+        filled = self.filled(fill_value)
+        return filled.argsort(axis=axis, kind=kind, order=order)
+
+    def argmin(self, axis=None, fill_value=None, out=None, *,
+                keepdims=np._NoValue):
+        """
+        Return array of indices to the minimum values along the given axis.
+
+        Parameters
+        ----------
+        axis : {None, integer}
+            If None, the index is into the flattened array, otherwise along
+            the specified axis
+        fill_value : scalar or None, optional
+            Value used to fill in the masked values.  If None, the output of
+            minimum_fill_value(self._data) is used instead.
+        out : {None, array}, optional
+            Array into which the result can be placed. Its type is preserved
+            and it must be of the right shape to hold the output.
+
+        Returns
+        -------
+        ndarray or scalar
+            If multi-dimension input, returns a new ndarray of indices to the
+            minimum values along the given axis.  Otherwise, returns a scalar
+            of index to the minimum values along the given axis.
+
+        Examples
+        --------
+        >>> x = np.ma.array(np.arange(4), mask=[1,1,0,0])
+        >>> x.shape = (2,2)
+        >>> x
+        masked_array(
+          data=[[--, --],
+                [2, 3]],
+          mask=[[ True,  True],
+                [False, False]],
+          fill_value=999999)
+        >>> x.argmin(axis=0, fill_value=-1)
+        array([0, 0])
+        >>> x.argmin(axis=0, fill_value=9)
+        array([1, 1])
+
+        """
+        if fill_value is None:
+            fill_value = minimum_fill_value(self)
+        d = self.filled(fill_value).view(ndarray)
+        keepdims = False if keepdims is np._NoValue else bool(keepdims)
+        return d.argmin(axis, out=out, keepdims=keepdims)
+
+    def argmax(self, axis=None, fill_value=None, out=None, *,
+                keepdims=np._NoValue):
+        """
+        Returns array of indices of the maximum values along the given axis.
+        Masked values are treated as if they had the value fill_value.
+
+        Parameters
+        ----------
+        axis : {None, integer}
+            If None, the index is into the flattened array, otherwise along
+            the specified axis
+        fill_value : scalar or None, optional
+            Value used to fill in the masked values.  If None, the output of
+            maximum_fill_value(self._data) is used instead.
+        out : {None, array}, optional
+            Array into which the result can be placed. Its type is preserved
+            and it must be of the right shape to hold the output.
+
+        Returns
+        -------
+        index_array : {integer_array}
+
+        Examples
+        --------
+        >>> a = np.arange(6).reshape(2,3)
+        >>> a.argmax()
+        5
+        >>> a.argmax(0)
+        array([1, 1, 1])
+        >>> a.argmax(1)
+        array([2, 2])
+
+        """
+        if fill_value is None:
+            fill_value = maximum_fill_value(self._data)
+        d = self.filled(fill_value).view(ndarray)
+        keepdims = False if keepdims is np._NoValue else bool(keepdims)
+        return d.argmax(axis, out=out, keepdims=keepdims)
+
+    def sort(self, axis=-1, kind=None, order=None,
+             endwith=True, fill_value=None):
+        """
+        Sort the array, in-place
+
+        Parameters
+        ----------
+        a : array_like
+            Array to be sorted.
+        axis : int, optional
+            Axis along which to sort. If None, the array is flattened before
+            sorting. The default is -1, which sorts along the last axis.
+        kind : {'quicksort', 'mergesort', 'heapsort', 'stable'}, optional
+            The sorting algorithm used.
+        order : list, optional
+            When `a` is a structured array, this argument specifies which fields
+            to compare first, second, and so on.  This list does not need to
+            include all of the fields.
+        endwith : {True, False}, optional
+            Whether missing values (if any) should be treated as the largest values
+            (True) or the smallest values (False)
+            When the array contains unmasked values sorting at the same extremes of the
+            datatype, the ordering of these values and the masked values is
+            undefined.
+        fill_value : scalar or None, optional
+            Value used internally for the masked values.
+            If ``fill_value`` is not None, it supersedes ``endwith``.
+
+        Returns
+        -------
+        sorted_array : ndarray
+            Array of the same type and shape as `a`.
+
+        See Also
+        --------
+        numpy.ndarray.sort : Method to sort an array in-place.
+        argsort : Indirect sort.
+        lexsort : Indirect stable sort on multiple keys.
+        searchsorted : Find elements in a sorted array.
+
+        Notes
+        -----
+        See ``sort`` for notes on the different sorting algorithms.
+
+        Examples
+        --------
+        >>> a = np.ma.array([1, 2, 5, 4, 3],mask=[0, 1, 0, 1, 0])
+        >>> # Default
+        >>> a.sort()
+        >>> a
+        masked_array(data=[1, 3, 5, --, --],
+                     mask=[False, False, False,  True,  True],
+               fill_value=999999)
+
+        >>> a = np.ma.array([1, 2, 5, 4, 3],mask=[0, 1, 0, 1, 0])
+        >>> # Put missing values in the front
+        >>> a.sort(endwith=False)
+        >>> a
+        masked_array(data=[--, --, 1, 3, 5],
+                     mask=[ True,  True, False, False, False],
+               fill_value=999999)
+
+        >>> a = np.ma.array([1, 2, 5, 4, 3],mask=[0, 1, 0, 1, 0])
+        >>> # fill_value takes over endwith
+        >>> a.sort(endwith=False, fill_value=3)
+        >>> a
+        masked_array(data=[1, --, --, 3, 5],
+                     mask=[False,  True,  True, False, False],
+               fill_value=999999)
+
+        """
+        if self._mask is nomask:
+            ndarray.sort(self, axis=axis, kind=kind, order=order)
+            return
+
+        if self is masked:
+            return
+
+        sidx = self.argsort(axis=axis, kind=kind, order=order,
+                            fill_value=fill_value, endwith=endwith)
+
+        self[...] = np.take_along_axis(self, sidx, axis=axis)
+
+    def min(self, axis=None, out=None, fill_value=None, keepdims=np._NoValue):
+        """
+        Return the minimum along a given axis.
+
+        Parameters
+        ----------
+        axis : None or int or tuple of ints, optional
+            Axis along which to operate.  By default, ``axis`` is None and the
+            flattened input is used.
+            .. versionadded:: 1.7.0
+            If this is a tuple of ints, the minimum is selected over multiple
+            axes, instead of a single axis or all the axes as before.
+        out : array_like, optional
+            Alternative output array in which to place the result.  Must be of
+            the same shape and buffer length as the expected output.
+        fill_value : scalar or None, optional
+            Value used to fill in the masked values.
+            If None, use the output of `minimum_fill_value`.
+        keepdims : bool, optional
+            If this is set to 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 array.
+
+        Returns
+        -------
+        amin : array_like
+            New array holding the result.
+            If ``out`` was specified, ``out`` is returned.
+
+        See Also
+        --------
+        ma.minimum_fill_value
+            Returns the minimum filling value for a given datatype.
+
+        Examples
+        --------
+        >>> import numpy.ma as ma
+        >>> x = [[1., -2., 3.], [0.2, -0.7, 0.1]]
+        >>> mask = [[1, 1, 0], [0, 0, 1]]
+        >>> masked_x = ma.masked_array(x, mask)
+        >>> masked_x
+        masked_array(
+          data=[[--, --, 3.0],
+                [0.2, -0.7, --]],
+          mask=[[ True,  True, False],
+                [False, False,  True]],
+          fill_value=1e+20)
+        >>> ma.min(masked_x)
+        -0.7
+        >>> ma.min(masked_x, axis=-1)
+        masked_array(data=[3.0, -0.7],
+                     mask=[False, False],
+                fill_value=1e+20)
+        >>> ma.min(masked_x, axis=0, keepdims=True)
+        masked_array(data=[[0.2, -0.7, 3.0]],
+                     mask=[[False, False, False]],
+                fill_value=1e+20)
+        >>> mask = [[1, 1, 1,], [1, 1, 1]]
+        >>> masked_x = ma.masked_array(x, mask)
+        >>> ma.min(masked_x, axis=0)
+        masked_array(data=[--, --, --],
+                     mask=[ True,  True,  True],
+                fill_value=1e+20,
+                    dtype=float64)
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        _mask = self._mask
+        newmask = _check_mask_axis(_mask, axis, **kwargs)
+        if fill_value is None:
+            fill_value = minimum_fill_value(self)
+        # No explicit output
+        if out is None:
+            result = self.filled(fill_value).min(
+                axis=axis, out=out, **kwargs).view(type(self))
+            if result.ndim:
+                # Set the mask
+                result.__setmask__(newmask)
+                # Get rid of Infs
+                if newmask.ndim:
+                    np.copyto(result, result.fill_value, where=newmask)
+            elif newmask:
+                result = masked
+            return result
+        # Explicit output
+        result = self.filled(fill_value).min(axis=axis, out=out, **kwargs)
+        if isinstance(out, MaskedArray):
+            outmask = getmask(out)
+            if outmask is nomask:
+                outmask = out._mask = make_mask_none(out.shape)
+            outmask.flat = newmask
+        else:
+            if out.dtype.kind in 'biu':
+                errmsg = "Masked data information would be lost in one or more"\
+                         " location."
+                raise MaskError(errmsg)
+            np.copyto(out, np.nan, where=newmask)
+        return out
+
+    def max(self, axis=None, out=None, fill_value=None, keepdims=np._NoValue):
+        """
+        Return the maximum along a given axis.
+
+        Parameters
+        ----------
+        axis : None or int or tuple of ints, optional
+            Axis along which to operate.  By default, ``axis`` is None and the
+            flattened input is used.
+            .. versionadded:: 1.7.0
+            If this is a tuple of ints, the maximum is selected over multiple
+            axes, instead of a single axis or all the axes as before.
+        out : array_like, optional
+            Alternative output array in which to place the result.  Must
+            be of the same shape and buffer length as the expected output.
+        fill_value : scalar or None, optional
+            Value used to fill in the masked values.
+            If None, use the output of maximum_fill_value().
+        keepdims : bool, optional
+            If this is set to 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 array.
+
+        Returns
+        -------
+        amax : array_like
+            New array holding the result.
+            If ``out`` was specified, ``out`` is returned.
+
+        See Also
+        --------
+        ma.maximum_fill_value
+            Returns the maximum filling value for a given datatype.
+
+        Examples
+        --------
+        >>> import numpy.ma as ma
+        >>> x = [[-1., 2.5], [4., -2.], [3., 0.]]
+        >>> mask = [[0, 0], [1, 0], [1, 0]]
+        >>> masked_x = ma.masked_array(x, mask)
+        >>> masked_x
+        masked_array(
+          data=[[-1.0, 2.5],
+                [--, -2.0],
+                [--, 0.0]],
+          mask=[[False, False],
+                [ True, False],
+                [ True, False]],
+          fill_value=1e+20)
+        >>> ma.max(masked_x)
+        2.5
+        >>> ma.max(masked_x, axis=0)
+        masked_array(data=[-1.0, 2.5],
+                     mask=[False, False],
+               fill_value=1e+20)
+        >>> ma.max(masked_x, axis=1, keepdims=True)
+        masked_array(
+          data=[[2.5],
+                [-2.0],
+                [0.0]],
+          mask=[[False],
+                [False],
+                [False]],
+          fill_value=1e+20)
+        >>> mask = [[1, 1], [1, 1], [1, 1]]
+        >>> masked_x = ma.masked_array(x, mask)
+        >>> ma.max(masked_x, axis=1)
+        masked_array(data=[--, --, --],
+                     mask=[ True,  True,  True],
+               fill_value=1e+20,
+                    dtype=float64)
+        """
+        kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+        _mask = self._mask
+        newmask = _check_mask_axis(_mask, axis, **kwargs)
+        if fill_value is None:
+            fill_value = maximum_fill_value(self)
+        # No explicit output
+        if out is None:
+            result = self.filled(fill_value).max(
+                axis=axis, out=out, **kwargs).view(type(self))
+            if result.ndim:
+                # Set the mask
+                result.__setmask__(newmask)
+                # Get rid of Infs
+                if newmask.ndim:
+                    np.copyto(result, result.fill_value, where=newmask)
+            elif newmask:
+                result = masked
+            return result
+        # Explicit output
+        result = self.filled(fill_value).max(axis=axis, out=out, **kwargs)
+        if isinstance(out, MaskedArray):
+            outmask = getmask(out)
+            if outmask is nomask:
+                outmask = out._mask = make_mask_none(out.shape)
+            outmask.flat = newmask
+        else:
+
+            if out.dtype.kind in 'biu':
+                errmsg = "Masked data information would be lost in one or more"\
+                         " location."
+                raise MaskError(errmsg)
+            np.copyto(out, np.nan, where=newmask)
+        return out
+
+    def ptp(self, axis=None, out=None, fill_value=None, keepdims=False):
+        """
+        Return (maximum - minimum) along the given dimension
+        (i.e. peak-to-peak value).
+
+        .. warning::
+            `ptp` preserves the data type of the array. This means the
+            return value for an input of signed integers with n bits
+            (e.g. `np.int8`, `np.int16`, etc) is also a signed integer
+            with n bits.  In that case, peak-to-peak values greater than
+            ``2**(n-1)-1`` will be returned as negative values. An example
+            with a work-around is shown below.
+
+        Parameters
+        ----------
+        axis : {None, int}, optional
+            Axis along which to find the peaks.  If None (default) the
+            flattened array is used.
+        out : {None, array_like}, optional
+            Alternative output array in which to place the result. It must
+            have the same shape and buffer length as the expected output
+            but the type will be cast if necessary.
+        fill_value : scalar or None, optional
+            Value used to fill in the masked values.
+        keepdims : bool, optional
+            If this is set to 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 array.
+
+        Returns
+        -------
+        ptp : ndarray.
+            A new array holding the result, unless ``out`` was
+            specified, in which case a reference to ``out`` is returned.
+
+        Examples
+        --------
+        >>> x = np.ma.MaskedArray([[4, 9, 2, 10],
+        ...                        [6, 9, 7, 12]])
+
+        >>> x.ptp(axis=1)
+        masked_array(data=[8, 6],
+                     mask=False,
+               fill_value=999999)
+
+        >>> x.ptp(axis=0)
+        masked_array(data=[2, 0, 5, 2],
+                     mask=False,
+               fill_value=999999)
+
+        >>> x.ptp()
+        10
+
+        This example shows that a negative value can be returned when
+        the input is an array of signed integers.
+
+        >>> y = np.ma.MaskedArray([[1, 127],
+        ...                        [0, 127],
+        ...                        [-1, 127],
+        ...                        [-2, 127]], dtype=np.int8)
+        >>> y.ptp(axis=1)
+        masked_array(data=[ 126,  127, -128, -127],
+                     mask=False,
+               fill_value=999999,
+                    dtype=int8)
+
+        A work-around is to use the `view()` method to view the result as
+        unsigned integers with the same bit width:
+
+        >>> y.ptp(axis=1).view(np.uint8)
+        masked_array(data=[126, 127, 128, 129],
+                     mask=False,
+               fill_value=999999,
+                    dtype=uint8)
+        """
+        if out is None:
+            result = self.max(axis=axis, fill_value=fill_value,
+                              keepdims=keepdims)
+            result -= self.min(axis=axis, fill_value=fill_value,
+                               keepdims=keepdims)
+            return result
+        out.flat = self.max(axis=axis, out=out, fill_value=fill_value,
+                            keepdims=keepdims)
+        min_value = self.min(axis=axis, fill_value=fill_value,
+                             keepdims=keepdims)
+        np.subtract(out, min_value, out=out, casting='unsafe')
+        return out
+
+    def partition(self, *args, **kwargs):
+        warnings.warn("Warning: 'partition' will ignore the 'mask' "
+                      f"of the {self.__class__.__name__}.",
+                      stacklevel=2)
+        return super().partition(*args, **kwargs)
+
+    def argpartition(self, *args, **kwargs):
+        warnings.warn("Warning: 'argpartition' will ignore the 'mask' "
+                      f"of the {self.__class__.__name__}.",
+                      stacklevel=2)
+        return super().argpartition(*args, **kwargs)
+
+    def take(self, indices, axis=None, out=None, mode='raise'):
+        """
+        """
+        (_data, _mask) = (self._data, self._mask)
+        cls = type(self)
+        # Make sure the indices are not masked
+        maskindices = getmask(indices)
+        if maskindices is not nomask:
+            indices = indices.filled(0)
+        # Get the data, promoting scalars to 0d arrays with [...] so that
+        # .view works correctly
+        if out is None:
+            out = _data.take(indices, axis=axis, mode=mode)[...].view(cls)
+        else:
+            np.take(_data, indices, axis=axis, mode=mode, out=out)
+        # Get the mask
+        if isinstance(out, MaskedArray):
+            if _mask is nomask:
+                outmask = maskindices
+            else:
+                outmask = _mask.take(indices, axis=axis, mode=mode)
+                outmask |= maskindices
+            out.__setmask__(outmask)
+        # demote 0d arrays back to scalars, for consistency with ndarray.take
+        return out[()]
+
+    # Array methods
+    copy = _arraymethod('copy')
+    diagonal = _arraymethod('diagonal')
+    flatten = _arraymethod('flatten')
+    repeat = _arraymethod('repeat')
+    squeeze = _arraymethod('squeeze')
+    swapaxes = _arraymethod('swapaxes')
+    T = property(fget=lambda self: self.transpose())
+    transpose = _arraymethod('transpose')
+
+    def tolist(self, fill_value=None):
+        """
+        Return the data portion of the masked array as a hierarchical Python list.
+
+        Data items are converted to the nearest compatible Python type.
+        Masked values are converted to `fill_value`. If `fill_value` is None,
+        the corresponding entries in the output list will be ``None``.
+
+        Parameters
+        ----------
+        fill_value : scalar, optional
+            The value to use for invalid entries. Default is None.
+
+        Returns
+        -------
+        result : list
+            The Python list representation of the masked array.
+
+        Examples
+        --------
+        >>> x = np.ma.array([[1,2,3], [4,5,6], [7,8,9]], mask=[0] + [1,0]*4)
+        >>> x.tolist()
+        [[1, None, 3], [None, 5, None], [7, None, 9]]
+        >>> x.tolist(-999)
+        [[1, -999, 3], [-999, 5, -999], [7, -999, 9]]
+
+        """
+        _mask = self._mask
+        # No mask ? Just return .data.tolist ?
+        if _mask is nomask:
+            return self._data.tolist()
+        # Explicit fill_value: fill the array and get the list
+        if fill_value is not None:
+            return self.filled(fill_value).tolist()
+        # Structured array.
+        names = self.dtype.names
+        if names:
+            result = self._data.astype([(_, object) for _ in names])
+            for n in names:
+                result[n][_mask[n]] = None
+            return result.tolist()
+        # Standard arrays.
+        if _mask is nomask:
+            return [None]
+        # Set temps to save time when dealing w/ marrays.
+        inishape = self.shape
+        result = np.array(self._data.ravel(), dtype=object)
+        result[_mask.ravel()] = None
+        result.shape = inishape
+        return result.tolist()
+
+    def tostring(self, fill_value=None, order='C'):
+        r"""
+        A compatibility alias for `tobytes`, with exactly the same behavior.
+
+        Despite its name, it returns `bytes` not `str`\ s.
+
+        .. deprecated:: 1.19.0
+        """
+        # 2020-03-30, Numpy 1.19.0
+        warnings.warn(
+            "tostring() is deprecated. Use tobytes() instead.",
+            DeprecationWarning, stacklevel=2)
+
+        return self.tobytes(fill_value, order=order)
+
+    def tobytes(self, fill_value=None, order='C'):
+        """
+        Return the array data as a string containing the raw bytes in the array.
+
+        The array is filled with a fill value before the string conversion.
+
+        .. versionadded:: 1.9.0
+
+        Parameters
+        ----------
+        fill_value : scalar, optional
+            Value used to fill in the masked values. Default is None, in which
+            case `MaskedArray.fill_value` is used.
+        order : {'C','F','A'}, optional
+            Order of the data item in the copy. Default is 'C'.
+
+            - 'C'   -- C order (row major).
+            - 'F'   -- Fortran order (column major).
+            - 'A'   -- Any, current order of array.
+            - None  -- Same as 'A'.
+
+        See Also
+        --------
+        numpy.ndarray.tobytes
+        tolist, tofile
+
+        Notes
+        -----
+        As for `ndarray.tobytes`, information about the shape, dtype, etc.,
+        but also about `fill_value`, will be lost.
+
+        Examples
+        --------
+        >>> x = np.ma.array(np.array([[1, 2], [3, 4]]), mask=[[0, 1], [1, 0]])
+        >>> x.tobytes()
+        b'\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00?B\\x0f\\x00\\x00\\x00\\x00\\x00?B\\x0f\\x00\\x00\\x00\\x00\\x00\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00'
+
+        """
+        return self.filled(fill_value).tobytes(order=order)
+
+    def tofile(self, fid, sep="", format="%s"):
+        """
+        Save a masked array to a file in binary format.
+
+        .. warning::
+          This function is not implemented yet.
+
+        Raises
+        ------
+        NotImplementedError
+            When `tofile` is called.
+
+        """
+        raise NotImplementedError("MaskedArray.tofile() not implemented yet.")
+
+    def toflex(self):
+        """
+        Transforms a masked array into a flexible-type array.
+
+        The flexible type array that is returned will have two fields:
+
+        * the ``_data`` field stores the ``_data`` part of the array.
+        * the ``_mask`` field stores the ``_mask`` part of the array.
+
+        Parameters
+        ----------
+        None
+
+        Returns
+        -------
+        record : ndarray
+            A new flexible-type `ndarray` with two fields: the first element
+            containing a value, the second element containing the corresponding
+            mask boolean. The returned record shape matches self.shape.
+
+        Notes
+        -----
+        A side-effect of transforming a masked array into a flexible `ndarray` is
+        that meta information (``fill_value``, ...) will be lost.
+
+        Examples
+        --------
+        >>> x = np.ma.array([[1,2,3],[4,5,6],[7,8,9]], mask=[0] + [1,0]*4)
+        >>> x
+        masked_array(
+          data=[[1, --, 3],
+                [--, 5, --],
+                [7, --, 9]],
+          mask=[[False,  True, False],
+                [ True, False,  True],
+                [False,  True, False]],
+          fill_value=999999)
+        >>> x.toflex()
+        array([[(1, False), (2,  True), (3, False)],
+               [(4,  True), (5, False), (6,  True)],
+               [(7, False), (8,  True), (9, False)]],
+              dtype=[('_data', '<i8'), ('_mask', '?')])
+
+        """
+        # Get the basic dtype.
+        ddtype = self.dtype
+        # Make sure we have a mask
+        _mask = self._mask
+        if _mask is None:
+            _mask = make_mask_none(self.shape, ddtype)
+        # And get its dtype
+        mdtype = self._mask.dtype
+
+        record = np.ndarray(shape=self.shape,
+                            dtype=[('_data', ddtype), ('_mask', mdtype)])
+        record['_data'] = self._data
+        record['_mask'] = self._mask
+        return record
+    torecords = toflex
+
+    # Pickling
+    def __getstate__(self):
+        """Return the internal state of the masked array, for pickling
+        purposes.
+
+        """
+        cf = 'CF'[self.flags.fnc]
+        data_state = super().__reduce__()[2]
+        return data_state + (getmaskarray(self).tobytes(cf), self._fill_value)
+
+    def __setstate__(self, state):
+        """Restore the internal state of the masked array, for
+        pickling purposes.  ``state`` is typically the output of the
+        ``__getstate__`` output, and is a 5-tuple:
+
+        - class name
+        - a tuple giving the shape of the data
+        - a typecode for the data
+        - a binary string for the data
+        - a binary string for the mask.
+
+        """
+        (_, shp, typ, isf, raw, msk, flv) = state
+        super().__setstate__((shp, typ, isf, raw))
+        self._mask.__setstate__((shp, make_mask_descr(typ), isf, msk))
+        self.fill_value = flv
+
+    def __reduce__(self):
+        """Return a 3-tuple for pickling a MaskedArray.
+
+        """
+        return (_mareconstruct,
+                (self.__class__, self._baseclass, (0,), 'b',),
+                self.__getstate__())
+
+    def __deepcopy__(self, memo=None):
+        from copy import deepcopy
+        copied = MaskedArray.__new__(type(self), self, copy=True)
+        if memo is None:
+            memo = {}
+        memo[id(self)] = copied
+        for (k, v) in self.__dict__.items():
+            copied.__dict__[k] = deepcopy(v, memo)
+        # as clearly documented for np.copy(), you need to use
+        # deepcopy() directly for arrays of object type that may
+        # contain compound types--you cannot depend on normal
+        # copy semantics to do the right thing here
+        if self.dtype.hasobject:
+            copied._data[...] = deepcopy(copied._data)
+        return copied
+
+
+def _mareconstruct(subtype, baseclass, baseshape, basetype,):
+    """Internal function that builds a new MaskedArray from the
+    information stored in a pickle.
+
+    """
+    _data = ndarray.__new__(baseclass, baseshape, basetype)
+    _mask = ndarray.__new__(ndarray, baseshape, make_mask_descr(basetype))
+    return subtype.__new__(subtype, _data, mask=_mask, dtype=basetype,)
+
+
+class mvoid(MaskedArray):
+    """
+    Fake a 'void' object to use for masked array with structured dtypes.
+    """
+
+    def __new__(self, data, mask=nomask, dtype=None, fill_value=None,
+                hardmask=False, copy=False, subok=True):
+        _data = np.array(data, copy=copy, subok=subok, dtype=dtype)
+        _data = _data.view(self)
+        _data._hardmask = hardmask
+        if mask is not nomask:
+            if isinstance(mask, np.void):
+                _data._mask = mask
+            else:
+                try:
+                    # Mask is already a 0D array
+                    _data._mask = np.void(mask)
+                except TypeError:
+                    # Transform the mask to a void
+                    mdtype = make_mask_descr(dtype)
+                    _data._mask = np.array(mask, dtype=mdtype)[()]
+        if fill_value is not None:
+            _data.fill_value = fill_value
+        return _data
+
+    @property
+    def _data(self):
+        # Make sure that the _data part is a np.void
+        return super()._data[()]
+
+    def __getitem__(self, indx):
+        """
+        Get the index.
+
+        """
+        m = self._mask
+        if isinstance(m[indx], ndarray):
+            # Can happen when indx is a multi-dimensional field:
+            # A = ma.masked_array(data=[([0,1],)], mask=[([True,
+            #                     False],)], dtype=[("A", ">i2", (2,))])
+            # x = A[0]; y = x["A"]; then y.mask["A"].size==2
+            # and we can not say masked/unmasked.
+            # The result is no longer mvoid!
+            # See also issue #6724.
+            return masked_array(
+                data=self._data[indx], mask=m[indx],
+                fill_value=self._fill_value[indx],
+                hard_mask=self._hardmask)
+        if m is not nomask and m[indx]:
+            return masked
+        return self._data[indx]
+
+    def __setitem__(self, indx, value):
+        self._data[indx] = value
+        if self._hardmask:
+            self._mask[indx] |= getattr(value, "_mask", False)
+        else:
+            self._mask[indx] = getattr(value, "_mask", False)
+
+    def __str__(self):
+        m = self._mask
+        if m is nomask:
+            return str(self._data)
+
+        rdtype = _replace_dtype_fields(self._data.dtype, "O")
+        data_arr = super()._data
+        res = data_arr.astype(rdtype)
+        _recursive_printoption(res, self._mask, masked_print_option)
+        return str(res)
+
+    __repr__ = __str__
+
+    def __iter__(self):
+        "Defines an iterator for mvoid"
+        (_data, _mask) = (self._data, self._mask)
+        if _mask is nomask:
+            yield from _data
+        else:
+            for (d, m) in zip(_data, _mask):
+                if m:
+                    yield masked
+                else:
+                    yield d
+
+    def __len__(self):
+        return self._data.__len__()
+
+    def filled(self, fill_value=None):
+        """
+        Return a copy with masked fields filled with a given value.
+
+        Parameters
+        ----------
+        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
+        -------
+        filled_void
+            A `np.void` object
+
+        See Also
+        --------
+        MaskedArray.filled
+
+        """
+        return asarray(self).filled(fill_value)[()]
+
+    def tolist(self):
+        """
+    Transforms the mvoid object into a tuple.
+
+    Masked fields are replaced by None.
+
+    Returns
+    -------
+    returned_tuple
+        Tuple of fields
+        """
+        _mask = self._mask
+        if _mask is nomask:
+            return self._data.tolist()
+        result = []
+        for (d, m) in zip(self._data, self._mask):
+            if m:
+                result.append(None)
+            else:
+                # .item() makes sure we return a standard Python object
+                result.append(d.item())
+        return tuple(result)
+
+
+##############################################################################
+#                                Shortcuts                                   #
+##############################################################################
+
+
+def isMaskedArray(x):
+    """
+    Test whether input is an instance of MaskedArray.
+
+    This function returns True if `x` is an instance of MaskedArray
+    and returns False otherwise.  Any object is accepted as input.
+
+    Parameters
+    ----------
+    x : object
+        Object to test.
+
+    Returns
+    -------
+    result : bool
+        True if `x` is a MaskedArray.
+
+    See Also
+    --------
+    isMA : Alias to isMaskedArray.
+    isarray : Alias to isMaskedArray.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.eye(3, 3)
+    >>> a
+    array([[ 1.,  0.,  0.],
+           [ 0.,  1.,  0.],
+           [ 0.,  0.,  1.]])
+    >>> m = ma.masked_values(a, 0)
+    >>> m
+    masked_array(
+      data=[[1.0, --, --],
+            [--, 1.0, --],
+            [--, --, 1.0]],
+      mask=[[False,  True,  True],
+            [ True, False,  True],
+            [ True,  True, False]],
+      fill_value=0.0)
+    >>> ma.isMaskedArray(a)
+    False
+    >>> ma.isMaskedArray(m)
+    True
+    >>> ma.isMaskedArray([0, 1, 2])
+    False
+
+    """
+    return isinstance(x, MaskedArray)
+
+
+isarray = isMaskedArray
+isMA = isMaskedArray  # backward compatibility
+
+
+class MaskedConstant(MaskedArray):
+    # the lone np.ma.masked instance
+    __singleton = None
+
+    @classmethod
+    def __has_singleton(cls):
+        # second case ensures `cls.__singleton` is not just a view on the
+        # superclass singleton
+        return cls.__singleton is not None and type(cls.__singleton) is cls
+
+    def __new__(cls):
+        if not cls.__has_singleton():
+            # We define the masked singleton as a float for higher precedence.
+            # Note that it can be tricky sometimes w/ type comparison
+            data = np.array(0.)
+            mask = np.array(True)
+
+            # prevent any modifications
+            data.flags.writeable = False
+            mask.flags.writeable = False
+
+            # don't fall back on MaskedArray.__new__(MaskedConstant), since
+            # that might confuse it - this way, the construction is entirely
+            # within our control
+            cls.__singleton = MaskedArray(data, mask=mask).view(cls)
+
+        return cls.__singleton
+
+    def __array_finalize__(self, obj):
+        if not self.__has_singleton():
+            # this handles the `.view` in __new__, which we want to copy across
+            # properties normally
+            return super().__array_finalize__(obj)
+        elif self is self.__singleton:
+            # not clear how this can happen, play it safe
+            pass
+        else:
+            # everywhere else, we want to downcast to MaskedArray, to prevent a
+            # duplicate maskedconstant.
+            self.__class__ = MaskedArray
+            MaskedArray.__array_finalize__(self, obj)
+
+    def __array_prepare__(self, obj, context=None):
+        return self.view(MaskedArray).__array_prepare__(obj, context)
+
+    def __array_wrap__(self, obj, context=None):
+        return self.view(MaskedArray).__array_wrap__(obj, context)
+
+    def __str__(self):
+        return str(masked_print_option._display)
+
+    def __repr__(self):
+        if self is MaskedConstant.__singleton:
+            return 'masked'
+        else:
+            # it's a subclass, or something is wrong, make it obvious
+            return object.__repr__(self)
+
+    def __format__(self, format_spec):
+        # Replace ndarray.__format__ with the default, which supports no format characters.
+        # Supporting format characters is unwise here, because we do not know what type
+        # the user was expecting - better to not guess.
+        try:
+            return object.__format__(self, format_spec)
+        except TypeError:
+            # 2020-03-23, NumPy 1.19.0
+            warnings.warn(
+                "Format strings passed to MaskedConstant are ignored, but in future may "
+                "error or produce different behavior",
+                FutureWarning, stacklevel=2
+            )
+            return object.__format__(self, "")
+
+    def __reduce__(self):
+        """Override of MaskedArray's __reduce__.
+        """
+        return (self.__class__, ())
+
+    # inplace operations have no effect. We have to override them to avoid
+    # trying to modify the readonly data and mask arrays
+    def __iop__(self, other):
+        return self
+    __iadd__ = \
+    __isub__ = \
+    __imul__ = \
+    __ifloordiv__ = \
+    __itruediv__ = \
+    __ipow__ = \
+        __iop__
+    del __iop__  # don't leave this around
+
+    def copy(self, *args, **kwargs):
+        """ Copy is a no-op on the maskedconstant, as it is a scalar """
+        # maskedconstant is a scalar, so copy doesn't need to copy. There's
+        # precedent for this with `np.bool_` scalars.
+        return self
+
+    def __copy__(self):
+        return self
+
+    def __deepcopy__(self, memo):
+        return self
+
+    def __setattr__(self, attr, value):
+        if not self.__has_singleton():
+            # allow the singleton to be initialized
+            return super().__setattr__(attr, value)
+        elif self is self.__singleton:
+            raise AttributeError(
+                f"attributes of {self!r} are not writeable")
+        else:
+            # duplicate instance - we can end up here from __array_finalize__,
+            # where we set the __class__ attribute
+            return super().__setattr__(attr, value)
+
+
+masked = masked_singleton = MaskedConstant()
+masked_array = MaskedArray
+
+
+def array(data, dtype=None, copy=False, order=None,
+          mask=nomask, fill_value=None, keep_mask=True,
+          hard_mask=False, shrink=True, subok=True, ndmin=0):
+    """
+    Shortcut to MaskedArray.
+
+    The options are in a different order for convenience and backwards
+    compatibility.
+
+    """
+    return MaskedArray(data, mask=mask, dtype=dtype, copy=copy,
+                       subok=subok, keep_mask=keep_mask,
+                       hard_mask=hard_mask, fill_value=fill_value,
+                       ndmin=ndmin, shrink=shrink, order=order)
+array.__doc__ = masked_array.__doc__
+
+
+def is_masked(x):
+    """
+    Determine whether input has masked values.
+
+    Accepts any object as input, but always returns False unless the
+    input is a MaskedArray containing masked values.
+
+    Parameters
+    ----------
+    x : array_like
+        Array to check for masked values.
+
+    Returns
+    -------
+    result : bool
+        True if `x` is a MaskedArray with masked values, False otherwise.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = ma.masked_equal([0, 1, 0, 2, 3], 0)
+    >>> x
+    masked_array(data=[--, 1, --, 2, 3],
+                 mask=[ True, False,  True, False, False],
+           fill_value=0)
+    >>> ma.is_masked(x)
+    True
+    >>> x = ma.masked_equal([0, 1, 0, 2, 3], 42)
+    >>> x
+    masked_array(data=[0, 1, 0, 2, 3],
+                 mask=False,
+           fill_value=42)
+    >>> ma.is_masked(x)
+    False
+
+    Always returns False if `x` isn't a MaskedArray.
+
+    >>> x = [False, True, False]
+    >>> ma.is_masked(x)
+    False
+    >>> x = 'a string'
+    >>> ma.is_masked(x)
+    False
+
+    """
+    m = getmask(x)
+    if m is nomask:
+        return False
+    elif m.any():
+        return True
+    return False
+
+
+##############################################################################
+#                             Extrema functions                              #
+##############################################################################
+
+
+class _extrema_operation(_MaskedUFunc):
+    """
+    Generic class for maximum/minimum functions.
+
+    .. note::
+      This is the base class for `_maximum_operation` and
+      `_minimum_operation`.
+
+    """
+    def __init__(self, ufunc, compare, fill_value):
+        super().__init__(ufunc)
+        self.compare = compare
+        self.fill_value_func = fill_value
+
+    def __call__(self, a, b):
+        "Executes the call behavior."
+
+        return where(self.compare(a, b), a, b)
+
+    def reduce(self, target, axis=np._NoValue):
+        "Reduce target along the given axis."
+        target = narray(target, copy=False, subok=True)
+        m = getmask(target)
+
+        if axis is np._NoValue and target.ndim > 1:
+            # 2017-05-06, Numpy 1.13.0: warn on axis default
+            warnings.warn(
+                f"In the future the default for ma.{self.__name__}.reduce will be axis=0, "
+                f"not the current None, to match np.{self.__name__}.reduce. "
+                "Explicitly pass 0 or None to silence this warning.",
+                MaskedArrayFutureWarning, stacklevel=2)
+            axis = None
+
+        if axis is not np._NoValue:
+            kwargs = dict(axis=axis)
+        else:
+            kwargs = dict()
+
+        if m is nomask:
+            t = self.f.reduce(target, **kwargs)
+        else:
+            target = target.filled(
+                self.fill_value_func(target)).view(type(target))
+            t = self.f.reduce(target, **kwargs)
+            m = umath.logical_and.reduce(m, **kwargs)
+            if hasattr(t, '_mask'):
+                t._mask = m
+            elif m:
+                t = masked
+        return t
+
+    def outer(self, a, b):
+        "Return the function applied to the outer product of a and b."
+        ma = getmask(a)
+        mb = getmask(b)
+        if ma is nomask and mb is nomask:
+            m = nomask
+        else:
+            ma = getmaskarray(a)
+            mb = getmaskarray(b)
+            m = logical_or.outer(ma, mb)
+        result = self.f.outer(filled(a), filled(b))
+        if not isinstance(result, MaskedArray):
+            result = result.view(MaskedArray)
+        result._mask = m
+        return result
+
+def min(obj, axis=None, out=None, fill_value=None, keepdims=np._NoValue):
+    kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+    try:
+        return obj.min(axis=axis, fill_value=fill_value, out=out, **kwargs)
+    except (AttributeError, TypeError):
+        # If obj doesn't have a min method, or if the method doesn't accept a
+        # fill_value argument
+        return asanyarray(obj).min(axis=axis, fill_value=fill_value,
+                                   out=out, **kwargs)
+min.__doc__ = MaskedArray.min.__doc__
+
+def max(obj, axis=None, out=None, fill_value=None, keepdims=np._NoValue):
+    kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+
+    try:
+        return obj.max(axis=axis, fill_value=fill_value, out=out, **kwargs)
+    except (AttributeError, TypeError):
+        # If obj doesn't have a max method, or if the method doesn't accept a
+        # fill_value argument
+        return asanyarray(obj).max(axis=axis, fill_value=fill_value,
+                                   out=out, **kwargs)
+max.__doc__ = MaskedArray.max.__doc__
+
+
+def ptp(obj, axis=None, out=None, fill_value=None, keepdims=np._NoValue):
+    kwargs = {} if keepdims is np._NoValue else {'keepdims': keepdims}
+    try:
+        return obj.ptp(axis, out=out, fill_value=fill_value, **kwargs)
+    except (AttributeError, TypeError):
+        # If obj doesn't have a ptp method or if the method doesn't accept
+        # a fill_value argument
+        return asanyarray(obj).ptp(axis=axis, fill_value=fill_value,
+                                   out=out, **kwargs)
+ptp.__doc__ = MaskedArray.ptp.__doc__
+
+
+##############################################################################
+#           Definition of functions from the corresponding methods           #
+##############################################################################
+
+
+class _frommethod:
+    """
+    Define functions from existing MaskedArray methods.
+
+    Parameters
+    ----------
+    methodname : str
+        Name of the method to transform.
+
+    """
+
+    def __init__(self, methodname, reversed=False):
+        self.__name__ = methodname
+        self.__doc__ = self.getdoc()
+        self.reversed = reversed
+
+    def getdoc(self):
+        "Return the doc of the function (from the doc of the method)."
+        meth = getattr(MaskedArray, self.__name__, None) or\
+            getattr(np, self.__name__, None)
+        signature = self.__name__ + get_object_signature(meth)
+        if meth is not None:
+            doc = """    %s\n%s""" % (
+                signature, getattr(meth, '__doc__', None))
+            return doc
+
+    def __call__(self, a, *args, **params):
+        if self.reversed:
+            args = list(args)
+            a, args[0] = args[0], a
+
+        marr = asanyarray(a)
+        method_name = self.__name__
+        method = getattr(type(marr), method_name, None)
+        if method is None:
+            # use the corresponding np function
+            method = getattr(np, method_name)
+
+        return method(marr, *args, **params)
+
+
+all = _frommethod('all')
+anomalies = anom = _frommethod('anom')
+any = _frommethod('any')
+compress = _frommethod('compress', reversed=True)
+cumprod = _frommethod('cumprod')
+cumsum = _frommethod('cumsum')
+copy = _frommethod('copy')
+diagonal = _frommethod('diagonal')
+harden_mask = _frommethod('harden_mask')
+ids = _frommethod('ids')
+maximum = _extrema_operation(umath.maximum, greater, maximum_fill_value)
+mean = _frommethod('mean')
+minimum = _extrema_operation(umath.minimum, less, minimum_fill_value)
+nonzero = _frommethod('nonzero')
+prod = _frommethod('prod')
+product = _frommethod('prod')
+ravel = _frommethod('ravel')
+repeat = _frommethod('repeat')
+shrink_mask = _frommethod('shrink_mask')
+soften_mask = _frommethod('soften_mask')
+std = _frommethod('std')
+sum = _frommethod('sum')
+swapaxes = _frommethod('swapaxes')
+#take = _frommethod('take')
+trace = _frommethod('trace')
+var = _frommethod('var')
+
+count = _frommethod('count')
+
+def take(a, indices, axis=None, out=None, mode='raise'):
+    """
+    """
+    a = masked_array(a)
+    return a.take(indices, axis=axis, out=out, mode=mode)
+
+
+def power(a, b, third=None):
+    """
+    Returns element-wise base array raised to power from second array.
+
+    This is the masked array version of `numpy.power`. For details see
+    `numpy.power`.
+
+    See Also
+    --------
+    numpy.power
+
+    Notes
+    -----
+    The *out* argument to `numpy.power` is not supported, `third` has to be
+    None.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = [11.2, -3.973, 0.801, -1.41]
+    >>> mask = [0, 0, 0, 1]
+    >>> masked_x = ma.masked_array(x, mask)
+    >>> masked_x
+    masked_array(data=[11.2, -3.973, 0.801, --],
+             mask=[False, False, False,  True],
+       fill_value=1e+20)
+    >>> ma.power(masked_x, 2)
+    masked_array(data=[125.43999999999998, 15.784728999999999,
+                   0.6416010000000001, --],
+             mask=[False, False, False,  True],
+       fill_value=1e+20)
+    >>> y = [-0.5, 2, 0, 17]
+    >>> masked_y = ma.masked_array(y, mask)
+    >>> masked_y
+    masked_array(data=[-0.5, 2.0, 0.0, --],
+             mask=[False, False, False,  True],
+       fill_value=1e+20)
+    >>> ma.power(masked_x, masked_y)
+    masked_array(data=[0.29880715233359845, 15.784728999999999, 1.0, --],
+             mask=[False, False, False,  True],
+       fill_value=1e+20)
+
+    """
+    if third is not None:
+        raise MaskError("3-argument power not supported.")
+    # Get the masks
+    ma = getmask(a)
+    mb = getmask(b)
+    m = mask_or(ma, mb)
+    # Get the rawdata
+    fa = getdata(a)
+    fb = getdata(b)
+    # Get the type of the result (so that we preserve subclasses)
+    if isinstance(a, MaskedArray):
+        basetype = type(a)
+    else:
+        basetype = MaskedArray
+    # Get the result and view it as a (subclass of) MaskedArray
+    with np.errstate(divide='ignore', invalid='ignore'):
+        result = np.where(m, fa, umath.power(fa, fb)).view(basetype)
+    result._update_from(a)
+    # Find where we're in trouble w/ NaNs and Infs
+    invalid = np.logical_not(np.isfinite(result.view(ndarray)))
+    # Add the initial mask
+    if m is not nomask:
+        if not result.ndim:
+            return masked
+        result._mask = np.logical_or(m, invalid)
+    # Fix the invalid parts
+    if invalid.any():
+        if not result.ndim:
+            return masked
+        elif result._mask is nomask:
+            result._mask = invalid
+        result._data[invalid] = result.fill_value
+    return result
+
+argmin = _frommethod('argmin')
+argmax = _frommethod('argmax')
+
+def argsort(a, axis=np._NoValue, kind=None, order=None, endwith=True, fill_value=None):
+    "Function version of the eponymous method."
+    a = np.asanyarray(a)
+
+    # 2017-04-11, Numpy 1.13.0, gh-8701: warn on axis default
+    if axis is np._NoValue:
+        axis = _deprecate_argsort_axis(a)
+
+    if isinstance(a, MaskedArray):
+        return a.argsort(axis=axis, kind=kind, order=order,
+                         endwith=endwith, fill_value=fill_value)
+    else:
+        return a.argsort(axis=axis, kind=kind, order=order)
+argsort.__doc__ = MaskedArray.argsort.__doc__
+
+def sort(a, axis=-1, kind=None, order=None, endwith=True, fill_value=None):
+    """
+    Return a sorted copy of the masked array.
+
+    Equivalent to creating a copy of the array
+    and applying the  MaskedArray ``sort()`` method.
+
+    Refer to ``MaskedArray.sort`` for the full documentation
+
+    See Also
+    --------
+    MaskedArray.sort : equivalent method
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = [11.2, -3.973, 0.801, -1.41]
+    >>> mask = [0, 0, 0, 1]
+    >>> masked_x = ma.masked_array(x, mask)
+    >>> masked_x
+    masked_array(data=[11.2, -3.973, 0.801, --],
+                 mask=[False, False, False,  True],
+           fill_value=1e+20)
+    >>> ma.sort(masked_x)
+    masked_array(data=[-3.973, 0.801, 11.2, --],
+                 mask=[False, False, False,  True],
+           fill_value=1e+20)
+    """
+    a = np.array(a, copy=True, subok=True)
+    if axis is None:
+        a = a.flatten()
+        axis = 0
+
+    if isinstance(a, MaskedArray):
+        a.sort(axis=axis, kind=kind, order=order,
+               endwith=endwith, fill_value=fill_value)
+    else:
+        a.sort(axis=axis, kind=kind, order=order)
+    return a
+
+
+def compressed(x):
+    """
+    Return all the non-masked data as a 1-D array.
+
+    This function is equivalent to calling the "compressed" method of a
+    `ma.MaskedArray`, see `ma.MaskedArray.compressed` for details.
+
+    See Also
+    --------
+    ma.MaskedArray.compressed : Equivalent method.
+
+    Examples
+    --------
+    
+    Create an array with negative values masked:
+
+    >>> import numpy as np
+    >>> x = np.array([[1, -1, 0], [2, -1, 3], [7, 4, -1]])
+    >>> masked_x = np.ma.masked_array(x, mask=x < 0)
+    >>> masked_x
+    masked_array(
+      data=[[1, --, 0],
+            [2, --, 3],
+            [7, 4, --]],
+      mask=[[False,  True, False],
+            [False,  True, False],
+            [False, False,  True]],
+      fill_value=999999)
+
+    Compress the masked array into a 1-D array of non-masked values:
+
+    >>> np.ma.compressed(masked_x)
+    array([1, 0, 2, 3, 7, 4])
+
+    """
+    return asanyarray(x).compressed()
+
+
+def concatenate(arrays, axis=0):
+    """
+    Concatenate a sequence of arrays along the given axis.
+
+    Parameters
+    ----------
+    arrays : sequence of array_like
+        The arrays must have the same shape, except in the dimension
+        corresponding to `axis` (the first, by default).
+    axis : int, optional
+        The axis along which the arrays will be joined. Default is 0.
+
+    Returns
+    -------
+    result : MaskedArray
+        The concatenated array with any masked entries preserved.
+
+    See Also
+    --------
+    numpy.concatenate : Equivalent function in the top-level NumPy module.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = ma.arange(3)
+    >>> a[1] = ma.masked
+    >>> b = ma.arange(2, 5)
+    >>> a
+    masked_array(data=[0, --, 2],
+                 mask=[False,  True, False],
+           fill_value=999999)
+    >>> b
+    masked_array(data=[2, 3, 4],
+                 mask=False,
+           fill_value=999999)
+    >>> ma.concatenate([a, b])
+    masked_array(data=[0, --, 2, 2, 3, 4],
+                 mask=[False,  True, False, False, False, False],
+           fill_value=999999)
+
+    """
+    d = np.concatenate([getdata(a) for a in arrays], axis)
+    rcls = get_masked_subclass(*arrays)
+    data = d.view(rcls)
+    # Check whether one of the arrays has a non-empty mask.
+    for x in arrays:
+        if getmask(x) is not nomask:
+            break
+    else:
+        return data
+    # OK, so we have to concatenate the masks
+    dm = np.concatenate([getmaskarray(a) for a in arrays], axis)
+    dm = dm.reshape(d.shape)
+
+    # If we decide to keep a '_shrinkmask' option, we want to check that
+    # all of them are True, and then check for dm.any()
+    data._mask = _shrink_mask(dm)
+    return data
+
+
+def diag(v, k=0):
+    """
+    Extract a diagonal or construct a diagonal array.
+
+    This function is the equivalent of `numpy.diag` that takes masked
+    values into account, see `numpy.diag` for details.
+
+    See Also
+    --------
+    numpy.diag : Equivalent function for ndarrays.
+
+    Examples
+    --------
+
+    Create an array with negative values masked:
+
+    >>> import numpy as np
+    >>> x = np.array([[11.2, -3.973, 18], [0.801, -1.41, 12], [7, 33, -12]])
+    >>> masked_x = np.ma.masked_array(x, mask=x < 0)
+    >>> masked_x
+    masked_array(
+      data=[[11.2, --, 18.0],
+            [0.801, --, 12.0],
+            [7.0, 33.0, --]],
+      mask=[[False,  True, False],
+            [False,  True, False],
+            [False, False,  True]],
+      fill_value=1e+20)
+
+    Isolate the main diagonal from the masked array:
+
+    >>> np.ma.diag(masked_x)
+    masked_array(data=[11.2, --, --],
+                 mask=[False,  True,  True],
+           fill_value=1e+20)
+
+    Isolate the first diagonal below the main diagonal:
+
+    >>> np.ma.diag(masked_x, -1)
+    masked_array(data=[0.801, 33.0],
+                 mask=[False, False],
+           fill_value=1e+20)
+
+    """
+    output = np.diag(v, k).view(MaskedArray)
+    if getmask(v) is not nomask:
+        output._mask = np.diag(v._mask, k)
+    return output
+
+
+def left_shift(a, n):
+    """
+    Shift the bits of an integer to the left.
+
+    This is the masked array version of `numpy.left_shift`, for details
+    see that function.
+
+    See Also
+    --------
+    numpy.left_shift
+
+    """
+    m = getmask(a)
+    if m is nomask:
+        d = umath.left_shift(filled(a), n)
+        return masked_array(d)
+    else:
+        d = umath.left_shift(filled(a, 0), n)
+        return masked_array(d, mask=m)
+
+
+def right_shift(a, n):
+    """
+    Shift the bits of an integer to the right.
+
+    This is the masked array version of `numpy.right_shift`, for details
+    see that function.
+
+    See Also
+    --------
+    numpy.right_shift
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = [11, 3, 8, 1]
+    >>> mask = [0, 0, 0, 1]
+    >>> masked_x = ma.masked_array(x, mask)
+    >>> masked_x
+    masked_array(data=[11, 3, 8, --],
+                 mask=[False, False, False,  True],
+           fill_value=999999)
+    >>> ma.right_shift(masked_x,1)
+    masked_array(data=[5, 1, 4, --],
+                 mask=[False, False, False,  True],
+           fill_value=999999)
+
+    """
+    m = getmask(a)
+    if m is nomask:
+        d = umath.right_shift(filled(a), n)
+        return masked_array(d)
+    else:
+        d = umath.right_shift(filled(a, 0), n)
+        return masked_array(d, mask=m)
+
+
+def put(a, indices, values, mode='raise'):
+    """
+    Set storage-indexed locations to corresponding values.
+
+    This function is equivalent to `MaskedArray.put`, see that method
+    for details.
+
+    See Also
+    --------
+    MaskedArray.put
+
+    """
+    # We can't use 'frommethod', the order of arguments is different
+    try:
+        return a.put(indices, values, mode=mode)
+    except AttributeError:
+        return narray(a, copy=False).put(indices, values, mode=mode)
+
+
+def putmask(a, mask, values):  # , mode='raise'):
+    """
+    Changes elements of an array based on conditional and input values.
+
+    This is the masked array version of `numpy.putmask`, for details see
+    `numpy.putmask`.
+
+    See Also
+    --------
+    numpy.putmask
+
+    Notes
+    -----
+    Using a masked array as `values` will **not** transform a `ndarray` into
+    a `MaskedArray`.
+
+    """
+    # We can't use 'frommethod', the order of arguments is different
+    if not isinstance(a, MaskedArray):
+        a = a.view(MaskedArray)
+    (valdata, valmask) = (getdata(values), getmask(values))
+    if getmask(a) is nomask:
+        if valmask is not nomask:
+            a._sharedmask = True
+            a._mask = make_mask_none(a.shape, a.dtype)
+            np.copyto(a._mask, valmask, where=mask)
+    elif a._hardmask:
+        if valmask is not nomask:
+            m = a._mask.copy()
+            np.copyto(m, valmask, where=mask)
+            a.mask |= m
+    else:
+        if valmask is nomask:
+            valmask = getmaskarray(values)
+        np.copyto(a._mask, valmask, where=mask)
+    np.copyto(a._data, valdata, where=mask)
+    return
+
+
+def transpose(a, axes=None):
+    """
+    Permute the dimensions of an array.
+
+    This function is exactly equivalent to `numpy.transpose`.
+
+    See Also
+    --------
+    numpy.transpose : Equivalent function in top-level NumPy module.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = ma.arange(4).reshape((2,2))
+    >>> x[1, 1] = ma.masked
+    >>> x
+    masked_array(
+      data=[[0, 1],
+            [2, --]],
+      mask=[[False, False],
+            [False,  True]],
+      fill_value=999999)
+
+    >>> ma.transpose(x)
+    masked_array(
+      data=[[0, 2],
+            [1, --]],
+      mask=[[False, False],
+            [False,  True]],
+      fill_value=999999)
+    """
+    # We can't use 'frommethod', as 'transpose' doesn't take keywords
+    try:
+        return a.transpose(axes)
+    except AttributeError:
+        return narray(a, copy=False).transpose(axes).view(MaskedArray)
+
+
+def reshape(a, new_shape, order='C'):
+    """
+    Returns an array containing the same data with a new shape.
+
+    Refer to `MaskedArray.reshape` for full documentation.
+
+    See Also
+    --------
+    MaskedArray.reshape : equivalent function
+
+    """
+    # We can't use 'frommethod', it whine about some parameters. Dmmit.
+    try:
+        return a.reshape(new_shape, order=order)
+    except AttributeError:
+        _tmp = narray(a, copy=False).reshape(new_shape, order=order)
+        return _tmp.view(MaskedArray)
+
+
+def resize(x, new_shape):
+    """
+    Return a new masked array with the specified size and shape.
+
+    This is the masked equivalent of the `numpy.resize` function. The new
+    array is filled with repeated copies of `x` (in the order that the
+    data are stored in memory). If `x` is masked, the new array will be
+    masked, and the new mask will be a repetition of the old one.
+
+    See Also
+    --------
+    numpy.resize : Equivalent function in the top level NumPy module.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = ma.array([[1, 2] ,[3, 4]])
+    >>> a[0, 1] = ma.masked
+    >>> a
+    masked_array(
+      data=[[1, --],
+            [3, 4]],
+      mask=[[False,  True],
+            [False, False]],
+      fill_value=999999)
+    >>> np.resize(a, (3, 3))
+    masked_array(
+      data=[[1, 2, 3],
+            [4, 1, 2],
+            [3, 4, 1]],
+      mask=False,
+      fill_value=999999)
+    >>> ma.resize(a, (3, 3))
+    masked_array(
+      data=[[1, --, 3],
+            [4, 1, --],
+            [3, 4, 1]],
+      mask=[[False,  True, False],
+            [False, False,  True],
+            [False, False, False]],
+      fill_value=999999)
+
+    A MaskedArray is always returned, regardless of the input type.
+
+    >>> a = np.array([[1, 2] ,[3, 4]])
+    >>> ma.resize(a, (3, 3))
+    masked_array(
+      data=[[1, 2, 3],
+            [4, 1, 2],
+            [3, 4, 1]],
+      mask=False,
+      fill_value=999999)
+
+    """
+    # We can't use _frommethods here, as N.resize is notoriously whiny.
+    m = getmask(x)
+    if m is not nomask:
+        m = np.resize(m, new_shape)
+    result = np.resize(x, new_shape).view(get_masked_subclass(x))
+    if result.ndim:
+        result._mask = m
+    return result
+
+
+def ndim(obj):
+    """
+    maskedarray version of the numpy function.
+
+    """
+    return np.ndim(getdata(obj))
+
+ndim.__doc__ = np.ndim.__doc__
+
+
+def shape(obj):
+    "maskedarray version of the numpy function."
+    return np.shape(getdata(obj))
+shape.__doc__ = np.shape.__doc__
+
+
+def size(obj, axis=None):
+    "maskedarray version of the numpy function."
+    return np.size(getdata(obj), axis)
+size.__doc__ = np.size.__doc__
+
+
+def diff(a, /, n=1, axis=-1, prepend=np._NoValue, append=np._NoValue):
+    """
+    Calculate the n-th discrete difference along the given axis.
+    The first difference is given by ``out[i] = a[i+1] - a[i]`` along
+    the given axis, higher differences are calculated by using `diff`
+    recursively.
+    Preserves the input mask.
+
+    Parameters
+    ----------
+    a : array_like
+        Input array
+    n : int, optional
+        The number of times values are differenced. If zero, the input
+        is returned as-is.
+    axis : int, optional
+        The axis along which the difference is taken, default is the
+        last axis.
+    prepend, append : array_like, optional
+        Values to prepend or append to `a` along axis prior to
+        performing the difference.  Scalar values are expanded to
+        arrays with length 1 in the direction of axis and the shape
+        of the input array in along all other axes.  Otherwise the
+        dimension and shape must match `a` except along axis.
+
+    Returns
+    -------
+    diff : MaskedArray
+        The n-th differences. The shape of the output is the same as `a`
+        except along `axis` where the dimension is smaller by `n`. The
+        type of the output is the same as the type of the difference
+        between any two elements of `a`. This is the same as the type of
+        `a` in most cases. A notable exception is `datetime64`, which
+        results in a `timedelta64` output array.
+
+    See Also
+    --------
+    numpy.diff : Equivalent function in the top-level NumPy module.
+
+    Notes
+    -----
+    Type is preserved for boolean arrays, so the result will contain
+    `False` when consecutive elements are the same and `True` when they
+    differ.
+
+    For unsigned integer arrays, the results will also be unsigned. This
+    should not be surprising, as the result is consistent with
+    calculating the difference directly:
+
+    >>> u8_arr = np.array([1, 0], dtype=np.uint8)
+    >>> np.ma.diff(u8_arr)
+    masked_array(data=[255],
+                 mask=False,
+           fill_value=999999,
+                dtype=uint8)
+    >>> u8_arr[1,...] - u8_arr[0,...]
+    255
+
+    If this is not desirable, then the array should be cast to a larger
+    integer type first:
+
+    >>> i16_arr = u8_arr.astype(np.int16)
+    >>> np.ma.diff(i16_arr)
+    masked_array(data=[-1],
+                 mask=False,
+           fill_value=999999,
+                dtype=int16)
+
+    Examples
+    --------
+    >>> a = np.array([1, 2, 3, 4, 7, 0, 2, 3])
+    >>> x = np.ma.masked_where(a < 2, a)
+    >>> np.ma.diff(x)
+    masked_array(data=[--, 1, 1, 3, --, --, 1],
+            mask=[ True, False, False, False,  True,  True, False],
+        fill_value=999999)
+
+    >>> np.ma.diff(x, n=2)
+    masked_array(data=[--, 0, 2, --, --, --],
+                mask=[ True, False, False,  True,  True,  True],
+        fill_value=999999)
+
+    >>> a = np.array([[1, 3, 1, 5, 10], [0, 1, 5, 6, 8]])
+    >>> x = np.ma.masked_equal(a, value=1)
+    >>> np.ma.diff(x)
+    masked_array(
+        data=[[--, --, --, 5],
+                [--, --, 1, 2]],
+        mask=[[ True,  True,  True, False],
+                [ True,  True, False, False]],
+        fill_value=1)
+
+    >>> np.ma.diff(x, axis=0)
+    masked_array(data=[[--, --, --, 1, -2]],
+            mask=[[ True,  True,  True, False, False]],
+        fill_value=1)
+
+    """
+    if n == 0:
+        return a
+    if n < 0:
+        raise ValueError("order must be non-negative but got " + repr(n))
+
+    a = np.ma.asanyarray(a)
+    if a.ndim == 0:
+        raise ValueError(
+            "diff requires input that is at least one dimensional"
+            )
+
+    combined = []
+    if prepend is not np._NoValue:
+        prepend = np.ma.asanyarray(prepend)
+        if prepend.ndim == 0:
+            shape = list(a.shape)
+            shape[axis] = 1
+            prepend = np.broadcast_to(prepend, tuple(shape))
+        combined.append(prepend)
+
+    combined.append(a)
+
+    if append is not np._NoValue:
+        append = np.ma.asanyarray(append)
+        if append.ndim == 0:
+            shape = list(a.shape)
+            shape[axis] = 1
+            append = np.broadcast_to(append, tuple(shape))
+        combined.append(append)
+
+    if len(combined) > 1:
+        a = np.ma.concatenate(combined, axis)
+
+    # GH 22465 np.diff without prepend/append preserves the mask
+    return np.diff(a, n, axis)
+
+
+##############################################################################
+#                            Extra functions                                 #
+##############################################################################
+
+
+def where(condition, x=_NoValue, y=_NoValue):
+    """
+    Return a masked array with elements from `x` or `y`, depending on condition.
+
+    .. note::
+        When only `condition` is provided, this function is identical to
+        `nonzero`. The rest of this documentation covers only the case where
+        all three arguments are provided.
+
+    Parameters
+    ----------
+    condition : array_like, bool
+        Where True, yield `x`, otherwise yield `y`.
+    x, y : array_like, optional
+        Values from which to choose. `x`, `y` and `condition` need to be
+        broadcastable to some shape.
+
+    Returns
+    -------
+    out : MaskedArray
+        An masked array with `masked` elements where the condition is masked,
+        elements from `x` where `condition` is True, and elements from `y`
+        elsewhere.
+
+    See Also
+    --------
+    numpy.where : Equivalent function in the top-level NumPy module.
+    nonzero : The function that is called when x and y are omitted
+
+    Examples
+    --------
+    >>> x = np.ma.array(np.arange(9.).reshape(3, 3), mask=[[0, 1, 0],
+    ...                                                    [1, 0, 1],
+    ...                                                    [0, 1, 0]])
+    >>> x
+    masked_array(
+      data=[[0.0, --, 2.0],
+            [--, 4.0, --],
+            [6.0, --, 8.0]],
+      mask=[[False,  True, False],
+            [ True, False,  True],
+            [False,  True, False]],
+      fill_value=1e+20)
+    >>> np.ma.where(x > 5, x, -3.1416)
+    masked_array(
+      data=[[-3.1416, --, -3.1416],
+            [--, -3.1416, --],
+            [6.0, --, 8.0]],
+      mask=[[False,  True, False],
+            [ True, False,  True],
+            [False,  True, False]],
+      fill_value=1e+20)
+
+    """
+
+    # handle the single-argument case
+    missing = (x is _NoValue, y is _NoValue).count(True)
+    if missing == 1:
+        raise ValueError("Must provide both 'x' and 'y' or neither.")
+    if missing == 2:
+        return nonzero(condition)
+
+    # we only care if the condition is true - false or masked pick y
+    cf = filled(condition, False)
+    xd = getdata(x)
+    yd = getdata(y)
+
+    # we need the full arrays here for correct final dimensions
+    cm = getmaskarray(condition)
+    xm = getmaskarray(x)
+    ym = getmaskarray(y)
+
+    # deal with the fact that masked.dtype == float64, but we don't actually
+    # want to treat it as that.
+    if x is masked and y is not masked:
+        xd = np.zeros((), dtype=yd.dtype)
+        xm = np.ones((),  dtype=ym.dtype)
+    elif y is masked and x is not masked:
+        yd = np.zeros((), dtype=xd.dtype)
+        ym = np.ones((),  dtype=xm.dtype)
+
+    data = np.where(cf, xd, yd)
+    mask = np.where(cf, xm, ym)
+    mask = np.where(cm, np.ones((), dtype=mask.dtype), mask)
+
+    # collapse the mask, for backwards compatibility
+    mask = _shrink_mask(mask)
+
+    return masked_array(data, mask=mask)
+
+
+def choose(indices, choices, out=None, mode='raise'):
+    """
+    Use an index array to construct a new array from a list of choices.
+
+    Given an array of integers and a list of n choice arrays, this method
+    will create a new array that merges each of the choice arrays.  Where a
+    value in `index` is i, the new array will have the value that choices[i]
+    contains in the same place.
+
+    Parameters
+    ----------
+    indices : ndarray of ints
+        This array must contain integers in ``[0, n-1]``, where n is the
+        number of choices.
+    choices : sequence of arrays
+        Choice arrays. The index array and all of the choices should be
+        broadcastable to the same shape.
+    out : array, optional
+        If provided, the result will be inserted into this array. It should
+        be of the appropriate shape and `dtype`.
+    mode : {'raise', 'wrap', 'clip'}, optional
+        Specifies how out-of-bounds indices will behave.
+
+        * 'raise' : raise an error
+        * 'wrap' : wrap around
+        * 'clip' : clip to the range
+
+    Returns
+    -------
+    merged_array : array
+
+    See Also
+    --------
+    choose : equivalent function
+
+    Examples
+    --------
+    >>> choice = np.array([[1,1,1], [2,2,2], [3,3,3]])
+    >>> a = np.array([2, 1, 0])
+    >>> np.ma.choose(a, choice)
+    masked_array(data=[3, 2, 1],
+                 mask=False,
+           fill_value=999999)
+
+    """
+    def fmask(x):
+        "Returns the filled array, or True if masked."
+        if x is masked:
+            return True
+        return filled(x)
+
+    def nmask(x):
+        "Returns the mask, True if ``masked``, False if ``nomask``."
+        if x is masked:
+            return True
+        return getmask(x)
+    # Get the indices.
+    c = filled(indices, 0)
+    # Get the masks.
+    masks = [nmask(x) for x in choices]
+    data = [fmask(x) for x in choices]
+    # Construct the mask
+    outputmask = np.choose(c, masks, mode=mode)
+    outputmask = make_mask(mask_or(outputmask, getmask(indices)),
+                           copy=False, shrink=True)
+    # Get the choices.
+    d = np.choose(c, data, mode=mode, out=out).view(MaskedArray)
+    if out is not None:
+        if isinstance(out, MaskedArray):
+            out.__setmask__(outputmask)
+        return out
+    d.__setmask__(outputmask)
+    return d
+
+
+def round_(a, decimals=0, out=None):
+    """
+    Return a copy of a, rounded to 'decimals' places.
+
+    When 'decimals' is negative, it specifies the number of positions
+    to the left of the decimal point.  The real and imaginary parts of
+    complex numbers are rounded separately. Nothing is done if the
+    array is not of float type and 'decimals' is greater than or equal
+    to 0.
+
+    Parameters
+    ----------
+    decimals : int
+        Number of decimals to round to. May be negative.
+    out : array_like
+        Existing array to use for output.
+        If not given, returns a default copy of a.
+
+    Notes
+    -----
+    If out is given and does not have a mask attribute, the mask of a
+    is lost!
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> x = [11.2, -3.973, 0.801, -1.41]
+    >>> mask = [0, 0, 0, 1]
+    >>> masked_x = ma.masked_array(x, mask)
+    >>> masked_x
+    masked_array(data=[11.2, -3.973, 0.801, --],
+                 mask=[False, False, False, True],
+        fill_value=1e+20)
+    >>> ma.round_(masked_x)
+    masked_array(data=[11.0, -4.0, 1.0, --],
+                 mask=[False, False, False, True],
+        fill_value=1e+20)
+    >>> ma.round(masked_x, decimals=1)
+    masked_array(data=[11.2, -4.0, 0.8, --],
+                 mask=[False, False, False, True],
+        fill_value=1e+20)
+    >>> ma.round_(masked_x, decimals=-1)
+    masked_array(data=[10.0, -0.0, 0.0, --],
+                 mask=[False, False, False, True],
+        fill_value=1e+20)
+    """
+    if out is None:
+        return np.round_(a, decimals, out)
+    else:
+        np.round_(getdata(a), decimals, out)
+        if hasattr(out, '_mask'):
+            out._mask = getmask(a)
+        return out
+round = round_
+
+
+def _mask_propagate(a, axis):
+    """
+    Mask whole 1-d vectors of an array that contain masked values.
+    """
+    a = array(a, subok=False)
+    m = getmask(a)
+    if m is nomask or not m.any() or axis is None:
+        return a
+    a._mask = a._mask.copy()
+    axes = normalize_axis_tuple(axis, a.ndim)
+    for ax in axes:
+        a._mask |= m.any(axis=ax, keepdims=True)
+    return a
+
+
+# Include masked dot here to avoid import problems in getting it from
+# extras.py. Note that it is not included in __all__, but rather exported
+# from extras in order to avoid backward compatibility problems.
+def dot(a, b, strict=False, out=None):
+    """
+    Return the dot product of two arrays.
+
+    This function is the equivalent of `numpy.dot` that takes masked values
+    into account. Note that `strict` and `out` are in different position
+    than in the method version. In order to maintain compatibility with the
+    corresponding method, it is recommended that the optional arguments be
+    treated as keyword only.  At some point that may be mandatory.
+
+    Parameters
+    ----------
+    a, b : masked_array_like
+        Inputs arrays.
+    strict : bool, optional
+        Whether masked data are propagated (True) or set to 0 (False) for
+        the computation. Default is False.  Propagating the mask means that
+        if a masked value appears in a row or column, the whole row or
+        column is considered masked.
+    out : masked_array, optional
+        Output argument. This must have the exact kind that would be returned
+        if it was not used. In particular, it must have the right type, must be
+        C-contiguous, and its dtype must be the dtype that would be returned
+        for `dot(a,b)`. This is a performance feature. Therefore, if these
+        conditions are not met, an exception is raised, instead of attempting
+        to be flexible.
+
+        .. versionadded:: 1.10.2
+
+    See Also
+    --------
+    numpy.dot : Equivalent function for ndarrays.
+
+    Examples
+    --------
+    >>> a = np.ma.array([[1, 2, 3], [4, 5, 6]], mask=[[1, 0, 0], [0, 0, 0]])
+    >>> b = np.ma.array([[1, 2], [3, 4], [5, 6]], mask=[[1, 0], [0, 0], [0, 0]])
+    >>> np.ma.dot(a, b)
+    masked_array(
+      data=[[21, 26],
+            [45, 64]],
+      mask=[[False, False],
+            [False, False]],
+      fill_value=999999)
+    >>> np.ma.dot(a, b, strict=True)
+    masked_array(
+      data=[[--, --],
+            [--, 64]],
+      mask=[[ True,  True],
+            [ True, False]],
+      fill_value=999999)
+
+    """
+    if strict is True:
+        if np.ndim(a) == 0 or np.ndim(b) == 0:
+            pass
+        elif b.ndim == 1:
+            a = _mask_propagate(a, a.ndim - 1)
+            b = _mask_propagate(b, b.ndim - 1)
+        else:
+            a = _mask_propagate(a, a.ndim - 1)
+            b = _mask_propagate(b, b.ndim - 2)
+    am = ~getmaskarray(a)
+    bm = ~getmaskarray(b)
+
+    if out is None:
+        d = np.dot(filled(a, 0), filled(b, 0))
+        m = ~np.dot(am, bm)
+        if np.ndim(d) == 0:
+            d = np.asarray(d)
+        r = d.view(get_masked_subclass(a, b))
+        r.__setmask__(m)
+        return r
+    else:
+        d = np.dot(filled(a, 0), filled(b, 0), out._data)
+        if out.mask.shape != d.shape:
+            out._mask = np.empty(d.shape, MaskType)
+        np.dot(am, bm, out._mask)
+        np.logical_not(out._mask, out._mask)
+        return out
+
+
+def inner(a, b):
+    """
+    Returns the inner product of a and b for arrays of floating point types.
+
+    Like the generic NumPy equivalent the product sum is over the last dimension
+    of a and b. The first argument is not conjugated.
+
+    """
+    fa = filled(a, 0)
+    fb = filled(b, 0)
+    if fa.ndim == 0:
+        fa.shape = (1,)
+    if fb.ndim == 0:
+        fb.shape = (1,)
+    return np.inner(fa, fb).view(MaskedArray)
+inner.__doc__ = doc_note(np.inner.__doc__,
+                         "Masked values are replaced by 0.")
+innerproduct = inner
+
+
+def outer(a, b):
+    "maskedarray version of the numpy function."
+    fa = filled(a, 0).ravel()
+    fb = filled(b, 0).ravel()
+    d = np.outer(fa, fb)
+    ma = getmask(a)
+    mb = getmask(b)
+    if ma is nomask and mb is nomask:
+        return masked_array(d)
+    ma = getmaskarray(a)
+    mb = getmaskarray(b)
+    m = make_mask(1 - np.outer(1 - ma, 1 - mb), copy=False)
+    return masked_array(d, mask=m)
+outer.__doc__ = doc_note(np.outer.__doc__,
+                         "Masked values are replaced by 0.")
+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 = (
+            f(getmaskarray(a), np.ones(np.shape(v), dtype=bool), mode=mode)
+          | f(np.ones(np.shape(a), dtype=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', propagate_mask=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'.
+    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
+    -------
+    out : MaskedArray
+        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, propagate_mask)
+
+
+def convolve(a, v, mode='full', propagate_mask=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.
+    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
+        contribute towards it
+
+    Returns
+    -------
+    out : MaskedArray
+        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, propagate_mask)
+
+
+def allequal(a, b, fill_value=True):
+    """
+    Return True if all entries of a and b are equal, using
+    fill_value as a truth value where either or both are masked.
+
+    Parameters
+    ----------
+    a, b : array_like
+        Input arrays to compare.
+    fill_value : bool, optional
+        Whether masked values in a or b are considered equal (True) or not
+        (False).
+
+    Returns
+    -------
+    y : bool
+        Returns True if the two arrays are equal within the given
+        tolerance, False otherwise. If either array contains NaN,
+        then False is returned.
+
+    See Also
+    --------
+    all, any
+    numpy.ma.allclose
+
+    Examples
+    --------
+    >>> a = np.ma.array([1e10, 1e-7, 42.0], mask=[0, 0, 1])
+    >>> a
+    masked_array(data=[10000000000.0, 1e-07, --],
+                 mask=[False, False,  True],
+           fill_value=1e+20)
+
+    >>> b = np.array([1e10, 1e-7, -42.0])
+    >>> b
+    array([  1.00000000e+10,   1.00000000e-07,  -4.20000000e+01])
+    >>> np.ma.allequal(a, b, fill_value=False)
+    False
+    >>> np.ma.allequal(a, b)
+    True
+
+    """
+    m = mask_or(getmask(a), getmask(b))
+    if m is nomask:
+        x = getdata(a)
+        y = getdata(b)
+        d = umath.equal(x, y)
+        return d.all()
+    elif fill_value:
+        x = getdata(a)
+        y = getdata(b)
+        d = umath.equal(x, y)
+        dm = array(d, mask=m, copy=False)
+        return dm.filled(True).all(None)
+    else:
+        return False
+
+
+def allclose(a, b, masked_equal=True, rtol=1e-5, atol=1e-8):
+    """
+    Returns True if two arrays are element-wise equal within a tolerance.
+
+    This function is equivalent to `allclose` except that masked values
+    are treated as equal (default) or unequal, depending on the `masked_equal`
+    argument.
+
+    Parameters
+    ----------
+    a, b : array_like
+        Input arrays to compare.
+    masked_equal : bool, optional
+        Whether masked values in `a` and `b` are considered equal (True) or not
+        (False). They are considered equal by default.
+    rtol : float, optional
+        Relative tolerance. The relative difference is equal to ``rtol * b``.
+        Default is 1e-5.
+    atol : float, optional
+        Absolute tolerance. The absolute difference is equal to `atol`.
+        Default is 1e-8.
+
+    Returns
+    -------
+    y : bool
+        Returns True if the two arrays are equal within the given
+        tolerance, False otherwise. If either array contains NaN, then
+        False is returned.
+
+    See Also
+    --------
+    all, any
+    numpy.allclose : the non-masked `allclose`.
+
+    Notes
+    -----
+    If the following equation is element-wise True, then `allclose` returns
+    True::
+
+      absolute(`a` - `b`) <= (`atol` + `rtol` * absolute(`b`))
+
+    Return True if all elements of `a` and `b` are equal subject to
+    given tolerances.
+
+    Examples
+    --------
+    >>> a = np.ma.array([1e10, 1e-7, 42.0], mask=[0, 0, 1])
+    >>> a
+    masked_array(data=[10000000000.0, 1e-07, --],
+                 mask=[False, False,  True],
+           fill_value=1e+20)
+    >>> b = np.ma.array([1e10, 1e-8, -42.0], mask=[0, 0, 1])
+    >>> np.ma.allclose(a, b)
+    False
+
+    >>> a = np.ma.array([1e10, 1e-8, 42.0], mask=[0, 0, 1])
+    >>> b = np.ma.array([1.00001e10, 1e-9, -42.0], mask=[0, 0, 1])
+    >>> np.ma.allclose(a, b)
+    True
+    >>> np.ma.allclose(a, b, masked_equal=False)
+    False
+
+    Masked values are not compared directly.
+
+    >>> a = np.ma.array([1e10, 1e-8, 42.0], mask=[0, 0, 1])
+    >>> b = np.ma.array([1.00001e10, 1e-9, 42.0], mask=[0, 0, 1])
+    >>> np.ma.allclose(a, b)
+    True
+    >>> np.ma.allclose(a, b, masked_equal=False)
+    False
+
+    """
+    x = masked_array(a, copy=False)
+    y = masked_array(b, copy=False)
+
+    # make sure y is an inexact type to avoid abs(MIN_INT); will cause
+    # casting of x later.
+    # NOTE: We explicitly allow timedelta, which used to work. This could
+    #       possibly be deprecated. See also gh-18286.
+    #       timedelta works if `atol` is an integer or also a timedelta.
+    #       Although, the default tolerances are unlikely to be useful
+    if y.dtype.kind != "m":
+        dtype = np.result_type(y, 1.)
+        if y.dtype != dtype:
+            y = masked_array(y, dtype=dtype, copy=False)
+
+    m = mask_or(getmask(x), getmask(y))
+    xinf = np.isinf(masked_array(x, copy=False, mask=m)).filled(False)
+    # If we have some infs, they should fall at the same place.
+    if not np.all(xinf == filled(np.isinf(y), False)):
+        return False
+    # No infs at all
+    if not np.any(xinf):
+        d = filled(less_equal(absolute(x - y), atol + rtol * absolute(y)),
+                   masked_equal)
+        return np.all(d)
+
+    if not np.all(filled(x[xinf] == y[xinf], masked_equal)):
+        return False
+    x = x[~xinf]
+    y = y[~xinf]
+
+    d = filled(less_equal(absolute(x - y), atol + rtol * absolute(y)),
+               masked_equal)
+
+    return np.all(d)
+
+
+def asarray(a, dtype=None, order=None):
+    """
+    Convert the input to a masked array of the given data-type.
+
+    No copy is performed if the input is already an `ndarray`. If `a` is
+    a subclass of `MaskedArray`, a base class `MaskedArray` is returned.
+
+    Parameters
+    ----------
+    a : array_like
+        Input data, in any form that can be converted to a masked array. This
+        includes lists, lists of tuples, tuples, tuples of tuples, tuples
+        of lists, ndarrays and masked arrays.
+    dtype : dtype, optional
+        By default, the data-type is inferred from the input data.
+    order : {'C', 'F'}, optional
+        Whether to use row-major ('C') or column-major ('FORTRAN') memory
+        representation.  Default is 'C'.
+
+    Returns
+    -------
+    out : MaskedArray
+        Masked array interpretation of `a`.
+
+    See Also
+    --------
+    asanyarray : Similar to `asarray`, but conserves subclasses.
+
+    Examples
+    --------
+    >>> x = np.arange(10.).reshape(2, 5)
+    >>> x
+    array([[0., 1., 2., 3., 4.],
+           [5., 6., 7., 8., 9.]])
+    >>> np.ma.asarray(x)
+    masked_array(
+      data=[[0., 1., 2., 3., 4.],
+            [5., 6., 7., 8., 9.]],
+      mask=False,
+      fill_value=1e+20)
+    >>> type(np.ma.asarray(x))
+    <class 'numpy.ma.core.MaskedArray'>
+
+    """
+    order = order or 'C'
+    return masked_array(a, dtype=dtype, copy=False, keep_mask=True,
+                        subok=False, order=order)
+
+
+def asanyarray(a, dtype=None):
+    """
+    Convert the input to a masked array, conserving subclasses.
+
+    If `a` is a subclass of `MaskedArray`, its class is conserved.
+    No copy is performed if the input is already an `ndarray`.
+
+    Parameters
+    ----------
+    a : array_like
+        Input data, in any form that can be converted to an array.
+    dtype : dtype, optional
+        By default, the data-type is inferred from the input data.
+    order : {'C', 'F'}, optional
+        Whether to use row-major ('C') or column-major ('FORTRAN') memory
+        representation.  Default is 'C'.
+
+    Returns
+    -------
+    out : MaskedArray
+        MaskedArray interpretation of `a`.
+
+    See Also
+    --------
+    asarray : Similar to `asanyarray`, but does not conserve subclass.
+
+    Examples
+    --------
+    >>> x = np.arange(10.).reshape(2, 5)
+    >>> x
+    array([[0., 1., 2., 3., 4.],
+           [5., 6., 7., 8., 9.]])
+    >>> np.ma.asanyarray(x)
+    masked_array(
+      data=[[0., 1., 2., 3., 4.],
+            [5., 6., 7., 8., 9.]],
+      mask=False,
+      fill_value=1e+20)
+    >>> type(np.ma.asanyarray(x))
+    <class 'numpy.ma.core.MaskedArray'>
+
+    """
+    # workaround for #8666, to preserve identity. Ideally the bottom line
+    # would handle this for us.
+    if isinstance(a, MaskedArray) and (dtype is None or dtype == a.dtype):
+        return a
+    return masked_array(a, dtype=dtype, copy=False, keep_mask=True, subok=True)
+
+
+##############################################################################
+#                               Pickling                                     #
+##############################################################################
+
+
+def fromfile(file, dtype=float, count=-1, sep=''):
+    raise NotImplementedError(
+        "fromfile() not yet implemented for a MaskedArray.")
+
+
+def fromflex(fxarray):
+    """
+    Build a masked array from a suitable flexible-type array.
+
+    The input array has to have a data-type with ``_data`` and ``_mask``
+    fields. This type of array is output by `MaskedArray.toflex`.
+
+    Parameters
+    ----------
+    fxarray : ndarray
+        The structured input array, containing ``_data`` and ``_mask``
+        fields. If present, other fields are discarded.
+
+    Returns
+    -------
+    result : MaskedArray
+        The constructed masked array.
+
+    See Also
+    --------
+    MaskedArray.toflex : Build a flexible-type array from a masked array.
+
+    Examples
+    --------
+    >>> x = np.ma.array(np.arange(9).reshape(3, 3), mask=[0] + [1, 0] * 4)
+    >>> rec = x.toflex()
+    >>> rec
+    array([[(0, False), (1,  True), (2, False)],
+           [(3,  True), (4, False), (5,  True)],
+           [(6, False), (7,  True), (8, False)]],
+          dtype=[('_data', '<i8'), ('_mask', '?')])
+    >>> x2 = np.ma.fromflex(rec)
+    >>> x2
+    masked_array(
+      data=[[0, --, 2],
+            [--, 4, --],
+            [6, --, 8]],
+      mask=[[False,  True, False],
+            [ True, False,  True],
+            [False,  True, False]],
+      fill_value=999999)
+
+    Extra fields can be present in the structured array but are discarded:
+
+    >>> dt = [('_data', '<i4'), ('_mask', '|b1'), ('field3', '<f4')]
+    >>> rec2 = np.zeros((2, 2), dtype=dt)
+    >>> rec2
+    array([[(0, False, 0.), (0, False, 0.)],
+           [(0, False, 0.), (0, False, 0.)]],
+          dtype=[('_data', '<i4'), ('_mask', '?'), ('field3', '<f4')])
+    >>> y = np.ma.fromflex(rec2)
+    >>> y
+    masked_array(
+      data=[[0, 0],
+            [0, 0]],
+      mask=[[False, False],
+            [False, False]],
+      fill_value=999999,
+      dtype=int32)
+
+    """
+    return masked_array(fxarray['_data'], mask=fxarray['_mask'])
+
+
+class _convert2ma:
+
+    """
+    Convert functions from numpy to numpy.ma.
+
+    Parameters
+    ----------
+        _methodname : string
+            Name of the method to transform.
+
+    """
+    __doc__ = None
+
+    def __init__(self, funcname, np_ret, np_ma_ret, params=None):
+        self._func = getattr(np, funcname)
+        self.__doc__ = self.getdoc(np_ret, np_ma_ret)
+        self._extras = params or {}
+
+    def getdoc(self, np_ret, np_ma_ret):
+        "Return the doc of the function (from the doc of the method)."
+        doc = getattr(self._func, '__doc__', None)
+        sig = get_object_signature(self._func)
+        if doc:
+            doc = self._replace_return_type(doc, np_ret, np_ma_ret)
+            # Add the signature of the function at the beginning of the doc
+            if sig:
+                sig = "%s%s\n" % (self._func.__name__, sig)
+            doc = sig + doc
+        return doc
+
+    def _replace_return_type(self, doc, np_ret, np_ma_ret):
+        """
+        Replace documentation of ``np`` function's return type.
+
+        Replaces it with the proper type for the ``np.ma`` function.
+
+        Parameters
+        ----------
+        doc : str
+            The documentation of the ``np`` method.
+        np_ret : str
+            The return type string of the ``np`` method that we want to
+            replace. (e.g. "out : ndarray")
+        np_ma_ret : str
+            The return type string of the ``np.ma`` method.
+            (e.g. "out : MaskedArray")
+        """
+        if np_ret not in doc:
+            raise RuntimeError(
+                f"Failed to replace `{np_ret}` with `{np_ma_ret}`. "
+                f"The documentation string for return type, {np_ret}, is not "
+                f"found in the docstring for `np.{self._func.__name__}`. "
+                f"Fix the docstring for `np.{self._func.__name__}` or "
+                "update the expected string for return type."
+            )
+
+        return doc.replace(np_ret, np_ma_ret)
+
+    def __call__(self, *args, **params):
+        # Find the common parameters to the call and the definition
+        _extras = self._extras
+        common_params = set(params).intersection(_extras)
+        # Drop the common parameters from the call
+        for p in common_params:
+            _extras[p] = params.pop(p)
+        # Get the result
+        result = self._func.__call__(*args, **params).view(MaskedArray)
+        if "fill_value" in common_params:
+            result.fill_value = _extras.get("fill_value", None)
+        if "hardmask" in common_params:
+            result._hardmask = bool(_extras.get("hard_mask", False))
+        return result
+
+
+arange = _convert2ma(
+    'arange',
+    params=dict(fill_value=None, hardmask=False),
+    np_ret='arange : ndarray',
+    np_ma_ret='arange : MaskedArray',
+)
+clip = _convert2ma(
+    'clip',
+    params=dict(fill_value=None, hardmask=False),
+    np_ret='clipped_array : ndarray',
+    np_ma_ret='clipped_array : MaskedArray',
+)
+empty = _convert2ma(
+    'empty',
+    params=dict(fill_value=None, hardmask=False),
+    np_ret='out : ndarray',
+    np_ma_ret='out : MaskedArray',
+)
+empty_like = _convert2ma(
+    'empty_like',
+    np_ret='out : ndarray',
+    np_ma_ret='out : MaskedArray',
+)
+frombuffer = _convert2ma(
+    'frombuffer',
+    np_ret='out : ndarray',
+    np_ma_ret='out: MaskedArray',
+)
+fromfunction = _convert2ma(
+   'fromfunction',
+   np_ret='fromfunction : any',
+   np_ma_ret='fromfunction: MaskedArray',
+)
+identity = _convert2ma(
+    'identity',
+    params=dict(fill_value=None, hardmask=False),
+    np_ret='out : ndarray',
+    np_ma_ret='out : MaskedArray',
+)
+indices = _convert2ma(
+    'indices',
+    params=dict(fill_value=None, hardmask=False),
+    np_ret='grid : one ndarray or tuple of ndarrays',
+    np_ma_ret='grid : one MaskedArray or tuple of MaskedArrays',
+)
+ones = _convert2ma(
+    'ones',
+    params=dict(fill_value=None, hardmask=False),
+    np_ret='out : ndarray',
+    np_ma_ret='out : MaskedArray',
+)
+ones_like = _convert2ma(
+    'ones_like',
+    np_ret='out : ndarray',
+    np_ma_ret='out : MaskedArray',
+)
+squeeze = _convert2ma(
+    'squeeze',
+    params=dict(fill_value=None, hardmask=False),
+    np_ret='squeezed : ndarray',
+    np_ma_ret='squeezed : MaskedArray',
+)
+zeros = _convert2ma(
+    'zeros',
+    params=dict(fill_value=None, hardmask=False),
+    np_ret='out : ndarray',
+    np_ma_ret='out : MaskedArray',
+)
+zeros_like = _convert2ma(
+    'zeros_like',
+    np_ret='out : ndarray',
+    np_ma_ret='out : MaskedArray',
+)
+
+
+def append(a, b, axis=None):
+    """Append values to the end of an array.
+
+    .. versionadded:: 1.9.0
+
+    Parameters
+    ----------
+    a : array_like
+        Values are appended to a copy of this array.
+    b : array_like
+        These values are appended to a copy of `a`.  It must be of the
+        correct shape (the same shape as `a`, excluding `axis`).  If `axis`
+        is not specified, `b` can be any shape and will be flattened
+        before use.
+    axis : int, optional
+        The axis along which `v` are appended.  If `axis` is not given,
+        both `a` and `b` are flattened before use.
+
+    Returns
+    -------
+    append : MaskedArray
+        A copy of `a` with `b` appended to `axis`.  Note that `append`
+        does not occur in-place: a new array is allocated and filled.  If
+        `axis` is None, the result is a flattened array.
+
+    See Also
+    --------
+    numpy.append : Equivalent function in the top-level NumPy module.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = ma.masked_values([1, 2, 3], 2)
+    >>> b = ma.masked_values([[4, 5, 6], [7, 8, 9]], 7)
+    >>> ma.append(a, b)
+    masked_array(data=[1, --, 3, 4, 5, 6, --, 8, 9],
+                 mask=[False,  True, False, False, False, False,  True, False,
+                       False],
+           fill_value=999999)
+    """
+    return concatenate([a, b], axis)
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/core.pyi b/.venv/lib/python3.12/site-packages/numpy/ma/core.pyi
new file mode 100644
index 00000000..e94ebce3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/core.pyi
@@ -0,0 +1,471 @@
+from collections.abc import Callable
+from typing import Any, TypeVar
+from numpy import ndarray, dtype, float64
+
+from numpy import (
+    amax as amax,
+    amin as amin,
+    bool_ as bool_,
+    expand_dims as expand_dims,
+    clip as clip,
+    indices as indices,
+    ones_like as ones_like,
+    squeeze as squeeze,
+    zeros_like as zeros_like,
+)
+
+from numpy.lib.function_base import (
+    angle as angle,
+)
+
+# TODO: Set the `bound` to something more suitable once we
+# have proper shape support
+_ShapeType = TypeVar("_ShapeType", bound=Any)
+_DType_co = TypeVar("_DType_co", bound=dtype[Any], covariant=True)
+
+__all__: list[str]
+
+MaskType = bool_
+nomask: bool_
+
+class MaskedArrayFutureWarning(FutureWarning): ...
+class MAError(Exception): ...
+class MaskError(MAError): ...
+
+def default_fill_value(obj): ...
+def minimum_fill_value(obj): ...
+def maximum_fill_value(obj): ...
+def set_fill_value(a, fill_value): ...
+def common_fill_value(a, b): ...
+def filled(a, fill_value=...): ...
+def getdata(a, subok=...): ...
+get_data = getdata
+
+def fix_invalid(a, mask=..., copy=..., fill_value=...): ...
+
+class _MaskedUFunc:
+    f: Any
+    __doc__: Any
+    __name__: Any
+    def __init__(self, ufunc): ...
+
+class _MaskedUnaryOperation(_MaskedUFunc):
+    fill: Any
+    domain: Any
+    def __init__(self, mufunc, fill=..., domain=...): ...
+    def __call__(self, a, *args, **kwargs): ...
+
+class _MaskedBinaryOperation(_MaskedUFunc):
+    fillx: Any
+    filly: Any
+    def __init__(self, mbfunc, fillx=..., filly=...): ...
+    def __call__(self, a, b, *args, **kwargs): ...
+    def reduce(self, target, axis=..., dtype=...): ...
+    def outer(self, a, b): ...
+    def accumulate(self, target, axis=...): ...
+
+class _DomainedBinaryOperation(_MaskedUFunc):
+    domain: Any
+    fillx: Any
+    filly: Any
+    def __init__(self, dbfunc, domain, fillx=..., filly=...): ...
+    def __call__(self, a, b, *args, **kwargs): ...
+
+exp: _MaskedUnaryOperation
+conjugate: _MaskedUnaryOperation
+sin: _MaskedUnaryOperation
+cos: _MaskedUnaryOperation
+arctan: _MaskedUnaryOperation
+arcsinh: _MaskedUnaryOperation
+sinh: _MaskedUnaryOperation
+cosh: _MaskedUnaryOperation
+tanh: _MaskedUnaryOperation
+abs: _MaskedUnaryOperation
+absolute: _MaskedUnaryOperation
+fabs: _MaskedUnaryOperation
+negative: _MaskedUnaryOperation
+floor: _MaskedUnaryOperation
+ceil: _MaskedUnaryOperation
+around: _MaskedUnaryOperation
+logical_not: _MaskedUnaryOperation
+sqrt: _MaskedUnaryOperation
+log: _MaskedUnaryOperation
+log2: _MaskedUnaryOperation
+log10: _MaskedUnaryOperation
+tan: _MaskedUnaryOperation
+arcsin: _MaskedUnaryOperation
+arccos: _MaskedUnaryOperation
+arccosh: _MaskedUnaryOperation
+arctanh: _MaskedUnaryOperation
+
+add: _MaskedBinaryOperation
+subtract: _MaskedBinaryOperation
+multiply: _MaskedBinaryOperation
+arctan2: _MaskedBinaryOperation
+equal: _MaskedBinaryOperation
+not_equal: _MaskedBinaryOperation
+less_equal: _MaskedBinaryOperation
+greater_equal: _MaskedBinaryOperation
+less: _MaskedBinaryOperation
+greater: _MaskedBinaryOperation
+logical_and: _MaskedBinaryOperation
+alltrue: _MaskedBinaryOperation
+logical_or: _MaskedBinaryOperation
+sometrue: Callable[..., Any]
+logical_xor: _MaskedBinaryOperation
+bitwise_and: _MaskedBinaryOperation
+bitwise_or: _MaskedBinaryOperation
+bitwise_xor: _MaskedBinaryOperation
+hypot: _MaskedBinaryOperation
+divide: _MaskedBinaryOperation
+true_divide: _MaskedBinaryOperation
+floor_divide: _MaskedBinaryOperation
+remainder: _MaskedBinaryOperation
+fmod: _MaskedBinaryOperation
+mod: _MaskedBinaryOperation
+
+def make_mask_descr(ndtype): ...
+def getmask(a): ...
+get_mask = getmask
+
+def getmaskarray(arr): ...
+def is_mask(m): ...
+def make_mask(m, copy=..., shrink=..., dtype=...): ...
+def make_mask_none(newshape, dtype=...): ...
+def mask_or(m1, m2, copy=..., shrink=...): ...
+def flatten_mask(mask): ...
+def masked_where(condition, a, copy=...): ...
+def masked_greater(x, value, copy=...): ...
+def masked_greater_equal(x, value, copy=...): ...
+def masked_less(x, value, copy=...): ...
+def masked_less_equal(x, value, copy=...): ...
+def masked_not_equal(x, value, copy=...): ...
+def masked_equal(x, value, copy=...): ...
+def masked_inside(x, v1, v2, copy=...): ...
+def masked_outside(x, v1, v2, copy=...): ...
+def masked_object(x, value, copy=..., shrink=...): ...
+def masked_values(x, value, rtol=..., atol=..., copy=..., shrink=...): ...
+def masked_invalid(a, copy=...): ...
+
+class _MaskedPrintOption:
+    def __init__(self, display): ...
+    def display(self): ...
+    def set_display(self, s): ...
+    def enabled(self): ...
+    def enable(self, shrink=...): ...
+
+masked_print_option: _MaskedPrintOption
+
+def flatten_structured_array(a): ...
+
+class MaskedIterator:
+    ma: Any
+    dataiter: Any
+    maskiter: Any
+    def __init__(self, ma): ...
+    def __iter__(self): ...
+    def __getitem__(self, indx): ...
+    def __setitem__(self, index, value): ...
+    def __next__(self): ...
+
+class MaskedArray(ndarray[_ShapeType, _DType_co]):
+    __array_priority__: Any
+    def __new__(cls, data=..., mask=..., dtype=..., copy=..., subok=..., ndmin=..., fill_value=..., keep_mask=..., hard_mask=..., shrink=..., order=...): ...
+    def __array_finalize__(self, obj): ...
+    def __array_wrap__(self, obj, context=...): ...
+    def view(self, dtype=..., type=..., fill_value=...): ...
+    def __getitem__(self, indx): ...
+    def __setitem__(self, indx, value): ...
+    @property
+    def dtype(self): ...
+    @dtype.setter
+    def dtype(self, dtype): ...
+    @property
+    def shape(self): ...
+    @shape.setter
+    def shape(self, shape): ...
+    def __setmask__(self, mask, copy=...): ...
+    @property
+    def mask(self): ...
+    @mask.setter
+    def mask(self, value): ...
+    @property
+    def recordmask(self): ...
+    @recordmask.setter
+    def recordmask(self, mask): ...
+    def harden_mask(self): ...
+    def soften_mask(self): ...
+    @property
+    def hardmask(self): ...
+    def unshare_mask(self): ...
+    @property
+    def sharedmask(self): ...
+    def shrink_mask(self): ...
+    @property
+    def baseclass(self): ...
+    data: Any
+    @property
+    def flat(self): ...
+    @flat.setter
+    def flat(self, value): ...
+    @property
+    def fill_value(self): ...
+    @fill_value.setter
+    def fill_value(self, value=...): ...
+    get_fill_value: Any
+    set_fill_value: Any
+    def filled(self, fill_value=...): ...
+    def compressed(self): ...
+    def compress(self, condition, axis=..., out=...): ...
+    def __eq__(self, other): ...
+    def __ne__(self, other): ...
+    def __ge__(self, other): ...
+    def __gt__(self, other): ...
+    def __le__(self, other): ...
+    def __lt__(self, other): ...
+    def __add__(self, other): ...
+    def __radd__(self, other): ...
+    def __sub__(self, other): ...
+    def __rsub__(self, other): ...
+    def __mul__(self, other): ...
+    def __rmul__(self, other): ...
+    def __div__(self, other): ...
+    def __truediv__(self, other): ...
+    def __rtruediv__(self, other): ...
+    def __floordiv__(self, other): ...
+    def __rfloordiv__(self, other): ...
+    def __pow__(self, other): ...
+    def __rpow__(self, other): ...
+    def __iadd__(self, other): ...
+    def __isub__(self, other): ...
+    def __imul__(self, other): ...
+    def __idiv__(self, other): ...
+    def __ifloordiv__(self, other): ...
+    def __itruediv__(self, other): ...
+    def __ipow__(self, other): ...
+    def __float__(self): ...
+    def __int__(self): ...
+    @property  # type: ignore[misc]
+    def imag(self): ...
+    get_imag: Any
+    @property  # type: ignore[misc]
+    def real(self): ...
+    get_real: Any
+    def count(self, axis=..., keepdims=...): ...
+    def ravel(self, order=...): ...
+    def reshape(self, *s, **kwargs): ...
+    def resize(self, newshape, refcheck=..., order=...): ...
+    def put(self, indices, values, mode=...): ...
+    def ids(self): ...
+    def iscontiguous(self): ...
+    def all(self, axis=..., out=..., keepdims=...): ...
+    def any(self, axis=..., out=..., keepdims=...): ...
+    def nonzero(self): ...
+    def trace(self, offset=..., axis1=..., axis2=..., dtype=..., out=...): ...
+    def dot(self, b, out=..., strict=...): ...
+    def sum(self, axis=..., dtype=..., out=..., keepdims=...): ...
+    def cumsum(self, axis=..., dtype=..., out=...): ...
+    def prod(self, axis=..., dtype=..., out=..., keepdims=...): ...
+    product: Any
+    def cumprod(self, axis=..., dtype=..., out=...): ...
+    def mean(self, axis=..., dtype=..., out=..., keepdims=...): ...
+    def anom(self, axis=..., dtype=...): ...
+    def var(self, axis=..., dtype=..., out=..., ddof=..., keepdims=...): ...
+    def std(self, axis=..., dtype=..., out=..., ddof=..., keepdims=...): ...
+    def round(self, decimals=..., out=...): ...
+    def argsort(self, axis=..., kind=..., order=..., endwith=..., fill_value=...): ...
+    def argmin(self, axis=..., fill_value=..., out=..., *, keepdims=...): ...
+    def argmax(self, axis=..., fill_value=..., out=..., *, keepdims=...): ...
+    def sort(self, axis=..., kind=..., order=..., endwith=..., fill_value=...): ...
+    def min(self, axis=..., out=..., fill_value=..., keepdims=...): ...
+    # NOTE: deprecated
+    # def tostring(self, fill_value=..., order=...): ...
+    def max(self, axis=..., out=..., fill_value=..., keepdims=...): ...
+    def ptp(self, axis=..., out=..., fill_value=..., keepdims=...): ...
+    def partition(self, *args, **kwargs): ...
+    def argpartition(self, *args, **kwargs): ...
+    def take(self, indices, axis=..., out=..., mode=...): ...
+    copy: Any
+    diagonal: Any
+    flatten: Any
+    repeat: Any
+    squeeze: Any
+    swapaxes: Any
+    T: Any
+    transpose: Any
+    def tolist(self, fill_value=...): ...
+    def tobytes(self, fill_value=..., order=...): ...
+    def tofile(self, fid, sep=..., format=...): ...
+    def toflex(self): ...
+    torecords: Any
+    def __reduce__(self): ...
+    def __deepcopy__(self, memo=...): ...
+
+class mvoid(MaskedArray[_ShapeType, _DType_co]):
+    def __new__(
+        self,
+        data,
+        mask=...,
+        dtype=...,
+        fill_value=...,
+        hardmask=...,
+        copy=...,
+        subok=...,
+    ): ...
+    def __getitem__(self, indx): ...
+    def __setitem__(self, indx, value): ...
+    def __iter__(self): ...
+    def __len__(self): ...
+    def filled(self, fill_value=...): ...
+    def tolist(self): ...
+
+def isMaskedArray(x): ...
+isarray = isMaskedArray
+isMA = isMaskedArray
+
+# 0D float64 array
+class MaskedConstant(MaskedArray[Any, dtype[float64]]):
+    def __new__(cls): ...
+    __class__: Any
+    def __array_finalize__(self, obj): ...
+    def __array_prepare__(self, obj, context=...): ...
+    def __array_wrap__(self, obj, context=...): ...
+    def __format__(self, format_spec): ...
+    def __reduce__(self): ...
+    def __iop__(self, other): ...
+    __iadd__: Any
+    __isub__: Any
+    __imul__: Any
+    __ifloordiv__: Any
+    __itruediv__: Any
+    __ipow__: Any
+    def copy(self, *args, **kwargs): ...
+    def __copy__(self): ...
+    def __deepcopy__(self, memo): ...
+    def __setattr__(self, attr, value): ...
+
+masked: MaskedConstant
+masked_singleton: MaskedConstant
+masked_array = MaskedArray
+
+def array(
+    data,
+    dtype=...,
+    copy=...,
+    order=...,
+    mask=...,
+    fill_value=...,
+    keep_mask=...,
+    hard_mask=...,
+    shrink=...,
+    subok=...,
+    ndmin=...,
+): ...
+def is_masked(x): ...
+
+class _extrema_operation(_MaskedUFunc):
+    compare: Any
+    fill_value_func: Any
+    def __init__(self, ufunc, compare, fill_value): ...
+    # NOTE: in practice `b` has a default value, but users should
+    # explicitly provide a value here as the default is deprecated
+    def __call__(self, a, b): ...
+    def reduce(self, target, axis=...): ...
+    def outer(self, a, b): ...
+
+def min(obj, axis=..., out=..., fill_value=..., keepdims=...): ...
+def max(obj, axis=..., out=..., fill_value=..., keepdims=...): ...
+def ptp(obj, axis=..., out=..., fill_value=..., keepdims=...): ...
+
+class _frommethod:
+    __name__: Any
+    __doc__: Any
+    reversed: Any
+    def __init__(self, methodname, reversed=...): ...
+    def getdoc(self): ...
+    def __call__(self, a, *args, **params): ...
+
+all: _frommethod
+anomalies: _frommethod
+anom: _frommethod
+any: _frommethod
+compress: _frommethod
+cumprod: _frommethod
+cumsum: _frommethod
+copy: _frommethod
+diagonal: _frommethod
+harden_mask: _frommethod
+ids: _frommethod
+mean: _frommethod
+nonzero: _frommethod
+prod: _frommethod
+product: _frommethod
+ravel: _frommethod
+repeat: _frommethod
+soften_mask: _frommethod
+std: _frommethod
+sum: _frommethod
+swapaxes: _frommethod
+trace: _frommethod
+var: _frommethod
+count: _frommethod
+argmin: _frommethod
+argmax: _frommethod
+
+minimum: _extrema_operation
+maximum: _extrema_operation
+
+def take(a, indices, axis=..., out=..., mode=...): ...
+def power(a, b, third=...): ...
+def argsort(a, axis=..., kind=..., order=..., endwith=..., fill_value=...): ...
+def sort(a, axis=..., kind=..., order=..., endwith=..., fill_value=...): ...
+def compressed(x): ...
+def concatenate(arrays, axis=...): ...
+def diag(v, k=...): ...
+def left_shift(a, n): ...
+def right_shift(a, n): ...
+def put(a, indices, values, mode=...): ...
+def putmask(a, mask, values): ...
+def transpose(a, axes=...): ...
+def reshape(a, new_shape, order=...): ...
+def resize(x, new_shape): ...
+def ndim(obj): ...
+def shape(obj): ...
+def size(obj, axis=...): ...
+def diff(a, /, n=..., axis=..., prepend=..., append=...): ...
+def where(condition, x=..., y=...): ...
+def choose(indices, choices, out=..., mode=...): ...
+def round(a, decimals=..., out=...): ...
+
+def inner(a, b): ...
+innerproduct = inner
+
+def outer(a, b): ...
+outerproduct = outer
+
+def correlate(a, v, mode=..., propagate_mask=...): ...
+def convolve(a, v, mode=..., propagate_mask=...): ...
+def allequal(a, b, fill_value=...): ...
+def allclose(a, b, masked_equal=..., rtol=..., atol=...): ...
+def asarray(a, dtype=..., order=...): ...
+def asanyarray(a, dtype=...): ...
+def fromflex(fxarray): ...
+
+class _convert2ma:
+    __doc__: Any
+    def __init__(self, funcname, params=...): ...
+    def getdoc(self): ...
+    def __call__(self, *args, **params): ...
+
+arange: _convert2ma
+empty: _convert2ma
+empty_like: _convert2ma
+frombuffer: _convert2ma
+fromfunction: _convert2ma
+identity: _convert2ma
+ones: _convert2ma
+zeros: _convert2ma
+
+def append(a, b, axis=...): ...
+def dot(a, b, strict=..., out=...): ...
+def mask_rowcols(a, axis=...): ...
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/extras.py b/.venv/lib/python3.12/site-packages/numpy/ma/extras.py
new file mode 100644
index 00000000..8a6246c3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/extras.py
@@ -0,0 +1,2133 @@
+"""
+Masked arrays add-ons.
+
+A collection of utilities for `numpy.ma`.
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: extras.py 3473 2007-10-29 15:18:13Z jarrod.millman $
+
+"""
+__all__ = [
+    'apply_along_axis', 'apply_over_axes', 'atleast_1d', 'atleast_2d',
+    'atleast_3d', 'average', 'clump_masked', 'clump_unmasked', 'column_stack',
+    'compress_cols', 'compress_nd', 'compress_rowcols', 'compress_rows',
+    'count_masked', 'corrcoef', 'cov', 'diagflat', 'dot', 'dstack', 'ediff1d',
+    'flatnotmasked_contiguous', 'flatnotmasked_edges', 'hsplit', 'hstack',
+    'isin', 'in1d', 'intersect1d', 'mask_cols', 'mask_rowcols', 'mask_rows',
+    'masked_all', 'masked_all_like', 'median', 'mr_', 'ndenumerate',
+    'notmasked_contiguous', 'notmasked_edges', 'polyfit', 'row_stack',
+    'setdiff1d', 'setxor1d', 'stack', 'unique', 'union1d', 'vander', 'vstack',
+    ]
+
+import itertools
+import warnings
+
+from . import core as ma
+from .core import (
+    MaskedArray, MAError, add, array, asarray, concatenate, filled, count,
+    getmask, getmaskarray, make_mask_descr, masked, masked_array, mask_or,
+    nomask, ones, sort, zeros, getdata, get_masked_subclass, dot
+    )
+
+import numpy as np
+from numpy import ndarray, array as nxarray
+from numpy.core.multiarray import normalize_axis_index
+from numpy.core.numeric import normalize_axis_tuple
+from numpy.lib.function_base import _ureduce
+from numpy.lib.index_tricks import AxisConcatenator
+
+
+def issequence(seq):
+    """
+    Is seq a sequence (ndarray, list or tuple)?
+
+    """
+    return isinstance(seq, (ndarray, tuple, list))
+
+
+def count_masked(arr, axis=None):
+    """
+    Count the number of masked elements along the given axis.
+
+    Parameters
+    ----------
+    arr : array_like
+        An array with (possibly) masked elements.
+    axis : int, optional
+        Axis along which to count. If None (default), a flattened
+        version of the array is used.
+
+    Returns
+    -------
+    count : int, ndarray
+        The total number of masked elements (axis=None) or the number
+        of masked elements along each slice of the given axis.
+
+    See Also
+    --------
+    MaskedArray.count : Count non-masked elements.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.arange(9).reshape((3,3))
+    >>> a = ma.array(a)
+    >>> a[1, 0] = ma.masked
+    >>> a[1, 2] = ma.masked
+    >>> a[2, 1] = ma.masked
+    >>> a
+    masked_array(
+      data=[[0, 1, 2],
+            [--, 4, --],
+            [6, --, 8]],
+      mask=[[False, False, False],
+            [ True, False,  True],
+            [False,  True, False]],
+      fill_value=999999)
+    >>> ma.count_masked(a)
+    3
+
+    When the `axis` keyword is used an array is returned.
+
+    >>> ma.count_masked(a, axis=0)
+    array([1, 1, 1])
+    >>> ma.count_masked(a, axis=1)
+    array([0, 2, 1])
+
+    """
+    m = getmaskarray(arr)
+    return m.sum(axis)
+
+
+def masked_all(shape, dtype=float):
+    """
+    Empty masked array with all elements masked.
+
+    Return an empty masked array of the given shape and dtype, where all the
+    data are masked.
+
+    Parameters
+    ----------
+    shape : int or tuple of ints
+        Shape of the required MaskedArray, e.g., ``(2, 3)`` or ``2``.
+    dtype : dtype, optional
+        Data type of the output.
+
+    Returns
+    -------
+    a : MaskedArray
+        A masked array with all data masked.
+
+    See Also
+    --------
+    masked_all_like : Empty masked array modelled on an existing array.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> ma.masked_all((3, 3))
+    masked_array(
+      data=[[--, --, --],
+            [--, --, --],
+            [--, --, --]],
+      mask=[[ True,  True,  True],
+            [ True,  True,  True],
+            [ True,  True,  True]],
+      fill_value=1e+20,
+      dtype=float64)
+
+    The `dtype` parameter defines the underlying data type.
+
+    >>> a = ma.masked_all((3, 3))
+    >>> a.dtype
+    dtype('float64')
+    >>> a = ma.masked_all((3, 3), dtype=np.int32)
+    >>> a.dtype
+    dtype('int32')
+
+    """
+    a = masked_array(np.empty(shape, dtype),
+                     mask=np.ones(shape, make_mask_descr(dtype)))
+    return a
+
+
+def masked_all_like(arr):
+    """
+    Empty masked array with the properties of an existing array.
+
+    Return an empty masked array of the same shape and dtype as
+    the array `arr`, where all the data are masked.
+
+    Parameters
+    ----------
+    arr : ndarray
+        An array describing the shape and dtype of the required MaskedArray.
+
+    Returns
+    -------
+    a : MaskedArray
+        A masked array with all data masked.
+
+    Raises
+    ------
+    AttributeError
+        If `arr` doesn't have a shape attribute (i.e. not an ndarray)
+
+    See Also
+    --------
+    masked_all : Empty masked array with all elements masked.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> arr = np.zeros((2, 3), dtype=np.float32)
+    >>> arr
+    array([[0., 0., 0.],
+           [0., 0., 0.]], dtype=float32)
+    >>> ma.masked_all_like(arr)
+    masked_array(
+      data=[[--, --, --],
+            [--, --, --]],
+      mask=[[ True,  True,  True],
+            [ True,  True,  True]],
+      fill_value=1e+20,
+      dtype=float32)
+
+    The dtype of the masked array matches the dtype of `arr`.
+
+    >>> arr.dtype
+    dtype('float32')
+    >>> ma.masked_all_like(arr).dtype
+    dtype('float32')
+
+    """
+    a = np.empty_like(arr).view(MaskedArray)
+    a._mask = np.ones(a.shape, dtype=make_mask_descr(a.dtype))
+    return a
+
+
+#####--------------------------------------------------------------------------
+#---- --- Standard functions ---
+#####--------------------------------------------------------------------------
+class _fromnxfunction:
+    """
+    Defines a wrapper to adapt NumPy functions to masked arrays.
+
+
+    An instance of `_fromnxfunction` can be called with the same parameters
+    as the wrapped NumPy function. The docstring of `newfunc` is adapted from
+    the wrapped function as well, see `getdoc`.
+
+    This class should not be used directly. Instead, one of its extensions that
+    provides support for a specific type of input should be used.
+
+    Parameters
+    ----------
+    funcname : str
+        The name of the function to be adapted. The function should be
+        in the NumPy namespace (i.e. ``np.funcname``).
+
+    """
+
+    def __init__(self, funcname):
+        self.__name__ = funcname
+        self.__doc__ = self.getdoc()
+
+    def getdoc(self):
+        """
+        Retrieve the docstring and signature from the function.
+
+        The ``__doc__`` attribute of the function is used as the docstring for
+        the new masked array version of the function. A note on application
+        of the function to the mask is appended.
+
+        Parameters
+        ----------
+        None
+
+        """
+        npfunc = getattr(np, self.__name__, None)
+        doc = getattr(npfunc, '__doc__', None)
+        if doc:
+            sig = self.__name__ + ma.get_object_signature(npfunc)
+            doc = ma.doc_note(doc, "The function is applied to both the _data "
+                                   "and the _mask, if any.")
+            return '\n\n'.join((sig, doc))
+        return
+
+    def __call__(self, *args, **params):
+        pass
+
+
+class _fromnxfunction_single(_fromnxfunction):
+    """
+    A version of `_fromnxfunction` that is called with a single array
+    argument followed by auxiliary args that are passed verbatim for
+    both the data and mask calls.
+    """
+    def __call__(self, x, *args, **params):
+        func = getattr(np, self.__name__)
+        if isinstance(x, ndarray):
+            _d = func(x.__array__(), *args, **params)
+            _m = func(getmaskarray(x), *args, **params)
+            return masked_array(_d, mask=_m)
+        else:
+            _d = func(np.asarray(x), *args, **params)
+            _m = func(getmaskarray(x), *args, **params)
+            return masked_array(_d, mask=_m)
+
+
+class _fromnxfunction_seq(_fromnxfunction):
+    """
+    A version of `_fromnxfunction` that is called with a single sequence
+    of arrays followed by auxiliary args that are passed verbatim for
+    both the data and mask calls.
+    """
+    def __call__(self, x, *args, **params):
+        func = getattr(np, self.__name__)
+        _d = func(tuple([np.asarray(a) for a in x]), *args, **params)
+        _m = func(tuple([getmaskarray(a) for a in x]), *args, **params)
+        return masked_array(_d, mask=_m)
+
+
+class _fromnxfunction_args(_fromnxfunction):
+    """
+    A version of `_fromnxfunction` that is called with multiple array
+    arguments. The first non-array-like input marks the beginning of the
+    arguments that are passed verbatim for both the data and mask calls.
+    Array arguments are processed independently and the results are
+    returned in a list. If only one array is found, the return value is
+    just the processed array instead of a list.
+    """
+    def __call__(self, *args, **params):
+        func = getattr(np, self.__name__)
+        arrays = []
+        args = list(args)
+        while len(args) > 0 and issequence(args[0]):
+            arrays.append(args.pop(0))
+        res = []
+        for x in arrays:
+            _d = func(np.asarray(x), *args, **params)
+            _m = func(getmaskarray(x), *args, **params)
+            res.append(masked_array(_d, mask=_m))
+        if len(arrays) == 1:
+            return res[0]
+        return res
+
+
+class _fromnxfunction_allargs(_fromnxfunction):
+    """
+    A version of `_fromnxfunction` that is called with multiple array
+    arguments. Similar to `_fromnxfunction_args` except that all args
+    are converted to arrays even if they are not so already. This makes
+    it possible to process scalars as 1-D arrays. Only keyword arguments
+    are passed through verbatim for the data and mask calls. Arrays
+    arguments are processed independently and the results are returned
+    in a list. If only one arg is present, the return value is just the
+    processed array instead of a list.
+    """
+    def __call__(self, *args, **params):
+        func = getattr(np, self.__name__)
+        res = []
+        for x in args:
+            _d = func(np.asarray(x), **params)
+            _m = func(getmaskarray(x), **params)
+            res.append(masked_array(_d, mask=_m))
+        if len(args) == 1:
+            return res[0]
+        return res
+
+
+atleast_1d = _fromnxfunction_allargs('atleast_1d')
+atleast_2d = _fromnxfunction_allargs('atleast_2d')
+atleast_3d = _fromnxfunction_allargs('atleast_3d')
+
+vstack = row_stack = _fromnxfunction_seq('vstack')
+hstack = _fromnxfunction_seq('hstack')
+column_stack = _fromnxfunction_seq('column_stack')
+dstack = _fromnxfunction_seq('dstack')
+stack = _fromnxfunction_seq('stack')
+
+hsplit = _fromnxfunction_single('hsplit')
+
+diagflat = _fromnxfunction_single('diagflat')
+
+
+#####--------------------------------------------------------------------------
+#----
+#####--------------------------------------------------------------------------
+def flatten_inplace(seq):
+    """Flatten a sequence in place."""
+    k = 0
+    while (k != len(seq)):
+        while hasattr(seq[k], '__iter__'):
+            seq[k:(k + 1)] = seq[k]
+        k += 1
+    return seq
+
+
+def apply_along_axis(func1d, axis, arr, *args, **kwargs):
+    """
+    (This docstring should be overwritten)
+    """
+    arr = array(arr, copy=False, subok=True)
+    nd = arr.ndim
+    axis = normalize_axis_index(axis, nd)
+    ind = [0] * (nd - 1)
+    i = np.zeros(nd, 'O')
+    indlist = list(range(nd))
+    indlist.remove(axis)
+    i[axis] = slice(None, None)
+    outshape = np.asarray(arr.shape).take(indlist)
+    i.put(indlist, ind)
+    res = func1d(arr[tuple(i.tolist())], *args, **kwargs)
+    #  if res is a number, then we have a smaller output array
+    asscalar = np.isscalar(res)
+    if not asscalar:
+        try:
+            len(res)
+        except TypeError:
+            asscalar = True
+    # Note: we shouldn't set the dtype of the output from the first result
+    # so we force the type to object, and build a list of dtypes.  We'll
+    # just take the largest, to avoid some downcasting
+    dtypes = []
+    if asscalar:
+        dtypes.append(np.asarray(res).dtype)
+        outarr = zeros(outshape, object)
+        outarr[tuple(ind)] = res
+        Ntot = np.prod(outshape)
+        k = 1
+        while k < Ntot:
+            # increment the index
+            ind[-1] += 1
+            n = -1
+            while (ind[n] >= outshape[n]) and (n > (1 - nd)):
+                ind[n - 1] += 1
+                ind[n] = 0
+                n -= 1
+            i.put(indlist, ind)
+            res = func1d(arr[tuple(i.tolist())], *args, **kwargs)
+            outarr[tuple(ind)] = res
+            dtypes.append(asarray(res).dtype)
+            k += 1
+    else:
+        res = array(res, copy=False, subok=True)
+        j = i.copy()
+        j[axis] = ([slice(None, None)] * res.ndim)
+        j.put(indlist, ind)
+        Ntot = np.prod(outshape)
+        holdshape = outshape
+        outshape = list(arr.shape)
+        outshape[axis] = res.shape
+        dtypes.append(asarray(res).dtype)
+        outshape = flatten_inplace(outshape)
+        outarr = zeros(outshape, object)
+        outarr[tuple(flatten_inplace(j.tolist()))] = res
+        k = 1
+        while k < Ntot:
+            # increment the index
+            ind[-1] += 1
+            n = -1
+            while (ind[n] >= holdshape[n]) and (n > (1 - nd)):
+                ind[n - 1] += 1
+                ind[n] = 0
+                n -= 1
+            i.put(indlist, ind)
+            j.put(indlist, ind)
+            res = func1d(arr[tuple(i.tolist())], *args, **kwargs)
+            outarr[tuple(flatten_inplace(j.tolist()))] = res
+            dtypes.append(asarray(res).dtype)
+            k += 1
+    max_dtypes = np.dtype(np.asarray(dtypes).max())
+    if not hasattr(arr, '_mask'):
+        result = np.asarray(outarr, dtype=max_dtypes)
+    else:
+        result = asarray(outarr, dtype=max_dtypes)
+        result.fill_value = ma.default_fill_value(result)
+    return result
+apply_along_axis.__doc__ = np.apply_along_axis.__doc__
+
+
+def apply_over_axes(func, a, axes):
+    """
+    (This docstring will be overwritten)
+    """
+    val = asarray(a)
+    N = a.ndim
+    if array(axes).ndim == 0:
+        axes = (axes,)
+    for axis in axes:
+        if axis < 0:
+            axis = N + axis
+        args = (val, axis)
+        res = func(*args)
+        if res.ndim == val.ndim:
+            val = res
+        else:
+            res = ma.expand_dims(res, axis)
+            if res.ndim == val.ndim:
+                val = res
+            else:
+                raise ValueError("function is not returning "
+                        "an array of the correct shape")
+    return val
+
+
+if apply_over_axes.__doc__ is not None:
+    apply_over_axes.__doc__ = np.apply_over_axes.__doc__[
+        :np.apply_over_axes.__doc__.find('Notes')].rstrip() + \
+    """
+
+    Examples
+    --------
+    >>> a = np.ma.arange(24).reshape(2,3,4)
+    >>> a[:,0,1] = np.ma.masked
+    >>> a[:,1,:] = np.ma.masked
+    >>> a
+    masked_array(
+      data=[[[0, --, 2, 3],
+             [--, --, --, --],
+             [8, 9, 10, 11]],
+            [[12, --, 14, 15],
+             [--, --, --, --],
+             [20, 21, 22, 23]]],
+      mask=[[[False,  True, False, False],
+             [ True,  True,  True,  True],
+             [False, False, False, False]],
+            [[False,  True, False, False],
+             [ True,  True,  True,  True],
+             [False, False, False, False]]],
+      fill_value=999999)
+    >>> np.ma.apply_over_axes(np.ma.sum, a, [0,2])
+    masked_array(
+      data=[[[46],
+             [--],
+             [124]]],
+      mask=[[[False],
+             [ True],
+             [False]]],
+      fill_value=999999)
+
+    Tuple axis arguments to ufuncs are equivalent:
+
+    >>> np.ma.sum(a, axis=(0,2)).reshape((1,-1,1))
+    masked_array(
+      data=[[[46],
+             [--],
+             [124]]],
+      mask=[[[False],
+             [ True],
+             [False]]],
+      fill_value=999999)
+    """
+
+
+def average(a, axis=None, weights=None, returned=False, *,
+            keepdims=np._NoValue):
+    """
+    Return the weighted average of array over the given axis.
+
+    Parameters
+    ----------
+    a : array_like
+        Data to be averaged.
+        Masked entries are not taken into account in the computation.
+    axis : int, optional
+        Axis along which to average `a`. If None, averaging is done over
+        the flattened array.
+    weights : array_like, optional
+        The importance that each element has in the computation of the average.
+        The weights array can either be 1-D (in which case its length must be
+        the size of `a` along the given axis) or of the same shape as `a`.
+        If ``weights=None``, then all data in `a` are assumed to have a
+        weight equal to one.  The 1-D calculation is::
+
+            avg = sum(a * weights) / sum(weights)
+
+        The only constraint on `weights` is that `sum(weights)` must not be 0.
+    returned : bool, optional
+        Flag indicating whether a tuple ``(result, sum of weights)``
+        should be returned as output (True), or just the result (False).
+        Default is False.
+    keepdims : bool, optional
+        If this is set to 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 `a`.
+        *Note:* `keepdims` will not work with instances of `numpy.matrix`
+        or other classes whose methods do not support `keepdims`.
+
+        .. versionadded:: 1.23.0
+
+    Returns
+    -------
+    average, [sum_of_weights] : (tuple of) scalar or MaskedArray
+        The average along the specified axis. When returned is `True`,
+        return a tuple with the average as the first element and the sum
+        of the weights as the second element. The return type is `np.float64`
+        if `a` is of integer type and floats smaller than `float64`, or the
+        input data-type, otherwise. If returned, `sum_of_weights` is always
+        `float64`.
+
+    Examples
+    --------
+    >>> a = np.ma.array([1., 2., 3., 4.], mask=[False, False, True, True])
+    >>> np.ma.average(a, weights=[3, 1, 0, 0])
+    1.25
+
+    >>> x = np.ma.arange(6.).reshape(3, 2)
+    >>> x
+    masked_array(
+      data=[[0., 1.],
+            [2., 3.],
+            [4., 5.]],
+      mask=False,
+      fill_value=1e+20)
+    >>> avg, sumweights = np.ma.average(x, axis=0, weights=[1, 2, 3],
+    ...                                 returned=True)
+    >>> avg
+    masked_array(data=[2.6666666666666665, 3.6666666666666665],
+                 mask=[False, False],
+           fill_value=1e+20)
+
+    With ``keepdims=True``, the following result has shape (3, 1).
+
+    >>> np.ma.average(x, axis=1, keepdims=True)
+    masked_array(
+      data=[[0.5],
+            [2.5],
+            [4.5]],
+      mask=False,
+      fill_value=1e+20)
+    """
+    a = asarray(a)
+    m = getmask(a)
+
+    # inspired by 'average' in numpy/lib/function_base.py
+
+    if keepdims is np._NoValue:
+        # Don't pass on the keepdims argument if one wasn't given.
+        keepdims_kw = {}
+    else:
+        keepdims_kw = {'keepdims': keepdims}
+
+    if weights is None:
+        avg = a.mean(axis, **keepdims_kw)
+        scl = avg.dtype.type(a.count(axis))
+    else:
+        wgt = asarray(weights)
+
+        if issubclass(a.dtype.type, (np.integer, np.bool_)):
+            result_dtype = np.result_type(a.dtype, wgt.dtype, 'f8')
+        else:
+            result_dtype = np.result_type(a.dtype, wgt.dtype)
+
+        # Sanity checks
+        if a.shape != wgt.shape:
+            if axis is None:
+                raise TypeError(
+                    "Axis must be specified when shapes of a and weights "
+                    "differ.")
+            if wgt.ndim != 1:
+                raise TypeError(
+                    "1D weights expected when shapes of a and weights differ.")
+            if wgt.shape[0] != a.shape[axis]:
+                raise ValueError(
+                    "Length of weights not compatible with specified axis.")
+
+            # setup wgt to broadcast along axis
+            wgt = np.broadcast_to(wgt, (a.ndim-1)*(1,) + wgt.shape, subok=True)
+            wgt = wgt.swapaxes(-1, axis)
+
+        if m is not nomask:
+            wgt = wgt*(~a.mask)
+            wgt.mask |= a.mask
+
+        scl = wgt.sum(axis=axis, dtype=result_dtype, **keepdims_kw)
+        avg = np.multiply(a, wgt,
+                          dtype=result_dtype).sum(axis, **keepdims_kw) / scl
+
+    if returned:
+        if scl.shape != avg.shape:
+            scl = np.broadcast_to(scl, avg.shape).copy()
+        return avg, scl
+    else:
+        return avg
+
+
+def median(a, axis=None, out=None, overwrite_input=False, keepdims=False):
+    """
+    Compute the median along the specified axis.
+
+    Returns the median of the array elements.
+
+    Parameters
+    ----------
+    a : array_like
+        Input array or object that can be converted to an array.
+    axis : int, optional
+        Axis along which the medians are computed. The default (None) is
+        to compute the median along a flattened version of the array.
+    out : ndarray, optional
+        Alternative output array in which to place the result. It must
+        have the same shape and buffer length as the expected output
+        but the type will be cast if necessary.
+    overwrite_input : bool, optional
+        If True, then allow use of memory of input array (a) for
+        calculations. The input array will be modified by the call to
+        median. This will save memory when you do not need to preserve
+        the contents of the input array. Treat the input as undefined,
+        but it will probably be fully or partially sorted. Default is
+        False. Note that, if `overwrite_input` is True, and the input
+        is not already an `ndarray`, an error will be raised.
+    keepdims : bool, optional
+        If this is set to 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 input array.
+
+        .. versionadded:: 1.10.0
+
+    Returns
+    -------
+    median : ndarray
+        A new array holding the result is returned unless out is
+        specified, in which case a reference to out is returned.
+        Return data-type is `float64` for integers and floats smaller than
+        `float64`, or the input data-type, otherwise.
+
+    See Also
+    --------
+    mean
+
+    Notes
+    -----
+    Given a vector ``V`` with ``N`` non masked values, the median of ``V``
+    is the middle value of a sorted copy of ``V`` (``Vs``) - i.e.
+    ``Vs[(N-1)/2]``, when ``N`` is odd, or ``{Vs[N/2 - 1] + Vs[N/2]}/2``
+    when ``N`` is even.
+
+    Examples
+    --------
+    >>> x = np.ma.array(np.arange(8), mask=[0]*4 + [1]*4)
+    >>> np.ma.median(x)
+    1.5
+
+    >>> x = np.ma.array(np.arange(10).reshape(2, 5), mask=[0]*6 + [1]*4)
+    >>> np.ma.median(x)
+    2.5
+    >>> np.ma.median(x, axis=-1, overwrite_input=True)
+    masked_array(data=[2.0, 5.0],
+                 mask=[False, False],
+           fill_value=1e+20)
+
+    """
+    if not hasattr(a, 'mask'):
+        m = np.median(getdata(a, subok=True), axis=axis,
+                      out=out, overwrite_input=overwrite_input,
+                      keepdims=keepdims)
+        if isinstance(m, np.ndarray) and 1 <= m.ndim:
+            return masked_array(m, copy=False)
+        else:
+            return m
+
+    return _ureduce(a, func=_median, keepdims=keepdims, axis=axis, out=out,
+                    overwrite_input=overwrite_input)
+
+
+def _median(a, axis=None, out=None, overwrite_input=False):
+    # when an unmasked NaN is present return it, so we need to sort the NaN
+    # values behind the mask
+    if np.issubdtype(a.dtype, np.inexact):
+        fill_value = np.inf
+    else:
+        fill_value = None
+    if overwrite_input:
+        if axis is None:
+            asorted = a.ravel()
+            asorted.sort(fill_value=fill_value)
+        else:
+            a.sort(axis=axis, fill_value=fill_value)
+            asorted = a
+    else:
+        asorted = sort(a, axis=axis, fill_value=fill_value)
+
+    if axis is None:
+        axis = 0
+    else:
+        axis = normalize_axis_index(axis, asorted.ndim)
+
+    if asorted.shape[axis] == 0:
+        # for empty axis integer indices fail so use slicing to get same result
+        # as median (which is mean of empty slice = nan)
+        indexer = [slice(None)] * asorted.ndim
+        indexer[axis] = slice(0, 0)
+        indexer = tuple(indexer)
+        return np.ma.mean(asorted[indexer], axis=axis, out=out)
+
+    if asorted.ndim == 1:
+        idx, odd = divmod(count(asorted), 2)
+        mid = asorted[idx + odd - 1:idx + 1]
+        if np.issubdtype(asorted.dtype, np.inexact) and asorted.size > 0:
+            # avoid inf / x = masked
+            s = mid.sum(out=out)
+            if not odd:
+                s = np.true_divide(s, 2., casting='safe', out=out)
+            s = np.lib.utils._median_nancheck(asorted, s, axis)
+        else:
+            s = mid.mean(out=out)
+
+        # if result is masked either the input contained enough
+        # minimum_fill_value so that it would be the median or all values
+        # masked
+        if np.ma.is_masked(s) and not np.all(asorted.mask):
+            return np.ma.minimum_fill_value(asorted)
+        return s
+
+    counts = count(asorted, axis=axis, keepdims=True)
+    h = counts // 2
+
+    # duplicate high if odd number of elements so mean does nothing
+    odd = counts % 2 == 1
+    l = np.where(odd, h, h-1)
+
+    lh = np.concatenate([l,h], axis=axis)
+
+    # get low and high median
+    low_high = np.take_along_axis(asorted, lh, axis=axis)
+
+    def replace_masked(s):
+        # Replace masked entries with minimum_full_value unless it all values
+        # are masked. This is required as the sort order of values equal or
+        # larger than the fill value is undefined and a valid value placed
+        # elsewhere, e.g. [4, --, inf].
+        if np.ma.is_masked(s):
+            rep = (~np.all(asorted.mask, axis=axis, keepdims=True)) & s.mask
+            s.data[rep] = np.ma.minimum_fill_value(asorted)
+            s.mask[rep] = False
+
+    replace_masked(low_high)
+
+    if np.issubdtype(asorted.dtype, np.inexact):
+        # avoid inf / x = masked
+        s = np.ma.sum(low_high, axis=axis, out=out)
+        np.true_divide(s.data, 2., casting='unsafe', out=s.data)
+
+        s = np.lib.utils._median_nancheck(asorted, s, axis)
+    else:
+        s = np.ma.mean(low_high, axis=axis, out=out)
+
+    return s
+
+
+def compress_nd(x, axis=None):
+    """Suppress slices from multiple dimensions which contain masked values.
+
+    Parameters
+    ----------
+    x : array_like, MaskedArray
+        The array to operate on. If not a MaskedArray instance (or if no array
+        elements are masked), `x` is interpreted as a MaskedArray with `mask`
+        set to `nomask`.
+    axis : tuple of ints or int, optional
+        Which dimensions to suppress slices from can be configured with this
+        parameter.
+        - If axis is a tuple of ints, those are the axes to suppress slices from.
+        - If axis is an int, then that is the only axis to suppress slices from.
+        - If axis is None, all axis are selected.
+
+    Returns
+    -------
+    compress_array : ndarray
+        The compressed array.
+    """
+    x = asarray(x)
+    m = getmask(x)
+    # Set axis to tuple of ints
+    if axis is None:
+        axis = tuple(range(x.ndim))
+    else:
+        axis = normalize_axis_tuple(axis, x.ndim)
+
+    # Nothing is masked: return x
+    if m is nomask or not m.any():
+        return x._data
+    # All is masked: return empty
+    if m.all():
+        return nxarray([])
+    # Filter elements through boolean indexing
+    data = x._data
+    for ax in axis:
+        axes = tuple(list(range(ax)) + list(range(ax + 1, x.ndim)))
+        data = data[(slice(None),)*ax + (~m.any(axis=axes),)]
+    return data
+
+
+def compress_rowcols(x, axis=None):
+    """
+    Suppress the rows and/or columns of a 2-D array that contain
+    masked values.
+
+    The suppression behavior is selected with the `axis` parameter.
+
+    - If axis is None, both rows and columns are suppressed.
+    - If axis is 0, only rows are suppressed.
+    - If axis is 1 or -1, only columns are suppressed.
+
+    Parameters
+    ----------
+    x : array_like, MaskedArray
+        The array to operate on.  If not a MaskedArray instance (or if no array
+        elements are masked), `x` is interpreted as a MaskedArray with
+        `mask` set to `nomask`. Must be a 2D array.
+    axis : int, optional
+        Axis along which to perform the operation. Default is None.
+
+    Returns
+    -------
+    compressed_array : ndarray
+        The compressed array.
+
+    Examples
+    --------
+    >>> x = np.ma.array(np.arange(9).reshape(3, 3), mask=[[1, 0, 0],
+    ...                                                   [1, 0, 0],
+    ...                                                   [0, 0, 0]])
+    >>> x
+    masked_array(
+      data=[[--, 1, 2],
+            [--, 4, 5],
+            [6, 7, 8]],
+      mask=[[ True, False, False],
+            [ True, False, False],
+            [False, False, False]],
+      fill_value=999999)
+
+    >>> np.ma.compress_rowcols(x)
+    array([[7, 8]])
+    >>> np.ma.compress_rowcols(x, 0)
+    array([[6, 7, 8]])
+    >>> np.ma.compress_rowcols(x, 1)
+    array([[1, 2],
+           [4, 5],
+           [7, 8]])
+
+    """
+    if asarray(x).ndim != 2:
+        raise NotImplementedError("compress_rowcols works for 2D arrays only.")
+    return compress_nd(x, axis=axis)
+
+
+def compress_rows(a):
+    """
+    Suppress whole rows of a 2-D array that contain masked values.
+
+    This is equivalent to ``np.ma.compress_rowcols(a, 0)``, see
+    `compress_rowcols` for details.
+
+    See Also
+    --------
+    compress_rowcols
+
+    """
+    a = asarray(a)
+    if a.ndim != 2:
+        raise NotImplementedError("compress_rows works for 2D arrays only.")
+    return compress_rowcols(a, 0)
+
+
+def compress_cols(a):
+    """
+    Suppress whole columns of a 2-D array that contain masked values.
+
+    This is equivalent to ``np.ma.compress_rowcols(a, 1)``, see
+    `compress_rowcols` for details.
+
+    See Also
+    --------
+    compress_rowcols
+
+    """
+    a = asarray(a)
+    if a.ndim != 2:
+        raise NotImplementedError("compress_cols works for 2D arrays only.")
+    return compress_rowcols(a, 1)
+
+
+def mask_rowcols(a, axis=None):
+    """
+    Mask rows and/or columns of a 2D array that contain masked values.
+
+    Mask whole rows and/or columns of a 2D array that contain
+    masked values.  The masking behavior is selected using the
+    `axis` parameter.
+
+      - If `axis` is None, rows *and* columns are masked.
+      - If `axis` is 0, only rows are masked.
+      - If `axis` is 1 or -1, only columns are masked.
+
+    Parameters
+    ----------
+    a : array_like, MaskedArray
+        The array to mask.  If not a MaskedArray instance (or if no array
+        elements are masked), the result is a MaskedArray with `mask` set
+        to `nomask` (False). Must be a 2D array.
+    axis : int, optional
+        Axis along which to perform the operation. If None, applies to a
+        flattened version of the array.
+
+    Returns
+    -------
+    a : MaskedArray
+        A modified version of the input array, masked depending on the value
+        of the `axis` parameter.
+
+    Raises
+    ------
+    NotImplementedError
+        If input array `a` is not 2D.
+
+    See Also
+    --------
+    mask_rows : Mask rows of a 2D array that contain masked values.
+    mask_cols : Mask cols of a 2D array that contain masked values.
+    masked_where : Mask where a condition is met.
+
+    Notes
+    -----
+    The input array's mask is modified by this function.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.zeros((3, 3), dtype=int)
+    >>> a[1, 1] = 1
+    >>> a
+    array([[0, 0, 0],
+           [0, 1, 0],
+           [0, 0, 0]])
+    >>> a = ma.masked_equal(a, 1)
+    >>> a
+    masked_array(
+      data=[[0, 0, 0],
+            [0, --, 0],
+            [0, 0, 0]],
+      mask=[[False, False, False],
+            [False,  True, False],
+            [False, False, False]],
+      fill_value=1)
+    >>> ma.mask_rowcols(a)
+    masked_array(
+      data=[[0, --, 0],
+            [--, --, --],
+            [0, --, 0]],
+      mask=[[False,  True, False],
+            [ True,  True,  True],
+            [False,  True, False]],
+      fill_value=1)
+
+    """
+    a = array(a, subok=False)
+    if a.ndim != 2:
+        raise NotImplementedError("mask_rowcols works for 2D arrays only.")
+    m = getmask(a)
+    # Nothing is masked: return a
+    if m is nomask or not m.any():
+        return a
+    maskedval = m.nonzero()
+    a._mask = a._mask.copy()
+    if not axis:
+        a[np.unique(maskedval[0])] = masked
+    if axis in [None, 1, -1]:
+        a[:, np.unique(maskedval[1])] = masked
+    return a
+
+
+def mask_rows(a, axis=np._NoValue):
+    """
+    Mask rows of a 2D array that contain masked values.
+
+    This function is a shortcut to ``mask_rowcols`` with `axis` equal to 0.
+
+    See Also
+    --------
+    mask_rowcols : Mask rows and/or columns of a 2D array.
+    masked_where : Mask where a condition is met.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.zeros((3, 3), dtype=int)
+    >>> a[1, 1] = 1
+    >>> a
+    array([[0, 0, 0],
+           [0, 1, 0],
+           [0, 0, 0]])
+    >>> a = ma.masked_equal(a, 1)
+    >>> a
+    masked_array(
+      data=[[0, 0, 0],
+            [0, --, 0],
+            [0, 0, 0]],
+      mask=[[False, False, False],
+            [False,  True, False],
+            [False, False, False]],
+      fill_value=1)
+
+    >>> ma.mask_rows(a)
+    masked_array(
+      data=[[0, 0, 0],
+            [--, --, --],
+            [0, 0, 0]],
+      mask=[[False, False, False],
+            [ True,  True,  True],
+            [False, False, False]],
+      fill_value=1)
+
+    """
+    if axis is not np._NoValue:
+        # remove the axis argument when this deprecation expires
+        # NumPy 1.18.0, 2019-11-28
+        warnings.warn(
+            "The axis argument has always been ignored, in future passing it "
+            "will raise TypeError", DeprecationWarning, stacklevel=2)
+    return mask_rowcols(a, 0)
+
+
+def mask_cols(a, axis=np._NoValue):
+    """
+    Mask columns of a 2D array that contain masked values.
+
+    This function is a shortcut to ``mask_rowcols`` with `axis` equal to 1.
+
+    See Also
+    --------
+    mask_rowcols : Mask rows and/or columns of a 2D array.
+    masked_where : Mask where a condition is met.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = np.zeros((3, 3), dtype=int)
+    >>> a[1, 1] = 1
+    >>> a
+    array([[0, 0, 0],
+           [0, 1, 0],
+           [0, 0, 0]])
+    >>> a = ma.masked_equal(a, 1)
+    >>> a
+    masked_array(
+      data=[[0, 0, 0],
+            [0, --, 0],
+            [0, 0, 0]],
+      mask=[[False, False, False],
+            [False,  True, False],
+            [False, False, False]],
+      fill_value=1)
+    >>> ma.mask_cols(a)
+    masked_array(
+      data=[[0, --, 0],
+            [0, --, 0],
+            [0, --, 0]],
+      mask=[[False,  True, False],
+            [False,  True, False],
+            [False,  True, False]],
+      fill_value=1)
+
+    """
+    if axis is not np._NoValue:
+        # remove the axis argument when this deprecation expires
+        # NumPy 1.18.0, 2019-11-28
+        warnings.warn(
+            "The axis argument has always been ignored, in future passing it "
+            "will raise TypeError", DeprecationWarning, stacklevel=2)
+    return mask_rowcols(a, 1)
+
+
+#####--------------------------------------------------------------------------
+#---- --- arraysetops ---
+#####--------------------------------------------------------------------------
+
+def ediff1d(arr, to_end=None, to_begin=None):
+    """
+    Compute the differences between consecutive elements of an array.
+
+    This function is the equivalent of `numpy.ediff1d` that takes masked
+    values into account, see `numpy.ediff1d` for details.
+
+    See Also
+    --------
+    numpy.ediff1d : Equivalent function for ndarrays.
+
+    """
+    arr = ma.asanyarray(arr).flat
+    ed = arr[1:] - arr[:-1]
+    arrays = [ed]
+    #
+    if to_begin is not None:
+        arrays.insert(0, to_begin)
+    if to_end is not None:
+        arrays.append(to_end)
+    #
+    if len(arrays) != 1:
+        # We'll save ourselves a copy of a potentially large array in the common
+        # case where neither to_begin or to_end was given.
+        ed = hstack(arrays)
+    #
+    return ed
+
+
+def unique(ar1, return_index=False, return_inverse=False):
+    """
+    Finds the unique elements of an array.
+
+    Masked values are considered the same element (masked). The output array
+    is always a masked array. See `numpy.unique` for more details.
+
+    See Also
+    --------
+    numpy.unique : Equivalent function for ndarrays.
+
+    Examples
+    --------
+    >>> import numpy.ma as ma
+    >>> a = [1, 2, 1000, 2, 3]
+    >>> mask = [0, 0, 1, 0, 0]
+    >>> masked_a = ma.masked_array(a, mask)
+    >>> masked_a
+    masked_array(data=[1, 2, --, 2, 3],
+                mask=[False, False,  True, False, False],
+        fill_value=999999)
+    >>> ma.unique(masked_a)
+    masked_array(data=[1, 2, 3, --],
+                mask=[False, False, False,  True],
+        fill_value=999999)
+    >>> ma.unique(masked_a, return_index=True)
+    (masked_array(data=[1, 2, 3, --],
+                mask=[False, False, False,  True],
+        fill_value=999999), array([0, 1, 4, 2]))
+    >>> ma.unique(masked_a, return_inverse=True)
+    (masked_array(data=[1, 2, 3, --],
+                mask=[False, False, False,  True],
+        fill_value=999999), array([0, 1, 3, 1, 2]))
+    >>> ma.unique(masked_a, return_index=True, return_inverse=True)
+    (masked_array(data=[1, 2, 3, --],
+                mask=[False, False, False,  True],
+        fill_value=999999), array([0, 1, 4, 2]), array([0, 1, 3, 1, 2]))
+    """
+    output = np.unique(ar1,
+                       return_index=return_index,
+                       return_inverse=return_inverse)
+    if isinstance(output, tuple):
+        output = list(output)
+        output[0] = output[0].view(MaskedArray)
+        output = tuple(output)
+    else:
+        output = output.view(MaskedArray)
+    return output
+
+
+def intersect1d(ar1, ar2, assume_unique=False):
+    """
+    Returns the unique elements common to both arrays.
+
+    Masked values are considered equal one to the other.
+    The output is always a masked array.
+
+    See `numpy.intersect1d` for more details.
+
+    See Also
+    --------
+    numpy.intersect1d : Equivalent function for ndarrays.
+
+    Examples
+    --------
+    >>> x = np.ma.array([1, 3, 3, 3], mask=[0, 0, 0, 1])
+    >>> y = np.ma.array([3, 1, 1, 1], mask=[0, 0, 0, 1])
+    >>> np.ma.intersect1d(x, y)
+    masked_array(data=[1, 3, --],
+                 mask=[False, False,  True],
+           fill_value=999999)
+
+    """
+    if assume_unique:
+        aux = ma.concatenate((ar1, ar2))
+    else:
+        # Might be faster than unique( intersect1d( ar1, ar2 ) )?
+        aux = ma.concatenate((unique(ar1), unique(ar2)))
+    aux.sort()
+    return aux[:-1][aux[1:] == aux[:-1]]
+
+
+def setxor1d(ar1, ar2, assume_unique=False):
+    """
+    Set exclusive-or of 1-D arrays with unique elements.
+
+    The output is always a masked array. See `numpy.setxor1d` for more details.
+
+    See Also
+    --------
+    numpy.setxor1d : Equivalent function for ndarrays.
+
+    """
+    if not assume_unique:
+        ar1 = unique(ar1)
+        ar2 = unique(ar2)
+
+    aux = ma.concatenate((ar1, ar2))
+    if aux.size == 0:
+        return aux
+    aux.sort()
+    auxf = aux.filled()
+#    flag = ediff1d( aux, to_end = 1, to_begin = 1 ) == 0
+    flag = ma.concatenate(([True], (auxf[1:] != auxf[:-1]), [True]))
+#    flag2 = ediff1d( flag ) == 0
+    flag2 = (flag[1:] == flag[:-1])
+    return aux[flag2]
+
+
+def in1d(ar1, ar2, assume_unique=False, invert=False):
+    """
+    Test whether each element of an array is also present in a second
+    array.
+
+    The output is always a masked array. See `numpy.in1d` for more details.
+
+    We recommend using :func:`isin` instead of `in1d` for new code.
+
+    See Also
+    --------
+    isin       : Version of this function that preserves the shape of ar1.
+    numpy.in1d : Equivalent function for ndarrays.
+
+    Notes
+    -----
+    .. versionadded:: 1.4.0
+
+    """
+    if not assume_unique:
+        ar1, rev_idx = unique(ar1, return_inverse=True)
+        ar2 = unique(ar2)
+
+    ar = ma.concatenate((ar1, ar2))
+    # We need this to be a stable sort, so always use 'mergesort'
+    # here. The values from the first array should always come before
+    # the values from the second array.
+    order = ar.argsort(kind='mergesort')
+    sar = ar[order]
+    if invert:
+        bool_ar = (sar[1:] != sar[:-1])
+    else:
+        bool_ar = (sar[1:] == sar[:-1])
+    flag = ma.concatenate((bool_ar, [invert]))
+    indx = order.argsort(kind='mergesort')[:len(ar1)]
+
+    if assume_unique:
+        return flag[indx]
+    else:
+        return flag[indx][rev_idx]
+
+
+def isin(element, test_elements, assume_unique=False, invert=False):
+    """
+    Calculates `element in test_elements`, broadcasting over
+    `element` only.
+
+    The output is always a masked array of the same shape as `element`.
+    See `numpy.isin` for more details.
+
+    See Also
+    --------
+    in1d       : Flattened version of this function.
+    numpy.isin : Equivalent function for ndarrays.
+
+    Notes
+    -----
+    .. versionadded:: 1.13.0
+
+    """
+    element = ma.asarray(element)
+    return in1d(element, test_elements, assume_unique=assume_unique,
+                invert=invert).reshape(element.shape)
+
+
+def union1d(ar1, ar2):
+    """
+    Union of two arrays.
+
+    The output is always a masked array. See `numpy.union1d` for more details.
+
+    See Also
+    --------
+    numpy.union1d : Equivalent function for ndarrays.
+
+    """
+    return unique(ma.concatenate((ar1, ar2), axis=None))
+
+
+def setdiff1d(ar1, ar2, assume_unique=False):
+    """
+    Set difference of 1D arrays with unique elements.
+
+    The output is always a masked array. See `numpy.setdiff1d` for more
+    details.
+
+    See Also
+    --------
+    numpy.setdiff1d : Equivalent function for ndarrays.
+
+    Examples
+    --------
+    >>> x = np.ma.array([1, 2, 3, 4], mask=[0, 1, 0, 1])
+    >>> np.ma.setdiff1d(x, [1, 2])
+    masked_array(data=[3, --],
+                 mask=[False,  True],
+           fill_value=999999)
+
+    """
+    if assume_unique:
+        ar1 = ma.asarray(ar1).ravel()
+    else:
+        ar1 = unique(ar1)
+        ar2 = unique(ar2)
+    return ar1[in1d(ar1, ar2, assume_unique=True, invert=True)]
+
+
+###############################################################################
+#                                Covariance                                   #
+###############################################################################
+
+
+def _covhelper(x, y=None, rowvar=True, allow_masked=True):
+    """
+    Private function for the computation of covariance and correlation
+    coefficients.
+
+    """
+    x = ma.array(x, ndmin=2, copy=True, dtype=float)
+    xmask = ma.getmaskarray(x)
+    # Quick exit if we can't process masked data
+    if not allow_masked and xmask.any():
+        raise ValueError("Cannot process masked data.")
+    #
+    if x.shape[0] == 1:
+        rowvar = True
+    # Make sure that rowvar is either 0 or 1
+    rowvar = int(bool(rowvar))
+    axis = 1 - rowvar
+    if rowvar:
+        tup = (slice(None), None)
+    else:
+        tup = (None, slice(None))
+    #
+    if y is None:
+        xnotmask = np.logical_not(xmask).astype(int)
+    else:
+        y = array(y, copy=False, ndmin=2, dtype=float)
+        ymask = ma.getmaskarray(y)
+        if not allow_masked and ymask.any():
+            raise ValueError("Cannot process masked data.")
+        if xmask.any() or ymask.any():
+            if y.shape == x.shape:
+                # Define some common mask
+                common_mask = np.logical_or(xmask, ymask)
+                if common_mask is not nomask:
+                    xmask = x._mask = y._mask = ymask = common_mask
+                    x._sharedmask = False
+                    y._sharedmask = False
+        x = ma.concatenate((x, y), axis)
+        xnotmask = np.logical_not(np.concatenate((xmask, ymask), axis)).astype(int)
+    x -= x.mean(axis=rowvar)[tup]
+    return (x, xnotmask, rowvar)
+
+
+def cov(x, y=None, rowvar=True, bias=False, allow_masked=True, ddof=None):
+    """
+    Estimate the covariance matrix.
+
+    Except for the handling of missing data this function does the same as
+    `numpy.cov`. For more details and examples, see `numpy.cov`.
+
+    By default, masked values are recognized as such. If `x` and `y` have the
+    same shape, a common mask is allocated: if ``x[i,j]`` is masked, then
+    ``y[i,j]`` will also be masked.
+    Setting `allow_masked` to False will raise an exception if values are
+    missing in either of the input arrays.
+
+    Parameters
+    ----------
+    x : array_like
+        A 1-D or 2-D array containing multiple variables and observations.
+        Each row of `x` represents a variable, and each column a single
+        observation of all those variables. Also see `rowvar` below.
+    y : array_like, optional
+        An additional set of variables and observations. `y` has the same
+        shape as `x`.
+    rowvar : bool, optional
+        If `rowvar` is True (default), then each row represents a
+        variable, with observations in the columns. Otherwise, the relationship
+        is transposed: each column represents a variable, while the rows
+        contain observations.
+    bias : bool, optional
+        Default normalization (False) is by ``(N-1)``, where ``N`` is the
+        number of observations given (unbiased estimate). If `bias` is True,
+        then normalization is by ``N``. This keyword can be overridden by
+        the keyword ``ddof`` in numpy versions >= 1.5.
+    allow_masked : bool, optional
+        If True, masked values are propagated pair-wise: if a value is masked
+        in `x`, the corresponding value is masked in `y`.
+        If False, raises a `ValueError` exception when some values are missing.
+    ddof : {None, int}, optional
+        If not ``None`` normalization is by ``(N - ddof)``, where ``N`` is
+        the number of observations; this overrides the value implied by
+        ``bias``. The default value is ``None``.
+
+        .. versionadded:: 1.5
+
+    Raises
+    ------
+    ValueError
+        Raised if some values are missing and `allow_masked` is False.
+
+    See Also
+    --------
+    numpy.cov
+
+    """
+    # Check inputs
+    if ddof is not None and ddof != int(ddof):
+        raise ValueError("ddof must be an integer")
+    # Set up ddof
+    if ddof is None:
+        if bias:
+            ddof = 0
+        else:
+            ddof = 1
+
+    (x, xnotmask, rowvar) = _covhelper(x, y, rowvar, allow_masked)
+    if not rowvar:
+        fact = np.dot(xnotmask.T, xnotmask) * 1. - ddof
+        result = (dot(x.T, x.conj(), strict=False) / fact).squeeze()
+    else:
+        fact = np.dot(xnotmask, xnotmask.T) * 1. - ddof
+        result = (dot(x, x.T.conj(), strict=False) / fact).squeeze()
+    return result
+
+
+def corrcoef(x, y=None, rowvar=True, bias=np._NoValue, allow_masked=True,
+             ddof=np._NoValue):
+    """
+    Return Pearson product-moment correlation coefficients.
+
+    Except for the handling of missing data this function does the same as
+    `numpy.corrcoef`. For more details and examples, see `numpy.corrcoef`.
+
+    Parameters
+    ----------
+    x : array_like
+        A 1-D or 2-D array containing multiple variables and observations.
+        Each row of `x` represents a variable, and each column a single
+        observation of all those variables. Also see `rowvar` below.
+    y : array_like, optional
+        An additional set of variables and observations. `y` has the same
+        shape as `x`.
+    rowvar : bool, optional
+        If `rowvar` is True (default), then each row represents a
+        variable, with observations in the columns. Otherwise, the relationship
+        is transposed: each column represents a variable, while the rows
+        contain observations.
+    bias : _NoValue, optional
+        Has no effect, do not use.
+
+        .. deprecated:: 1.10.0
+    allow_masked : bool, optional
+        If True, masked values are propagated pair-wise: if a value is masked
+        in `x`, the corresponding value is masked in `y`.
+        If False, raises an exception.  Because `bias` is deprecated, this
+        argument needs to be treated as keyword only to avoid a warning.
+    ddof : _NoValue, optional
+        Has no effect, do not use.
+
+        .. deprecated:: 1.10.0
+
+    See Also
+    --------
+    numpy.corrcoef : Equivalent function in top-level NumPy module.
+    cov : Estimate the covariance matrix.
+
+    Notes
+    -----
+    This function accepts but discards arguments `bias` and `ddof`.  This is
+    for backwards compatibility with previous versions of this function.  These
+    arguments had no effect on the return values of the function and can be
+    safely ignored in this and previous versions of numpy.
+    """
+    msg = 'bias and ddof have no effect and are deprecated'
+    if bias is not np._NoValue or ddof is not np._NoValue:
+        # 2015-03-15, 1.10
+        warnings.warn(msg, DeprecationWarning, stacklevel=2)
+    # Get the data
+    (x, xnotmask, rowvar) = _covhelper(x, y, rowvar, allow_masked)
+    # Compute the covariance matrix
+    if not rowvar:
+        fact = np.dot(xnotmask.T, xnotmask) * 1.
+        c = (dot(x.T, x.conj(), strict=False) / fact).squeeze()
+    else:
+        fact = np.dot(xnotmask, xnotmask.T) * 1.
+        c = (dot(x, x.T.conj(), strict=False) / fact).squeeze()
+    # Check whether we have a scalar
+    try:
+        diag = ma.diagonal(c)
+    except ValueError:
+        return 1
+    #
+    if xnotmask.all():
+        _denom = ma.sqrt(ma.multiply.outer(diag, diag))
+    else:
+        _denom = diagflat(diag)
+        _denom._sharedmask = False  # We know return is always a copy
+        n = x.shape[1 - rowvar]
+        if rowvar:
+            for i in range(n - 1):
+                for j in range(i + 1, n):
+                    _x = mask_cols(vstack((x[i], x[j]))).var(axis=1)
+                    _denom[i, j] = _denom[j, i] = ma.sqrt(ma.multiply.reduce(_x))
+        else:
+            for i in range(n - 1):
+                for j in range(i + 1, n):
+                    _x = mask_cols(
+                            vstack((x[:, i], x[:, j]))).var(axis=1)
+                    _denom[i, j] = _denom[j, i] = ma.sqrt(ma.multiply.reduce(_x))
+    return c / _denom
+
+#####--------------------------------------------------------------------------
+#---- --- Concatenation helpers ---
+#####--------------------------------------------------------------------------
+
+class MAxisConcatenator(AxisConcatenator):
+    """
+    Translate slice objects to concatenation along an axis.
+
+    For documentation on usage, see `mr_class`.
+
+    See Also
+    --------
+    mr_class
+
+    """
+    concatenate = staticmethod(concatenate)
+
+    @classmethod
+    def makemat(cls, arr):
+        # There used to be a view as np.matrix here, but we may eventually
+        # deprecate that class. In preparation, we use the unmasked version
+        # to construct the matrix (with copy=False for backwards compatibility
+        # with the .view)
+        data = super().makemat(arr.data, copy=False)
+        return array(data, mask=arr.mask)
+
+    def __getitem__(self, key):
+        # matrix builder syntax, like 'a, b; c, d'
+        if isinstance(key, str):
+            raise MAError("Unavailable for masked array.")
+
+        return super().__getitem__(key)
+
+
+class mr_class(MAxisConcatenator):
+    """
+    Translate slice objects to concatenation along the first axis.
+
+    This is the masked array version of `lib.index_tricks.RClass`.
+
+    See Also
+    --------
+    lib.index_tricks.RClass
+
+    Examples
+    --------
+    >>> np.ma.mr_[np.ma.array([1,2,3]), 0, 0, np.ma.array([4,5,6])]
+    masked_array(data=[1, 2, 3, ..., 4, 5, 6],
+                 mask=False,
+           fill_value=999999)
+
+    """
+    def __init__(self):
+        MAxisConcatenator.__init__(self, 0)
+
+mr_ = mr_class()
+
+
+#####--------------------------------------------------------------------------
+#---- Find unmasked data ---
+#####--------------------------------------------------------------------------
+
+def ndenumerate(a, compressed=True):
+    """
+    Multidimensional index iterator.
+
+    Return an iterator yielding pairs of array coordinates and values,
+    skipping elements that are masked. With `compressed=False`,
+    `ma.masked` is yielded as the value of masked elements. This
+    behavior differs from that of `numpy.ndenumerate`, which yields the
+    value of the underlying data array.
+
+    Notes
+    -----
+    .. versionadded:: 1.23.0
+
+    Parameters
+    ----------
+    a : array_like
+        An array with (possibly) masked elements.
+    compressed : bool, optional
+        If True (default), masked elements are skipped.
+
+    See Also
+    --------
+    numpy.ndenumerate : Equivalent function ignoring any mask.
+
+    Examples
+    --------
+    >>> a = np.ma.arange(9).reshape((3, 3))
+    >>> a[1, 0] = np.ma.masked
+    >>> a[1, 2] = np.ma.masked
+    >>> a[2, 1] = np.ma.masked
+    >>> a
+    masked_array(
+      data=[[0, 1, 2],
+            [--, 4, --],
+            [6, --, 8]],
+      mask=[[False, False, False],
+            [ True, False,  True],
+            [False,  True, False]],
+      fill_value=999999)
+    >>> for index, x in np.ma.ndenumerate(a):
+    ...     print(index, x)
+    (0, 0) 0
+    (0, 1) 1
+    (0, 2) 2
+    (1, 1) 4
+    (2, 0) 6
+    (2, 2) 8
+
+    >>> for index, x in np.ma.ndenumerate(a, compressed=False):
+    ...     print(index, x)
+    (0, 0) 0
+    (0, 1) 1
+    (0, 2) 2
+    (1, 0) --
+    (1, 1) 4
+    (1, 2) --
+    (2, 0) 6
+    (2, 1) --
+    (2, 2) 8
+    """
+    for it, mask in zip(np.ndenumerate(a), getmaskarray(a).flat):
+        if not mask:
+            yield it
+        elif not compressed:
+            yield it[0], masked
+
+
+def flatnotmasked_edges(a):
+    """
+    Find the indices of the first and last unmasked values.
+
+    Expects a 1-D `MaskedArray`, returns None if all values are masked.
+
+    Parameters
+    ----------
+    a : array_like
+        Input 1-D `MaskedArray`
+
+    Returns
+    -------
+    edges : ndarray or None
+        The indices of first and last non-masked value in the array.
+        Returns None if all values are masked.
+
+    See Also
+    --------
+    flatnotmasked_contiguous, notmasked_contiguous, notmasked_edges
+    clump_masked, clump_unmasked
+
+    Notes
+    -----
+    Only accepts 1-D arrays.
+
+    Examples
+    --------
+    >>> a = np.ma.arange(10)
+    >>> np.ma.flatnotmasked_edges(a)
+    array([0, 9])
+
+    >>> mask = (a < 3) | (a > 8) | (a == 5)
+    >>> a[mask] = np.ma.masked
+    >>> np.array(a[~a.mask])
+    array([3, 4, 6, 7, 8])
+
+    >>> np.ma.flatnotmasked_edges(a)
+    array([3, 8])
+
+    >>> a[:] = np.ma.masked
+    >>> print(np.ma.flatnotmasked_edges(a))
+    None
+
+    """
+    m = getmask(a)
+    if m is nomask or not np.any(m):
+        return np.array([0, a.size - 1])
+    unmasked = np.flatnonzero(~m)
+    if len(unmasked) > 0:
+        return unmasked[[0, -1]]
+    else:
+        return None
+
+
+def notmasked_edges(a, axis=None):
+    """
+    Find the indices of the first and last unmasked values along an axis.
+
+    If all values are masked, return None.  Otherwise, return a list
+    of two tuples, corresponding to the indices of the first and last
+    unmasked values respectively.
+
+    Parameters
+    ----------
+    a : array_like
+        The input array.
+    axis : int, optional
+        Axis along which to perform the operation.
+        If None (default), applies to a flattened version of the array.
+
+    Returns
+    -------
+    edges : ndarray or list
+        An array of start and end indexes if there are any masked data in
+        the array. If there are no masked data in the array, `edges` is a
+        list of the first and last index.
+
+    See Also
+    --------
+    flatnotmasked_contiguous, flatnotmasked_edges, notmasked_contiguous
+    clump_masked, clump_unmasked
+
+    Examples
+    --------
+    >>> a = np.arange(9).reshape((3, 3))
+    >>> m = np.zeros_like(a)
+    >>> m[1:, 1:] = 1
+
+    >>> am = np.ma.array(a, mask=m)
+    >>> np.array(am[~am.mask])
+    array([0, 1, 2, 3, 6])
+
+    >>> np.ma.notmasked_edges(am)
+    array([0, 6])
+
+    """
+    a = asarray(a)
+    if axis is None or a.ndim == 1:
+        return flatnotmasked_edges(a)
+    m = getmaskarray(a)
+    idx = array(np.indices(a.shape), mask=np.asarray([m] * a.ndim))
+    return [tuple([idx[i].min(axis).compressed() for i in range(a.ndim)]),
+            tuple([idx[i].max(axis).compressed() for i in range(a.ndim)]), ]
+
+
+def flatnotmasked_contiguous(a):
+    """
+    Find contiguous unmasked data in a masked array.
+
+    Parameters
+    ----------
+    a : array_like
+        The input array.
+
+    Returns
+    -------
+    slice_list : list
+        A sorted sequence of `slice` objects (start index, end index).
+
+        .. versionchanged:: 1.15.0
+            Now returns an empty list instead of None for a fully masked array
+
+    See Also
+    --------
+    flatnotmasked_edges, notmasked_contiguous, notmasked_edges
+    clump_masked, clump_unmasked
+
+    Notes
+    -----
+    Only accepts 2-D arrays at most.
+
+    Examples
+    --------
+    >>> a = np.ma.arange(10)
+    >>> np.ma.flatnotmasked_contiguous(a)
+    [slice(0, 10, None)]
+
+    >>> mask = (a < 3) | (a > 8) | (a == 5)
+    >>> a[mask] = np.ma.masked
+    >>> np.array(a[~a.mask])
+    array([3, 4, 6, 7, 8])
+
+    >>> np.ma.flatnotmasked_contiguous(a)
+    [slice(3, 5, None), slice(6, 9, None)]
+    >>> a[:] = np.ma.masked
+    >>> np.ma.flatnotmasked_contiguous(a)
+    []
+
+    """
+    m = getmask(a)
+    if m is nomask:
+        return [slice(0, a.size)]
+    i = 0
+    result = []
+    for (k, g) in itertools.groupby(m.ravel()):
+        n = len(list(g))
+        if not k:
+            result.append(slice(i, i + n))
+        i += n
+    return result
+
+
+def notmasked_contiguous(a, axis=None):
+    """
+    Find contiguous unmasked data in a masked array along the given axis.
+
+    Parameters
+    ----------
+    a : array_like
+        The input array.
+    axis : int, optional
+        Axis along which to perform the operation.
+        If None (default), applies to a flattened version of the array, and this
+        is the same as `flatnotmasked_contiguous`.
+
+    Returns
+    -------
+    endpoints : list
+        A list of slices (start and end indexes) of unmasked indexes
+        in the array.
+
+        If the input is 2d and axis is specified, the result is a list of lists.
+
+    See Also
+    --------
+    flatnotmasked_edges, flatnotmasked_contiguous, notmasked_edges
+    clump_masked, clump_unmasked
+
+    Notes
+    -----
+    Only accepts 2-D arrays at most.
+
+    Examples
+    --------
+    >>> a = np.arange(12).reshape((3, 4))
+    >>> mask = np.zeros_like(a)
+    >>> mask[1:, :-1] = 1; mask[0, 1] = 1; mask[-1, 0] = 0
+    >>> ma = np.ma.array(a, mask=mask)
+    >>> ma
+    masked_array(
+      data=[[0, --, 2, 3],
+            [--, --, --, 7],
+            [8, --, --, 11]],
+      mask=[[False,  True, False, False],
+            [ True,  True,  True, False],
+            [False,  True,  True, False]],
+      fill_value=999999)
+    >>> np.array(ma[~ma.mask])
+    array([ 0,  2,  3,  7, 8, 11])
+
+    >>> np.ma.notmasked_contiguous(ma)
+    [slice(0, 1, None), slice(2, 4, None), slice(7, 9, None), slice(11, 12, None)]
+
+    >>> np.ma.notmasked_contiguous(ma, axis=0)
+    [[slice(0, 1, None), slice(2, 3, None)], [], [slice(0, 1, None)], [slice(0, 3, None)]]
+
+    >>> np.ma.notmasked_contiguous(ma, axis=1)
+    [[slice(0, 1, None), slice(2, 4, None)], [slice(3, 4, None)], [slice(0, 1, None), slice(3, 4, None)]]
+
+    """
+    a = asarray(a)
+    nd = a.ndim
+    if nd > 2:
+        raise NotImplementedError("Currently limited to at most 2D array.")
+    if axis is None or nd == 1:
+        return flatnotmasked_contiguous(a)
+    #
+    result = []
+    #
+    other = (axis + 1) % 2
+    idx = [0, 0]
+    idx[axis] = slice(None, None)
+    #
+    for i in range(a.shape[other]):
+        idx[other] = i
+        result.append(flatnotmasked_contiguous(a[tuple(idx)]))
+    return result
+
+
+def _ezclump(mask):
+    """
+    Finds the clumps (groups of data with the same values) for a 1D bool array.
+
+    Returns a series of slices.
+    """
+    if mask.ndim > 1:
+        mask = mask.ravel()
+    idx = (mask[1:] ^ mask[:-1]).nonzero()
+    idx = idx[0] + 1
+
+    if mask[0]:
+        if len(idx) == 0:
+            return [slice(0, mask.size)]
+
+        r = [slice(0, idx[0])]
+        r.extend((slice(left, right)
+                  for left, right in zip(idx[1:-1:2], idx[2::2])))
+    else:
+        if len(idx) == 0:
+            return []
+
+        r = [slice(left, right) for left, right in zip(idx[:-1:2], idx[1::2])]
+
+    if mask[-1]:
+        r.append(slice(idx[-1], mask.size))
+    return r
+
+
+def clump_unmasked(a):
+    """
+    Return list of slices corresponding to the unmasked clumps of a 1-D array.
+    (A "clump" is defined as a contiguous region of the array).
+
+    Parameters
+    ----------
+    a : ndarray
+        A one-dimensional masked array.
+
+    Returns
+    -------
+    slices : list of slice
+        The list of slices, one for each continuous region of unmasked
+        elements in `a`.
+
+    Notes
+    -----
+    .. versionadded:: 1.4.0
+
+    See Also
+    --------
+    flatnotmasked_edges, flatnotmasked_contiguous, notmasked_edges
+    notmasked_contiguous, clump_masked
+
+    Examples
+    --------
+    >>> a = np.ma.masked_array(np.arange(10))
+    >>> a[[0, 1, 2, 6, 8, 9]] = np.ma.masked
+    >>> np.ma.clump_unmasked(a)
+    [slice(3, 6, None), slice(7, 8, None)]
+
+    """
+    mask = getattr(a, '_mask', nomask)
+    if mask is nomask:
+        return [slice(0, a.size)]
+    return _ezclump(~mask)
+
+
+def clump_masked(a):
+    """
+    Returns a list of slices corresponding to the masked clumps of a 1-D array.
+    (A "clump" is defined as a contiguous region of the array).
+
+    Parameters
+    ----------
+    a : ndarray
+        A one-dimensional masked array.
+
+    Returns
+    -------
+    slices : list of slice
+        The list of slices, one for each continuous region of masked elements
+        in `a`.
+
+    Notes
+    -----
+    .. versionadded:: 1.4.0
+
+    See Also
+    --------
+    flatnotmasked_edges, flatnotmasked_contiguous, notmasked_edges
+    notmasked_contiguous, clump_unmasked
+
+    Examples
+    --------
+    >>> a = np.ma.masked_array(np.arange(10))
+    >>> a[[0, 1, 2, 6, 8, 9]] = np.ma.masked
+    >>> np.ma.clump_masked(a)
+    [slice(0, 3, None), slice(6, 7, None), slice(8, 10, None)]
+
+    """
+    mask = ma.getmask(a)
+    if mask is nomask:
+        return []
+    return _ezclump(mask)
+
+
+###############################################################################
+#                              Polynomial fit                                 #
+###############################################################################
+
+
+def vander(x, n=None):
+    """
+    Masked values in the input array result in rows of zeros.
+
+    """
+    _vander = np.vander(x, n)
+    m = getmask(x)
+    if m is not nomask:
+        _vander[m] = 0
+    return _vander
+
+vander.__doc__ = ma.doc_note(np.vander.__doc__, vander.__doc__)
+
+
+def polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False):
+    """
+    Any masked values in x is propagated in y, and vice-versa.
+
+    """
+    x = asarray(x)
+    y = asarray(y)
+
+    m = getmask(x)
+    if y.ndim == 1:
+        m = mask_or(m, getmask(y))
+    elif y.ndim == 2:
+        my = getmask(mask_rows(y))
+        if my is not nomask:
+            m = mask_or(m, my[:, 0])
+    else:
+        raise TypeError("Expected a 1D or 2D array for y!")
+
+    if w is not None:
+        w = asarray(w)
+        if w.ndim != 1:
+            raise TypeError("expected a 1-d array for weights")
+        if w.shape[0] != y.shape[0]:
+            raise TypeError("expected w and y to have the same length")
+        m = mask_or(m, getmask(w))
+
+    if m is not nomask:
+        not_m = ~m
+        if w is not None:
+            w = w[not_m]
+        return np.polyfit(x[not_m], y[not_m], deg, rcond, full, w, cov)
+    else:
+        return np.polyfit(x, y, deg, rcond, full, w, cov)
+
+polyfit.__doc__ = ma.doc_note(np.polyfit.__doc__, polyfit.__doc__)
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/extras.pyi b/.venv/lib/python3.12/site-packages/numpy/ma/extras.pyi
new file mode 100644
index 00000000..56228b92
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/extras.pyi
@@ -0,0 +1,85 @@
+from typing import Any
+from numpy.lib.index_tricks import AxisConcatenator
+
+from numpy.ma.core import (
+    dot as dot,
+    mask_rowcols as mask_rowcols,
+)
+
+__all__: list[str]
+
+def count_masked(arr, axis=...): ...
+def masked_all(shape, dtype = ...): ...
+def masked_all_like(arr): ...
+
+class _fromnxfunction:
+    __name__: Any
+    __doc__: Any
+    def __init__(self, funcname): ...
+    def getdoc(self): ...
+    def __call__(self, *args, **params): ...
+
+class _fromnxfunction_single(_fromnxfunction):
+    def __call__(self, x, *args, **params): ...
+
+class _fromnxfunction_seq(_fromnxfunction):
+    def __call__(self, x, *args, **params): ...
+
+class _fromnxfunction_allargs(_fromnxfunction):
+    def __call__(self, *args, **params): ...
+
+atleast_1d: _fromnxfunction_allargs
+atleast_2d: _fromnxfunction_allargs
+atleast_3d: _fromnxfunction_allargs
+
+vstack: _fromnxfunction_seq
+row_stack: _fromnxfunction_seq
+hstack: _fromnxfunction_seq
+column_stack: _fromnxfunction_seq
+dstack: _fromnxfunction_seq
+stack: _fromnxfunction_seq
+
+hsplit: _fromnxfunction_single
+diagflat: _fromnxfunction_single
+
+def apply_along_axis(func1d, axis, arr, *args, **kwargs): ...
+def apply_over_axes(func, a, axes): ...
+def average(a, axis=..., weights=..., returned=..., keepdims=...): ...
+def median(a, axis=..., out=..., overwrite_input=..., keepdims=...): ...
+def compress_nd(x, axis=...): ...
+def compress_rowcols(x, axis=...): ...
+def compress_rows(a): ...
+def compress_cols(a): ...
+def mask_rows(a, axis = ...): ...
+def mask_cols(a, axis = ...): ...
+def ediff1d(arr, to_end=..., to_begin=...): ...
+def unique(ar1, return_index=..., return_inverse=...): ...
+def intersect1d(ar1, ar2, assume_unique=...): ...
+def setxor1d(ar1, ar2, assume_unique=...): ...
+def in1d(ar1, ar2, assume_unique=..., invert=...): ...
+def isin(element, test_elements, assume_unique=..., invert=...): ...
+def union1d(ar1, ar2): ...
+def setdiff1d(ar1, ar2, assume_unique=...): ...
+def cov(x, y=..., rowvar=..., bias=..., allow_masked=..., ddof=...): ...
+def corrcoef(x, y=..., rowvar=..., bias = ..., allow_masked=..., ddof = ...): ...
+
+class MAxisConcatenator(AxisConcatenator):
+    concatenate: Any
+    @classmethod
+    def makemat(cls, arr): ...
+    def __getitem__(self, key): ...
+
+class mr_class(MAxisConcatenator):
+    def __init__(self): ...
+
+mr_: mr_class
+
+def ndenumerate(a, compressed=...): ...
+def flatnotmasked_edges(a): ...
+def notmasked_edges(a, axis=...): ...
+def flatnotmasked_contiguous(a): ...
+def notmasked_contiguous(a, axis=...): ...
+def clump_unmasked(a): ...
+def clump_masked(a): ...
+def vander(x, n=...): ...
+def polyfit(x, y, deg, rcond=..., full=..., w=..., cov=...): ...
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/mrecords.py b/.venv/lib/python3.12/site-packages/numpy/ma/mrecords.py
new file mode 100644
index 00000000..1e8103bc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/mrecords.py
@@ -0,0 +1,783 @@
+""":mod:`numpy.ma..mrecords`
+
+Defines the equivalent of :class:`numpy.recarrays` for masked arrays,
+where fields can be accessed as attributes.
+Note that :class:`numpy.ma.MaskedArray` already supports structured datatypes
+and the masking of individual fields.
+
+.. moduleauthor:: Pierre Gerard-Marchant
+
+"""
+#  We should make sure that no field is called '_mask','mask','_fieldmask',
+#  or whatever restricted keywords.  An idea would be to no bother in the
+#  first place, and then rename the invalid fields with a trailing
+#  underscore. Maybe we could just overload the parser function ?
+
+from numpy.ma import (
+    MAError, MaskedArray, masked, nomask, masked_array, getdata,
+    getmaskarray, filled
+)
+import numpy.ma as ma
+import warnings
+
+import numpy as np
+from numpy import (
+    bool_, dtype, ndarray, recarray, array as narray
+)
+from numpy.core.records import (
+    fromarrays as recfromarrays, fromrecords as recfromrecords
+)
+
+_byteorderconv = np.core.records._byteorderconv
+
+
+_check_fill_value = ma.core._check_fill_value
+
+
+__all__ = [
+    'MaskedRecords', 'mrecarray', 'fromarrays', 'fromrecords',
+    'fromtextfile', 'addfield',
+]
+
+reserved_fields = ['_data', '_mask', '_fieldmask', 'dtype']
+
+
+def _checknames(descr, names=None):
+    """
+    Checks that field names ``descr`` are not reserved keywords.
+
+    If this is the case, a default 'f%i' is substituted.  If the argument
+    `names` is not None, updates the field names to valid names.
+
+    """
+    ndescr = len(descr)
+    default_names = ['f%i' % i for i in range(ndescr)]
+    if names is None:
+        new_names = default_names
+    else:
+        if isinstance(names, (tuple, list)):
+            new_names = names
+        elif isinstance(names, str):
+            new_names = names.split(',')
+        else:
+            raise NameError(f'illegal input names {names!r}')
+        nnames = len(new_names)
+        if nnames < ndescr:
+            new_names += default_names[nnames:]
+    ndescr = []
+    for (n, d, t) in zip(new_names, default_names, descr.descr):
+        if n in reserved_fields:
+            if t[0] in reserved_fields:
+                ndescr.append((d, t[1]))
+            else:
+                ndescr.append(t)
+        else:
+            ndescr.append((n, t[1]))
+    return np.dtype(ndescr)
+
+
+def _get_fieldmask(self):
+    mdescr = [(n, '|b1') for n in self.dtype.names]
+    fdmask = np.empty(self.shape, dtype=mdescr)
+    fdmask.flat = tuple([False] * len(mdescr))
+    return fdmask
+
+
+class MaskedRecords(MaskedArray):
+    """
+
+    Attributes
+    ----------
+    _data : recarray
+        Underlying data, as a record array.
+    _mask : boolean array
+        Mask of the records. A record is masked when all its fields are
+        masked.
+    _fieldmask : boolean recarray
+        Record array of booleans, setting the mask of each individual field
+        of each record.
+    _fill_value : record
+        Filling values for each field.
+
+    """
+
+    def __new__(cls, shape, dtype=None, buf=None, offset=0, strides=None,
+                formats=None, names=None, titles=None,
+                byteorder=None, aligned=False,
+                mask=nomask, hard_mask=False, fill_value=None, keep_mask=True,
+                copy=False,
+                **options):
+
+        self = recarray.__new__(cls, shape, dtype=dtype, buf=buf, offset=offset,
+                                strides=strides, formats=formats, names=names,
+                                titles=titles, byteorder=byteorder,
+                                aligned=aligned,)
+
+        mdtype = ma.make_mask_descr(self.dtype)
+        if mask is nomask or not np.size(mask):
+            if not keep_mask:
+                self._mask = tuple([False] * len(mdtype))
+        else:
+            mask = np.array(mask, copy=copy)
+            if mask.shape != self.shape:
+                (nd, nm) = (self.size, mask.size)
+                if nm == 1:
+                    mask = np.resize(mask, self.shape)
+                elif nm == nd:
+                    mask = np.reshape(mask, self.shape)
+                else:
+                    msg = "Mask and data not compatible: data size is %i, " + \
+                          "mask size is %i."
+                    raise MAError(msg % (nd, nm))
+            if not keep_mask:
+                self.__setmask__(mask)
+                self._sharedmask = True
+            else:
+                if mask.dtype == mdtype:
+                    _mask = mask
+                else:
+                    _mask = np.array([tuple([m] * len(mdtype)) for m in mask],
+                                     dtype=mdtype)
+                self._mask = _mask
+        return self
+
+    def __array_finalize__(self, obj):
+        # Make sure we have a _fieldmask by default
+        _mask = getattr(obj, '_mask', None)
+        if _mask is None:
+            objmask = getattr(obj, '_mask', nomask)
+            _dtype = ndarray.__getattribute__(self, 'dtype')
+            if objmask is nomask:
+                _mask = ma.make_mask_none(self.shape, dtype=_dtype)
+            else:
+                mdescr = ma.make_mask_descr(_dtype)
+                _mask = narray([tuple([m] * len(mdescr)) for m in objmask],
+                               dtype=mdescr).view(recarray)
+        # Update some of the attributes
+        _dict = self.__dict__
+        _dict.update(_mask=_mask)
+        self._update_from(obj)
+        if _dict['_baseclass'] == ndarray:
+            _dict['_baseclass'] = recarray
+        return
+
+    @property
+    def _data(self):
+        """
+        Returns the data as a recarray.
+
+        """
+        return ndarray.view(self, recarray)
+
+    @property
+    def _fieldmask(self):
+        """
+        Alias to mask.
+
+        """
+        return self._mask
+
+    def __len__(self):
+        """
+        Returns the length
+
+        """
+        # We have more than one record
+        if self.ndim:
+            return len(self._data)
+        # We have only one record: return the nb of fields
+        return len(self.dtype)
+
+    def __getattribute__(self, attr):
+        try:
+            return object.__getattribute__(self, attr)
+        except AttributeError:
+            # attr must be a fieldname
+            pass
+        fielddict = ndarray.__getattribute__(self, 'dtype').fields
+        try:
+            res = fielddict[attr][:2]
+        except (TypeError, KeyError) as e:
+            raise AttributeError(
+                f'record array has no attribute {attr}') from e
+        # So far, so good
+        _localdict = ndarray.__getattribute__(self, '__dict__')
+        _data = ndarray.view(self, _localdict['_baseclass'])
+        obj = _data.getfield(*res)
+        if obj.dtype.names is not None:
+            raise NotImplementedError("MaskedRecords is currently limited to"
+                                      "simple records.")
+        # Get some special attributes
+        # Reset the object's mask
+        hasmasked = False
+        _mask = _localdict.get('_mask', None)
+        if _mask is not None:
+            try:
+                _mask = _mask[attr]
+            except IndexError:
+                # Couldn't find a mask: use the default (nomask)
+                pass
+            tp_len = len(_mask.dtype)
+            hasmasked = _mask.view((bool, ((tp_len,) if tp_len else ()))).any()
+        if (obj.shape or hasmasked):
+            obj = obj.view(MaskedArray)
+            obj._baseclass = ndarray
+            obj._isfield = True
+            obj._mask = _mask
+            # Reset the field values
+            _fill_value = _localdict.get('_fill_value', None)
+            if _fill_value is not None:
+                try:
+                    obj._fill_value = _fill_value[attr]
+                except ValueError:
+                    obj._fill_value = None
+        else:
+            obj = obj.item()
+        return obj
+
+    def __setattr__(self, attr, val):
+        """
+        Sets the attribute attr to the value val.
+
+        """
+        # Should we call __setmask__ first ?
+        if attr in ['mask', 'fieldmask']:
+            self.__setmask__(val)
+            return
+        # Create a shortcut (so that we don't have to call getattr all the time)
+        _localdict = object.__getattribute__(self, '__dict__')
+        # Check whether we're creating a new field
+        newattr = attr not in _localdict
+        try:
+            # Is attr a generic attribute ?
+            ret = object.__setattr__(self, attr, val)
+        except Exception:
+            # Not a generic attribute: exit if it's not a valid field
+            fielddict = ndarray.__getattribute__(self, 'dtype').fields or {}
+            optinfo = ndarray.__getattribute__(self, '_optinfo') or {}
+            if not (attr in fielddict or attr in optinfo):
+                raise
+        else:
+            # Get the list of names
+            fielddict = ndarray.__getattribute__(self, 'dtype').fields or {}
+            # Check the attribute
+            if attr not in fielddict:
+                return ret
+            if newattr:
+                # We just added this one or this setattr worked on an
+                # internal attribute.
+                try:
+                    object.__delattr__(self, attr)
+                except Exception:
+                    return ret
+        # Let's try to set the field
+        try:
+            res = fielddict[attr][:2]
+        except (TypeError, KeyError) as e:
+            raise AttributeError(
+                f'record array has no attribute {attr}') from e
+
+        if val is masked:
+            _fill_value = _localdict['_fill_value']
+            if _fill_value is not None:
+                dval = _localdict['_fill_value'][attr]
+            else:
+                dval = val
+            mval = True
+        else:
+            dval = filled(val)
+            mval = getmaskarray(val)
+        obj = ndarray.__getattribute__(self, '_data').setfield(dval, *res)
+        _localdict['_mask'].__setitem__(attr, mval)
+        return obj
+
+    def __getitem__(self, indx):
+        """
+        Returns all the fields sharing the same fieldname base.
+
+        The fieldname base is either `_data` or `_mask`.
+
+        """
+        _localdict = self.__dict__
+        _mask = ndarray.__getattribute__(self, '_mask')
+        _data = ndarray.view(self, _localdict['_baseclass'])
+        # We want a field
+        if isinstance(indx, str):
+            # Make sure _sharedmask is True to propagate back to _fieldmask
+            # Don't use _set_mask, there are some copies being made that
+            # break propagation Don't force the mask to nomask, that wreaks
+            # easy masking
+            obj = _data[indx].view(MaskedArray)
+            obj._mask = _mask[indx]
+            obj._sharedmask = True
+            fval = _localdict['_fill_value']
+            if fval is not None:
+                obj._fill_value = fval[indx]
+            # Force to masked if the mask is True
+            if not obj.ndim and obj._mask:
+                return masked
+            return obj
+        # We want some elements.
+        # First, the data.
+        obj = np.array(_data[indx], copy=False).view(mrecarray)
+        obj._mask = np.array(_mask[indx], copy=False).view(recarray)
+        return obj
+
+    def __setitem__(self, indx, value):
+        """
+        Sets the given record to value.
+
+        """
+        MaskedArray.__setitem__(self, indx, value)
+        if isinstance(indx, str):
+            self._mask[indx] = ma.getmaskarray(value)
+
+    def __str__(self):
+        """
+        Calculates the string representation.
+
+        """
+        if self.size > 1:
+            mstr = [f"({','.join([str(i) for i in s])})"
+                    for s in zip(*[getattr(self, f) for f in self.dtype.names])]
+            return f"[{', '.join(mstr)}]"
+        else:
+            mstr = [f"{','.join([str(i) for i in s])}"
+                    for s in zip([getattr(self, f) for f in self.dtype.names])]
+            return f"({', '.join(mstr)})"
+
+    def __repr__(self):
+        """
+        Calculates the repr representation.
+
+        """
+        _names = self.dtype.names
+        fmt = "%%%is : %%s" % (max([len(n) for n in _names]) + 4,)
+        reprstr = [fmt % (f, getattr(self, f)) for f in self.dtype.names]
+        reprstr.insert(0, 'masked_records(')
+        reprstr.extend([fmt % ('    fill_value', self.fill_value),
+                        '              )'])
+        return str("\n".join(reprstr))
+
+    def view(self, dtype=None, type=None):
+        """
+        Returns a view of the mrecarray.
+
+        """
+        # OK, basic copy-paste from MaskedArray.view.
+        if dtype is None:
+            if type is None:
+                output = ndarray.view(self)
+            else:
+                output = ndarray.view(self, type)
+        # Here again.
+        elif type is None:
+            try:
+                if issubclass(dtype, ndarray):
+                    output = ndarray.view(self, dtype)
+                else:
+                    output = ndarray.view(self, dtype)
+            # OK, there's the change
+            except TypeError:
+                dtype = np.dtype(dtype)
+                # we need to revert to MaskedArray, but keeping the possibility
+                # of subclasses (eg, TimeSeriesRecords), so we'll force a type
+                # set to the first parent
+                if dtype.fields is None:
+                    basetype = self.__class__.__bases__[0]
+                    output = self.__array__().view(dtype, basetype)
+                    output._update_from(self)
+                else:
+                    output = ndarray.view(self, dtype)
+                output._fill_value = None
+        else:
+            output = ndarray.view(self, dtype, type)
+        # Update the mask, just like in MaskedArray.view
+        if (getattr(output, '_mask', nomask) is not nomask):
+            mdtype = ma.make_mask_descr(output.dtype)
+            output._mask = self._mask.view(mdtype, ndarray)
+            output._mask.shape = output.shape
+        return output
+
+    def harden_mask(self):
+        """
+        Forces the mask to hard.
+
+        """
+        self._hardmask = True
+
+    def soften_mask(self):
+        """
+        Forces the mask to soft
+
+        """
+        self._hardmask = False
+
+    def copy(self):
+        """
+        Returns a copy of the masked record.
+
+        """
+        copied = self._data.copy().view(type(self))
+        copied._mask = self._mask.copy()
+        return copied
+
+    def tolist(self, fill_value=None):
+        """
+        Return the data portion of the array as a list.
+
+        Data items are converted to the nearest compatible Python type.
+        Masked values are converted to fill_value. If fill_value is None,
+        the corresponding entries in the output list will be ``None``.
+
+        """
+        if fill_value is not None:
+            return self.filled(fill_value).tolist()
+        result = narray(self.filled().tolist(), dtype=object)
+        mask = narray(self._mask.tolist())
+        result[mask] = None
+        return result.tolist()
+
+    def __getstate__(self):
+        """Return the internal state of the masked array.
+
+        This is for pickling.
+
+        """
+        state = (1,
+                 self.shape,
+                 self.dtype,
+                 self.flags.fnc,
+                 self._data.tobytes(),
+                 self._mask.tobytes(),
+                 self._fill_value,
+                 )
+        return state
+
+    def __setstate__(self, state):
+        """
+        Restore the internal state of the masked array.
+
+        This is for pickling.  ``state`` is typically the output of the
+        ``__getstate__`` output, and is a 5-tuple:
+
+        - class name
+        - a tuple giving the shape of the data
+        - a typecode for the data
+        - a binary string for the data
+        - a binary string for the mask.
+
+        """
+        (ver, shp, typ, isf, raw, msk, flv) = state
+        ndarray.__setstate__(self, (shp, typ, isf, raw))
+        mdtype = dtype([(k, bool_) for (k, _) in self.dtype.descr])
+        self.__dict__['_mask'].__setstate__((shp, mdtype, isf, msk))
+        self.fill_value = flv
+
+    def __reduce__(self):
+        """
+        Return a 3-tuple for pickling a MaskedArray.
+
+        """
+        return (_mrreconstruct,
+                (self.__class__, self._baseclass, (0,), 'b',),
+                self.__getstate__())
+
+
+def _mrreconstruct(subtype, baseclass, baseshape, basetype,):
+    """
+    Build a new MaskedArray from the information stored in a pickle.
+
+    """
+    _data = ndarray.__new__(baseclass, baseshape, basetype).view(subtype)
+    _mask = ndarray.__new__(ndarray, baseshape, 'b1')
+    return subtype.__new__(subtype, _data, mask=_mask, dtype=basetype,)
+
+mrecarray = MaskedRecords
+
+
+###############################################################################
+#                             Constructors                                    #
+###############################################################################
+
+
+def fromarrays(arraylist, dtype=None, shape=None, formats=None,
+               names=None, titles=None, aligned=False, byteorder=None,
+               fill_value=None):
+    """
+    Creates a mrecarray from a (flat) list of masked arrays.
+
+    Parameters
+    ----------
+    arraylist : sequence
+        A list of (masked) arrays. Each element of the sequence is first converted
+        to a masked array if needed. If a 2D array is passed as argument, it is
+        processed line by line
+    dtype : {None, dtype}, optional
+        Data type descriptor.
+    shape : {None, integer}, optional
+        Number of records. If None, shape is defined from the shape of the
+        first array in the list.
+    formats : {None, sequence}, optional
+        Sequence of formats for each individual field. If None, the formats will
+        be autodetected by inspecting the fields and selecting the highest dtype
+        possible.
+    names : {None, sequence}, optional
+        Sequence of the names of each field.
+    fill_value : {None, sequence}, optional
+        Sequence of data to be used as filling values.
+
+    Notes
+    -----
+    Lists of tuples should be preferred over lists of lists for faster processing.
+
+    """
+    datalist = [getdata(x) for x in arraylist]
+    masklist = [np.atleast_1d(getmaskarray(x)) for x in arraylist]
+    _array = recfromarrays(datalist,
+                           dtype=dtype, shape=shape, formats=formats,
+                           names=names, titles=titles, aligned=aligned,
+                           byteorder=byteorder).view(mrecarray)
+    _array._mask.flat = list(zip(*masklist))
+    if fill_value is not None:
+        _array.fill_value = fill_value
+    return _array
+
+
+def fromrecords(reclist, dtype=None, shape=None, formats=None, names=None,
+                titles=None, aligned=False, byteorder=None,
+                fill_value=None, mask=nomask):
+    """
+    Creates a MaskedRecords from a list of records.
+
+    Parameters
+    ----------
+    reclist : sequence
+        A list of records. Each element of the sequence is first converted
+        to a masked array if needed. If a 2D array is passed as argument, it is
+        processed line by line
+    dtype : {None, dtype}, optional
+        Data type descriptor.
+    shape : {None,int}, optional
+        Number of records. If None, ``shape`` is defined from the shape of the
+        first array in the list.
+    formats : {None, sequence}, optional
+        Sequence of formats for each individual field. If None, the formats will
+        be autodetected by inspecting the fields and selecting the highest dtype
+        possible.
+    names : {None, sequence}, optional
+        Sequence of the names of each field.
+    fill_value : {None, sequence}, optional
+        Sequence of data to be used as filling values.
+    mask : {nomask, sequence}, optional.
+        External mask to apply on the data.
+
+    Notes
+    -----
+    Lists of tuples should be preferred over lists of lists for faster processing.
+
+    """
+    # Grab the initial _fieldmask, if needed:
+    _mask = getattr(reclist, '_mask', None)
+    # Get the list of records.
+    if isinstance(reclist, ndarray):
+        # Make sure we don't have some hidden mask
+        if isinstance(reclist, MaskedArray):
+            reclist = reclist.filled().view(ndarray)
+        # Grab the initial dtype, just in case
+        if dtype is None:
+            dtype = reclist.dtype
+        reclist = reclist.tolist()
+    mrec = recfromrecords(reclist, dtype=dtype, shape=shape, formats=formats,
+                          names=names, titles=titles,
+                          aligned=aligned, byteorder=byteorder).view(mrecarray)
+    # Set the fill_value if needed
+    if fill_value is not None:
+        mrec.fill_value = fill_value
+    # Now, let's deal w/ the mask
+    if mask is not nomask:
+        mask = np.array(mask, copy=False)
+        maskrecordlength = len(mask.dtype)
+        if maskrecordlength:
+            mrec._mask.flat = mask
+        elif mask.ndim == 2:
+            mrec._mask.flat = [tuple(m) for m in mask]
+        else:
+            mrec.__setmask__(mask)
+    if _mask is not None:
+        mrec._mask[:] = _mask
+    return mrec
+
+
+def _guessvartypes(arr):
+    """
+    Tries to guess the dtypes of the str_ ndarray `arr`.
+
+    Guesses by testing element-wise conversion. Returns a list of dtypes.
+    The array is first converted to ndarray. If the array is 2D, the test
+    is performed on the first line. An exception is raised if the file is
+    3D or more.
+
+    """
+    vartypes = []
+    arr = np.asarray(arr)
+    if arr.ndim == 2:
+        arr = arr[0]
+    elif arr.ndim > 2:
+        raise ValueError("The array should be 2D at most!")
+    # Start the conversion loop.
+    for f in arr:
+        try:
+            int(f)
+        except (ValueError, TypeError):
+            try:
+                float(f)
+            except (ValueError, TypeError):
+                try:
+                    complex(f)
+                except (ValueError, TypeError):
+                    vartypes.append(arr.dtype)
+                else:
+                    vartypes.append(np.dtype(complex))
+            else:
+                vartypes.append(np.dtype(float))
+        else:
+            vartypes.append(np.dtype(int))
+    return vartypes
+
+
+def openfile(fname):
+    """
+    Opens the file handle of file `fname`.
+
+    """
+    # A file handle
+    if hasattr(fname, 'readline'):
+        return fname
+    # Try to open the file and guess its type
+    try:
+        f = open(fname)
+    except FileNotFoundError as e:
+        raise FileNotFoundError(f"No such file: '{fname}'") from e
+    if f.readline()[:2] != "\\x":
+        f.seek(0, 0)
+        return f
+    f.close()
+    raise NotImplementedError("Wow, binary file")
+
+
+def fromtextfile(fname, delimiter=None, commentchar='#', missingchar='',
+                 varnames=None, vartypes=None,
+                 *, delimitor=np._NoValue):  # backwards compatibility
+    """
+    Creates a mrecarray from data stored in the file `filename`.
+
+    Parameters
+    ----------
+    fname : {file name/handle}
+        Handle of an opened file.
+    delimiter : {None, string}, optional
+        Alphanumeric character used to separate columns in the file.
+        If None, any (group of) white spacestring(s) will be used.
+    commentchar : {'#', string}, optional
+        Alphanumeric character used to mark the start of a comment.
+    missingchar : {'', string}, optional
+        String indicating missing data, and used to create the masks.
+    varnames : {None, sequence}, optional
+        Sequence of the variable names. If None, a list will be created from
+        the first non empty line of the file.
+    vartypes : {None, sequence}, optional
+        Sequence of the variables dtypes. If None, it will be estimated from
+        the first non-commented line.
+
+
+    Ultra simple: the varnames are in the header, one line"""
+    if delimitor is not np._NoValue:
+        if delimiter is not None:
+            raise TypeError("fromtextfile() got multiple values for argument "
+                            "'delimiter'")
+        # NumPy 1.22.0, 2021-09-23
+        warnings.warn("The 'delimitor' keyword argument of "
+                      "numpy.ma.mrecords.fromtextfile() is deprecated "
+                      "since NumPy 1.22.0, use 'delimiter' instead.",
+                      DeprecationWarning, stacklevel=2)
+        delimiter = delimitor
+
+    # Try to open the file.
+    ftext = openfile(fname)
+
+    # Get the first non-empty line as the varnames
+    while True:
+        line = ftext.readline()
+        firstline = line[:line.find(commentchar)].strip()
+        _varnames = firstline.split(delimiter)
+        if len(_varnames) > 1:
+            break
+    if varnames is None:
+        varnames = _varnames
+
+    # Get the data.
+    _variables = masked_array([line.strip().split(delimiter) for line in ftext
+                               if line[0] != commentchar and len(line) > 1])
+    (_, nfields) = _variables.shape
+    ftext.close()
+
+    # Try to guess the dtype.
+    if vartypes is None:
+        vartypes = _guessvartypes(_variables[0])
+    else:
+        vartypes = [np.dtype(v) for v in vartypes]
+        if len(vartypes) != nfields:
+            msg = "Attempting to %i dtypes for %i fields!"
+            msg += " Reverting to default."
+            warnings.warn(msg % (len(vartypes), nfields), stacklevel=2)
+            vartypes = _guessvartypes(_variables[0])
+
+    # Construct the descriptor.
+    mdescr = [(n, f) for (n, f) in zip(varnames, vartypes)]
+    mfillv = [ma.default_fill_value(f) for f in vartypes]
+
+    # Get the data and the mask.
+    # We just need a list of masked_arrays. It's easier to create it like that:
+    _mask = (_variables.T == missingchar)
+    _datalist = [masked_array(a, mask=m, dtype=t, fill_value=f)
+                 for (a, m, t, f) in zip(_variables.T, _mask, vartypes, mfillv)]
+
+    return fromarrays(_datalist, dtype=mdescr)
+
+
+def addfield(mrecord, newfield, newfieldname=None):
+    """Adds a new field to the masked record array
+
+    Uses `newfield` as data and `newfieldname` as name. If `newfieldname`
+    is None, the new field name is set to 'fi', where `i` is the number of
+    existing fields.
+
+    """
+    _data = mrecord._data
+    _mask = mrecord._mask
+    if newfieldname is None or newfieldname in reserved_fields:
+        newfieldname = 'f%i' % len(_data.dtype)
+    newfield = ma.array(newfield)
+    # Get the new data.
+    # Create a new empty recarray
+    newdtype = np.dtype(_data.dtype.descr + [(newfieldname, newfield.dtype)])
+    newdata = recarray(_data.shape, newdtype)
+    # Add the existing field
+    [newdata.setfield(_data.getfield(*f), *f)
+     for f in _data.dtype.fields.values()]
+    # Add the new field
+    newdata.setfield(newfield._data, *newdata.dtype.fields[newfieldname])
+    newdata = newdata.view(MaskedRecords)
+    # Get the new mask
+    # Create a new empty recarray
+    newmdtype = np.dtype([(n, bool_) for n in newdtype.names])
+    newmask = recarray(_data.shape, newmdtype)
+    # Add the old masks
+    [newmask.setfield(_mask.getfield(*f), *f)
+     for f in _mask.dtype.fields.values()]
+    # Add the mask of the new field
+    newmask.setfield(getmaskarray(newfield),
+                     *newmask.dtype.fields[newfieldname])
+    newdata._mask = newmask
+    return newdata
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/mrecords.pyi b/.venv/lib/python3.12/site-packages/numpy/ma/mrecords.pyi
new file mode 100644
index 00000000..264807e0
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/mrecords.pyi
@@ -0,0 +1,90 @@
+from typing import Any, TypeVar
+
+from numpy import dtype
+from numpy.ma import MaskedArray
+
+__all__: list[str]
+
+# TODO: Set the `bound` to something more suitable once we
+# have proper shape support
+_ShapeType = TypeVar("_ShapeType", bound=Any)
+_DType_co = TypeVar("_DType_co", bound=dtype[Any], covariant=True)
+
+class MaskedRecords(MaskedArray[_ShapeType, _DType_co]):
+    def __new__(
+        cls,
+        shape,
+        dtype=...,
+        buf=...,
+        offset=...,
+        strides=...,
+        formats=...,
+        names=...,
+        titles=...,
+        byteorder=...,
+        aligned=...,
+        mask=...,
+        hard_mask=...,
+        fill_value=...,
+        keep_mask=...,
+        copy=...,
+        **options,
+    ): ...
+    _mask: Any
+    _fill_value: Any
+    @property
+    def _data(self): ...
+    @property
+    def _fieldmask(self): ...
+    def __array_finalize__(self, obj): ...
+    def __len__(self): ...
+    def __getattribute__(self, attr): ...
+    def __setattr__(self, attr, val): ...
+    def __getitem__(self, indx): ...
+    def __setitem__(self, indx, value): ...
+    def view(self, dtype=..., type=...): ...
+    def harden_mask(self): ...
+    def soften_mask(self): ...
+    def copy(self): ...
+    def tolist(self, fill_value=...): ...
+    def __reduce__(self): ...
+
+mrecarray = MaskedRecords
+
+def fromarrays(
+    arraylist,
+    dtype=...,
+    shape=...,
+    formats=...,
+    names=...,
+    titles=...,
+    aligned=...,
+    byteorder=...,
+    fill_value=...,
+): ...
+
+def fromrecords(
+    reclist,
+    dtype=...,
+    shape=...,
+    formats=...,
+    names=...,
+    titles=...,
+    aligned=...,
+    byteorder=...,
+    fill_value=...,
+    mask=...,
+): ...
+
+def fromtextfile(
+    fname,
+    delimiter=...,
+    commentchar=...,
+    missingchar=...,
+    varnames=...,
+    vartypes=...,
+    # NOTE: deprecated: NumPy 1.22.0, 2021-09-23
+    # delimitor=...,
+): ...
+
+def addfield(mrecord, newfield, newfieldname=...): ...
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/setup.py b/.venv/lib/python3.12/site-packages/numpy/ma/setup.py
new file mode 100644
index 00000000..018d38cd
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/setup.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python3
+def configuration(parent_package='',top_path=None):
+    from numpy.distutils.misc_util import Configuration
+    config = Configuration('ma', parent_package, top_path)
+    config.add_subpackage('tests')
+    config.add_data_files('*.pyi')
+    return config
+
+if __name__ == "__main__":
+    from numpy.distutils.core import setup
+    config = configuration(top_path='').todict()
+    setup(**config)
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/tests/__init__.py b/.venv/lib/python3.12/site-packages/numpy/ma/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/tests/__init__.py
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_core.py b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_core.py
new file mode 100644
index 00000000..08ddc46c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_core.py
@@ -0,0 +1,5687 @@
+# pylint: disable-msg=W0400,W0511,W0611,W0612,W0614,R0201,E1102
+"""Tests suite for MaskedArray & subclassing.
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+"""
+__author__ = "Pierre GF Gerard-Marchant"
+
+import sys
+import warnings
+import copy
+import operator
+import itertools
+import textwrap
+import pytest
+
+from functools import reduce
+
+
+import numpy as np
+import numpy.ma.core
+import numpy.core.fromnumeric as fromnumeric
+import numpy.core.umath as umath
+from numpy.testing import (
+    assert_raises, assert_warns, suppress_warnings, IS_WASM
+    )
+from numpy.testing._private.utils import requires_memory
+from numpy import ndarray
+from numpy.compat import asbytes
+from numpy.ma.testutils import (
+    assert_, assert_array_equal, assert_equal, assert_almost_equal,
+    assert_equal_records, fail_if_equal, assert_not_equal,
+    assert_mask_equal
+    )
+from numpy.ma.core import (
+    MAError, MaskError, MaskType, MaskedArray, abs, absolute, add, all,
+    allclose, allequal, alltrue, angle, anom, arange, arccos, arccosh, arctan2,
+    arcsin, arctan, argsort, array, asarray, choose, concatenate,
+    conjugate, cos, cosh, count, default_fill_value, diag, divide, doc_note,
+    empty, empty_like, equal, exp, flatten_mask, filled, fix_invalid,
+    flatten_structured_array, fromflex, getmask, getmaskarray, greater,
+    greater_equal, identity, inner, isMaskedArray, less, less_equal, log,
+    log10, make_mask, make_mask_descr, mask_or, masked, masked_array,
+    masked_equal, masked_greater, masked_greater_equal, masked_inside,
+    masked_less, masked_less_equal, masked_not_equal, masked_outside,
+    masked_print_option, masked_values, masked_where, max, maximum,
+    maximum_fill_value, min, minimum, minimum_fill_value, mod, multiply,
+    mvoid, nomask, not_equal, ones, ones_like, outer, power, product, put,
+    putmask, ravel, repeat, reshape, resize, shape, sin, sinh, sometrue, sort,
+    sqrt, subtract, sum, take, tan, tanh, transpose, where, zeros, zeros_like,
+    )
+from numpy.compat import pickle
+
+pi = np.pi
+
+
+suppress_copy_mask_on_assignment = suppress_warnings()
+suppress_copy_mask_on_assignment.filter(
+    numpy.ma.core.MaskedArrayFutureWarning,
+    "setting an item on a masked array which has a shared mask will not copy")
+
+
+# For parametrized numeric testing
+num_dts = [np.dtype(dt_) for dt_ in '?bhilqBHILQefdgFD']
+num_ids = [dt_.char for dt_ in num_dts]
+
+
+class TestMaskedArray:
+    # Base test class for MaskedArrays.
+
+    def setup_method(self):
+        # Base data definition.
+        x = np.array([1., 1., 1., -2., pi/2.0, 4., 5., -10., 10., 1., 2., 3.])
+        y = np.array([5., 0., 3., 2., -1., -4., 0., -10., 10., 1., 0., 3.])
+        a10 = 10.
+        m1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        m2 = [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1]
+        xm = masked_array(x, mask=m1)
+        ym = masked_array(y, mask=m2)
+        z = np.array([-.5, 0., .5, .8])
+        zm = masked_array(z, mask=[0, 1, 0, 0])
+        xf = np.where(m1, 1e+20, x)
+        xm.set_fill_value(1e+20)
+        self.d = (x, y, a10, m1, m2, xm, ym, z, zm, xf)
+
+    def test_basicattributes(self):
+        # Tests some basic array attributes.
+        a = array([1, 3, 2])
+        b = array([1, 3, 2], mask=[1, 0, 1])
+        assert_equal(a.ndim, 1)
+        assert_equal(b.ndim, 1)
+        assert_equal(a.size, 3)
+        assert_equal(b.size, 3)
+        assert_equal(a.shape, (3,))
+        assert_equal(b.shape, (3,))
+
+    def test_basic0d(self):
+        # Checks masking a scalar
+        x = masked_array(0)
+        assert_equal(str(x), '0')
+        x = masked_array(0, mask=True)
+        assert_equal(str(x), str(masked_print_option))
+        x = masked_array(0, mask=False)
+        assert_equal(str(x), '0')
+        x = array(0, mask=1)
+        assert_(x.filled().dtype is x._data.dtype)
+
+    def test_basic1d(self):
+        # Test of basic array creation and properties in 1 dimension.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        assert_(not isMaskedArray(x))
+        assert_(isMaskedArray(xm))
+        assert_((xm - ym).filled(0).any())
+        fail_if_equal(xm.mask.astype(int), ym.mask.astype(int))
+        s = x.shape
+        assert_equal(np.shape(xm), s)
+        assert_equal(xm.shape, s)
+        assert_equal(xm.dtype, x.dtype)
+        assert_equal(zm.dtype, z.dtype)
+        assert_equal(xm.size, reduce(lambda x, y:x * y, s))
+        assert_equal(count(xm), len(m1) - reduce(lambda x, y:x + y, m1))
+        assert_array_equal(xm, xf)
+        assert_array_equal(filled(xm, 1.e20), xf)
+        assert_array_equal(x, xm)
+
+    def test_basic2d(self):
+        # Test of basic array creation and properties in 2 dimensions.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        for s in [(4, 3), (6, 2)]:
+            x.shape = s
+            y.shape = s
+            xm.shape = s
+            ym.shape = s
+            xf.shape = s
+
+            assert_(not isMaskedArray(x))
+            assert_(isMaskedArray(xm))
+            assert_equal(shape(xm), s)
+            assert_equal(xm.shape, s)
+            assert_equal(xm.size, reduce(lambda x, y:x * y, s))
+            assert_equal(count(xm), len(m1) - reduce(lambda x, y:x + y, m1))
+            assert_equal(xm, xf)
+            assert_equal(filled(xm, 1.e20), xf)
+            assert_equal(x, xm)
+
+    def test_concatenate_basic(self):
+        # Tests concatenations.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        # basic concatenation
+        assert_equal(np.concatenate((x, y)), concatenate((xm, ym)))
+        assert_equal(np.concatenate((x, y)), concatenate((x, y)))
+        assert_equal(np.concatenate((x, y)), concatenate((xm, y)))
+        assert_equal(np.concatenate((x, y, x)), concatenate((x, ym, x)))
+
+    def test_concatenate_alongaxis(self):
+        # Tests concatenations.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        # Concatenation along an axis
+        s = (3, 4)
+        x.shape = y.shape = xm.shape = ym.shape = s
+        assert_equal(xm.mask, np.reshape(m1, s))
+        assert_equal(ym.mask, np.reshape(m2, s))
+        xmym = concatenate((xm, ym), 1)
+        assert_equal(np.concatenate((x, y), 1), xmym)
+        assert_equal(np.concatenate((xm.mask, ym.mask), 1), xmym._mask)
+
+        x = zeros(2)
+        y = array(ones(2), mask=[False, True])
+        z = concatenate((x, y))
+        assert_array_equal(z, [0, 0, 1, 1])
+        assert_array_equal(z.mask, [False, False, False, True])
+        z = concatenate((y, x))
+        assert_array_equal(z, [1, 1, 0, 0])
+        assert_array_equal(z.mask, [False, True, False, False])
+
+    def test_concatenate_flexible(self):
+        # Tests the concatenation on flexible arrays.
+        data = masked_array(list(zip(np.random.rand(10),
+                                     np.arange(10))),
+                            dtype=[('a', float), ('b', int)])
+
+        test = concatenate([data[:5], data[5:]])
+        assert_equal_records(test, data)
+
+    def test_creation_ndmin(self):
+        # Check the use of ndmin
+        x = array([1, 2, 3], mask=[1, 0, 0], ndmin=2)
+        assert_equal(x.shape, (1, 3))
+        assert_equal(x._data, [[1, 2, 3]])
+        assert_equal(x._mask, [[1, 0, 0]])
+
+    def test_creation_ndmin_from_maskedarray(self):
+        # Make sure we're not losing the original mask w/ ndmin
+        x = array([1, 2, 3])
+        x[-1] = masked
+        xx = array(x, ndmin=2, dtype=float)
+        assert_equal(x.shape, x._mask.shape)
+        assert_equal(xx.shape, xx._mask.shape)
+
+    def test_creation_maskcreation(self):
+        # Tests how masks are initialized at the creation of Maskedarrays.
+        data = arange(24, dtype=float)
+        data[[3, 6, 15]] = masked
+        dma_1 = MaskedArray(data)
+        assert_equal(dma_1.mask, data.mask)
+        dma_2 = MaskedArray(dma_1)
+        assert_equal(dma_2.mask, dma_1.mask)
+        dma_3 = MaskedArray(dma_1, mask=[1, 0, 0, 0] * 6)
+        fail_if_equal(dma_3.mask, dma_1.mask)
+
+        x = array([1, 2, 3], mask=True)
+        assert_equal(x._mask, [True, True, True])
+        x = array([1, 2, 3], mask=False)
+        assert_equal(x._mask, [False, False, False])
+        y = array([1, 2, 3], mask=x._mask, copy=False)
+        assert_(np.may_share_memory(x.mask, y.mask))
+        y = array([1, 2, 3], mask=x._mask, copy=True)
+        assert_(not np.may_share_memory(x.mask, y.mask))
+        x = array([1, 2, 3], mask=None)
+        assert_equal(x._mask, [False, False, False])
+
+    def test_masked_singleton_array_creation_warns(self):
+        # The first works, but should not (ideally), there may be no way
+        # to solve this, however, as long as `np.ma.masked` is an ndarray.
+        np.array(np.ma.masked)
+        with pytest.warns(UserWarning):
+            # Tries to create a float array, using `float(np.ma.masked)`.
+            # We may want to define this is invalid behaviour in the future!
+            # (requiring np.ma.masked to be a known NumPy scalar probably
+            # with a DType.)
+            np.array([3., np.ma.masked])
+
+    def test_creation_with_list_of_maskedarrays(self):
+        # Tests creating a masked array from a list of masked arrays.
+        x = array(np.arange(5), mask=[1, 0, 0, 0, 0])
+        data = array((x, x[::-1]))
+        assert_equal(data, [[0, 1, 2, 3, 4], [4, 3, 2, 1, 0]])
+        assert_equal(data._mask, [[1, 0, 0, 0, 0], [0, 0, 0, 0, 1]])
+
+        x.mask = nomask
+        data = array((x, x[::-1]))
+        assert_equal(data, [[0, 1, 2, 3, 4], [4, 3, 2, 1, 0]])
+        assert_(data.mask is nomask)
+
+    def test_creation_with_list_of_maskedarrays_no_bool_cast(self):
+        # Tests the regression in gh-18551
+        masked_str = np.ma.masked_array(['a', 'b'], mask=[True, False])
+        normal_int = np.arange(2)
+        res = np.ma.asarray([masked_str, normal_int], dtype="U21")
+        assert_array_equal(res.mask, [[True, False], [False, False]])
+
+        # The above only failed due a long chain of oddity, try also with
+        # an object array that cannot be converted to bool always:
+        class NotBool():
+            def __bool__(self):
+                raise ValueError("not a bool!")
+        masked_obj = np.ma.masked_array([NotBool(), 'b'], mask=[True, False])
+        # Check that the NotBool actually fails like we would expect:
+        with pytest.raises(ValueError, match="not a bool!"):
+            np.asarray([masked_obj], dtype=bool)
+
+        res = np.ma.asarray([masked_obj, normal_int])
+        assert_array_equal(res.mask, [[True, False], [False, False]])
+
+    def test_creation_from_ndarray_with_padding(self):
+        x = np.array([('A', 0)], dtype={'names':['f0','f1'],
+                                        'formats':['S4','i8'],
+                                        'offsets':[0,8]})
+        array(x)  # used to fail due to 'V' padding field in x.dtype.descr
+
+    def test_unknown_keyword_parameter(self):
+        with pytest.raises(TypeError, match="unexpected keyword argument"):
+            MaskedArray([1, 2, 3], maks=[0, 1, 0])  # `mask` is misspelled.
+
+    def test_asarray(self):
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        xm.fill_value = -9999
+        xm._hardmask = True
+        xmm = asarray(xm)
+        assert_equal(xmm._data, xm._data)
+        assert_equal(xmm._mask, xm._mask)
+        assert_equal(xmm.fill_value, xm.fill_value)
+        assert_equal(xmm._hardmask, xm._hardmask)
+
+    def test_asarray_default_order(self):
+        # See Issue #6646
+        m = np.eye(3).T
+        assert_(not m.flags.c_contiguous)
+
+        new_m = asarray(m)
+        assert_(new_m.flags.c_contiguous)
+
+    def test_asarray_enforce_order(self):
+        # See Issue #6646
+        m = np.eye(3).T
+        assert_(not m.flags.c_contiguous)
+
+        new_m = asarray(m, order='C')
+        assert_(new_m.flags.c_contiguous)
+
+    def test_fix_invalid(self):
+        # Checks fix_invalid.
+        with np.errstate(invalid='ignore'):
+            data = masked_array([np.nan, 0., 1.], mask=[0, 0, 1])
+            data_fixed = fix_invalid(data)
+            assert_equal(data_fixed._data, [data.fill_value, 0., 1.])
+            assert_equal(data_fixed._mask, [1., 0., 1.])
+
+    def test_maskedelement(self):
+        # Test of masked element
+        x = arange(6)
+        x[1] = masked
+        assert_(str(masked) == '--')
+        assert_(x[1] is masked)
+        assert_equal(filled(x[1], 0), 0)
+
+    def test_set_element_as_object(self):
+        # Tests setting elements with object
+        a = empty(1, dtype=object)
+        x = (1, 2, 3, 4, 5)
+        a[0] = x
+        assert_equal(a[0], x)
+        assert_(a[0] is x)
+
+        import datetime
+        dt = datetime.datetime.now()
+        a[0] = dt
+        assert_(a[0] is dt)
+
+    def test_indexing(self):
+        # Tests conversions and indexing
+        x1 = np.array([1, 2, 4, 3])
+        x2 = array(x1, mask=[1, 0, 0, 0])
+        x3 = array(x1, mask=[0, 1, 0, 1])
+        x4 = array(x1)
+        # test conversion to strings
+        str(x2)  # raises?
+        repr(x2)  # raises?
+        assert_equal(np.sort(x1), sort(x2, endwith=False))
+        # tests of indexing
+        assert_(type(x2[1]) is type(x1[1]))
+        assert_(x1[1] == x2[1])
+        assert_(x2[0] is masked)
+        assert_equal(x1[2], x2[2])
+        assert_equal(x1[2:5], x2[2:5])
+        assert_equal(x1[:], x2[:])
+        assert_equal(x1[1:], x3[1:])
+        x1[2] = 9
+        x2[2] = 9
+        assert_equal(x1, x2)
+        x1[1:3] = 99
+        x2[1:3] = 99
+        assert_equal(x1, x2)
+        x2[1] = masked
+        assert_equal(x1, x2)
+        x2[1:3] = masked
+        assert_equal(x1, x2)
+        x2[:] = x1
+        x2[1] = masked
+        assert_(allequal(getmask(x2), array([0, 1, 0, 0])))
+        x3[:] = masked_array([1, 2, 3, 4], [0, 1, 1, 0])
+        assert_(allequal(getmask(x3), array([0, 1, 1, 0])))
+        x4[:] = masked_array([1, 2, 3, 4], [0, 1, 1, 0])
+        assert_(allequal(getmask(x4), array([0, 1, 1, 0])))
+        assert_(allequal(x4, array([1, 2, 3, 4])))
+        x1 = np.arange(5) * 1.0
+        x2 = masked_values(x1, 3.0)
+        assert_equal(x1, x2)
+        assert_(allequal(array([0, 0, 0, 1, 0], MaskType), x2.mask))
+        assert_equal(3.0, x2.fill_value)
+        x1 = array([1, 'hello', 2, 3], object)
+        x2 = np.array([1, 'hello', 2, 3], object)
+        s1 = x1[1]
+        s2 = x2[1]
+        assert_equal(type(s2), str)
+        assert_equal(type(s1), str)
+        assert_equal(s1, s2)
+        assert_(x1[1:1].shape == (0,))
+
+    def test_setitem_no_warning(self):
+        # Setitem shouldn't warn, because the assignment might be masked
+        # and warning for a masked assignment is weird (see gh-23000)
+        # (When the value is masked, otherwise a warning would be acceptable
+        # but is not given currently.)
+        x = np.ma.arange(60).reshape((6, 10))
+        index = (slice(1, 5, 2), [7, 5])
+        value = np.ma.masked_all((2, 2))
+        value._data[...] = np.inf  # not a valid integer...
+        x[index] = value
+        # The masked scalar is special cased, but test anyway (it's NaN):
+        x[...] = np.ma.masked
+        # Finally, a large value that cannot be cast to the float32 `x`
+        x = np.ma.arange(3., dtype=np.float32)
+        value = np.ma.array([2e234, 1, 1], mask=[True, False, False])
+        x[...] = value
+        x[[0, 1, 2]] = value
+
+    @suppress_copy_mask_on_assignment
+    def test_copy(self):
+        # Tests of some subtle points of copying and sizing.
+        n = [0, 0, 1, 0, 0]
+        m = make_mask(n)
+        m2 = make_mask(m)
+        assert_(m is m2)
+        m3 = make_mask(m, copy=True)
+        assert_(m is not m3)
+
+        x1 = np.arange(5)
+        y1 = array(x1, mask=m)
+        assert_equal(y1._data.__array_interface__, x1.__array_interface__)
+        assert_(allequal(x1, y1.data))
+        assert_equal(y1._mask.__array_interface__, m.__array_interface__)
+
+        y1a = array(y1)
+        # Default for masked array is not to copy; see gh-10318.
+        assert_(y1a._data.__array_interface__ ==
+                        y1._data.__array_interface__)
+        assert_(y1a._mask.__array_interface__ ==
+                        y1._mask.__array_interface__)
+
+        y2 = array(x1, mask=m3)
+        assert_(y2._data.__array_interface__ == x1.__array_interface__)
+        assert_(y2._mask.__array_interface__ == m3.__array_interface__)
+        assert_(y2[2] is masked)
+        y2[2] = 9
+        assert_(y2[2] is not masked)
+        assert_(y2._mask.__array_interface__ == m3.__array_interface__)
+        assert_(allequal(y2.mask, 0))
+
+        y2a = array(x1, mask=m, copy=1)
+        assert_(y2a._data.__array_interface__ != x1.__array_interface__)
+        #assert_( y2a._mask is not m)
+        assert_(y2a._mask.__array_interface__ != m.__array_interface__)
+        assert_(y2a[2] is masked)
+        y2a[2] = 9
+        assert_(y2a[2] is not masked)
+        #assert_( y2a._mask is not m)
+        assert_(y2a._mask.__array_interface__ != m.__array_interface__)
+        assert_(allequal(y2a.mask, 0))
+
+        y3 = array(x1 * 1.0, mask=m)
+        assert_(filled(y3).dtype is (x1 * 1.0).dtype)
+
+        x4 = arange(4)
+        x4[2] = masked
+        y4 = resize(x4, (8,))
+        assert_equal(concatenate([x4, x4]), y4)
+        assert_equal(getmask(y4), [0, 0, 1, 0, 0, 0, 1, 0])
+        y5 = repeat(x4, (2, 2, 2, 2), axis=0)
+        assert_equal(y5, [0, 0, 1, 1, 2, 2, 3, 3])
+        y6 = repeat(x4, 2, axis=0)
+        assert_equal(y5, y6)
+        y7 = x4.repeat((2, 2, 2, 2), axis=0)
+        assert_equal(y5, y7)
+        y8 = x4.repeat(2, 0)
+        assert_equal(y5, y8)
+
+        y9 = x4.copy()
+        assert_equal(y9._data, x4._data)
+        assert_equal(y9._mask, x4._mask)
+
+        x = masked_array([1, 2, 3], mask=[0, 1, 0])
+        # Copy is False by default
+        y = masked_array(x)
+        assert_equal(y._data.ctypes.data, x._data.ctypes.data)
+        assert_equal(y._mask.ctypes.data, x._mask.ctypes.data)
+        y = masked_array(x, copy=True)
+        assert_not_equal(y._data.ctypes.data, x._data.ctypes.data)
+        assert_not_equal(y._mask.ctypes.data, x._mask.ctypes.data)
+
+    def test_copy_0d(self):
+        # gh-9430
+        x = np.ma.array(43, mask=True)
+        xc = x.copy()
+        assert_equal(xc.mask, True)
+
+    def test_copy_on_python_builtins(self):
+        # Tests copy works on python builtins (issue#8019)
+        assert_(isMaskedArray(np.ma.copy([1,2,3])))
+        assert_(isMaskedArray(np.ma.copy((1,2,3))))
+
+    def test_copy_immutable(self):
+        # Tests that the copy method is immutable, GitHub issue #5247
+        a = np.ma.array([1, 2, 3])
+        b = np.ma.array([4, 5, 6])
+        a_copy_method = a.copy
+        b.copy
+        assert_equal(a_copy_method(), [1, 2, 3])
+
+    def test_deepcopy(self):
+        from copy import deepcopy
+        a = array([0, 1, 2], mask=[False, True, False])
+        copied = deepcopy(a)
+        assert_equal(copied.mask, a.mask)
+        assert_not_equal(id(a._mask), id(copied._mask))
+
+        copied[1] = 1
+        assert_equal(copied.mask, [0, 0, 0])
+        assert_equal(a.mask, [0, 1, 0])
+
+        copied = deepcopy(a)
+        assert_equal(copied.mask, a.mask)
+        copied.mask[1] = False
+        assert_equal(copied.mask, [0, 0, 0])
+        assert_equal(a.mask, [0, 1, 0])
+
+    def test_format(self):
+        a = array([0, 1, 2], mask=[False, True, False])
+        assert_equal(format(a), "[0 -- 2]")
+        assert_equal(format(masked), "--")
+        assert_equal(format(masked, ""), "--")
+
+        # Postponed from PR #15410, perhaps address in the future.
+        # assert_equal(format(masked, " >5"), "   --")
+        # assert_equal(format(masked, " <5"), "--   ")
+
+        # Expect a FutureWarning for using format_spec with MaskedElement
+        with assert_warns(FutureWarning):
+            with_format_string = format(masked, " >5")
+        assert_equal(with_format_string, "--")
+
+    def test_str_repr(self):
+        a = array([0, 1, 2], mask=[False, True, False])
+        assert_equal(str(a), '[0 -- 2]')
+        assert_equal(
+            repr(a),
+            textwrap.dedent('''\
+            masked_array(data=[0, --, 2],
+                         mask=[False,  True, False],
+                   fill_value=999999)''')
+        )
+
+        # arrays with a continuation
+        a = np.ma.arange(2000)
+        a[1:50] = np.ma.masked
+        assert_equal(
+            repr(a),
+            textwrap.dedent('''\
+            masked_array(data=[0, --, --, ..., 1997, 1998, 1999],
+                         mask=[False,  True,  True, ..., False, False, False],
+                   fill_value=999999)''')
+        )
+
+        # line-wrapped 1d arrays are correctly aligned
+        a = np.ma.arange(20)
+        assert_equal(
+            repr(a),
+            textwrap.dedent('''\
+            masked_array(data=[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,
+                               14, 15, 16, 17, 18, 19],
+                         mask=False,
+                   fill_value=999999)''')
+        )
+
+        # 2d arrays cause wrapping
+        a = array([[1, 2, 3], [4, 5, 6]], dtype=np.int8)
+        a[1,1] = np.ma.masked
+        assert_equal(
+            repr(a),
+            textwrap.dedent('''\
+            masked_array(
+              data=[[1, 2, 3],
+                    [4, --, 6]],
+              mask=[[False, False, False],
+                    [False,  True, False]],
+              fill_value=999999,
+              dtype=int8)''')
+        )
+
+        # but not it they're a row vector
+        assert_equal(
+            repr(a[:1]),
+            textwrap.dedent('''\
+            masked_array(data=[[1, 2, 3]],
+                         mask=[[False, False, False]],
+                   fill_value=999999,
+                        dtype=int8)''')
+        )
+
+        # dtype=int is implied, so not shown
+        assert_equal(
+            repr(a.astype(int)),
+            textwrap.dedent('''\
+            masked_array(
+              data=[[1, 2, 3],
+                    [4, --, 6]],
+              mask=[[False, False, False],
+                    [False,  True, False]],
+              fill_value=999999)''')
+        )
+
+    def test_str_repr_legacy(self):
+        oldopts = np.get_printoptions()
+        np.set_printoptions(legacy='1.13')
+        try:
+            a = array([0, 1, 2], mask=[False, True, False])
+            assert_equal(str(a), '[0 -- 2]')
+            assert_equal(repr(a), 'masked_array(data = [0 -- 2],\n'
+                                  '             mask = [False  True False],\n'
+                                  '       fill_value = 999999)\n')
+
+            a = np.ma.arange(2000)
+            a[1:50] = np.ma.masked
+            assert_equal(
+                repr(a),
+                'masked_array(data = [0 -- -- ..., 1997 1998 1999],\n'
+                '             mask = [False  True  True ..., False False False],\n'
+                '       fill_value = 999999)\n'
+            )
+        finally:
+            np.set_printoptions(**oldopts)
+
+    def test_0d_unicode(self):
+        u = 'caf\xe9'
+        utype = type(u)
+
+        arr_nomask = np.ma.array(u)
+        arr_masked = np.ma.array(u, mask=True)
+
+        assert_equal(utype(arr_nomask), u)
+        assert_equal(utype(arr_masked), '--')
+
+    def test_pickling(self):
+        # Tests pickling
+        for dtype in (int, float, str, object):
+            a = arange(10).astype(dtype)
+            a.fill_value = 999
+
+            masks = ([0, 0, 0, 1, 0, 1, 0, 1, 0, 1],  # partially masked
+                     True,                            # Fully masked
+                     False)                           # Fully unmasked
+
+            for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
+                for mask in masks:
+                    a.mask = mask
+                    a_pickled = pickle.loads(pickle.dumps(a, protocol=proto))
+                    assert_equal(a_pickled._mask, a._mask)
+                    assert_equal(a_pickled._data, a._data)
+                    if dtype in (object, int):
+                        assert_equal(a_pickled.fill_value, 999)
+                    else:
+                        assert_equal(a_pickled.fill_value, dtype(999))
+                    assert_array_equal(a_pickled.mask, mask)
+
+    def test_pickling_subbaseclass(self):
+        # Test pickling w/ a subclass of ndarray
+        x = np.array([(1.0, 2), (3.0, 4)],
+                     dtype=[('x', float), ('y', int)]).view(np.recarray)
+        a = masked_array(x, mask=[(True, False), (False, True)])
+        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
+            a_pickled = pickle.loads(pickle.dumps(a, protocol=proto))
+            assert_equal(a_pickled._mask, a._mask)
+            assert_equal(a_pickled, a)
+            assert_(isinstance(a_pickled._data, np.recarray))
+
+    def test_pickling_maskedconstant(self):
+        # Test pickling MaskedConstant
+        mc = np.ma.masked
+        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
+            mc_pickled = pickle.loads(pickle.dumps(mc, protocol=proto))
+            assert_equal(mc_pickled._baseclass, mc._baseclass)
+            assert_equal(mc_pickled._mask, mc._mask)
+            assert_equal(mc_pickled._data, mc._data)
+
+    def test_pickling_wstructured(self):
+        # Tests pickling w/ structured array
+        a = array([(1, 1.), (2, 2.)], mask=[(0, 0), (0, 1)],
+                  dtype=[('a', int), ('b', float)])
+        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
+            a_pickled = pickle.loads(pickle.dumps(a, protocol=proto))
+            assert_equal(a_pickled._mask, a._mask)
+            assert_equal(a_pickled, a)
+
+    def test_pickling_keepalignment(self):
+        # Tests pickling w/ F_CONTIGUOUS arrays
+        a = arange(10)
+        a.shape = (-1, 2)
+        b = a.T
+        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
+            test = pickle.loads(pickle.dumps(b, protocol=proto))
+            assert_equal(test, b)
+
+    def test_single_element_subscript(self):
+        # Tests single element subscripts of Maskedarrays.
+        a = array([1, 3, 2])
+        b = array([1, 3, 2], mask=[1, 0, 1])
+        assert_equal(a[0].shape, ())
+        assert_equal(b[0].shape, ())
+        assert_equal(b[1].shape, ())
+
+    def test_topython(self):
+        # Tests some communication issues with Python.
+        assert_equal(1, int(array(1)))
+        assert_equal(1.0, float(array(1)))
+        assert_equal(1, int(array([[[1]]])))
+        assert_equal(1.0, float(array([[1]])))
+        assert_raises(TypeError, float, array([1, 1]))
+
+        with suppress_warnings() as sup:
+            sup.filter(UserWarning, 'Warning: converting a masked element')
+            assert_(np.isnan(float(array([1], mask=[1]))))
+
+            a = array([1, 2, 3], mask=[1, 0, 0])
+            assert_raises(TypeError, lambda: float(a))
+            assert_equal(float(a[-1]), 3.)
+            assert_(np.isnan(float(a[0])))
+        assert_raises(TypeError, int, a)
+        assert_equal(int(a[-1]), 3)
+        assert_raises(MAError, lambda:int(a[0]))
+
+    def test_oddfeatures_1(self):
+        # Test of other odd features
+        x = arange(20)
+        x = x.reshape(4, 5)
+        x.flat[5] = 12
+        assert_(x[1, 0] == 12)
+        z = x + 10j * x
+        assert_equal(z.real, x)
+        assert_equal(z.imag, 10 * x)
+        assert_equal((z * conjugate(z)).real, 101 * x * x)
+        z.imag[...] = 0.0
+
+        x = arange(10)
+        x[3] = masked
+        assert_(str(x[3]) == str(masked))
+        c = x >= 8
+        assert_(count(where(c, masked, masked)) == 0)
+        assert_(shape(where(c, masked, masked)) == c.shape)
+
+        z = masked_where(c, x)
+        assert_(z.dtype is x.dtype)
+        assert_(z[3] is masked)
+        assert_(z[4] is not masked)
+        assert_(z[7] is not masked)
+        assert_(z[8] is masked)
+        assert_(z[9] is masked)
+        assert_equal(x, z)
+
+    def test_oddfeatures_2(self):
+        # Tests some more features.
+        x = array([1., 2., 3., 4., 5.])
+        c = array([1, 1, 1, 0, 0])
+        x[2] = masked
+        z = where(c, x, -x)
+        assert_equal(z, [1., 2., 0., -4., -5])
+        c[0] = masked
+        z = where(c, x, -x)
+        assert_equal(z, [1., 2., 0., -4., -5])
+        assert_(z[0] is masked)
+        assert_(z[1] is not masked)
+        assert_(z[2] is masked)
+
+    @suppress_copy_mask_on_assignment
+    def test_oddfeatures_3(self):
+        # Tests some generic features
+        atest = array([10], mask=True)
+        btest = array([20])
+        idx = atest.mask
+        atest[idx] = btest[idx]
+        assert_equal(atest, [20])
+
+    def test_filled_with_object_dtype(self):
+        a = np.ma.masked_all(1, dtype='O')
+        assert_equal(a.filled('x')[0], 'x')
+
+    def test_filled_with_flexible_dtype(self):
+        # Test filled w/ flexible dtype
+        flexi = array([(1, 1, 1)],
+                      dtype=[('i', int), ('s', '|S8'), ('f', float)])
+        flexi[0] = masked
+        assert_equal(flexi.filled(),
+                     np.array([(default_fill_value(0),
+                                default_fill_value('0'),
+                                default_fill_value(0.),)], dtype=flexi.dtype))
+        flexi[0] = masked
+        assert_equal(flexi.filled(1),
+                     np.array([(1, '1', 1.)], dtype=flexi.dtype))
+
+    def test_filled_with_mvoid(self):
+        # Test filled w/ mvoid
+        ndtype = [('a', int), ('b', float)]
+        a = mvoid((1, 2.), mask=[(0, 1)], dtype=ndtype)
+        # Filled using default
+        test = a.filled()
+        assert_equal(tuple(test), (1, default_fill_value(1.)))
+        # Explicit fill_value
+        test = a.filled((-1, -1))
+        assert_equal(tuple(test), (1, -1))
+        # Using predefined filling values
+        a.fill_value = (-999, -999)
+        assert_equal(tuple(a.filled()), (1, -999))
+
+    def test_filled_with_nested_dtype(self):
+        # Test filled w/ nested dtype
+        ndtype = [('A', int), ('B', [('BA', int), ('BB', int)])]
+        a = array([(1, (1, 1)), (2, (2, 2))],
+                  mask=[(0, (1, 0)), (0, (0, 1))], dtype=ndtype)
+        test = a.filled(0)
+        control = np.array([(1, (0, 1)), (2, (2, 0))], dtype=ndtype)
+        assert_equal(test, control)
+
+        test = a['B'].filled(0)
+        control = np.array([(0, 1), (2, 0)], dtype=a['B'].dtype)
+        assert_equal(test, control)
+
+        # test if mask gets set correctly (see #6760)
+        Z = numpy.ma.zeros(2, numpy.dtype([("A", "(2,2)i1,(2,2)i1", (2,2))]))
+        assert_equal(Z.data.dtype, numpy.dtype([('A', [('f0', 'i1', (2, 2)),
+                                          ('f1', 'i1', (2, 2))], (2, 2))]))
+        assert_equal(Z.mask.dtype, numpy.dtype([('A', [('f0', '?', (2, 2)),
+                                          ('f1', '?', (2, 2))], (2, 2))]))
+
+    def test_filled_with_f_order(self):
+        # Test filled w/ F-contiguous array
+        a = array(np.array([(0, 1, 2), (4, 5, 6)], order='F'),
+                  mask=np.array([(0, 0, 1), (1, 0, 0)], order='F'),
+                  order='F')  # this is currently ignored
+        assert_(a.flags['F_CONTIGUOUS'])
+        assert_(a.filled(0).flags['F_CONTIGUOUS'])
+
+    def test_optinfo_propagation(self):
+        # Checks that _optinfo dictionary isn't back-propagated
+        x = array([1, 2, 3, ], dtype=float)
+        x._optinfo['info'] = '???'
+        y = x.copy()
+        assert_equal(y._optinfo['info'], '???')
+        y._optinfo['info'] = '!!!'
+        assert_equal(x._optinfo['info'], '???')
+
+    def test_optinfo_forward_propagation(self):
+        a = array([1,2,2,4])
+        a._optinfo["key"] = "value"
+        assert_equal(a._optinfo["key"], (a == 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], (a != 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], (a > 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], (a >= 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], (a <= 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], (a + 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], (a - 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], (a * 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], (a / 2)._optinfo["key"])
+        assert_equal(a._optinfo["key"], a[:2]._optinfo["key"])
+        assert_equal(a._optinfo["key"], a[[0,0,2]]._optinfo["key"])
+        assert_equal(a._optinfo["key"], np.exp(a)._optinfo["key"])
+        assert_equal(a._optinfo["key"], np.abs(a)._optinfo["key"])
+        assert_equal(a._optinfo["key"], array(a, copy=True)._optinfo["key"])
+        assert_equal(a._optinfo["key"], np.zeros_like(a)._optinfo["key"])
+
+    def test_fancy_printoptions(self):
+        # Test printing a masked array w/ fancy dtype.
+        fancydtype = np.dtype([('x', int), ('y', [('t', int), ('s', float)])])
+        test = array([(1, (2, 3.0)), (4, (5, 6.0))],
+                     mask=[(1, (0, 1)), (0, (1, 0))],
+                     dtype=fancydtype)
+        control = "[(--, (2, --)) (4, (--, 6.0))]"
+        assert_equal(str(test), control)
+
+        # Test 0-d array with multi-dimensional dtype
+        t_2d0 = masked_array(data = (0, [[0.0, 0.0, 0.0],
+                                        [0.0, 0.0, 0.0]],
+                                    0.0),
+                             mask = (False, [[True, False, True],
+                                             [False, False, True]],
+                                     False),
+                             dtype = "int, (2,3)float, float")
+        control = "(0, [[--, 0.0, --], [0.0, 0.0, --]], 0.0)"
+        assert_equal(str(t_2d0), control)
+
+    def test_flatten_structured_array(self):
+        # Test flatten_structured_array on arrays
+        # On ndarray
+        ndtype = [('a', int), ('b', float)]
+        a = np.array([(1, 1), (2, 2)], dtype=ndtype)
+        test = flatten_structured_array(a)
+        control = np.array([[1., 1.], [2., 2.]], dtype=float)
+        assert_equal(test, control)
+        assert_equal(test.dtype, control.dtype)
+        # On masked_array
+        a = array([(1, 1), (2, 2)], mask=[(0, 1), (1, 0)], dtype=ndtype)
+        test = flatten_structured_array(a)
+        control = array([[1., 1.], [2., 2.]],
+                        mask=[[0, 1], [1, 0]], dtype=float)
+        assert_equal(test, control)
+        assert_equal(test.dtype, control.dtype)
+        assert_equal(test.mask, control.mask)
+        # On masked array with nested structure
+        ndtype = [('a', int), ('b', [('ba', int), ('bb', float)])]
+        a = array([(1, (1, 1.1)), (2, (2, 2.2))],
+                  mask=[(0, (1, 0)), (1, (0, 1))], dtype=ndtype)
+        test = flatten_structured_array(a)
+        control = array([[1., 1., 1.1], [2., 2., 2.2]],
+                        mask=[[0, 1, 0], [1, 0, 1]], dtype=float)
+        assert_equal(test, control)
+        assert_equal(test.dtype, control.dtype)
+        assert_equal(test.mask, control.mask)
+        # Keeping the initial shape
+        ndtype = [('a', int), ('b', float)]
+        a = np.array([[(1, 1), ], [(2, 2), ]], dtype=ndtype)
+        test = flatten_structured_array(a)
+        control = np.array([[[1., 1.], ], [[2., 2.], ]], dtype=float)
+        assert_equal(test, control)
+        assert_equal(test.dtype, control.dtype)
+
+    def test_void0d(self):
+        # Test creating a mvoid object
+        ndtype = [('a', int), ('b', int)]
+        a = np.array([(1, 2,)], dtype=ndtype)[0]
+        f = mvoid(a)
+        assert_(isinstance(f, mvoid))
+
+        a = masked_array([(1, 2)], mask=[(1, 0)], dtype=ndtype)[0]
+        assert_(isinstance(a, mvoid))
+
+        a = masked_array([(1, 2), (1, 2)], mask=[(1, 0), (0, 0)], dtype=ndtype)
+        f = mvoid(a._data[0], a._mask[0])
+        assert_(isinstance(f, mvoid))
+
+    def test_mvoid_getitem(self):
+        # Test mvoid.__getitem__
+        ndtype = [('a', int), ('b', int)]
+        a = masked_array([(1, 2,), (3, 4)], mask=[(0, 0), (1, 0)],
+                         dtype=ndtype)
+        # w/o mask
+        f = a[0]
+        assert_(isinstance(f, mvoid))
+        assert_equal((f[0], f['a']), (1, 1))
+        assert_equal(f['b'], 2)
+        # w/ mask
+        f = a[1]
+        assert_(isinstance(f, mvoid))
+        assert_(f[0] is masked)
+        assert_(f['a'] is masked)
+        assert_equal(f[1], 4)
+
+        # exotic dtype
+        A = masked_array(data=[([0,1],)],
+                         mask=[([True, False],)],
+                         dtype=[("A", ">i2", (2,))])
+        assert_equal(A[0]["A"], A["A"][0])
+        assert_equal(A[0]["A"], masked_array(data=[0, 1],
+                         mask=[True, False], dtype=">i2"))
+
+    def test_mvoid_iter(self):
+        # Test iteration on __getitem__
+        ndtype = [('a', int), ('b', int)]
+        a = masked_array([(1, 2,), (3, 4)], mask=[(0, 0), (1, 0)],
+                         dtype=ndtype)
+        # w/o mask
+        assert_equal(list(a[0]), [1, 2])
+        # w/ mask
+        assert_equal(list(a[1]), [masked, 4])
+
+    def test_mvoid_print(self):
+        # Test printing a mvoid
+        mx = array([(1, 1), (2, 2)], dtype=[('a', int), ('b', int)])
+        assert_equal(str(mx[0]), "(1, 1)")
+        mx['b'][0] = masked
+        ini_display = masked_print_option._display
+        masked_print_option.set_display("-X-")
+        try:
+            assert_equal(str(mx[0]), "(1, -X-)")
+            assert_equal(repr(mx[0]), "(1, -X-)")
+        finally:
+            masked_print_option.set_display(ini_display)
+
+        # also check if there are object datatypes (see gh-7493)
+        mx = array([(1,), (2,)], dtype=[('a', 'O')])
+        assert_equal(str(mx[0]), "(1,)")
+
+    def test_mvoid_multidim_print(self):
+
+        # regression test for gh-6019
+        t_ma = masked_array(data = [([1, 2, 3],)],
+                            mask = [([False, True, False],)],
+                            fill_value = ([999999, 999999, 999999],),
+                            dtype = [('a', '<i4', (3,))])
+        assert_(str(t_ma[0]) == "([1, --, 3],)")
+        assert_(repr(t_ma[0]) == "([1, --, 3],)")
+
+        # additional tests with structured arrays
+
+        t_2d = masked_array(data = [([[1, 2], [3,4]],)],
+                            mask = [([[False, True], [True, False]],)],
+                            dtype = [('a', '<i4', (2,2))])
+        assert_(str(t_2d[0]) == "([[1, --], [--, 4]],)")
+        assert_(repr(t_2d[0]) == "([[1, --], [--, 4]],)")
+
+        t_0d = masked_array(data = [(1,2)],
+                            mask = [(True,False)],
+                            dtype = [('a', '<i4'), ('b', '<i4')])
+        assert_(str(t_0d[0]) == "(--, 2)")
+        assert_(repr(t_0d[0]) == "(--, 2)")
+
+        t_2d = masked_array(data = [([[1, 2], [3,4]], 1)],
+                            mask = [([[False, True], [True, False]], False)],
+                            dtype = [('a', '<i4', (2,2)), ('b', float)])
+        assert_(str(t_2d[0]) == "([[1, --], [--, 4]], 1.0)")
+        assert_(repr(t_2d[0]) == "([[1, --], [--, 4]], 1.0)")
+
+        t_ne = masked_array(data=[(1, (1, 1))],
+                            mask=[(True, (True, False))],
+                            dtype = [('a', '<i4'), ('b', 'i4,i4')])
+        assert_(str(t_ne[0]) == "(--, (--, 1))")
+        assert_(repr(t_ne[0]) == "(--, (--, 1))")
+
+    def test_object_with_array(self):
+        mx1 = masked_array([1.], mask=[True])
+        mx2 = masked_array([1., 2.])
+        mx = masked_array([mx1, mx2], mask=[False, True], dtype=object)
+        assert_(mx[0] is mx1)
+        assert_(mx[1] is not mx2)
+        assert_(np.all(mx[1].data == mx2.data))
+        assert_(np.all(mx[1].mask))
+        # check that we return a view.
+        mx[1].data[0] = 0.
+        assert_(mx2[0] == 0.)
+
+
+class TestMaskedArrayArithmetic:
+    # Base test class for MaskedArrays.
+
+    def setup_method(self):
+        # Base data definition.
+        x = np.array([1., 1., 1., -2., pi/2.0, 4., 5., -10., 10., 1., 2., 3.])
+        y = np.array([5., 0., 3., 2., -1., -4., 0., -10., 10., 1., 0., 3.])
+        a10 = 10.
+        m1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        m2 = [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1]
+        xm = masked_array(x, mask=m1)
+        ym = masked_array(y, mask=m2)
+        z = np.array([-.5, 0., .5, .8])
+        zm = masked_array(z, mask=[0, 1, 0, 0])
+        xf = np.where(m1, 1e+20, x)
+        xm.set_fill_value(1e+20)
+        self.d = (x, y, a10, m1, m2, xm, ym, z, zm, xf)
+        self.err_status = np.geterr()
+        np.seterr(divide='ignore', invalid='ignore')
+
+    def teardown_method(self):
+        np.seterr(**self.err_status)
+
+    def test_basic_arithmetic(self):
+        # Test of basic arithmetic.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        a2d = array([[1, 2], [0, 4]])
+        a2dm = masked_array(a2d, [[0, 0], [1, 0]])
+        assert_equal(a2d * a2d, a2d * a2dm)
+        assert_equal(a2d + a2d, a2d + a2dm)
+        assert_equal(a2d - a2d, a2d - a2dm)
+        for s in [(12,), (4, 3), (2, 6)]:
+            x = x.reshape(s)
+            y = y.reshape(s)
+            xm = xm.reshape(s)
+            ym = ym.reshape(s)
+            xf = xf.reshape(s)
+            assert_equal(-x, -xm)
+            assert_equal(x + y, xm + ym)
+            assert_equal(x - y, xm - ym)
+            assert_equal(x * y, xm * ym)
+            assert_equal(x / y, xm / ym)
+            assert_equal(a10 + y, a10 + ym)
+            assert_equal(a10 - y, a10 - ym)
+            assert_equal(a10 * y, a10 * ym)
+            assert_equal(a10 / y, a10 / ym)
+            assert_equal(x + a10, xm + a10)
+            assert_equal(x - a10, xm - a10)
+            assert_equal(x * a10, xm * a10)
+            assert_equal(x / a10, xm / a10)
+            assert_equal(x ** 2, xm ** 2)
+            assert_equal(abs(x) ** 2.5, abs(xm) ** 2.5)
+            assert_equal(x ** y, xm ** ym)
+            assert_equal(np.add(x, y), add(xm, ym))
+            assert_equal(np.subtract(x, y), subtract(xm, ym))
+            assert_equal(np.multiply(x, y), multiply(xm, ym))
+            assert_equal(np.divide(x, y), divide(xm, ym))
+
+    def test_divide_on_different_shapes(self):
+        x = arange(6, dtype=float)
+        x.shape = (2, 3)
+        y = arange(3, dtype=float)
+
+        z = x / y
+        assert_equal(z, [[-1., 1., 1.], [-1., 4., 2.5]])
+        assert_equal(z.mask, [[1, 0, 0], [1, 0, 0]])
+
+        z = x / y[None,:]
+        assert_equal(z, [[-1., 1., 1.], [-1., 4., 2.5]])
+        assert_equal(z.mask, [[1, 0, 0], [1, 0, 0]])
+
+        y = arange(2, dtype=float)
+        z = x / y[:, None]
+        assert_equal(z, [[-1., -1., -1.], [3., 4., 5.]])
+        assert_equal(z.mask, [[1, 1, 1], [0, 0, 0]])
+
+    def test_mixed_arithmetic(self):
+        # Tests mixed arithmetic.
+        na = np.array([1])
+        ma = array([1])
+        assert_(isinstance(na + ma, MaskedArray))
+        assert_(isinstance(ma + na, MaskedArray))
+
+    def test_limits_arithmetic(self):
+        tiny = np.finfo(float).tiny
+        a = array([tiny, 1. / tiny, 0.])
+        assert_equal(getmaskarray(a / 2), [0, 0, 0])
+        assert_equal(getmaskarray(2 / a), [1, 0, 1])
+
+    def test_masked_singleton_arithmetic(self):
+        # Tests some scalar arithmetic on MaskedArrays.
+        # Masked singleton should remain masked no matter what
+        xm = array(0, mask=1)
+        assert_((1 / array(0)).mask)
+        assert_((1 + xm).mask)
+        assert_((-xm).mask)
+        assert_(maximum(xm, xm).mask)
+        assert_(minimum(xm, xm).mask)
+
+    def test_masked_singleton_equality(self):
+        # Tests (in)equality on masked singleton
+        a = array([1, 2, 3], mask=[1, 1, 0])
+        assert_((a[0] == 0) is masked)
+        assert_((a[0] != 0) is masked)
+        assert_equal((a[-1] == 0), False)
+        assert_equal((a[-1] != 0), True)
+
+    def test_arithmetic_with_masked_singleton(self):
+        # Checks that there's no collapsing to masked
+        x = masked_array([1, 2])
+        y = x * masked
+        assert_equal(y.shape, x.shape)
+        assert_equal(y._mask, [True, True])
+        y = x[0] * masked
+        assert_(y is masked)
+        y = x + masked
+        assert_equal(y.shape, x.shape)
+        assert_equal(y._mask, [True, True])
+
+    def test_arithmetic_with_masked_singleton_on_1d_singleton(self):
+        # Check that we're not losing the shape of a singleton
+        x = masked_array([1, ])
+        y = x + masked
+        assert_equal(y.shape, x.shape)
+        assert_equal(y.mask, [True, ])
+
+    def test_scalar_arithmetic(self):
+        x = array(0, mask=0)
+        assert_equal(x.filled().ctypes.data, x.ctypes.data)
+        # Make sure we don't lose the shape in some circumstances
+        xm = array((0, 0)) / 0.
+        assert_equal(xm.shape, (2,))
+        assert_equal(xm.mask, [1, 1])
+
+    def test_basic_ufuncs(self):
+        # Test various functions such as sin, cos.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        assert_equal(np.cos(x), cos(xm))
+        assert_equal(np.cosh(x), cosh(xm))
+        assert_equal(np.sin(x), sin(xm))
+        assert_equal(np.sinh(x), sinh(xm))
+        assert_equal(np.tan(x), tan(xm))
+        assert_equal(np.tanh(x), tanh(xm))
+        assert_equal(np.sqrt(abs(x)), sqrt(xm))
+        assert_equal(np.log(abs(x)), log(xm))
+        assert_equal(np.log10(abs(x)), log10(xm))
+        assert_equal(np.exp(x), exp(xm))
+        assert_equal(np.arcsin(z), arcsin(zm))
+        assert_equal(np.arccos(z), arccos(zm))
+        assert_equal(np.arctan(z), arctan(zm))
+        assert_equal(np.arctan2(x, y), arctan2(xm, ym))
+        assert_equal(np.absolute(x), absolute(xm))
+        assert_equal(np.angle(x + 1j*y), angle(xm + 1j*ym))
+        assert_equal(np.angle(x + 1j*y, deg=True), angle(xm + 1j*ym, deg=True))
+        assert_equal(np.equal(x, y), equal(xm, ym))
+        assert_equal(np.not_equal(x, y), not_equal(xm, ym))
+        assert_equal(np.less(x, y), less(xm, ym))
+        assert_equal(np.greater(x, y), greater(xm, ym))
+        assert_equal(np.less_equal(x, y), less_equal(xm, ym))
+        assert_equal(np.greater_equal(x, y), greater_equal(xm, ym))
+        assert_equal(np.conjugate(x), conjugate(xm))
+
+    def test_count_func(self):
+        # Tests count
+        assert_equal(1, count(1))
+        assert_equal(0, array(1, mask=[1]))
+
+        ott = array([0., 1., 2., 3.], mask=[1, 0, 0, 0])
+        res = count(ott)
+        assert_(res.dtype.type is np.intp)
+        assert_equal(3, res)
+
+        ott = ott.reshape((2, 2))
+        res = count(ott)
+        assert_(res.dtype.type is np.intp)
+        assert_equal(3, res)
+        res = count(ott, 0)
+        assert_(isinstance(res, ndarray))
+        assert_equal([1, 2], res)
+        assert_(getmask(res) is nomask)
+
+        ott = array([0., 1., 2., 3.])
+        res = count(ott, 0)
+        assert_(isinstance(res, ndarray))
+        assert_(res.dtype.type is np.intp)
+        assert_raises(np.AxisError, ott.count, axis=1)
+
+    def test_count_on_python_builtins(self):
+        # Tests count works on python builtins (issue#8019)
+        assert_equal(3, count([1,2,3]))
+        assert_equal(2, count((1,2)))
+
+    def test_minmax_func(self):
+        # Tests minimum and maximum.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        # max doesn't work if shaped
+        xr = np.ravel(x)
+        xmr = ravel(xm)
+        # following are true because of careful selection of data
+        assert_equal(max(xr), maximum.reduce(xmr))
+        assert_equal(min(xr), minimum.reduce(xmr))
+
+        assert_equal(minimum([1, 2, 3], [4, 0, 9]), [1, 0, 3])
+        assert_equal(maximum([1, 2, 3], [4, 0, 9]), [4, 2, 9])
+        x = arange(5)
+        y = arange(5) - 2
+        x[3] = masked
+        y[0] = masked
+        assert_equal(minimum(x, y), where(less(x, y), x, y))
+        assert_equal(maximum(x, y), where(greater(x, y), x, y))
+        assert_(minimum.reduce(x) == 0)
+        assert_(maximum.reduce(x) == 4)
+
+        x = arange(4).reshape(2, 2)
+        x[-1, -1] = masked
+        assert_equal(maximum.reduce(x, axis=None), 2)
+
+    def test_minimummaximum_func(self):
+        a = np.ones((2, 2))
+        aminimum = minimum(a, a)
+        assert_(isinstance(aminimum, MaskedArray))
+        assert_equal(aminimum, np.minimum(a, a))
+
+        aminimum = minimum.outer(a, a)
+        assert_(isinstance(aminimum, MaskedArray))
+        assert_equal(aminimum, np.minimum.outer(a, a))
+
+        amaximum = maximum(a, a)
+        assert_(isinstance(amaximum, MaskedArray))
+        assert_equal(amaximum, np.maximum(a, a))
+
+        amaximum = maximum.outer(a, a)
+        assert_(isinstance(amaximum, MaskedArray))
+        assert_equal(amaximum, np.maximum.outer(a, a))
+
+    def test_minmax_reduce(self):
+        # Test np.min/maximum.reduce on array w/ full False mask
+        a = array([1, 2, 3], mask=[False, False, False])
+        b = np.maximum.reduce(a)
+        assert_equal(b, 3)
+
+    def test_minmax_funcs_with_output(self):
+        # Tests the min/max functions with explicit outputs
+        mask = np.random.rand(12).round()
+        xm = array(np.random.uniform(0, 10, 12), mask=mask)
+        xm.shape = (3, 4)
+        for funcname in ('min', 'max'):
+            # Initialize
+            npfunc = getattr(np, funcname)
+            mafunc = getattr(numpy.ma.core, funcname)
+            # Use the np version
+            nout = np.empty((4,), dtype=int)
+            try:
+                result = npfunc(xm, axis=0, out=nout)
+            except MaskError:
+                pass
+            nout = np.empty((4,), dtype=float)
+            result = npfunc(xm, axis=0, out=nout)
+            assert_(result is nout)
+            # Use the ma version
+            nout.fill(-999)
+            result = mafunc(xm, axis=0, out=nout)
+            assert_(result is nout)
+
+    def test_minmax_methods(self):
+        # Additional tests on max/min
+        (_, _, _, _, _, xm, _, _, _, _) = self.d
+        xm.shape = (xm.size,)
+        assert_equal(xm.max(), 10)
+        assert_(xm[0].max() is masked)
+        assert_(xm[0].max(0) is masked)
+        assert_(xm[0].max(-1) is masked)
+        assert_equal(xm.min(), -10.)
+        assert_(xm[0].min() is masked)
+        assert_(xm[0].min(0) is masked)
+        assert_(xm[0].min(-1) is masked)
+        assert_equal(xm.ptp(), 20.)
+        assert_(xm[0].ptp() is masked)
+        assert_(xm[0].ptp(0) is masked)
+        assert_(xm[0].ptp(-1) is masked)
+
+        x = array([1, 2, 3], mask=True)
+        assert_(x.min() is masked)
+        assert_(x.max() is masked)
+        assert_(x.ptp() is masked)
+
+    def test_minmax_dtypes(self):
+        # Additional tests on max/min for non-standard float and complex dtypes
+        x = np.array([1., 1., 1., -2., pi/2.0, 4., 5., -10., 10., 1., 2., 3.])
+        a10 = 10.
+        an10 = -10.0
+        m1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        xm = masked_array(x, mask=m1)
+        xm.set_fill_value(1e+20)
+        float_dtypes = [np.float16, np.float32, np.float64, np.longdouble,
+                        np.complex64, np.complex128, np.clongdouble]
+        for float_dtype in float_dtypes:
+            assert_equal(masked_array(x, mask=m1, dtype=float_dtype).max(),
+                         float_dtype(a10))
+            assert_equal(masked_array(x, mask=m1, dtype=float_dtype).min(),
+                         float_dtype(an10))
+
+        assert_equal(xm.min(), an10)
+        assert_equal(xm.max(), a10)
+
+        # Non-complex type only test
+        for float_dtype in float_dtypes[:4]:
+            assert_equal(masked_array(x, mask=m1, dtype=float_dtype).max(),
+                         float_dtype(a10))
+            assert_equal(masked_array(x, mask=m1, dtype=float_dtype).min(),
+                         float_dtype(an10))
+
+        # Complex types only test
+        for float_dtype in float_dtypes[-3:]:
+            ym = masked_array([1e20+1j, 1e20-2j, 1e20-1j], mask=[0, 1, 0],
+                          dtype=float_dtype)
+            assert_equal(ym.min(), float_dtype(1e20-1j))
+            assert_equal(ym.max(), float_dtype(1e20+1j))
+
+            zm = masked_array([np.inf+2j, np.inf+3j, -np.inf-1j], mask=[0, 1, 0],
+                              dtype=float_dtype)
+            assert_equal(zm.min(), float_dtype(-np.inf-1j))
+            assert_equal(zm.max(), float_dtype(np.inf+2j))
+
+            cmax = np.inf - 1j * np.finfo(np.float64).max
+            assert masked_array([-cmax, 0], mask=[0, 1]).max() == -cmax
+            assert masked_array([cmax, 0], mask=[0, 1]).min() == cmax
+
+    def test_addsumprod(self):
+        # Tests add, sum, product.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        assert_equal(np.add.reduce(x), add.reduce(x))
+        assert_equal(np.add.accumulate(x), add.accumulate(x))
+        assert_equal(4, sum(array(4), axis=0))
+        assert_equal(4, sum(array(4), axis=0))
+        assert_equal(np.sum(x, axis=0), sum(x, axis=0))
+        assert_equal(np.sum(filled(xm, 0), axis=0), sum(xm, axis=0))
+        assert_equal(np.sum(x, 0), sum(x, 0))
+        assert_equal(np.prod(x, axis=0), product(x, axis=0))
+        assert_equal(np.prod(x, 0), product(x, 0))
+        assert_equal(np.prod(filled(xm, 1), axis=0), product(xm, axis=0))
+        s = (3, 4)
+        x.shape = y.shape = xm.shape = ym.shape = s
+        if len(s) > 1:
+            assert_equal(np.concatenate((x, y), 1), concatenate((xm, ym), 1))
+            assert_equal(np.add.reduce(x, 1), add.reduce(x, 1))
+            assert_equal(np.sum(x, 1), sum(x, 1))
+            assert_equal(np.prod(x, 1), product(x, 1))
+
+    def test_binops_d2D(self):
+        # Test binary operations on 2D data
+        a = array([[1.], [2.], [3.]], mask=[[False], [True], [True]])
+        b = array([[2., 3.], [4., 5.], [6., 7.]])
+
+        test = a * b
+        control = array([[2., 3.], [2., 2.], [3., 3.]],
+                        mask=[[0, 0], [1, 1], [1, 1]])
+        assert_equal(test, control)
+        assert_equal(test.data, control.data)
+        assert_equal(test.mask, control.mask)
+
+        test = b * a
+        control = array([[2., 3.], [4., 5.], [6., 7.]],
+                        mask=[[0, 0], [1, 1], [1, 1]])
+        assert_equal(test, control)
+        assert_equal(test.data, control.data)
+        assert_equal(test.mask, control.mask)
+
+        a = array([[1.], [2.], [3.]])
+        b = array([[2., 3.], [4., 5.], [6., 7.]],
+                  mask=[[0, 0], [0, 0], [0, 1]])
+        test = a * b
+        control = array([[2, 3], [8, 10], [18, 3]],
+                        mask=[[0, 0], [0, 0], [0, 1]])
+        assert_equal(test, control)
+        assert_equal(test.data, control.data)
+        assert_equal(test.mask, control.mask)
+
+        test = b * a
+        control = array([[2, 3], [8, 10], [18, 7]],
+                        mask=[[0, 0], [0, 0], [0, 1]])
+        assert_equal(test, control)
+        assert_equal(test.data, control.data)
+        assert_equal(test.mask, control.mask)
+
+    def test_domained_binops_d2D(self):
+        # Test domained binary operations on 2D data
+        a = array([[1.], [2.], [3.]], mask=[[False], [True], [True]])
+        b = array([[2., 3.], [4., 5.], [6., 7.]])
+
+        test = a / b
+        control = array([[1. / 2., 1. / 3.], [2., 2.], [3., 3.]],
+                        mask=[[0, 0], [1, 1], [1, 1]])
+        assert_equal(test, control)
+        assert_equal(test.data, control.data)
+        assert_equal(test.mask, control.mask)
+
+        test = b / a
+        control = array([[2. / 1., 3. / 1.], [4., 5.], [6., 7.]],
+                        mask=[[0, 0], [1, 1], [1, 1]])
+        assert_equal(test, control)
+        assert_equal(test.data, control.data)
+        assert_equal(test.mask, control.mask)
+
+        a = array([[1.], [2.], [3.]])
+        b = array([[2., 3.], [4., 5.], [6., 7.]],
+                  mask=[[0, 0], [0, 0], [0, 1]])
+        test = a / b
+        control = array([[1. / 2, 1. / 3], [2. / 4, 2. / 5], [3. / 6, 3]],
+                        mask=[[0, 0], [0, 0], [0, 1]])
+        assert_equal(test, control)
+        assert_equal(test.data, control.data)
+        assert_equal(test.mask, control.mask)
+
+        test = b / a
+        control = array([[2 / 1., 3 / 1.], [4 / 2., 5 / 2.], [6 / 3., 7]],
+                        mask=[[0, 0], [0, 0], [0, 1]])
+        assert_equal(test, control)
+        assert_equal(test.data, control.data)
+        assert_equal(test.mask, control.mask)
+
+    def test_noshrinking(self):
+        # Check that we don't shrink a mask when not wanted
+        # Binary operations
+        a = masked_array([1., 2., 3.], mask=[False, False, False],
+                         shrink=False)
+        b = a + 1
+        assert_equal(b.mask, [0, 0, 0])
+        # In place binary operation
+        a += 1
+        assert_equal(a.mask, [0, 0, 0])
+        # Domained binary operation
+        b = a / 1.
+        assert_equal(b.mask, [0, 0, 0])
+        # In place binary operation
+        a /= 1.
+        assert_equal(a.mask, [0, 0, 0])
+
+    def test_ufunc_nomask(self):
+        # check the case ufuncs should set the mask to false
+        m = np.ma.array([1])
+        # check we don't get array([False], dtype=bool)
+        assert_equal(np.true_divide(m, 5).mask.shape, ())
+
+    def test_noshink_on_creation(self):
+        # Check that the mask is not shrunk on array creation when not wanted
+        a = np.ma.masked_values([1., 2.5, 3.1], 1.5, shrink=False)
+        assert_equal(a.mask, [0, 0, 0])
+
+    def test_mod(self):
+        # Tests mod
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf) = self.d
+        assert_equal(mod(x, y), mod(xm, ym))
+        test = mod(ym, xm)
+        assert_equal(test, np.mod(ym, xm))
+        assert_equal(test.mask, mask_or(xm.mask, ym.mask))
+        test = mod(xm, ym)
+        assert_equal(test, np.mod(xm, ym))
+        assert_equal(test.mask, mask_or(mask_or(xm.mask, ym.mask), (ym == 0)))
+
+    def test_TakeTransposeInnerOuter(self):
+        # Test of take, transpose, inner, outer products
+        x = arange(24)
+        y = np.arange(24)
+        x[5:6] = masked
+        x = x.reshape(2, 3, 4)
+        y = y.reshape(2, 3, 4)
+        assert_equal(np.transpose(y, (2, 0, 1)), transpose(x, (2, 0, 1)))
+        assert_equal(np.take(y, (2, 0, 1), 1), take(x, (2, 0, 1), 1))
+        assert_equal(np.inner(filled(x, 0), filled(y, 0)),
+                     inner(x, y))
+        assert_equal(np.outer(filled(x, 0), filled(y, 0)),
+                     outer(x, y))
+        y = array(['abc', 1, 'def', 2, 3], object)
+        y[2] = masked
+        t = take(y, [0, 3, 4])
+        assert_(t[0] == 'abc')
+        assert_(t[1] == 2)
+        assert_(t[2] == 3)
+
+    def test_imag_real(self):
+        # Check complex
+        xx = array([1 + 10j, 20 + 2j], mask=[1, 0])
+        assert_equal(xx.imag, [10, 2])
+        assert_equal(xx.imag.filled(), [1e+20, 2])
+        assert_equal(xx.imag.dtype, xx._data.imag.dtype)
+        assert_equal(xx.real, [1, 20])
+        assert_equal(xx.real.filled(), [1e+20, 20])
+        assert_equal(xx.real.dtype, xx._data.real.dtype)
+
+    def test_methods_with_output(self):
+        xm = array(np.random.uniform(0, 10, 12)).reshape(3, 4)
+        xm[:, 0] = xm[0] = xm[-1, -1] = masked
+
+        funclist = ('sum', 'prod', 'var', 'std', 'max', 'min', 'ptp', 'mean',)
+
+        for funcname in funclist:
+            npfunc = getattr(np, funcname)
+            xmmeth = getattr(xm, funcname)
+            # A ndarray as explicit input
+            output = np.empty(4, dtype=float)
+            output.fill(-9999)
+            result = npfunc(xm, axis=0, out=output)
+            # ... the result should be the given output
+            assert_(result is output)
+            assert_equal(result, xmmeth(axis=0, out=output))
+
+            output = empty(4, dtype=int)
+            result = xmmeth(axis=0, out=output)
+            assert_(result is output)
+            assert_(output[0] is masked)
+
+    def test_eq_on_structured(self):
+        # Test the equality of structured arrays
+        ndtype = [('A', int), ('B', int)]
+        a = array([(1, 1), (2, 2)], mask=[(0, 1), (0, 0)], dtype=ndtype)
+
+        test = (a == a)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [False, False])
+        assert_(test.fill_value == True)
+
+        test = (a == a[0])
+        assert_equal(test.data, [True, False])
+        assert_equal(test.mask, [False, False])
+        assert_(test.fill_value == True)
+
+        b = array([(1, 1), (2, 2)], mask=[(1, 0), (0, 0)], dtype=ndtype)
+        test = (a == b)
+        assert_equal(test.data, [False, True])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        test = (a[0] == b)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        b = array([(1, 1), (2, 2)], mask=[(0, 1), (1, 0)], dtype=ndtype)
+        test = (a == b)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [False, False])
+        assert_(test.fill_value == True)
+
+        # complicated dtype, 2-dimensional array.
+        ndtype = [('A', int), ('B', [('BA', int), ('BB', int)])]
+        a = array([[(1, (1, 1)), (2, (2, 2))],
+                   [(3, (3, 3)), (4, (4, 4))]],
+                  mask=[[(0, (1, 0)), (0, (0, 1))],
+                        [(1, (0, 0)), (1, (1, 1))]], dtype=ndtype)
+        test = (a[0, 0] == a)
+        assert_equal(test.data, [[True, False], [False, False]])
+        assert_equal(test.mask, [[False, False], [False, True]])
+        assert_(test.fill_value == True)
+
+    def test_ne_on_structured(self):
+        # Test the equality of structured arrays
+        ndtype = [('A', int), ('B', int)]
+        a = array([(1, 1), (2, 2)], mask=[(0, 1), (0, 0)], dtype=ndtype)
+
+        test = (a != a)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [False, False])
+        assert_(test.fill_value == True)
+
+        test = (a != a[0])
+        assert_equal(test.data, [False, True])
+        assert_equal(test.mask, [False, False])
+        assert_(test.fill_value == True)
+
+        b = array([(1, 1), (2, 2)], mask=[(1, 0), (0, 0)], dtype=ndtype)
+        test = (a != b)
+        assert_equal(test.data, [True, False])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        test = (a[0] != b)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        b = array([(1, 1), (2, 2)], mask=[(0, 1), (1, 0)], dtype=ndtype)
+        test = (a != b)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [False, False])
+        assert_(test.fill_value == True)
+
+        # complicated dtype, 2-dimensional array.
+        ndtype = [('A', int), ('B', [('BA', int), ('BB', int)])]
+        a = array([[(1, (1, 1)), (2, (2, 2))],
+                   [(3, (3, 3)), (4, (4, 4))]],
+                  mask=[[(0, (1, 0)), (0, (0, 1))],
+                        [(1, (0, 0)), (1, (1, 1))]], dtype=ndtype)
+        test = (a[0, 0] != a)
+        assert_equal(test.data, [[False, True], [True, True]])
+        assert_equal(test.mask, [[False, False], [False, True]])
+        assert_(test.fill_value == True)
+
+    def test_eq_ne_structured_with_non_masked(self):
+        a = array([(1, 1), (2, 2), (3, 4)],
+                  mask=[(0, 1), (0, 0), (1, 1)], dtype='i4,i4')
+        eq = a == a.data
+        ne = a.data != a
+        # Test the obvious.
+        assert_(np.all(eq))
+        assert_(not np.any(ne))
+        # Expect the mask set only for items with all fields masked.
+        expected_mask = a.mask == np.ones((), a.mask.dtype)
+        assert_array_equal(eq.mask, expected_mask)
+        assert_array_equal(ne.mask, expected_mask)
+        # The masked element will indicated not equal, because the
+        # masks did not match.
+        assert_equal(eq.data, [True, True, False])
+        assert_array_equal(eq.data, ~ne.data)
+
+    def test_eq_ne_structured_extra(self):
+        # ensure simple examples are symmetric and make sense.
+        # from https://github.com/numpy/numpy/pull/8590#discussion_r101126465
+        dt = np.dtype('i4,i4')
+        for m1 in (mvoid((1, 2), mask=(0, 0), dtype=dt),
+                   mvoid((1, 2), mask=(0, 1), dtype=dt),
+                   mvoid((1, 2), mask=(1, 0), dtype=dt),
+                   mvoid((1, 2), mask=(1, 1), dtype=dt)):
+            ma1 = m1.view(MaskedArray)
+            r1 = ma1.view('2i4')
+            for m2 in (np.array((1, 1), dtype=dt),
+                       mvoid((1, 1), dtype=dt),
+                       mvoid((1, 0), mask=(0, 1), dtype=dt),
+                       mvoid((3, 2), mask=(0, 1), dtype=dt)):
+                ma2 = m2.view(MaskedArray)
+                r2 = ma2.view('2i4')
+                eq_expected = (r1 == r2).all()
+                assert_equal(m1 == m2, eq_expected)
+                assert_equal(m2 == m1, eq_expected)
+                assert_equal(ma1 == m2, eq_expected)
+                assert_equal(m1 == ma2, eq_expected)
+                assert_equal(ma1 == ma2, eq_expected)
+                # Also check it is the same if we do it element by element.
+                el_by_el = [m1[name] == m2[name] for name in dt.names]
+                assert_equal(array(el_by_el, dtype=bool).all(), eq_expected)
+                ne_expected = (r1 != r2).any()
+                assert_equal(m1 != m2, ne_expected)
+                assert_equal(m2 != m1, ne_expected)
+                assert_equal(ma1 != m2, ne_expected)
+                assert_equal(m1 != ma2, ne_expected)
+                assert_equal(ma1 != ma2, ne_expected)
+                el_by_el = [m1[name] != m2[name] for name in dt.names]
+                assert_equal(array(el_by_el, dtype=bool).any(), ne_expected)
+
+    @pytest.mark.parametrize('dt', ['S', 'U'])
+    @pytest.mark.parametrize('fill', [None, 'A'])
+    def test_eq_for_strings(self, dt, fill):
+        # Test the equality of structured arrays
+        a = array(['a', 'b'], dtype=dt, mask=[0, 1], fill_value=fill)
+
+        test = (a == a)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        test = (a == a[0])
+        assert_equal(test.data, [True, False])
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        b = array(['a', 'b'], dtype=dt, mask=[1, 0], fill_value=fill)
+        test = (a == b)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [True, True])
+        assert_(test.fill_value == True)
+
+        test = (a[0] == b)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        test = (b == a[0])
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+    @pytest.mark.parametrize('dt', ['S', 'U'])
+    @pytest.mark.parametrize('fill', [None, 'A'])
+    def test_ne_for_strings(self, dt, fill):
+        # Test the equality of structured arrays
+        a = array(['a', 'b'], dtype=dt, mask=[0, 1], fill_value=fill)
+
+        test = (a != a)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        test = (a != a[0])
+        assert_equal(test.data, [False, True])
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        b = array(['a', 'b'], dtype=dt, mask=[1, 0], fill_value=fill)
+        test = (a != b)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [True, True])
+        assert_(test.fill_value == True)
+
+        test = (a[0] != b)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        test = (b != a[0])
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+    @pytest.mark.parametrize('dt1', num_dts, ids=num_ids)
+    @pytest.mark.parametrize('dt2', num_dts, ids=num_ids)
+    @pytest.mark.parametrize('fill', [None, 1])
+    def test_eq_for_numeric(self, dt1, dt2, fill):
+        # Test the equality of structured arrays
+        a = array([0, 1], dtype=dt1, mask=[0, 1], fill_value=fill)
+
+        test = (a == a)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        test = (a == a[0])
+        assert_equal(test.data, [True, False])
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        b = array([0, 1], dtype=dt2, mask=[1, 0], fill_value=fill)
+        test = (a == b)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [True, True])
+        assert_(test.fill_value == True)
+
+        test = (a[0] == b)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        test = (b == a[0])
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+    @pytest.mark.parametrize("op", [operator.eq, operator.lt])
+    def test_eq_broadcast_with_unmasked(self, op):
+        a = array([0, 1], mask=[0, 1])
+        b = np.arange(10).reshape(5, 2)
+        result = op(a, b)
+        assert_(result.mask.shape == b.shape)
+        assert_equal(result.mask, np.zeros(b.shape, bool) | a.mask)
+
+    @pytest.mark.parametrize("op", [operator.eq, operator.gt])
+    def test_comp_no_mask_not_broadcast(self, op):
+        # Regression test for failing doctest in MaskedArray.nonzero
+        # after gh-24556.
+        a = array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
+        result = op(a, 3)
+        assert_(not result.mask.shape)
+        assert_(result.mask is nomask)
+
+    @pytest.mark.parametrize('dt1', num_dts, ids=num_ids)
+    @pytest.mark.parametrize('dt2', num_dts, ids=num_ids)
+    @pytest.mark.parametrize('fill', [None, 1])
+    def test_ne_for_numeric(self, dt1, dt2, fill):
+        # Test the equality of structured arrays
+        a = array([0, 1], dtype=dt1, mask=[0, 1], fill_value=fill)
+
+        test = (a != a)
+        assert_equal(test.data, [False, False])
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        test = (a != a[0])
+        assert_equal(test.data, [False, True])
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        b = array([0, 1], dtype=dt2, mask=[1, 0], fill_value=fill)
+        test = (a != b)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [True, True])
+        assert_(test.fill_value == True)
+
+        test = (a[0] != b)
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        test = (b != a[0])
+        assert_equal(test.data, [True, True])
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+    @pytest.mark.parametrize('dt1', num_dts, ids=num_ids)
+    @pytest.mark.parametrize('dt2', num_dts, ids=num_ids)
+    @pytest.mark.parametrize('fill', [None, 1])
+    @pytest.mark.parametrize('op',
+            [operator.le, operator.lt, operator.ge, operator.gt])
+    def test_comparisons_for_numeric(self, op, dt1, dt2, fill):
+        # Test the equality of structured arrays
+        a = array([0, 1], dtype=dt1, mask=[0, 1], fill_value=fill)
+
+        test = op(a, a)
+        assert_equal(test.data, op(a._data, a._data))
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        test = op(a, a[0])
+        assert_equal(test.data, op(a._data, a._data[0]))
+        assert_equal(test.mask, [False, True])
+        assert_(test.fill_value == True)
+
+        b = array([0, 1], dtype=dt2, mask=[1, 0], fill_value=fill)
+        test = op(a, b)
+        assert_equal(test.data, op(a._data, b._data))
+        assert_equal(test.mask, [True, True])
+        assert_(test.fill_value == True)
+
+        test = op(a[0], b)
+        assert_equal(test.data, op(a._data[0], b._data))
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+        test = op(b, a[0])
+        assert_equal(test.data, op(b._data, a._data[0]))
+        assert_equal(test.mask, [True, False])
+        assert_(test.fill_value == True)
+
+    @pytest.mark.parametrize('op',
+            [operator.le, operator.lt, operator.ge, operator.gt])
+    @pytest.mark.parametrize('fill', [None, "N/A"])
+    def test_comparisons_strings(self, op, fill):
+        # See gh-21770, mask propagation is broken for strings (and some other
+        # cases) so we explicitly test strings here.
+        # In principle only == and != may need special handling...
+        ma1 = masked_array(["a", "b", "cde"], mask=[0, 1, 0], fill_value=fill)
+        ma2 = masked_array(["cde", "b", "a"], mask=[0, 1, 0], fill_value=fill)
+        assert_equal(op(ma1, ma2)._data, op(ma1._data, ma2._data))
+
+    def test_eq_with_None(self):
+        # Really, comparisons with None should not be done, but check them
+        # anyway. Note that pep8 will flag these tests.
+        # Deprecation is in place for arrays, and when it happens this
+        # test will fail (and have to be changed accordingly).
+
+        # With partial mask
+        with suppress_warnings() as sup:
+            sup.filter(FutureWarning, "Comparison to `None`")
+            a = array([None, 1], mask=[0, 1])
+            assert_equal(a == None, array([True, False], mask=[0, 1]))
+            assert_equal(a.data == None, [True, False])
+            assert_equal(a != None, array([False, True], mask=[0, 1]))
+            # With nomask
+            a = array([None, 1], mask=False)
+            assert_equal(a == None, [True, False])
+            assert_equal(a != None, [False, True])
+            # With complete mask
+            a = array([None, 2], mask=True)
+            assert_equal(a == None, array([False, True], mask=True))
+            assert_equal(a != None, array([True, False], mask=True))
+            # Fully masked, even comparison to None should return "masked"
+            a = masked
+            assert_equal(a == None, masked)
+
+    def test_eq_with_scalar(self):
+        a = array(1)
+        assert_equal(a == 1, True)
+        assert_equal(a == 0, False)
+        assert_equal(a != 1, False)
+        assert_equal(a != 0, True)
+        b = array(1, mask=True)
+        assert_equal(b == 0, masked)
+        assert_equal(b == 1, masked)
+        assert_equal(b != 0, masked)
+        assert_equal(b != 1, masked)
+
+    def test_eq_different_dimensions(self):
+        m1 = array([1, 1], mask=[0, 1])
+        # test comparison with both masked and regular arrays.
+        for m2 in (array([[0, 1], [1, 2]]),
+                   np.array([[0, 1], [1, 2]])):
+            test = (m1 == m2)
+            assert_equal(test.data, [[False, False],
+                                     [True, False]])
+            assert_equal(test.mask, [[False, True],
+                                     [False, True]])
+
+    def test_numpyarithmetic(self):
+        # Check that the mask is not back-propagated when using numpy functions
+        a = masked_array([-1, 0, 1, 2, 3], mask=[0, 0, 0, 0, 1])
+        control = masked_array([np.nan, np.nan, 0, np.log(2), -1],
+                               mask=[1, 1, 0, 0, 1])
+
+        test = log(a)
+        assert_equal(test, control)
+        assert_equal(test.mask, control.mask)
+        assert_equal(a.mask, [0, 0, 0, 0, 1])
+
+        test = np.log(a)
+        assert_equal(test, control)
+        assert_equal(test.mask, control.mask)
+        assert_equal(a.mask, [0, 0, 0, 0, 1])
+
+
+class TestMaskedArrayAttributes:
+
+    def test_keepmask(self):
+        # Tests the keep mask flag
+        x = masked_array([1, 2, 3], mask=[1, 0, 0])
+        mx = masked_array(x)
+        assert_equal(mx.mask, x.mask)
+        mx = masked_array(x, mask=[0, 1, 0], keep_mask=False)
+        assert_equal(mx.mask, [0, 1, 0])
+        mx = masked_array(x, mask=[0, 1, 0], keep_mask=True)
+        assert_equal(mx.mask, [1, 1, 0])
+        # We default to true
+        mx = masked_array(x, mask=[0, 1, 0])
+        assert_equal(mx.mask, [1, 1, 0])
+
+    def test_hardmask(self):
+        # Test hard_mask
+        d = arange(5)
+        n = [0, 0, 0, 1, 1]
+        m = make_mask(n)
+        xh = array(d, mask=m, hard_mask=True)
+        # We need to copy, to avoid updating d in xh !
+        xs = array(d, mask=m, hard_mask=False, copy=True)
+        xh[[1, 4]] = [10, 40]
+        xs[[1, 4]] = [10, 40]
+        assert_equal(xh._data, [0, 10, 2, 3, 4])
+        assert_equal(xs._data, [0, 10, 2, 3, 40])
+        assert_equal(xs.mask, [0, 0, 0, 1, 0])
+        assert_(xh._hardmask)
+        assert_(not xs._hardmask)
+        xh[1:4] = [10, 20, 30]
+        xs[1:4] = [10, 20, 30]
+        assert_equal(xh._data, [0, 10, 20, 3, 4])
+        assert_equal(xs._data, [0, 10, 20, 30, 40])
+        assert_equal(xs.mask, nomask)
+        xh[0] = masked
+        xs[0] = masked
+        assert_equal(xh.mask, [1, 0, 0, 1, 1])
+        assert_equal(xs.mask, [1, 0, 0, 0, 0])
+        xh[:] = 1
+        xs[:] = 1
+        assert_equal(xh._data, [0, 1, 1, 3, 4])
+        assert_equal(xs._data, [1, 1, 1, 1, 1])
+        assert_equal(xh.mask, [1, 0, 0, 1, 1])
+        assert_equal(xs.mask, nomask)
+        # Switch to soft mask
+        xh.soften_mask()
+        xh[:] = arange(5)
+        assert_equal(xh._data, [0, 1, 2, 3, 4])
+        assert_equal(xh.mask, nomask)
+        # Switch back to hard mask
+        xh.harden_mask()
+        xh[xh < 3] = masked
+        assert_equal(xh._data, [0, 1, 2, 3, 4])
+        assert_equal(xh._mask, [1, 1, 1, 0, 0])
+        xh[filled(xh > 1, False)] = 5
+        assert_equal(xh._data, [0, 1, 2, 5, 5])
+        assert_equal(xh._mask, [1, 1, 1, 0, 0])
+
+        xh = array([[1, 2], [3, 4]], mask=[[1, 0], [0, 0]], hard_mask=True)
+        xh[0] = 0
+        assert_equal(xh._data, [[1, 0], [3, 4]])
+        assert_equal(xh._mask, [[1, 0], [0, 0]])
+        xh[-1, -1] = 5
+        assert_equal(xh._data, [[1, 0], [3, 5]])
+        assert_equal(xh._mask, [[1, 0], [0, 0]])
+        xh[filled(xh < 5, False)] = 2
+        assert_equal(xh._data, [[1, 2], [2, 5]])
+        assert_equal(xh._mask, [[1, 0], [0, 0]])
+
+    def test_hardmask_again(self):
+        # Another test of hardmask
+        d = arange(5)
+        n = [0, 0, 0, 1, 1]
+        m = make_mask(n)
+        xh = array(d, mask=m, hard_mask=True)
+        xh[4:5] = 999
+        xh[0:1] = 999
+        assert_equal(xh._data, [999, 1, 2, 3, 4])
+
+    def test_hardmask_oncemore_yay(self):
+        # OK, yet another test of hardmask
+        # Make sure that harden_mask/soften_mask//unshare_mask returns self
+        a = array([1, 2, 3], mask=[1, 0, 0])
+        b = a.harden_mask()
+        assert_equal(a, b)
+        b[0] = 0
+        assert_equal(a, b)
+        assert_equal(b, array([1, 2, 3], mask=[1, 0, 0]))
+        a = b.soften_mask()
+        a[0] = 0
+        assert_equal(a, b)
+        assert_equal(b, array([0, 2, 3], mask=[0, 0, 0]))
+
+    def test_smallmask(self):
+        # Checks the behaviour of _smallmask
+        a = arange(10)
+        a[1] = masked
+        a[1] = 1
+        assert_equal(a._mask, nomask)
+        a = arange(10)
+        a._smallmask = False
+        a[1] = masked
+        a[1] = 1
+        assert_equal(a._mask, zeros(10))
+
+    def test_shrink_mask(self):
+        # Tests .shrink_mask()
+        a = array([1, 2, 3], mask=[0, 0, 0])
+        b = a.shrink_mask()
+        assert_equal(a, b)
+        assert_equal(a.mask, nomask)
+
+        # Mask cannot be shrunk on structured types, so is a no-op
+        a = np.ma.array([(1, 2.0)], [('a', int), ('b', float)])
+        b = a.copy()
+        a.shrink_mask()
+        assert_equal(a.mask, b.mask)
+
+    def test_flat(self):
+        # Test that flat can return all types of items [#4585, #4615]
+        # test 2-D record array
+        # ... on structured array w/ masked records
+        x = array([[(1, 1.1, 'one'), (2, 2.2, 'two'), (3, 3.3, 'thr')],
+                   [(4, 4.4, 'fou'), (5, 5.5, 'fiv'), (6, 6.6, 'six')]],
+                  dtype=[('a', int), ('b', float), ('c', '|S8')])
+        x['a'][0, 1] = masked
+        x['b'][1, 0] = masked
+        x['c'][0, 2] = masked
+        x[-1, -1] = masked
+        xflat = x.flat
+        assert_equal(xflat[0], x[0, 0])
+        assert_equal(xflat[1], x[0, 1])
+        assert_equal(xflat[2], x[0, 2])
+        assert_equal(xflat[:3], x[0])
+        assert_equal(xflat[3], x[1, 0])
+        assert_equal(xflat[4], x[1, 1])
+        assert_equal(xflat[5], x[1, 2])
+        assert_equal(xflat[3:], x[1])
+        assert_equal(xflat[-1], x[-1, -1])
+        i = 0
+        j = 0
+        for xf in xflat:
+            assert_equal(xf, x[j, i])
+            i += 1
+            if i >= x.shape[-1]:
+                i = 0
+                j += 1
+
+    def test_assign_dtype(self):
+        # check that the mask's dtype is updated when dtype is changed
+        a = np.zeros(4, dtype='f4,i4')
+
+        m = np.ma.array(a)
+        m.dtype = np.dtype('f4')
+        repr(m)  # raises?
+        assert_equal(m.dtype, np.dtype('f4'))
+
+        # check that dtype changes that change shape of mask too much
+        # are not allowed
+        def assign():
+            m = np.ma.array(a)
+            m.dtype = np.dtype('f8')
+        assert_raises(ValueError, assign)
+
+        b = a.view(dtype='f4', type=np.ma.MaskedArray)  # raises?
+        assert_equal(b.dtype, np.dtype('f4'))
+
+        # check that nomask is preserved
+        a = np.zeros(4, dtype='f4')
+        m = np.ma.array(a)
+        m.dtype = np.dtype('f4,i4')
+        assert_equal(m.dtype, np.dtype('f4,i4'))
+        assert_equal(m._mask, np.ma.nomask)
+
+
+class TestFillingValues:
+
+    def test_check_on_scalar(self):
+        # Test _check_fill_value set to valid and invalid values
+        _check_fill_value = np.ma.core._check_fill_value
+
+        fval = _check_fill_value(0, int)
+        assert_equal(fval, 0)
+        fval = _check_fill_value(None, int)
+        assert_equal(fval, default_fill_value(0))
+
+        fval = _check_fill_value(0, "|S3")
+        assert_equal(fval, b"0")
+        fval = _check_fill_value(None, "|S3")
+        assert_equal(fval, default_fill_value(b"camelot!"))
+        assert_raises(TypeError, _check_fill_value, 1e+20, int)
+        assert_raises(TypeError, _check_fill_value, 'stuff', int)
+
+    def test_check_on_fields(self):
+        # Tests _check_fill_value with records
+        _check_fill_value = np.ma.core._check_fill_value
+        ndtype = [('a', int), ('b', float), ('c', "|S3")]
+        # A check on a list should return a single record
+        fval = _check_fill_value([-999, -12345678.9, "???"], ndtype)
+        assert_(isinstance(fval, ndarray))
+        assert_equal(fval.item(), [-999, -12345678.9, b"???"])
+        # A check on None should output the defaults
+        fval = _check_fill_value(None, ndtype)
+        assert_(isinstance(fval, ndarray))
+        assert_equal(fval.item(), [default_fill_value(0),
+                                   default_fill_value(0.),
+                                   asbytes(default_fill_value("0"))])
+        #.....Using a structured type as fill_value should work
+        fill_val = np.array((-999, -12345678.9, "???"), dtype=ndtype)
+        fval = _check_fill_value(fill_val, ndtype)
+        assert_(isinstance(fval, ndarray))
+        assert_equal(fval.item(), [-999, -12345678.9, b"???"])
+
+        #.....Using a flexible type w/ a different type shouldn't matter
+        # BEHAVIOR in 1.5 and earlier, and 1.13 and later: match structured
+        # types by position
+        fill_val = np.array((-999, -12345678.9, "???"),
+                            dtype=[("A", int), ("B", float), ("C", "|S3")])
+        fval = _check_fill_value(fill_val, ndtype)
+        assert_(isinstance(fval, ndarray))
+        assert_equal(fval.item(), [-999, -12345678.9, b"???"])
+
+        #.....Using an object-array shouldn't matter either
+        fill_val = np.ndarray(shape=(1,), dtype=object)
+        fill_val[0] = (-999, -12345678.9, b"???")
+        fval = _check_fill_value(fill_val, object)
+        assert_(isinstance(fval, ndarray))
+        assert_equal(fval.item(), [-999, -12345678.9, b"???"])
+        # NOTE: This test was never run properly as "fill_value" rather than
+        # "fill_val" was assigned.  Written properly, it fails.
+        #fill_val = np.array((-999, -12345678.9, "???"))
+        #fval = _check_fill_value(fill_val, ndtype)
+        #assert_(isinstance(fval, ndarray))
+        #assert_equal(fval.item(), [-999, -12345678.9, b"???"])
+        #.....One-field-only flexible type should work as well
+        ndtype = [("a", int)]
+        fval = _check_fill_value(-999999999, ndtype)
+        assert_(isinstance(fval, ndarray))
+        assert_equal(fval.item(), (-999999999,))
+
+    def test_fillvalue_conversion(self):
+        # Tests the behavior of fill_value during conversion
+        # We had a tailored comment to make sure special attributes are
+        # properly dealt with
+        a = array([b'3', b'4', b'5'])
+        a._optinfo.update({'comment':"updated!"})
+
+        b = array(a, dtype=int)
+        assert_equal(b._data, [3, 4, 5])
+        assert_equal(b.fill_value, default_fill_value(0))
+
+        b = array(a, dtype=float)
+        assert_equal(b._data, [3, 4, 5])
+        assert_equal(b.fill_value, default_fill_value(0.))
+
+        b = a.astype(int)
+        assert_equal(b._data, [3, 4, 5])
+        assert_equal(b.fill_value, default_fill_value(0))
+        assert_equal(b._optinfo['comment'], "updated!")
+
+        b = a.astype([('a', '|S3')])
+        assert_equal(b['a']._data, a._data)
+        assert_equal(b['a'].fill_value, a.fill_value)
+
+    def test_default_fill_value(self):
+        # check all calling conventions
+        f1 = default_fill_value(1.)
+        f2 = default_fill_value(np.array(1.))
+        f3 = default_fill_value(np.array(1.).dtype)
+        assert_equal(f1, f2)
+        assert_equal(f1, f3)
+
+    def test_default_fill_value_structured(self):
+        fields = array([(1, 1, 1)],
+                      dtype=[('i', int), ('s', '|S8'), ('f', float)])
+
+        f1 = default_fill_value(fields)
+        f2 = default_fill_value(fields.dtype)
+        expected = np.array((default_fill_value(0),
+                             default_fill_value('0'),
+                             default_fill_value(0.)), dtype=fields.dtype)
+        assert_equal(f1, expected)
+        assert_equal(f2, expected)
+
+    def test_default_fill_value_void(self):
+        dt = np.dtype([('v', 'V7')])
+        f = default_fill_value(dt)
+        assert_equal(f['v'], np.array(default_fill_value(dt['v']), dt['v']))
+
+    def test_fillvalue(self):
+        # Yet more fun with the fill_value
+        data = masked_array([1, 2, 3], fill_value=-999)
+        series = data[[0, 2, 1]]
+        assert_equal(series._fill_value, data._fill_value)
+
+        mtype = [('f', float), ('s', '|S3')]
+        x = array([(1, 'a'), (2, 'b'), (pi, 'pi')], dtype=mtype)
+        x.fill_value = 999
+        assert_equal(x.fill_value.item(), [999., b'999'])
+        assert_equal(x['f'].fill_value, 999)
+        assert_equal(x['s'].fill_value, b'999')
+
+        x.fill_value = (9, '???')
+        assert_equal(x.fill_value.item(), (9, b'???'))
+        assert_equal(x['f'].fill_value, 9)
+        assert_equal(x['s'].fill_value, b'???')
+
+        x = array([1, 2, 3.1])
+        x.fill_value = 999
+        assert_equal(np.asarray(x.fill_value).dtype, float)
+        assert_equal(x.fill_value, 999.)
+        assert_equal(x._fill_value, np.array(999.))
+
+    def test_subarray_fillvalue(self):
+        # gh-10483   test multi-field index fill value
+        fields = array([(1, 1, 1)],
+                      dtype=[('i', int), ('s', '|S8'), ('f', float)])
+        with suppress_warnings() as sup:
+            sup.filter(FutureWarning, "Numpy has detected")
+            subfields = fields[['i', 'f']]
+            assert_equal(tuple(subfields.fill_value), (999999, 1.e+20))
+            # test comparison does not raise:
+            subfields[1:] == subfields[:-1]
+
+    def test_fillvalue_exotic_dtype(self):
+        # Tests yet more exotic flexible dtypes
+        _check_fill_value = np.ma.core._check_fill_value
+        ndtype = [('i', int), ('s', '|S8'), ('f', float)]
+        control = np.array((default_fill_value(0),
+                            default_fill_value('0'),
+                            default_fill_value(0.),),
+                           dtype=ndtype)
+        assert_equal(_check_fill_value(None, ndtype), control)
+        # The shape shouldn't matter
+        ndtype = [('f0', float, (2, 2))]
+        control = np.array((default_fill_value(0.),),
+                           dtype=[('f0', float)]).astype(ndtype)
+        assert_equal(_check_fill_value(None, ndtype), control)
+        control = np.array((0,), dtype=[('f0', float)]).astype(ndtype)
+        assert_equal(_check_fill_value(0, ndtype), control)
+
+        ndtype = np.dtype("int, (2,3)float, float")
+        control = np.array((default_fill_value(0),
+                            default_fill_value(0.),
+                            default_fill_value(0.),),
+                           dtype="int, float, float").astype(ndtype)
+        test = _check_fill_value(None, ndtype)
+        assert_equal(test, control)
+        control = np.array((0, 0, 0), dtype="int, float, float").astype(ndtype)
+        assert_equal(_check_fill_value(0, ndtype), control)
+        # but when indexing, fill value should become scalar not tuple
+        # See issue #6723
+        M = masked_array(control)
+        assert_equal(M["f1"].fill_value.ndim, 0)
+
+    def test_fillvalue_datetime_timedelta(self):
+        # Test default fillvalue for datetime64 and timedelta64 types.
+        # See issue #4476, this would return '?' which would cause errors
+        # elsewhere
+
+        for timecode in ("as", "fs", "ps", "ns", "us", "ms", "s", "m",
+                         "h", "D", "W", "M", "Y"):
+            control = numpy.datetime64("NaT", timecode)
+            test = default_fill_value(numpy.dtype("<M8[" + timecode + "]"))
+            np.testing.assert_equal(test, control)
+
+            control = numpy.timedelta64("NaT", timecode)
+            test = default_fill_value(numpy.dtype("<m8[" + timecode + "]"))
+            np.testing.assert_equal(test, control)
+
+    def test_extremum_fill_value(self):
+        # Tests extremum fill values for flexible type.
+        a = array([(1, (2, 3)), (4, (5, 6))],
+                  dtype=[('A', int), ('B', [('BA', int), ('BB', int)])])
+        test = a.fill_value
+        assert_equal(test.dtype, a.dtype)
+        assert_equal(test['A'], default_fill_value(a['A']))
+        assert_equal(test['B']['BA'], default_fill_value(a['B']['BA']))
+        assert_equal(test['B']['BB'], default_fill_value(a['B']['BB']))
+
+        test = minimum_fill_value(a)
+        assert_equal(test.dtype, a.dtype)
+        assert_equal(test[0], minimum_fill_value(a['A']))
+        assert_equal(test[1][0], minimum_fill_value(a['B']['BA']))
+        assert_equal(test[1][1], minimum_fill_value(a['B']['BB']))
+        assert_equal(test[1], minimum_fill_value(a['B']))
+
+        test = maximum_fill_value(a)
+        assert_equal(test.dtype, a.dtype)
+        assert_equal(test[0], maximum_fill_value(a['A']))
+        assert_equal(test[1][0], maximum_fill_value(a['B']['BA']))
+        assert_equal(test[1][1], maximum_fill_value(a['B']['BB']))
+        assert_equal(test[1], maximum_fill_value(a['B']))
+
+    def test_extremum_fill_value_subdtype(self):
+        a = array(([2, 3, 4],), dtype=[('value', np.int8, 3)])
+
+        test = minimum_fill_value(a)
+        assert_equal(test.dtype, a.dtype)
+        assert_equal(test[0], np.full(3, minimum_fill_value(a['value'])))
+
+        test = maximum_fill_value(a)
+        assert_equal(test.dtype, a.dtype)
+        assert_equal(test[0], np.full(3, maximum_fill_value(a['value'])))
+
+    def test_fillvalue_individual_fields(self):
+        # Test setting fill_value on individual fields
+        ndtype = [('a', int), ('b', int)]
+        # Explicit fill_value
+        a = array(list(zip([1, 2, 3], [4, 5, 6])),
+                  fill_value=(-999, -999), dtype=ndtype)
+        aa = a['a']
+        aa.set_fill_value(10)
+        assert_equal(aa._fill_value, np.array(10))
+        assert_equal(tuple(a.fill_value), (10, -999))
+        a.fill_value['b'] = -10
+        assert_equal(tuple(a.fill_value), (10, -10))
+        # Implicit fill_value
+        t = array(list(zip([1, 2, 3], [4, 5, 6])), dtype=ndtype)
+        tt = t['a']
+        tt.set_fill_value(10)
+        assert_equal(tt._fill_value, np.array(10))
+        assert_equal(tuple(t.fill_value), (10, default_fill_value(0)))
+
+    def test_fillvalue_implicit_structured_array(self):
+        # Check that fill_value is always defined for structured arrays
+        ndtype = ('b', float)
+        adtype = ('a', float)
+        a = array([(1.,), (2.,)], mask=[(False,), (False,)],
+                  fill_value=(np.nan,), dtype=np.dtype([adtype]))
+        b = empty(a.shape, dtype=[adtype, ndtype])
+        b['a'] = a['a']
+        b['a'].set_fill_value(a['a'].fill_value)
+        f = b._fill_value[()]
+        assert_(np.isnan(f[0]))
+        assert_equal(f[-1], default_fill_value(1.))
+
+    def test_fillvalue_as_arguments(self):
+        # Test adding a fill_value parameter to empty/ones/zeros
+        a = empty(3, fill_value=999.)
+        assert_equal(a.fill_value, 999.)
+
+        a = ones(3, fill_value=999., dtype=float)
+        assert_equal(a.fill_value, 999.)
+
+        a = zeros(3, fill_value=0., dtype=complex)
+        assert_equal(a.fill_value, 0.)
+
+        a = identity(3, fill_value=0., dtype=complex)
+        assert_equal(a.fill_value, 0.)
+
+    def test_shape_argument(self):
+        # Test that shape can be provides as an argument
+        # GH issue 6106
+        a = empty(shape=(3, ))
+        assert_equal(a.shape, (3, ))
+
+        a = ones(shape=(3, ), dtype=float)
+        assert_equal(a.shape, (3, ))
+
+        a = zeros(shape=(3, ), dtype=complex)
+        assert_equal(a.shape, (3, ))
+
+    def test_fillvalue_in_view(self):
+        # Test the behavior of fill_value in view
+
+        # Create initial masked array
+        x = array([1, 2, 3], fill_value=1, dtype=np.int64)
+
+        # Check that fill_value is preserved by default
+        y = x.view()
+        assert_(y.fill_value == 1)
+
+        # Check that fill_value is preserved if dtype is specified and the
+        # dtype is an ndarray sub-class and has a _fill_value attribute
+        y = x.view(MaskedArray)
+        assert_(y.fill_value == 1)
+
+        # Check that fill_value is preserved if type is specified and the
+        # dtype is an ndarray sub-class and has a _fill_value attribute (by
+        # default, the first argument is dtype, not type)
+        y = x.view(type=MaskedArray)
+        assert_(y.fill_value == 1)
+
+        # Check that code does not crash if passed an ndarray sub-class that
+        # does not have a _fill_value attribute
+        y = x.view(np.ndarray)
+        y = x.view(type=np.ndarray)
+
+        # Check that fill_value can be overridden with view
+        y = x.view(MaskedArray, fill_value=2)
+        assert_(y.fill_value == 2)
+
+        # Check that fill_value can be overridden with view (using type=)
+        y = x.view(type=MaskedArray, fill_value=2)
+        assert_(y.fill_value == 2)
+
+        # Check that fill_value gets reset if passed a dtype but not a
+        # fill_value. This is because even though in some cases one can safely
+        # cast the fill_value, e.g. if taking an int64 view of an int32 array,
+        # in other cases, this cannot be done (e.g. int32 view of an int64
+        # array with a large fill_value).
+        y = x.view(dtype=np.int32)
+        assert_(y.fill_value == 999999)
+
+    def test_fillvalue_bytes_or_str(self):
+        # Test whether fill values work as expected for structured dtypes
+        # containing bytes or str.  See issue #7259.
+        a = empty(shape=(3, ), dtype="(2)3S,(2)3U")
+        assert_equal(a["f0"].fill_value, default_fill_value(b"spam"))
+        assert_equal(a["f1"].fill_value, default_fill_value("eggs"))
+
+
+class TestUfuncs:
+    # Test class for the application of ufuncs on MaskedArrays.
+
+    def setup_method(self):
+        # Base data definition.
+        self.d = (array([1.0, 0, -1, pi / 2] * 2, mask=[0, 1] + [0] * 6),
+                  array([1.0, 0, -1, pi / 2] * 2, mask=[1, 0] + [0] * 6),)
+        self.err_status = np.geterr()
+        np.seterr(divide='ignore', invalid='ignore')
+
+    def teardown_method(self):
+        np.seterr(**self.err_status)
+
+    def test_testUfuncRegression(self):
+        # Tests new ufuncs on MaskedArrays.
+        for f in ['sqrt', 'log', 'log10', 'exp', 'conjugate',
+                  'sin', 'cos', 'tan',
+                  'arcsin', 'arccos', 'arctan',
+                  'sinh', 'cosh', 'tanh',
+                  'arcsinh',
+                  'arccosh',
+                  'arctanh',
+                  'absolute', 'fabs', 'negative',
+                  'floor', 'ceil',
+                  'logical_not',
+                  'add', 'subtract', 'multiply',
+                  'divide', 'true_divide', 'floor_divide',
+                  'remainder', 'fmod', 'hypot', 'arctan2',
+                  'equal', 'not_equal', 'less_equal', 'greater_equal',
+                  'less', 'greater',
+                  'logical_and', 'logical_or', 'logical_xor',
+                  ]:
+            try:
+                uf = getattr(umath, f)
+            except AttributeError:
+                uf = getattr(fromnumeric, f)
+            mf = getattr(numpy.ma.core, f)
+            args = self.d[:uf.nin]
+            ur = uf(*args)
+            mr = mf(*args)
+            assert_equal(ur.filled(0), mr.filled(0), f)
+            assert_mask_equal(ur.mask, mr.mask, err_msg=f)
+
+    def test_reduce(self):
+        # Tests reduce on MaskedArrays.
+        a = self.d[0]
+        assert_(not alltrue(a, axis=0))
+        assert_(sometrue(a, axis=0))
+        assert_equal(sum(a[:3], axis=0), 0)
+        assert_equal(product(a, axis=0), 0)
+        assert_equal(add.reduce(a), pi)
+
+    def test_minmax(self):
+        # Tests extrema on MaskedArrays.
+        a = arange(1, 13).reshape(3, 4)
+        amask = masked_where(a < 5, a)
+        assert_equal(amask.max(), a.max())
+        assert_equal(amask.min(), 5)
+        assert_equal(amask.max(0), a.max(0))
+        assert_equal(amask.min(0), [5, 6, 7, 8])
+        assert_(amask.max(1)[0].mask)
+        assert_(amask.min(1)[0].mask)
+
+    def test_ndarray_mask(self):
+        # Check that the mask of the result is a ndarray (not a MaskedArray...)
+        a = masked_array([-1, 0, 1, 2, 3], mask=[0, 0, 0, 0, 1])
+        test = np.sqrt(a)
+        control = masked_array([-1, 0, 1, np.sqrt(2), -1],
+                               mask=[1, 0, 0, 0, 1])
+        assert_equal(test, control)
+        assert_equal(test.mask, control.mask)
+        assert_(not isinstance(test.mask, MaskedArray))
+
+    def test_treatment_of_NotImplemented(self):
+        # Check that NotImplemented is returned at appropriate places
+
+        a = masked_array([1., 2.], mask=[1, 0])
+        assert_raises(TypeError, operator.mul, a, "abc")
+        assert_raises(TypeError, operator.truediv, a, "abc")
+
+        class MyClass:
+            __array_priority__ = a.__array_priority__ + 1
+
+            def __mul__(self, other):
+                return "My mul"
+
+            def __rmul__(self, other):
+                return "My rmul"
+
+        me = MyClass()
+        assert_(me * a == "My mul")
+        assert_(a * me == "My rmul")
+
+        # and that __array_priority__ is respected
+        class MyClass2:
+            __array_priority__ = 100
+
+            def __mul__(self, other):
+                return "Me2mul"
+
+            def __rmul__(self, other):
+                return "Me2rmul"
+
+            def __rdiv__(self, other):
+                return "Me2rdiv"
+
+            __rtruediv__ = __rdiv__
+
+        me_too = MyClass2()
+        assert_(a.__mul__(me_too) is NotImplemented)
+        assert_(all(multiply.outer(a, me_too) == "Me2rmul"))
+        assert_(a.__truediv__(me_too) is NotImplemented)
+        assert_(me_too * a == "Me2mul")
+        assert_(a * me_too == "Me2rmul")
+        assert_(a / me_too == "Me2rdiv")
+
+    def test_no_masked_nan_warnings(self):
+        # check that a nan in masked position does not
+        # cause ufunc warnings
+
+        m = np.ma.array([0.5, np.nan], mask=[0,1])
+
+        with warnings.catch_warnings():
+            warnings.filterwarnings("error")
+
+            # test unary and binary ufuncs
+            exp(m)
+            add(m, 1)
+            m > 0
+
+            # test different unary domains
+            sqrt(m)
+            log(m)
+            tan(m)
+            arcsin(m)
+            arccos(m)
+            arccosh(m)
+
+            # test binary domains
+            divide(m, 2)
+
+            # also check that allclose uses ma ufuncs, to avoid warning
+            allclose(m, 0.5)
+
+class TestMaskedArrayInPlaceArithmetic:
+    # Test MaskedArray Arithmetic
+
+    def setup_method(self):
+        x = arange(10)
+        y = arange(10)
+        xm = arange(10)
+        xm[2] = masked
+        self.intdata = (x, y, xm)
+        self.floatdata = (x.astype(float), y.astype(float), xm.astype(float))
+        self.othertypes = np.typecodes['AllInteger'] + np.typecodes['AllFloat']
+        self.othertypes = [np.dtype(_).type for _ in self.othertypes]
+        self.uint8data = (
+            x.astype(np.uint8),
+            y.astype(np.uint8),
+            xm.astype(np.uint8)
+        )
+
+    def test_inplace_addition_scalar(self):
+        # Test of inplace additions
+        (x, y, xm) = self.intdata
+        xm[2] = masked
+        x += 1
+        assert_equal(x, y + 1)
+        xm += 1
+        assert_equal(xm, y + 1)
+
+        (x, _, xm) = self.floatdata
+        id1 = x.data.ctypes.data
+        x += 1.
+        assert_(id1 == x.data.ctypes.data)
+        assert_equal(x, y + 1.)
+
+    def test_inplace_addition_array(self):
+        # Test of inplace additions
+        (x, y, xm) = self.intdata
+        m = xm.mask
+        a = arange(10, dtype=np.int16)
+        a[-1] = masked
+        x += a
+        xm += a
+        assert_equal(x, y + a)
+        assert_equal(xm, y + a)
+        assert_equal(xm.mask, mask_or(m, a.mask))
+
+    def test_inplace_subtraction_scalar(self):
+        # Test of inplace subtractions
+        (x, y, xm) = self.intdata
+        x -= 1
+        assert_equal(x, y - 1)
+        xm -= 1
+        assert_equal(xm, y - 1)
+
+    def test_inplace_subtraction_array(self):
+        # Test of inplace subtractions
+        (x, y, xm) = self.floatdata
+        m = xm.mask
+        a = arange(10, dtype=float)
+        a[-1] = masked
+        x -= a
+        xm -= a
+        assert_equal(x, y - a)
+        assert_equal(xm, y - a)
+        assert_equal(xm.mask, mask_or(m, a.mask))
+
+    def test_inplace_multiplication_scalar(self):
+        # Test of inplace multiplication
+        (x, y, xm) = self.floatdata
+        x *= 2.0
+        assert_equal(x, y * 2)
+        xm *= 2.0
+        assert_equal(xm, y * 2)
+
+    def test_inplace_multiplication_array(self):
+        # Test of inplace multiplication
+        (x, y, xm) = self.floatdata
+        m = xm.mask
+        a = arange(10, dtype=float)
+        a[-1] = masked
+        x *= a
+        xm *= a
+        assert_equal(x, y * a)
+        assert_equal(xm, y * a)
+        assert_equal(xm.mask, mask_or(m, a.mask))
+
+    def test_inplace_division_scalar_int(self):
+        # Test of inplace division
+        (x, y, xm) = self.intdata
+        x = arange(10) * 2
+        xm = arange(10) * 2
+        xm[2] = masked
+        x //= 2
+        assert_equal(x, y)
+        xm //= 2
+        assert_equal(xm, y)
+
+    def test_inplace_division_scalar_float(self):
+        # Test of inplace division
+        (x, y, xm) = self.floatdata
+        x /= 2.0
+        assert_equal(x, y / 2.0)
+        xm /= arange(10)
+        assert_equal(xm, ones((10,)))
+
+    def test_inplace_division_array_float(self):
+        # Test of inplace division
+        (x, y, xm) = self.floatdata
+        m = xm.mask
+        a = arange(10, dtype=float)
+        a[-1] = masked
+        x /= a
+        xm /= a
+        assert_equal(x, y / a)
+        assert_equal(xm, y / a)
+        assert_equal(xm.mask, mask_or(mask_or(m, a.mask), (a == 0)))
+
+    def test_inplace_division_misc(self):
+
+        x = [1., 1., 1., -2., pi / 2., 4., 5., -10., 10., 1., 2., 3.]
+        y = [5., 0., 3., 2., -1., -4., 0., -10., 10., 1., 0., 3.]
+        m1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        m2 = [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1]
+        xm = masked_array(x, mask=m1)
+        ym = masked_array(y, mask=m2)
+
+        z = xm / ym
+        assert_equal(z._mask, [1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1])
+        assert_equal(z._data,
+                     [1., 1., 1., -1., -pi / 2., 4., 5., 1., 1., 1., 2., 3.])
+
+        xm = xm.copy()
+        xm /= ym
+        assert_equal(xm._mask, [1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1])
+        assert_equal(z._data,
+                     [1., 1., 1., -1., -pi / 2., 4., 5., 1., 1., 1., 2., 3.])
+
+    def test_datafriendly_add(self):
+        # Test keeping data w/ (inplace) addition
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        # Test add w/ scalar
+        xx = x + 1
+        assert_equal(xx.data, [2, 3, 3])
+        assert_equal(xx.mask, [0, 0, 1])
+        # Test iadd w/ scalar
+        x += 1
+        assert_equal(x.data, [2, 3, 3])
+        assert_equal(x.mask, [0, 0, 1])
+        # Test add w/ array
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        xx = x + array([1, 2, 3], mask=[1, 0, 0])
+        assert_equal(xx.data, [1, 4, 3])
+        assert_equal(xx.mask, [1, 0, 1])
+        # Test iadd w/ array
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        x += array([1, 2, 3], mask=[1, 0, 0])
+        assert_equal(x.data, [1, 4, 3])
+        assert_equal(x.mask, [1, 0, 1])
+
+    def test_datafriendly_sub(self):
+        # Test keeping data w/ (inplace) subtraction
+        # Test sub w/ scalar
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        xx = x - 1
+        assert_equal(xx.data, [0, 1, 3])
+        assert_equal(xx.mask, [0, 0, 1])
+        # Test isub w/ scalar
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        x -= 1
+        assert_equal(x.data, [0, 1, 3])
+        assert_equal(x.mask, [0, 0, 1])
+        # Test sub w/ array
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        xx = x - array([1, 2, 3], mask=[1, 0, 0])
+        assert_equal(xx.data, [1, 0, 3])
+        assert_equal(xx.mask, [1, 0, 1])
+        # Test isub w/ array
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        x -= array([1, 2, 3], mask=[1, 0, 0])
+        assert_equal(x.data, [1, 0, 3])
+        assert_equal(x.mask, [1, 0, 1])
+
+    def test_datafriendly_mul(self):
+        # Test keeping data w/ (inplace) multiplication
+        # Test mul w/ scalar
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        xx = x * 2
+        assert_equal(xx.data, [2, 4, 3])
+        assert_equal(xx.mask, [0, 0, 1])
+        # Test imul w/ scalar
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        x *= 2
+        assert_equal(x.data, [2, 4, 3])
+        assert_equal(x.mask, [0, 0, 1])
+        # Test mul w/ array
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        xx = x * array([10, 20, 30], mask=[1, 0, 0])
+        assert_equal(xx.data, [1, 40, 3])
+        assert_equal(xx.mask, [1, 0, 1])
+        # Test imul w/ array
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        x *= array([10, 20, 30], mask=[1, 0, 0])
+        assert_equal(x.data, [1, 40, 3])
+        assert_equal(x.mask, [1, 0, 1])
+
+    def test_datafriendly_div(self):
+        # Test keeping data w/ (inplace) division
+        # Test div on scalar
+        x = array([1, 2, 3], mask=[0, 0, 1])
+        xx = x / 2.
+        assert_equal(xx.data, [1 / 2., 2 / 2., 3])
+        assert_equal(xx.mask, [0, 0, 1])
+        # Test idiv on scalar
+        x = array([1., 2., 3.], mask=[0, 0, 1])
+        x /= 2.
+        assert_equal(x.data, [1 / 2., 2 / 2., 3])
+        assert_equal(x.mask, [0, 0, 1])
+        # Test div on array
+        x = array([1., 2., 3.], mask=[0, 0, 1])
+        xx = x / array([10., 20., 30.], mask=[1, 0, 0])
+        assert_equal(xx.data, [1., 2. / 20., 3.])
+        assert_equal(xx.mask, [1, 0, 1])
+        # Test idiv on array
+        x = array([1., 2., 3.], mask=[0, 0, 1])
+        x /= array([10., 20., 30.], mask=[1, 0, 0])
+        assert_equal(x.data, [1., 2 / 20., 3.])
+        assert_equal(x.mask, [1, 0, 1])
+
+    def test_datafriendly_pow(self):
+        # Test keeping data w/ (inplace) power
+        # Test pow on scalar
+        x = array([1., 2., 3.], mask=[0, 0, 1])
+        xx = x ** 2.5
+        assert_equal(xx.data, [1., 2. ** 2.5, 3.])
+        assert_equal(xx.mask, [0, 0, 1])
+        # Test ipow on scalar
+        x **= 2.5
+        assert_equal(x.data, [1., 2. ** 2.5, 3])
+        assert_equal(x.mask, [0, 0, 1])
+
+    def test_datafriendly_add_arrays(self):
+        a = array([[1, 1], [3, 3]])
+        b = array([1, 1], mask=[0, 0])
+        a += b
+        assert_equal(a, [[2, 2], [4, 4]])
+        if a.mask is not nomask:
+            assert_equal(a.mask, [[0, 0], [0, 0]])
+
+        a = array([[1, 1], [3, 3]])
+        b = array([1, 1], mask=[0, 1])
+        a += b
+        assert_equal(a, [[2, 2], [4, 4]])
+        assert_equal(a.mask, [[0, 1], [0, 1]])
+
+    def test_datafriendly_sub_arrays(self):
+        a = array([[1, 1], [3, 3]])
+        b = array([1, 1], mask=[0, 0])
+        a -= b
+        assert_equal(a, [[0, 0], [2, 2]])
+        if a.mask is not nomask:
+            assert_equal(a.mask, [[0, 0], [0, 0]])
+
+        a = array([[1, 1], [3, 3]])
+        b = array([1, 1], mask=[0, 1])
+        a -= b
+        assert_equal(a, [[0, 0], [2, 2]])
+        assert_equal(a.mask, [[0, 1], [0, 1]])
+
+    def test_datafriendly_mul_arrays(self):
+        a = array([[1, 1], [3, 3]])
+        b = array([1, 1], mask=[0, 0])
+        a *= b
+        assert_equal(a, [[1, 1], [3, 3]])
+        if a.mask is not nomask:
+            assert_equal(a.mask, [[0, 0], [0, 0]])
+
+        a = array([[1, 1], [3, 3]])
+        b = array([1, 1], mask=[0, 1])
+        a *= b
+        assert_equal(a, [[1, 1], [3, 3]])
+        assert_equal(a.mask, [[0, 1], [0, 1]])
+
+    def test_inplace_addition_scalar_type(self):
+        # Test of inplace additions
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                xm[2] = masked
+                x += t(1)
+                assert_equal(x, y + t(1))
+                xm += t(1)
+                assert_equal(xm, y + t(1))
+
+    def test_inplace_addition_array_type(self):
+        # Test of inplace additions
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                m = xm.mask
+                a = arange(10, dtype=t)
+                a[-1] = masked
+                x += a
+                xm += a
+                assert_equal(x, y + a)
+                assert_equal(xm, y + a)
+                assert_equal(xm.mask, mask_or(m, a.mask))
+
+    def test_inplace_subtraction_scalar_type(self):
+        # Test of inplace subtractions
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                x -= t(1)
+                assert_equal(x, y - t(1))
+                xm -= t(1)
+                assert_equal(xm, y - t(1))
+
+    def test_inplace_subtraction_array_type(self):
+        # Test of inplace subtractions
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                m = xm.mask
+                a = arange(10, dtype=t)
+                a[-1] = masked
+                x -= a
+                xm -= a
+                assert_equal(x, y - a)
+                assert_equal(xm, y - a)
+                assert_equal(xm.mask, mask_or(m, a.mask))
+
+    def test_inplace_multiplication_scalar_type(self):
+        # Test of inplace multiplication
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                x *= t(2)
+                assert_equal(x, y * t(2))
+                xm *= t(2)
+                assert_equal(xm, y * t(2))
+
+    def test_inplace_multiplication_array_type(self):
+        # Test of inplace multiplication
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                m = xm.mask
+                a = arange(10, dtype=t)
+                a[-1] = masked
+                x *= a
+                xm *= a
+                assert_equal(x, y * a)
+                assert_equal(xm, y * a)
+                assert_equal(xm.mask, mask_or(m, a.mask))
+
+    def test_inplace_floor_division_scalar_type(self):
+        # Test of inplace division
+        # Check for TypeError in case of unsupported types
+        unsupported = {np.dtype(t).type for t in np.typecodes["Complex"]}
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                x = arange(10, dtype=t) * t(2)
+                xm = arange(10, dtype=t) * t(2)
+                xm[2] = masked
+                try:
+                    x //= t(2)
+                    xm //= t(2)
+                    assert_equal(x, y)
+                    assert_equal(xm, y)
+                except TypeError:
+                    msg = f"Supported type {t} throwing TypeError"
+                    assert t in unsupported, msg
+
+    def test_inplace_floor_division_array_type(self):
+        # Test of inplace division
+        # Check for TypeError in case of unsupported types
+        unsupported = {np.dtype(t).type for t in np.typecodes["Complex"]}
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                m = xm.mask
+                a = arange(10, dtype=t)
+                a[-1] = masked
+                try:
+                    x //= a
+                    xm //= a
+                    assert_equal(x, y // a)
+                    assert_equal(xm, y // a)
+                    assert_equal(
+                        xm.mask,
+                        mask_or(mask_or(m, a.mask), (a == t(0)))
+                    )
+                except TypeError:
+                    msg = f"Supported type {t} throwing TypeError"
+                    assert t in unsupported, msg
+
+    def test_inplace_division_scalar_type(self):
+        # Test of inplace division
+        for t in self.othertypes:
+            with suppress_warnings() as sup:
+                sup.record(UserWarning)
+
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                x = arange(10, dtype=t) * t(2)
+                xm = arange(10, dtype=t) * t(2)
+                xm[2] = masked
+
+                # May get a DeprecationWarning or a TypeError.
+                #
+                # This is a consequence of the fact that this is true divide
+                # and will require casting to float for calculation and
+                # casting back to the original type. This will only be raised
+                # with integers. Whether it is an error or warning is only
+                # dependent on how stringent the casting rules are.
+                #
+                # Will handle the same way.
+                try:
+                    x /= t(2)
+                    assert_equal(x, y)
+                except (DeprecationWarning, TypeError) as e:
+                    warnings.warn(str(e), stacklevel=1)
+                try:
+                    xm /= t(2)
+                    assert_equal(xm, y)
+                except (DeprecationWarning, TypeError) as e:
+                    warnings.warn(str(e), stacklevel=1)
+
+                if issubclass(t, np.integer):
+                    assert_equal(len(sup.log), 2, f'Failed on type={t}.')
+                else:
+                    assert_equal(len(sup.log), 0, f'Failed on type={t}.')
+
+    def test_inplace_division_array_type(self):
+        # Test of inplace division
+        for t in self.othertypes:
+            with suppress_warnings() as sup:
+                sup.record(UserWarning)
+                (x, y, xm) = (_.astype(t) for _ in self.uint8data)
+                m = xm.mask
+                a = arange(10, dtype=t)
+                a[-1] = masked
+
+                # May get a DeprecationWarning or a TypeError.
+                #
+                # This is a consequence of the fact that this is true divide
+                # and will require casting to float for calculation and
+                # casting back to the original type. This will only be raised
+                # with integers. Whether it is an error or warning is only
+                # dependent on how stringent the casting rules are.
+                #
+                # Will handle the same way.
+                try:
+                    x /= a
+                    assert_equal(x, y / a)
+                except (DeprecationWarning, TypeError) as e:
+                    warnings.warn(str(e), stacklevel=1)
+                try:
+                    xm /= a
+                    assert_equal(xm, y / a)
+                    assert_equal(
+                        xm.mask,
+                        mask_or(mask_or(m, a.mask), (a == t(0)))
+                    )
+                except (DeprecationWarning, TypeError) as e:
+                    warnings.warn(str(e), stacklevel=1)
+
+                if issubclass(t, np.integer):
+                    assert_equal(len(sup.log), 2, f'Failed on type={t}.')
+                else:
+                    assert_equal(len(sup.log), 0, f'Failed on type={t}.')
+
+    def test_inplace_pow_type(self):
+        # Test keeping data w/ (inplace) power
+        for t in self.othertypes:
+            with warnings.catch_warnings():
+                warnings.filterwarnings("error")
+                # Test pow on scalar
+                x = array([1, 2, 3], mask=[0, 0, 1], dtype=t)
+                xx = x ** t(2)
+                xx_r = array([1, 2 ** 2, 3], mask=[0, 0, 1], dtype=t)
+                assert_equal(xx.data, xx_r.data)
+                assert_equal(xx.mask, xx_r.mask)
+                # Test ipow on scalar
+                x **= t(2)
+                assert_equal(x.data, xx_r.data)
+                assert_equal(x.mask, xx_r.mask)
+
+
+class TestMaskedArrayMethods:
+    # Test class for miscellaneous MaskedArrays methods.
+    def setup_method(self):
+        # Base data definition.
+        x = np.array([8.375, 7.545, 8.828, 8.5, 1.757, 5.928,
+                      8.43, 7.78, 9.865, 5.878, 8.979, 4.732,
+                      3.012, 6.022, 5.095, 3.116, 5.238, 3.957,
+                      6.04, 9.63, 7.712, 3.382, 4.489, 6.479,
+                      7.189, 9.645, 5.395, 4.961, 9.894, 2.893,
+                      7.357, 9.828, 6.272, 3.758, 6.693, 0.993])
+        X = x.reshape(6, 6)
+        XX = x.reshape(3, 2, 2, 3)
+
+        m = np.array([0, 1, 0, 1, 0, 0,
+                     1, 0, 1, 1, 0, 1,
+                     0, 0, 0, 1, 0, 1,
+                     0, 0, 0, 1, 1, 1,
+                     1, 0, 0, 1, 0, 0,
+                     0, 0, 1, 0, 1, 0])
+        mx = array(data=x, mask=m)
+        mX = array(data=X, mask=m.reshape(X.shape))
+        mXX = array(data=XX, mask=m.reshape(XX.shape))
+
+        m2 = np.array([1, 1, 0, 1, 0, 0,
+                      1, 1, 1, 1, 0, 1,
+                      0, 0, 1, 1, 0, 1,
+                      0, 0, 0, 1, 1, 1,
+                      1, 0, 0, 1, 1, 0,
+                      0, 0, 1, 0, 1, 1])
+        m2x = array(data=x, mask=m2)
+        m2X = array(data=X, mask=m2.reshape(X.shape))
+        m2XX = array(data=XX, mask=m2.reshape(XX.shape))
+        self.d = (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX)
+
+    def test_generic_methods(self):
+        # Tests some MaskedArray methods.
+        a = array([1, 3, 2])
+        assert_equal(a.any(), a._data.any())
+        assert_equal(a.all(), a._data.all())
+        assert_equal(a.argmax(), a._data.argmax())
+        assert_equal(a.argmin(), a._data.argmin())
+        assert_equal(a.choose(0, 1, 2, 3, 4), a._data.choose(0, 1, 2, 3, 4))
+        assert_equal(a.compress([1, 0, 1]), a._data.compress([1, 0, 1]))
+        assert_equal(a.conj(), a._data.conj())
+        assert_equal(a.conjugate(), a._data.conjugate())
+
+        m = array([[1, 2], [3, 4]])
+        assert_equal(m.diagonal(), m._data.diagonal())
+        assert_equal(a.sum(), a._data.sum())
+        assert_equal(a.take([1, 2]), a._data.take([1, 2]))
+        assert_equal(m.transpose(), m._data.transpose())
+
+    def test_allclose(self):
+        # Tests allclose on arrays
+        a = np.random.rand(10)
+        b = a + np.random.rand(10) * 1e-8
+        assert_(allclose(a, b))
+        # Test allclose w/ infs
+        a[0] = np.inf
+        assert_(not allclose(a, b))
+        b[0] = np.inf
+        assert_(allclose(a, b))
+        # Test allclose w/ masked
+        a = masked_array(a)
+        a[-1] = masked
+        assert_(allclose(a, b, masked_equal=True))
+        assert_(not allclose(a, b, masked_equal=False))
+        # Test comparison w/ scalar
+        a *= 1e-8
+        a[0] = 0
+        assert_(allclose(a, 0, masked_equal=True))
+
+        # Test that the function works for MIN_INT integer typed arrays
+        a = masked_array([np.iinfo(np.int_).min], dtype=np.int_)
+        assert_(allclose(a, a))
+
+    def test_allclose_timedelta(self):
+        # Allclose currently works for timedelta64 as long as `atol` is
+        # an integer or also a timedelta64
+        a = np.array([[1, 2, 3, 4]], dtype="m8[ns]")
+        assert allclose(a, a, atol=0)
+        assert allclose(a, a, atol=np.timedelta64(1, "ns"))
+
+    def test_allany(self):
+        # Checks the any/all methods/functions.
+        x = np.array([[0.13, 0.26, 0.90],
+                      [0.28, 0.33, 0.63],
+                      [0.31, 0.87, 0.70]])
+        m = np.array([[True, False, False],
+                      [False, False, False],
+                      [True, True, False]], dtype=np.bool_)
+        mx = masked_array(x, mask=m)
+        mxbig = (mx > 0.5)
+        mxsmall = (mx < 0.5)
+
+        assert_(not mxbig.all())
+        assert_(mxbig.any())
+        assert_equal(mxbig.all(0), [False, False, True])
+        assert_equal(mxbig.all(1), [False, False, True])
+        assert_equal(mxbig.any(0), [False, False, True])
+        assert_equal(mxbig.any(1), [True, True, True])
+
+        assert_(not mxsmall.all())
+        assert_(mxsmall.any())
+        assert_equal(mxsmall.all(0), [True, True, False])
+        assert_equal(mxsmall.all(1), [False, False, False])
+        assert_equal(mxsmall.any(0), [True, True, False])
+        assert_equal(mxsmall.any(1), [True, True, False])
+
+    def test_allany_oddities(self):
+        # Some fun with all and any
+        store = empty((), dtype=bool)
+        full = array([1, 2, 3], mask=True)
+
+        assert_(full.all() is masked)
+        full.all(out=store)
+        assert_(store)
+        assert_(store._mask, True)
+        assert_(store is not masked)
+
+        store = empty((), dtype=bool)
+        assert_(full.any() is masked)
+        full.any(out=store)
+        assert_(not store)
+        assert_(store._mask, True)
+        assert_(store is not masked)
+
+    def test_argmax_argmin(self):
+        # Tests argmin & argmax on MaskedArrays.
+        (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX) = self.d
+
+        assert_equal(mx.argmin(), 35)
+        assert_equal(mX.argmin(), 35)
+        assert_equal(m2x.argmin(), 4)
+        assert_equal(m2X.argmin(), 4)
+        assert_equal(mx.argmax(), 28)
+        assert_equal(mX.argmax(), 28)
+        assert_equal(m2x.argmax(), 31)
+        assert_equal(m2X.argmax(), 31)
+
+        assert_equal(mX.argmin(0), [2, 2, 2, 5, 0, 5])
+        assert_equal(m2X.argmin(0), [2, 2, 4, 5, 0, 4])
+        assert_equal(mX.argmax(0), [0, 5, 0, 5, 4, 0])
+        assert_equal(m2X.argmax(0), [5, 5, 0, 5, 1, 0])
+
+        assert_equal(mX.argmin(1), [4, 1, 0, 0, 5, 5, ])
+        assert_equal(m2X.argmin(1), [4, 4, 0, 0, 5, 3])
+        assert_equal(mX.argmax(1), [2, 4, 1, 1, 4, 1])
+        assert_equal(m2X.argmax(1), [2, 4, 1, 1, 1, 1])
+
+    def test_clip(self):
+        # Tests clip on MaskedArrays.
+        x = np.array([8.375, 7.545, 8.828, 8.5, 1.757, 5.928,
+                      8.43, 7.78, 9.865, 5.878, 8.979, 4.732,
+                      3.012, 6.022, 5.095, 3.116, 5.238, 3.957,
+                      6.04, 9.63, 7.712, 3.382, 4.489, 6.479,
+                      7.189, 9.645, 5.395, 4.961, 9.894, 2.893,
+                      7.357, 9.828, 6.272, 3.758, 6.693, 0.993])
+        m = np.array([0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
+                      0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1,
+                      1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0])
+        mx = array(x, mask=m)
+        clipped = mx.clip(2, 8)
+        assert_equal(clipped.mask, mx.mask)
+        assert_equal(clipped._data, x.clip(2, 8))
+        assert_equal(clipped._data, mx._data.clip(2, 8))
+
+    def test_clip_out(self):
+        # gh-14140
+        a = np.arange(10)
+        m = np.ma.MaskedArray(a, mask=[0, 1] * 5)
+        m.clip(0, 5, out=m)
+        assert_equal(m.mask, [0, 1] * 5)
+
+    def test_compress(self):
+        # test compress
+        a = masked_array([1., 2., 3., 4., 5.], fill_value=9999)
+        condition = (a > 1.5) & (a < 3.5)
+        assert_equal(a.compress(condition), [2., 3.])
+
+        a[[2, 3]] = masked
+        b = a.compress(condition)
+        assert_equal(b._data, [2., 3.])
+        assert_equal(b._mask, [0, 1])
+        assert_equal(b.fill_value, 9999)
+        assert_equal(b, a[condition])
+
+        condition = (a < 4.)
+        b = a.compress(condition)
+        assert_equal(b._data, [1., 2., 3.])
+        assert_equal(b._mask, [0, 0, 1])
+        assert_equal(b.fill_value, 9999)
+        assert_equal(b, a[condition])
+
+        a = masked_array([[10, 20, 30], [40, 50, 60]],
+                         mask=[[0, 0, 1], [1, 0, 0]])
+        b = a.compress(a.ravel() >= 22)
+        assert_equal(b._data, [30, 40, 50, 60])
+        assert_equal(b._mask, [1, 1, 0, 0])
+
+        x = np.array([3, 1, 2])
+        b = a.compress(x >= 2, axis=1)
+        assert_equal(b._data, [[10, 30], [40, 60]])
+        assert_equal(b._mask, [[0, 1], [1, 0]])
+
+    def test_compressed(self):
+        # Tests compressed
+        a = array([1, 2, 3, 4], mask=[0, 0, 0, 0])
+        b = a.compressed()
+        assert_equal(b, a)
+        a[0] = masked
+        b = a.compressed()
+        assert_equal(b, [2, 3, 4])
+
+    def test_empty(self):
+        # Tests empty/like
+        datatype = [('a', int), ('b', float), ('c', '|S8')]
+        a = masked_array([(1, 1.1, '1.1'), (2, 2.2, '2.2'), (3, 3.3, '3.3')],
+                         dtype=datatype)
+        assert_equal(len(a.fill_value.item()), len(datatype))
+
+        b = empty_like(a)
+        assert_equal(b.shape, a.shape)
+        assert_equal(b.fill_value, a.fill_value)
+
+        b = empty(len(a), dtype=datatype)
+        assert_equal(b.shape, a.shape)
+        assert_equal(b.fill_value, a.fill_value)
+
+        # check empty_like mask handling
+        a = masked_array([1, 2, 3], mask=[False, True, False])
+        b = empty_like(a)
+        assert_(not np.may_share_memory(a.mask, b.mask))
+        b = a.view(masked_array)
+        assert_(np.may_share_memory(a.mask, b.mask))
+
+    def test_zeros(self):
+        # Tests zeros/like
+        datatype = [('a', int), ('b', float), ('c', '|S8')]
+        a = masked_array([(1, 1.1, '1.1'), (2, 2.2, '2.2'), (3, 3.3, '3.3')],
+                         dtype=datatype)
+        assert_equal(len(a.fill_value.item()), len(datatype))
+
+        b = zeros(len(a), dtype=datatype)
+        assert_equal(b.shape, a.shape)
+        assert_equal(b.fill_value, a.fill_value)
+
+        b = zeros_like(a)
+        assert_equal(b.shape, a.shape)
+        assert_equal(b.fill_value, a.fill_value)
+
+        # check zeros_like mask handling
+        a = masked_array([1, 2, 3], mask=[False, True, False])
+        b = zeros_like(a)
+        assert_(not np.may_share_memory(a.mask, b.mask))
+        b = a.view()
+        assert_(np.may_share_memory(a.mask, b.mask))
+
+    def test_ones(self):
+        # Tests ones/like
+        datatype = [('a', int), ('b', float), ('c', '|S8')]
+        a = masked_array([(1, 1.1, '1.1'), (2, 2.2, '2.2'), (3, 3.3, '3.3')],
+                         dtype=datatype)
+        assert_equal(len(a.fill_value.item()), len(datatype))
+
+        b = ones(len(a), dtype=datatype)
+        assert_equal(b.shape, a.shape)
+        assert_equal(b.fill_value, a.fill_value)
+
+        b = ones_like(a)
+        assert_equal(b.shape, a.shape)
+        assert_equal(b.fill_value, a.fill_value)
+
+        # check ones_like mask handling
+        a = masked_array([1, 2, 3], mask=[False, True, False])
+        b = ones_like(a)
+        assert_(not np.may_share_memory(a.mask, b.mask))
+        b = a.view()
+        assert_(np.may_share_memory(a.mask, b.mask))
+
+    @suppress_copy_mask_on_assignment
+    def test_put(self):
+        # Tests put.
+        d = arange(5)
+        n = [0, 0, 0, 1, 1]
+        m = make_mask(n)
+        x = array(d, mask=m)
+        assert_(x[3] is masked)
+        assert_(x[4] is masked)
+        x[[1, 4]] = [10, 40]
+        assert_(x[3] is masked)
+        assert_(x[4] is not masked)
+        assert_equal(x, [0, 10, 2, -1, 40])
+
+        x = masked_array(arange(10), mask=[1, 0, 0, 0, 0] * 2)
+        i = [0, 2, 4, 6]
+        x.put(i, [6, 4, 2, 0])
+        assert_equal(x, asarray([6, 1, 4, 3, 2, 5, 0, 7, 8, 9, ]))
+        assert_equal(x.mask, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0])
+        x.put(i, masked_array([0, 2, 4, 6], [1, 0, 1, 0]))
+        assert_array_equal(x, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ])
+        assert_equal(x.mask, [1, 0, 0, 0, 1, 1, 0, 0, 0, 0])
+
+        x = masked_array(arange(10), mask=[1, 0, 0, 0, 0] * 2)
+        put(x, i, [6, 4, 2, 0])
+        assert_equal(x, asarray([6, 1, 4, 3, 2, 5, 0, 7, 8, 9, ]))
+        assert_equal(x.mask, [0, 0, 0, 0, 0, 1, 0, 0, 0, 0])
+        put(x, i, masked_array([0, 2, 4, 6], [1, 0, 1, 0]))
+        assert_array_equal(x, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ])
+        assert_equal(x.mask, [1, 0, 0, 0, 1, 1, 0, 0, 0, 0])
+
+    def test_put_nomask(self):
+        # GitHub issue 6425
+        x = zeros(10)
+        z = array([3., -1.], mask=[False, True])
+
+        x.put([1, 2], z)
+        assert_(x[0] is not masked)
+        assert_equal(x[0], 0)
+        assert_(x[1] is not masked)
+        assert_equal(x[1], 3)
+        assert_(x[2] is masked)
+        assert_(x[3] is not masked)
+        assert_equal(x[3], 0)
+
+    def test_put_hardmask(self):
+        # Tests put on hardmask
+        d = arange(5)
+        n = [0, 0, 0, 1, 1]
+        m = make_mask(n)
+        xh = array(d + 1, mask=m, hard_mask=True, copy=True)
+        xh.put([4, 2, 0, 1, 3], [1, 2, 3, 4, 5])
+        assert_equal(xh._data, [3, 4, 2, 4, 5])
+
+    def test_putmask(self):
+        x = arange(6) + 1
+        mx = array(x, mask=[0, 0, 0, 1, 1, 1])
+        mask = [0, 0, 1, 0, 0, 1]
+        # w/o mask, w/o masked values
+        xx = x.copy()
+        putmask(xx, mask, 99)
+        assert_equal(xx, [1, 2, 99, 4, 5, 99])
+        # w/ mask, w/o masked values
+        mxx = mx.copy()
+        putmask(mxx, mask, 99)
+        assert_equal(mxx._data, [1, 2, 99, 4, 5, 99])
+        assert_equal(mxx._mask, [0, 0, 0, 1, 1, 0])
+        # w/o mask, w/ masked values
+        values = array([10, 20, 30, 40, 50, 60], mask=[1, 1, 1, 0, 0, 0])
+        xx = x.copy()
+        putmask(xx, mask, values)
+        assert_equal(xx._data, [1, 2, 30, 4, 5, 60])
+        assert_equal(xx._mask, [0, 0, 1, 0, 0, 0])
+        # w/ mask, w/ masked values
+        mxx = mx.copy()
+        putmask(mxx, mask, values)
+        assert_equal(mxx._data, [1, 2, 30, 4, 5, 60])
+        assert_equal(mxx._mask, [0, 0, 1, 1, 1, 0])
+        # w/ mask, w/ masked values + hardmask
+        mxx = mx.copy()
+        mxx.harden_mask()
+        putmask(mxx, mask, values)
+        assert_equal(mxx, [1, 2, 30, 4, 5, 60])
+
+    def test_ravel(self):
+        # Tests ravel
+        a = array([[1, 2, 3, 4, 5]], mask=[[0, 1, 0, 0, 0]])
+        aravel = a.ravel()
+        assert_equal(aravel._mask.shape, aravel.shape)
+        a = array([0, 0], mask=[1, 1])
+        aravel = a.ravel()
+        assert_equal(aravel._mask.shape, a.shape)
+        # Checks that small_mask is preserved
+        a = array([1, 2, 3, 4], mask=[0, 0, 0, 0], shrink=False)
+        assert_equal(a.ravel()._mask, [0, 0, 0, 0])
+        # Test that the fill_value is preserved
+        a.fill_value = -99
+        a.shape = (2, 2)
+        ar = a.ravel()
+        assert_equal(ar._mask, [0, 0, 0, 0])
+        assert_equal(ar._data, [1, 2, 3, 4])
+        assert_equal(ar.fill_value, -99)
+        # Test index ordering
+        assert_equal(a.ravel(order='C'), [1, 2, 3, 4])
+        assert_equal(a.ravel(order='F'), [1, 3, 2, 4])
+
+    @pytest.mark.parametrize("order", "AKCF")
+    @pytest.mark.parametrize("data_order", "CF")
+    def test_ravel_order(self, order, data_order):
+        # Ravelling must ravel mask and data in the same order always to avoid
+        # misaligning the two in the ravel result.
+        arr = np.ones((5, 10), order=data_order)
+        arr[0, :] = 0
+        mask = np.ones((10, 5), dtype=bool, order=data_order).T
+        mask[0, :] = False
+        x = array(arr, mask=mask)
+        assert x._data.flags.fnc != x._mask.flags.fnc
+        assert (x.filled(0) == 0).all()
+        raveled = x.ravel(order)
+        assert (raveled.filled(0) == 0).all()
+
+        # NOTE: Can be wrong if arr order is neither C nor F and `order="K"`
+        assert_array_equal(arr.ravel(order), x.ravel(order)._data)
+
+    def test_reshape(self):
+        # Tests reshape
+        x = arange(4)
+        x[0] = masked
+        y = x.reshape(2, 2)
+        assert_equal(y.shape, (2, 2,))
+        assert_equal(y._mask.shape, (2, 2,))
+        assert_equal(x.shape, (4,))
+        assert_equal(x._mask.shape, (4,))
+
+    def test_sort(self):
+        # Test sort
+        x = array([1, 4, 2, 3], mask=[0, 1, 0, 0], dtype=np.uint8)
+
+        sortedx = sort(x)
+        assert_equal(sortedx._data, [1, 2, 3, 4])
+        assert_equal(sortedx._mask, [0, 0, 0, 1])
+
+        sortedx = sort(x, endwith=False)
+        assert_equal(sortedx._data, [4, 1, 2, 3])
+        assert_equal(sortedx._mask, [1, 0, 0, 0])
+
+        x.sort()
+        assert_equal(x._data, [1, 2, 3, 4])
+        assert_equal(x._mask, [0, 0, 0, 1])
+
+        x = array([1, 4, 2, 3], mask=[0, 1, 0, 0], dtype=np.uint8)
+        x.sort(endwith=False)
+        assert_equal(x._data, [4, 1, 2, 3])
+        assert_equal(x._mask, [1, 0, 0, 0])
+
+        x = [1, 4, 2, 3]
+        sortedx = sort(x)
+        assert_(not isinstance(sorted, MaskedArray))
+
+        x = array([0, 1, -1, -2, 2], mask=nomask, dtype=np.int8)
+        sortedx = sort(x, endwith=False)
+        assert_equal(sortedx._data, [-2, -1, 0, 1, 2])
+        x = array([0, 1, -1, -2, 2], mask=[0, 1, 0, 0, 1], dtype=np.int8)
+        sortedx = sort(x, endwith=False)
+        assert_equal(sortedx._data, [1, 2, -2, -1, 0])
+        assert_equal(sortedx._mask, [1, 1, 0, 0, 0])
+
+        x = array([0, -1], dtype=np.int8)
+        sortedx = sort(x, kind="stable")
+        assert_equal(sortedx, array([-1, 0], dtype=np.int8))
+
+    def test_stable_sort(self):
+        x = array([1, 2, 3, 1, 2, 3], dtype=np.uint8)
+        expected = array([0, 3, 1, 4, 2, 5])
+        computed = argsort(x, kind='stable')
+        assert_equal(computed, expected)
+
+    def test_argsort_matches_sort(self):
+        x = array([1, 4, 2, 3], mask=[0, 1, 0, 0], dtype=np.uint8)
+
+        for kwargs in [dict(),
+                       dict(endwith=True),
+                       dict(endwith=False),
+                       dict(fill_value=2),
+                       dict(fill_value=2, endwith=True),
+                       dict(fill_value=2, endwith=False)]:
+            sortedx = sort(x, **kwargs)
+            argsortedx = x[argsort(x, **kwargs)]
+            assert_equal(sortedx._data, argsortedx._data)
+            assert_equal(sortedx._mask, argsortedx._mask)
+
+    def test_sort_2d(self):
+        # Check sort of 2D array.
+        # 2D array w/o mask
+        a = masked_array([[8, 4, 1], [2, 0, 9]])
+        a.sort(0)
+        assert_equal(a, [[2, 0, 1], [8, 4, 9]])
+        a = masked_array([[8, 4, 1], [2, 0, 9]])
+        a.sort(1)
+        assert_equal(a, [[1, 4, 8], [0, 2, 9]])
+        # 2D array w/mask
+        a = masked_array([[8, 4, 1], [2, 0, 9]], mask=[[1, 0, 0], [0, 0, 1]])
+        a.sort(0)
+        assert_equal(a, [[2, 0, 1], [8, 4, 9]])
+        assert_equal(a._mask, [[0, 0, 0], [1, 0, 1]])
+        a = masked_array([[8, 4, 1], [2, 0, 9]], mask=[[1, 0, 0], [0, 0, 1]])
+        a.sort(1)
+        assert_equal(a, [[1, 4, 8], [0, 2, 9]])
+        assert_equal(a._mask, [[0, 0, 1], [0, 0, 1]])
+        # 3D
+        a = masked_array([[[7, 8, 9], [4, 5, 6], [1, 2, 3]],
+                          [[1, 2, 3], [7, 8, 9], [4, 5, 6]],
+                          [[7, 8, 9], [1, 2, 3], [4, 5, 6]],
+                          [[4, 5, 6], [1, 2, 3], [7, 8, 9]]])
+        a[a % 4 == 0] = masked
+        am = a.copy()
+        an = a.filled(99)
+        am.sort(0)
+        an.sort(0)
+        assert_equal(am, an)
+        am = a.copy()
+        an = a.filled(99)
+        am.sort(1)
+        an.sort(1)
+        assert_equal(am, an)
+        am = a.copy()
+        an = a.filled(99)
+        am.sort(2)
+        an.sort(2)
+        assert_equal(am, an)
+
+    def test_sort_flexible(self):
+        # Test sort on structured dtype.
+        a = array(
+            data=[(3, 3), (3, 2), (2, 2), (2, 1), (1, 0), (1, 1), (1, 2)],
+            mask=[(0, 0), (0, 1), (0, 0), (0, 0), (1, 0), (0, 0), (0, 0)],
+            dtype=[('A', int), ('B', int)])
+        mask_last = array(
+            data=[(1, 1), (1, 2), (2, 1), (2, 2), (3, 3), (3, 2), (1, 0)],
+            mask=[(0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 1), (1, 0)],
+            dtype=[('A', int), ('B', int)])
+        mask_first = array(
+            data=[(1, 0), (1, 1), (1, 2), (2, 1), (2, 2), (3, 2), (3, 3)],
+            mask=[(1, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 1), (0, 0)],
+            dtype=[('A', int), ('B', int)])
+
+        test = sort(a)
+        assert_equal(test, mask_last)
+        assert_equal(test.mask, mask_last.mask)
+
+        test = sort(a, endwith=False)
+        assert_equal(test, mask_first)
+        assert_equal(test.mask, mask_first.mask)
+
+        # Test sort on dtype with subarray (gh-8069)
+        # Just check that the sort does not error, structured array subarrays
+        # are treated as byte strings and that leads to differing behavior
+        # depending on endianness and `endwith`.
+        dt = np.dtype([('v', int, 2)])
+        a = a.view(dt)
+        test = sort(a)
+        test = sort(a, endwith=False)
+
+    def test_argsort(self):
+        # Test argsort
+        a = array([1, 5, 2, 4, 3], mask=[1, 0, 0, 1, 0])
+        assert_equal(np.argsort(a), argsort(a))
+
+    def test_squeeze(self):
+        # Check squeeze
+        data = masked_array([[1, 2, 3]])
+        assert_equal(data.squeeze(), [1, 2, 3])
+        data = masked_array([[1, 2, 3]], mask=[[1, 1, 1]])
+        assert_equal(data.squeeze(), [1, 2, 3])
+        assert_equal(data.squeeze()._mask, [1, 1, 1])
+
+        # normal ndarrays return a view
+        arr = np.array([[1]])
+        arr_sq = arr.squeeze()
+        assert_equal(arr_sq, 1)
+        arr_sq[...] = 2
+        assert_equal(arr[0,0], 2)
+
+        # so maskedarrays should too
+        m_arr = masked_array([[1]], mask=True)
+        m_arr_sq = m_arr.squeeze()
+        assert_(m_arr_sq is not np.ma.masked)
+        assert_equal(m_arr_sq.mask, True)
+        m_arr_sq[...] = 2
+        assert_equal(m_arr[0,0], 2)
+
+    def test_swapaxes(self):
+        # Tests swapaxes on MaskedArrays.
+        x = np.array([8.375, 7.545, 8.828, 8.5, 1.757, 5.928,
+                      8.43, 7.78, 9.865, 5.878, 8.979, 4.732,
+                      3.012, 6.022, 5.095, 3.116, 5.238, 3.957,
+                      6.04, 9.63, 7.712, 3.382, 4.489, 6.479,
+                      7.189, 9.645, 5.395, 4.961, 9.894, 2.893,
+                      7.357, 9.828, 6.272, 3.758, 6.693, 0.993])
+        m = np.array([0, 1, 0, 1, 0, 0,
+                      1, 0, 1, 1, 0, 1,
+                      0, 0, 0, 1, 0, 1,
+                      0, 0, 0, 1, 1, 1,
+                      1, 0, 0, 1, 0, 0,
+                      0, 0, 1, 0, 1, 0])
+        mX = array(x, mask=m).reshape(6, 6)
+        mXX = mX.reshape(3, 2, 2, 3)
+
+        mXswapped = mX.swapaxes(0, 1)
+        assert_equal(mXswapped[-1], mX[:, -1])
+
+        mXXswapped = mXX.swapaxes(0, 2)
+        assert_equal(mXXswapped.shape, (2, 2, 3, 3))
+
+    def test_take(self):
+        # Tests take
+        x = masked_array([10, 20, 30, 40], [0, 1, 0, 1])
+        assert_equal(x.take([0, 0, 3]), masked_array([10, 10, 40], [0, 0, 1]))
+        assert_equal(x.take([0, 0, 3]), x[[0, 0, 3]])
+        assert_equal(x.take([[0, 1], [0, 1]]),
+                     masked_array([[10, 20], [10, 20]], [[0, 1], [0, 1]]))
+
+        # assert_equal crashes when passed np.ma.mask
+        assert_(x[1] is np.ma.masked)
+        assert_(x.take(1) is np.ma.masked)
+
+        x = array([[10, 20, 30], [40, 50, 60]], mask=[[0, 0, 1], [1, 0, 0, ]])
+        assert_equal(x.take([0, 2], axis=1),
+                     array([[10, 30], [40, 60]], mask=[[0, 1], [1, 0]]))
+        assert_equal(take(x, [0, 2], axis=1),
+                     array([[10, 30], [40, 60]], mask=[[0, 1], [1, 0]]))
+
+    def test_take_masked_indices(self):
+        # Test take w/ masked indices
+        a = np.array((40, 18, 37, 9, 22))
+        indices = np.arange(3)[None,:] + np.arange(5)[:, None]
+        mindices = array(indices, mask=(indices >= len(a)))
+        # No mask
+        test = take(a, mindices, mode='clip')
+        ctrl = array([[40, 18, 37],
+                      [18, 37, 9],
+                      [37, 9, 22],
+                      [9, 22, 22],
+                      [22, 22, 22]])
+        assert_equal(test, ctrl)
+        # Masked indices
+        test = take(a, mindices)
+        ctrl = array([[40, 18, 37],
+                      [18, 37, 9],
+                      [37, 9, 22],
+                      [9, 22, 40],
+                      [22, 40, 40]])
+        ctrl[3, 2] = ctrl[4, 1] = ctrl[4, 2] = masked
+        assert_equal(test, ctrl)
+        assert_equal(test.mask, ctrl.mask)
+        # Masked input + masked indices
+        a = array((40, 18, 37, 9, 22), mask=(0, 1, 0, 0, 0))
+        test = take(a, mindices)
+        ctrl[0, 1] = ctrl[1, 0] = masked
+        assert_equal(test, ctrl)
+        assert_equal(test.mask, ctrl.mask)
+
+    def test_tolist(self):
+        # Tests to list
+        # ... on 1D
+        x = array(np.arange(12))
+        x[[1, -2]] = masked
+        xlist = x.tolist()
+        assert_(xlist[1] is None)
+        assert_(xlist[-2] is None)
+        # ... on 2D
+        x.shape = (3, 4)
+        xlist = x.tolist()
+        ctrl = [[0, None, 2, 3], [4, 5, 6, 7], [8, 9, None, 11]]
+        assert_equal(xlist[0], [0, None, 2, 3])
+        assert_equal(xlist[1], [4, 5, 6, 7])
+        assert_equal(xlist[2], [8, 9, None, 11])
+        assert_equal(xlist, ctrl)
+        # ... on structured array w/ masked records
+        x = array(list(zip([1, 2, 3],
+                           [1.1, 2.2, 3.3],
+                           ['one', 'two', 'thr'])),
+                  dtype=[('a', int), ('b', float), ('c', '|S8')])
+        x[-1] = masked
+        assert_equal(x.tolist(),
+                     [(1, 1.1, b'one'),
+                      (2, 2.2, b'two'),
+                      (None, None, None)])
+        # ... on structured array w/ masked fields
+        a = array([(1, 2,), (3, 4)], mask=[(0, 1), (0, 0)],
+                  dtype=[('a', int), ('b', int)])
+        test = a.tolist()
+        assert_equal(test, [[1, None], [3, 4]])
+        # ... on mvoid
+        a = a[0]
+        test = a.tolist()
+        assert_equal(test, [1, None])
+
+    def test_tolist_specialcase(self):
+        # Test mvoid.tolist: make sure we return a standard Python object
+        a = array([(0, 1), (2, 3)], dtype=[('a', int), ('b', int)])
+        # w/o mask: each entry is a np.void whose elements are standard Python
+        for entry in a:
+            for item in entry.tolist():
+                assert_(not isinstance(item, np.generic))
+        # w/ mask: each entry is a ma.void whose elements should be
+        # standard Python
+        a.mask[0] = (0, 1)
+        for entry in a:
+            for item in entry.tolist():
+                assert_(not isinstance(item, np.generic))
+
+    def test_toflex(self):
+        # Test the conversion to records
+        data = arange(10)
+        record = data.toflex()
+        assert_equal(record['_data'], data._data)
+        assert_equal(record['_mask'], data._mask)
+
+        data[[0, 1, 2, -1]] = masked
+        record = data.toflex()
+        assert_equal(record['_data'], data._data)
+        assert_equal(record['_mask'], data._mask)
+
+        ndtype = [('i', int), ('s', '|S3'), ('f', float)]
+        data = array([(i, s, f) for (i, s, f) in zip(np.arange(10),
+                                                     'ABCDEFGHIJKLM',
+                                                     np.random.rand(10))],
+                     dtype=ndtype)
+        data[[0, 1, 2, -1]] = masked
+        record = data.toflex()
+        assert_equal(record['_data'], data._data)
+        assert_equal(record['_mask'], data._mask)
+
+        ndtype = np.dtype("int, (2,3)float, float")
+        data = array([(i, f, ff) for (i, f, ff) in zip(np.arange(10),
+                                                       np.random.rand(10),
+                                                       np.random.rand(10))],
+                     dtype=ndtype)
+        data[[0, 1, 2, -1]] = masked
+        record = data.toflex()
+        assert_equal_records(record['_data'], data._data)
+        assert_equal_records(record['_mask'], data._mask)
+
+    def test_fromflex(self):
+        # Test the reconstruction of a masked_array from a record
+        a = array([1, 2, 3])
+        test = fromflex(a.toflex())
+        assert_equal(test, a)
+        assert_equal(test.mask, a.mask)
+
+        a = array([1, 2, 3], mask=[0, 0, 1])
+        test = fromflex(a.toflex())
+        assert_equal(test, a)
+        assert_equal(test.mask, a.mask)
+
+        a = array([(1, 1.), (2, 2.), (3, 3.)], mask=[(1, 0), (0, 0), (0, 1)],
+                  dtype=[('A', int), ('B', float)])
+        test = fromflex(a.toflex())
+        assert_equal(test, a)
+        assert_equal(test.data, a.data)
+
+    def test_arraymethod(self):
+        # Test a _arraymethod w/ n argument
+        marray = masked_array([[1, 2, 3, 4, 5]], mask=[0, 0, 1, 0, 0])
+        control = masked_array([[1], [2], [3], [4], [5]],
+                               mask=[0, 0, 1, 0, 0])
+        assert_equal(marray.T, control)
+        assert_equal(marray.transpose(), control)
+
+        assert_equal(MaskedArray.cumsum(marray.T, 0), control.cumsum(0))
+
+    def test_arraymethod_0d(self):
+        # gh-9430
+        x = np.ma.array(42, mask=True)
+        assert_equal(x.T.mask, x.mask)
+        assert_equal(x.T.data, x.data)
+
+    def test_transpose_view(self):
+        x = np.ma.array([[1, 2, 3], [4, 5, 6]])
+        x[0,1] = np.ma.masked
+        xt = x.T
+
+        xt[1,0] = 10
+        xt[0,1] = np.ma.masked
+
+        assert_equal(x.data, xt.T.data)
+        assert_equal(x.mask, xt.T.mask)
+
+    def test_diagonal_view(self):
+        x = np.ma.zeros((3,3))
+        x[0,0] = 10
+        x[1,1] = np.ma.masked
+        x[2,2] = 20
+        xd = x.diagonal()
+        x[1,1] = 15
+        assert_equal(xd.mask, x.diagonal().mask)
+        assert_equal(xd.data, x.diagonal().data)
+
+
+class TestMaskedArrayMathMethods:
+
+    def setup_method(self):
+        # Base data definition.
+        x = np.array([8.375, 7.545, 8.828, 8.5, 1.757, 5.928,
+                      8.43, 7.78, 9.865, 5.878, 8.979, 4.732,
+                      3.012, 6.022, 5.095, 3.116, 5.238, 3.957,
+                      6.04, 9.63, 7.712, 3.382, 4.489, 6.479,
+                      7.189, 9.645, 5.395, 4.961, 9.894, 2.893,
+                      7.357, 9.828, 6.272, 3.758, 6.693, 0.993])
+        X = x.reshape(6, 6)
+        XX = x.reshape(3, 2, 2, 3)
+
+        m = np.array([0, 1, 0, 1, 0, 0,
+                     1, 0, 1, 1, 0, 1,
+                     0, 0, 0, 1, 0, 1,
+                     0, 0, 0, 1, 1, 1,
+                     1, 0, 0, 1, 0, 0,
+                     0, 0, 1, 0, 1, 0])
+        mx = array(data=x, mask=m)
+        mX = array(data=X, mask=m.reshape(X.shape))
+        mXX = array(data=XX, mask=m.reshape(XX.shape))
+
+        m2 = np.array([1, 1, 0, 1, 0, 0,
+                      1, 1, 1, 1, 0, 1,
+                      0, 0, 1, 1, 0, 1,
+                      0, 0, 0, 1, 1, 1,
+                      1, 0, 0, 1, 1, 0,
+                      0, 0, 1, 0, 1, 1])
+        m2x = array(data=x, mask=m2)
+        m2X = array(data=X, mask=m2.reshape(X.shape))
+        m2XX = array(data=XX, mask=m2.reshape(XX.shape))
+        self.d = (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX)
+
+    def test_cumsumprod(self):
+        # Tests cumsum & cumprod on MaskedArrays.
+        (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX) = self.d
+        mXcp = mX.cumsum(0)
+        assert_equal(mXcp._data, mX.filled(0).cumsum(0))
+        mXcp = mX.cumsum(1)
+        assert_equal(mXcp._data, mX.filled(0).cumsum(1))
+
+        mXcp = mX.cumprod(0)
+        assert_equal(mXcp._data, mX.filled(1).cumprod(0))
+        mXcp = mX.cumprod(1)
+        assert_equal(mXcp._data, mX.filled(1).cumprod(1))
+
+    def test_cumsumprod_with_output(self):
+        # Tests cumsum/cumprod w/ output
+        xm = array(np.random.uniform(0, 10, 12)).reshape(3, 4)
+        xm[:, 0] = xm[0] = xm[-1, -1] = masked
+
+        for funcname in ('cumsum', 'cumprod'):
+            npfunc = getattr(np, funcname)
+            xmmeth = getattr(xm, funcname)
+
+            # A ndarray as explicit input
+            output = np.empty((3, 4), dtype=float)
+            output.fill(-9999)
+            result = npfunc(xm, axis=0, out=output)
+            # ... the result should be the given output
+            assert_(result is output)
+            assert_equal(result, xmmeth(axis=0, out=output))
+
+            output = empty((3, 4), dtype=int)
+            result = xmmeth(axis=0, out=output)
+            assert_(result is output)
+
+    def test_ptp(self):
+        # Tests ptp on MaskedArrays.
+        (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX) = self.d
+        (n, m) = X.shape
+        assert_equal(mx.ptp(), mx.compressed().ptp())
+        rows = np.zeros(n, float)
+        cols = np.zeros(m, float)
+        for k in range(m):
+            cols[k] = mX[:, k].compressed().ptp()
+        for k in range(n):
+            rows[k] = mX[k].compressed().ptp()
+        assert_equal(mX.ptp(0), cols)
+        assert_equal(mX.ptp(1), rows)
+
+    def test_add_object(self):
+        x = masked_array(['a', 'b'], mask=[1, 0], dtype=object)
+        y = x + 'x'
+        assert_equal(y[1], 'bx')
+        assert_(y.mask[0])
+
+    def test_sum_object(self):
+        # Test sum on object dtype
+        a = masked_array([1, 2, 3], mask=[1, 0, 0], dtype=object)
+        assert_equal(a.sum(), 5)
+        a = masked_array([[1, 2, 3], [4, 5, 6]], dtype=object)
+        assert_equal(a.sum(axis=0), [5, 7, 9])
+
+    def test_prod_object(self):
+        # Test prod on object dtype
+        a = masked_array([1, 2, 3], mask=[1, 0, 0], dtype=object)
+        assert_equal(a.prod(), 2 * 3)
+        a = masked_array([[1, 2, 3], [4, 5, 6]], dtype=object)
+        assert_equal(a.prod(axis=0), [4, 10, 18])
+
+    def test_meananom_object(self):
+        # Test mean/anom on object dtype
+        a = masked_array([1, 2, 3], dtype=object)
+        assert_equal(a.mean(), 2)
+        assert_equal(a.anom(), [-1, 0, 1])
+
+    def test_anom_shape(self):
+        a = masked_array([1, 2, 3])
+        assert_equal(a.anom().shape, a.shape)
+        a.mask = True
+        assert_equal(a.anom().shape, a.shape)
+        assert_(np.ma.is_masked(a.anom()))
+
+    def test_anom(self):
+        a = masked_array(np.arange(1, 7).reshape(2, 3))
+        assert_almost_equal(a.anom(),
+                            [[-2.5, -1.5, -0.5], [0.5, 1.5, 2.5]])
+        assert_almost_equal(a.anom(axis=0),
+                            [[-1.5, -1.5, -1.5], [1.5, 1.5, 1.5]])
+        assert_almost_equal(a.anom(axis=1),
+                            [[-1., 0., 1.], [-1., 0., 1.]])
+        a.mask = [[0, 0, 1], [0, 1, 0]]
+        mval = -99
+        assert_almost_equal(a.anom().filled(mval),
+                            [[-2.25, -1.25, mval], [0.75, mval, 2.75]])
+        assert_almost_equal(a.anom(axis=0).filled(mval),
+                            [[-1.5, 0.0, mval], [1.5, mval, 0.0]])
+        assert_almost_equal(a.anom(axis=1).filled(mval),
+                            [[-0.5, 0.5, mval], [-1.0, mval, 1.0]])
+
+    def test_trace(self):
+        # Tests trace on MaskedArrays.
+        (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX) = self.d
+        mXdiag = mX.diagonal()
+        assert_equal(mX.trace(), mX.diagonal().compressed().sum())
+        assert_almost_equal(mX.trace(),
+                            X.trace() - sum(mXdiag.mask * X.diagonal(),
+                                            axis=0))
+        assert_equal(np.trace(mX), mX.trace())
+
+        # gh-5560
+        arr = np.arange(2*4*4).reshape(2,4,4)
+        m_arr = np.ma.masked_array(arr, False)
+        assert_equal(arr.trace(axis1=1, axis2=2), m_arr.trace(axis1=1, axis2=2))
+
+    def test_dot(self):
+        # Tests dot on MaskedArrays.
+        (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX) = self.d
+        fx = mx.filled(0)
+        r = mx.dot(mx)
+        assert_almost_equal(r.filled(0), fx.dot(fx))
+        assert_(r.mask is nomask)
+
+        fX = mX.filled(0)
+        r = mX.dot(mX)
+        assert_almost_equal(r.filled(0), fX.dot(fX))
+        assert_(r.mask[1,3])
+        r1 = empty_like(r)
+        mX.dot(mX, out=r1)
+        assert_almost_equal(r, r1)
+
+        mYY = mXX.swapaxes(-1, -2)
+        fXX, fYY = mXX.filled(0), mYY.filled(0)
+        r = mXX.dot(mYY)
+        assert_almost_equal(r.filled(0), fXX.dot(fYY))
+        r1 = empty_like(r)
+        mXX.dot(mYY, out=r1)
+        assert_almost_equal(r, r1)
+
+    def test_dot_shape_mismatch(self):
+        # regression test
+        x = masked_array([[1,2],[3,4]], mask=[[0,1],[0,0]])
+        y = masked_array([[1,2],[3,4]], mask=[[0,1],[0,0]])
+        z = masked_array([[0,1],[3,3]])
+        x.dot(y, out=z)
+        assert_almost_equal(z.filled(0), [[1, 0], [15, 16]])
+        assert_almost_equal(z.mask, [[0, 1], [0, 0]])
+
+    def test_varmean_nomask(self):
+        # gh-5769
+        foo = array([1,2,3,4], dtype='f8')
+        bar = array([1,2,3,4], dtype='f8')
+        assert_equal(type(foo.mean()), np.float64)
+        assert_equal(type(foo.var()), np.float64)
+        assert((foo.mean() == bar.mean()) is np.bool_(True))
+
+        # check array type is preserved and out works
+        foo = array(np.arange(16).reshape((4,4)), dtype='f8')
+        bar = empty(4, dtype='f4')
+        assert_equal(type(foo.mean(axis=1)), MaskedArray)
+        assert_equal(type(foo.var(axis=1)), MaskedArray)
+        assert_(foo.mean(axis=1, out=bar) is bar)
+        assert_(foo.var(axis=1, out=bar) is bar)
+
+    def test_varstd(self):
+        # Tests var & std on MaskedArrays.
+        (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX) = self.d
+        assert_almost_equal(mX.var(axis=None), mX.compressed().var())
+        assert_almost_equal(mX.std(axis=None), mX.compressed().std())
+        assert_almost_equal(mX.std(axis=None, ddof=1),
+                            mX.compressed().std(ddof=1))
+        assert_almost_equal(mX.var(axis=None, ddof=1),
+                            mX.compressed().var(ddof=1))
+        assert_equal(mXX.var(axis=3).shape, XX.var(axis=3).shape)
+        assert_equal(mX.var().shape, X.var().shape)
+        (mXvar0, mXvar1) = (mX.var(axis=0), mX.var(axis=1))
+        assert_almost_equal(mX.var(axis=None, ddof=2),
+                            mX.compressed().var(ddof=2))
+        assert_almost_equal(mX.std(axis=None, ddof=2),
+                            mX.compressed().std(ddof=2))
+        for k in range(6):
+            assert_almost_equal(mXvar1[k], mX[k].compressed().var())
+            assert_almost_equal(mXvar0[k], mX[:, k].compressed().var())
+            assert_almost_equal(np.sqrt(mXvar0[k]),
+                                mX[:, k].compressed().std())
+
+    @suppress_copy_mask_on_assignment
+    def test_varstd_specialcases(self):
+        # Test a special case for var
+        nout = np.array(-1, dtype=float)
+        mout = array(-1, dtype=float)
+
+        x = array(arange(10), mask=True)
+        for methodname in ('var', 'std'):
+            method = getattr(x, methodname)
+            assert_(method() is masked)
+            assert_(method(0) is masked)
+            assert_(method(-1) is masked)
+            # Using a masked array as explicit output
+            method(out=mout)
+            assert_(mout is not masked)
+            assert_equal(mout.mask, True)
+            # Using a ndarray as explicit output
+            method(out=nout)
+            assert_(np.isnan(nout))
+
+        x = array(arange(10), mask=True)
+        x[-1] = 9
+        for methodname in ('var', 'std'):
+            method = getattr(x, methodname)
+            assert_(method(ddof=1) is masked)
+            assert_(method(0, ddof=1) is masked)
+            assert_(method(-1, ddof=1) is masked)
+            # Using a masked array as explicit output
+            method(out=mout, ddof=1)
+            assert_(mout is not masked)
+            assert_equal(mout.mask, True)
+            # Using a ndarray as explicit output
+            method(out=nout, ddof=1)
+            assert_(np.isnan(nout))
+
+    def test_varstd_ddof(self):
+        a = array([[1, 1, 0], [1, 1, 0]], mask=[[0, 0, 1], [0, 0, 1]])
+        test = a.std(axis=0, ddof=0)
+        assert_equal(test.filled(0), [0, 0, 0])
+        assert_equal(test.mask, [0, 0, 1])
+        test = a.std(axis=0, ddof=1)
+        assert_equal(test.filled(0), [0, 0, 0])
+        assert_equal(test.mask, [0, 0, 1])
+        test = a.std(axis=0, ddof=2)
+        assert_equal(test.filled(0), [0, 0, 0])
+        assert_equal(test.mask, [1, 1, 1])
+
+    def test_diag(self):
+        # Test diag
+        x = arange(9).reshape((3, 3))
+        x[1, 1] = masked
+        out = np.diag(x)
+        assert_equal(out, [0, 4, 8])
+        out = diag(x)
+        assert_equal(out, [0, 4, 8])
+        assert_equal(out.mask, [0, 1, 0])
+        out = diag(out)
+        control = array([[0, 0, 0], [0, 4, 0], [0, 0, 8]],
+                        mask=[[0, 0, 0], [0, 1, 0], [0, 0, 0]])
+        assert_equal(out, control)
+
+    def test_axis_methods_nomask(self):
+        # Test the combination nomask & methods w/ axis
+        a = array([[1, 2, 3], [4, 5, 6]])
+
+        assert_equal(a.sum(0), [5, 7, 9])
+        assert_equal(a.sum(-1), [6, 15])
+        assert_equal(a.sum(1), [6, 15])
+
+        assert_equal(a.prod(0), [4, 10, 18])
+        assert_equal(a.prod(-1), [6, 120])
+        assert_equal(a.prod(1), [6, 120])
+
+        assert_equal(a.min(0), [1, 2, 3])
+        assert_equal(a.min(-1), [1, 4])
+        assert_equal(a.min(1), [1, 4])
+
+        assert_equal(a.max(0), [4, 5, 6])
+        assert_equal(a.max(-1), [3, 6])
+        assert_equal(a.max(1), [3, 6])
+
+    @requires_memory(free_bytes=2 * 10000 * 1000 * 2)
+    def test_mean_overflow(self):
+        # Test overflow in masked arrays
+        # gh-20272
+        a = masked_array(np.full((10000, 10000), 65535, dtype=np.uint16),
+                         mask=np.zeros((10000, 10000)))
+        assert_equal(a.mean(), 65535.0)
+
+    def test_diff_with_prepend(self):
+        # GH 22465
+        x = np.array([1, 2, 2, 3, 4, 2, 1, 1])
+
+        a = np.ma.masked_equal(x[3:], value=2)
+        a_prep = np.ma.masked_equal(x[:3], value=2)
+        diff1 = np.ma.diff(a, prepend=a_prep, axis=0)
+
+        b = np.ma.masked_equal(x, value=2)
+        diff2 = np.ma.diff(b, axis=0)
+
+        assert_(np.ma.allequal(diff1, diff2))
+
+    def test_diff_with_append(self):
+        # GH 22465
+        x = np.array([1, 2, 2, 3, 4, 2, 1, 1])
+
+        a = np.ma.masked_equal(x[:3], value=2)
+        a_app = np.ma.masked_equal(x[3:], value=2)
+        diff1 = np.ma.diff(a, append=a_app, axis=0)
+
+        b = np.ma.masked_equal(x, value=2)
+        diff2 = np.ma.diff(b, axis=0)
+
+        assert_(np.ma.allequal(diff1, diff2))
+
+    def test_diff_with_dim_0(self):
+        with pytest.raises(
+            ValueError,
+            match="diff requires input that is at least one dimensional"
+            ):
+            np.ma.diff(np.array(1))
+
+    def test_diff_with_n_0(self):
+        a = np.ma.masked_equal([1, 2, 2, 3, 4, 2, 1, 1], value=2)
+        diff = np.ma.diff(a, n=0, axis=0)
+
+        assert_(np.ma.allequal(a, diff))
+
+
+class TestMaskedArrayMathMethodsComplex:
+    # Test class for miscellaneous MaskedArrays methods.
+    def setup_method(self):
+        # Base data definition.
+        x = np.array([8.375j, 7.545j, 8.828j, 8.5j, 1.757j, 5.928,
+                      8.43, 7.78, 9.865, 5.878, 8.979, 4.732,
+                      3.012, 6.022, 5.095, 3.116, 5.238, 3.957,
+                      6.04, 9.63, 7.712, 3.382, 4.489, 6.479j,
+                      7.189j, 9.645, 5.395, 4.961, 9.894, 2.893,
+                      7.357, 9.828, 6.272, 3.758, 6.693, 0.993j])
+        X = x.reshape(6, 6)
+        XX = x.reshape(3, 2, 2, 3)
+
+        m = np.array([0, 1, 0, 1, 0, 0,
+                     1, 0, 1, 1, 0, 1,
+                     0, 0, 0, 1, 0, 1,
+                     0, 0, 0, 1, 1, 1,
+                     1, 0, 0, 1, 0, 0,
+                     0, 0, 1, 0, 1, 0])
+        mx = array(data=x, mask=m)
+        mX = array(data=X, mask=m.reshape(X.shape))
+        mXX = array(data=XX, mask=m.reshape(XX.shape))
+
+        m2 = np.array([1, 1, 0, 1, 0, 0,
+                      1, 1, 1, 1, 0, 1,
+                      0, 0, 1, 1, 0, 1,
+                      0, 0, 0, 1, 1, 1,
+                      1, 0, 0, 1, 1, 0,
+                      0, 0, 1, 0, 1, 1])
+        m2x = array(data=x, mask=m2)
+        m2X = array(data=X, mask=m2.reshape(X.shape))
+        m2XX = array(data=XX, mask=m2.reshape(XX.shape))
+        self.d = (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX)
+
+    def test_varstd(self):
+        # Tests var & std on MaskedArrays.
+        (x, X, XX, m, mx, mX, mXX, m2x, m2X, m2XX) = self.d
+        assert_almost_equal(mX.var(axis=None), mX.compressed().var())
+        assert_almost_equal(mX.std(axis=None), mX.compressed().std())
+        assert_equal(mXX.var(axis=3).shape, XX.var(axis=3).shape)
+        assert_equal(mX.var().shape, X.var().shape)
+        (mXvar0, mXvar1) = (mX.var(axis=0), mX.var(axis=1))
+        assert_almost_equal(mX.var(axis=None, ddof=2),
+                            mX.compressed().var(ddof=2))
+        assert_almost_equal(mX.std(axis=None, ddof=2),
+                            mX.compressed().std(ddof=2))
+        for k in range(6):
+            assert_almost_equal(mXvar1[k], mX[k].compressed().var())
+            assert_almost_equal(mXvar0[k], mX[:, k].compressed().var())
+            assert_almost_equal(np.sqrt(mXvar0[k]),
+                                mX[:, k].compressed().std())
+
+
+class TestMaskedArrayFunctions:
+    # Test class for miscellaneous functions.
+
+    def setup_method(self):
+        x = np.array([1., 1., 1., -2., pi/2.0, 4., 5., -10., 10., 1., 2., 3.])
+        y = np.array([5., 0., 3., 2., -1., -4., 0., -10., 10., 1., 0., 3.])
+        m1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        m2 = [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1]
+        xm = masked_array(x, mask=m1)
+        ym = masked_array(y, mask=m2)
+        xm.set_fill_value(1e+20)
+        self.info = (xm, ym)
+
+    def test_masked_where_bool(self):
+        x = [1, 2]
+        y = masked_where(False, x)
+        assert_equal(y, [1, 2])
+        assert_equal(y[1], 2)
+
+    def test_masked_equal_wlist(self):
+        x = [1, 2, 3]
+        mx = masked_equal(x, 3)
+        assert_equal(mx, x)
+        assert_equal(mx._mask, [0, 0, 1])
+        mx = masked_not_equal(x, 3)
+        assert_equal(mx, x)
+        assert_equal(mx._mask, [1, 1, 0])
+
+    def test_masked_equal_fill_value(self):
+        x = [1, 2, 3]
+        mx = masked_equal(x, 3)
+        assert_equal(mx._mask, [0, 0, 1])
+        assert_equal(mx.fill_value, 3)
+
+    def test_masked_where_condition(self):
+        # Tests masking functions.
+        x = array([1., 2., 3., 4., 5.])
+        x[2] = masked
+        assert_equal(masked_where(greater(x, 2), x), masked_greater(x, 2))
+        assert_equal(masked_where(greater_equal(x, 2), x),
+                     masked_greater_equal(x, 2))
+        assert_equal(masked_where(less(x, 2), x), masked_less(x, 2))
+        assert_equal(masked_where(less_equal(x, 2), x),
+                     masked_less_equal(x, 2))
+        assert_equal(masked_where(not_equal(x, 2), x), masked_not_equal(x, 2))
+        assert_equal(masked_where(equal(x, 2), x), masked_equal(x, 2))
+        assert_equal(masked_where(not_equal(x, 2), x), masked_not_equal(x, 2))
+        assert_equal(masked_where([1, 1, 0, 0, 0], [1, 2, 3, 4, 5]),
+                     [99, 99, 3, 4, 5])
+
+    def test_masked_where_oddities(self):
+        # Tests some generic features.
+        atest = ones((10, 10, 10), dtype=float)
+        btest = zeros(atest.shape, MaskType)
+        ctest = masked_where(btest, atest)
+        assert_equal(atest, ctest)
+
+    def test_masked_where_shape_constraint(self):
+        a = arange(10)
+        with assert_raises(IndexError):
+            masked_equal(1, a)
+        test = masked_equal(a, 1)
+        assert_equal(test.mask, [0, 1, 0, 0, 0, 0, 0, 0, 0, 0])
+
+    def test_masked_where_structured(self):
+        # test that masked_where on a structured array sets a structured
+        # mask (see issue #2972)
+        a = np.zeros(10, dtype=[("A", "<f2"), ("B", "<f4")])
+        with np.errstate(over="ignore"):
+            # NOTE: The float16 "uses" 1e20 as mask, which overflows to inf
+            #       and warns.  Unrelated to this test, but probably undesired.
+            #       But NumPy previously did not warn for this overflow.
+            am = np.ma.masked_where(a["A"] < 5, a)
+        assert_equal(am.mask.dtype.names, am.dtype.names)
+        assert_equal(am["A"],
+                    np.ma.masked_array(np.zeros(10), np.ones(10)))
+
+    def test_masked_where_mismatch(self):
+        # gh-4520
+        x = np.arange(10)
+        y = np.arange(5)
+        assert_raises(IndexError, np.ma.masked_where, y > 6, x)
+
+    def test_masked_otherfunctions(self):
+        assert_equal(masked_inside(list(range(5)), 1, 3),
+                     [0, 199, 199, 199, 4])
+        assert_equal(masked_outside(list(range(5)), 1, 3), [199, 1, 2, 3, 199])
+        assert_equal(masked_inside(array(list(range(5)),
+                                         mask=[1, 0, 0, 0, 0]), 1, 3).mask,
+                     [1, 1, 1, 1, 0])
+        assert_equal(masked_outside(array(list(range(5)),
+                                          mask=[0, 1, 0, 0, 0]), 1, 3).mask,
+                     [1, 1, 0, 0, 1])
+        assert_equal(masked_equal(array(list(range(5)),
+                                        mask=[1, 0, 0, 0, 0]), 2).mask,
+                     [1, 0, 1, 0, 0])
+        assert_equal(masked_not_equal(array([2, 2, 1, 2, 1],
+                                            mask=[1, 0, 0, 0, 0]), 2).mask,
+                     [1, 0, 1, 0, 1])
+
+    def test_round(self):
+        a = array([1.23456, 2.34567, 3.45678, 4.56789, 5.67890],
+                  mask=[0, 1, 0, 0, 0])
+        assert_equal(a.round(), [1., 2., 3., 5., 6.])
+        assert_equal(a.round(1), [1.2, 2.3, 3.5, 4.6, 5.7])
+        assert_equal(a.round(3), [1.235, 2.346, 3.457, 4.568, 5.679])
+        b = empty_like(a)
+        a.round(out=b)
+        assert_equal(b, [1., 2., 3., 5., 6.])
+
+        x = array([1., 2., 3., 4., 5.])
+        c = array([1, 1, 1, 0, 0])
+        x[2] = masked
+        z = where(c, x, -x)
+        assert_equal(z, [1., 2., 0., -4., -5])
+        c[0] = masked
+        z = where(c, x, -x)
+        assert_equal(z, [1., 2., 0., -4., -5])
+        assert_(z[0] is masked)
+        assert_(z[1] is not masked)
+        assert_(z[2] is masked)
+
+    def test_round_with_output(self):
+        # Testing round with an explicit output
+
+        xm = array(np.random.uniform(0, 10, 12)).reshape(3, 4)
+        xm[:, 0] = xm[0] = xm[-1, -1] = masked
+
+        # A ndarray as explicit input
+        output = np.empty((3, 4), dtype=float)
+        output.fill(-9999)
+        result = np.round(xm, decimals=2, out=output)
+        # ... the result should be the given output
+        assert_(result is output)
+        assert_equal(result, xm.round(decimals=2, out=output))
+
+        output = empty((3, 4), dtype=float)
+        result = xm.round(decimals=2, out=output)
+        assert_(result is output)
+
+    def test_round_with_scalar(self):
+        # Testing round with scalar/zero dimension input
+        # GH issue 2244
+        a = array(1.1, mask=[False])
+        assert_equal(a.round(), 1)
+
+        a = array(1.1, mask=[True])
+        assert_(a.round() is masked)
+
+        a = array(1.1, mask=[False])
+        output = np.empty(1, dtype=float)
+        output.fill(-9999)
+        a.round(out=output)
+        assert_equal(output, 1)
+
+        a = array(1.1, mask=[False])
+        output = array(-9999., mask=[True])
+        a.round(out=output)
+        assert_equal(output[()], 1)
+
+        a = array(1.1, mask=[True])
+        output = array(-9999., mask=[False])
+        a.round(out=output)
+        assert_(output[()] is masked)
+
+    def test_identity(self):
+        a = identity(5)
+        assert_(isinstance(a, MaskedArray))
+        assert_equal(a, np.identity(5))
+
+    def test_power(self):
+        x = -1.1
+        assert_almost_equal(power(x, 2.), 1.21)
+        assert_(power(x, masked) is masked)
+        x = array([-1.1, -1.1, 1.1, 1.1, 0.])
+        b = array([0.5, 2., 0.5, 2., -1.], mask=[0, 0, 0, 0, 1])
+        y = power(x, b)
+        assert_almost_equal(y, [0, 1.21, 1.04880884817, 1.21, 0.])
+        assert_equal(y._mask, [1, 0, 0, 0, 1])
+        b.mask = nomask
+        y = power(x, b)
+        assert_equal(y._mask, [1, 0, 0, 0, 1])
+        z = x ** b
+        assert_equal(z._mask, y._mask)
+        assert_almost_equal(z, y)
+        assert_almost_equal(z._data, y._data)
+        x **= b
+        assert_equal(x._mask, y._mask)
+        assert_almost_equal(x, y)
+        assert_almost_equal(x._data, y._data)
+
+    def test_power_with_broadcasting(self):
+        # Test power w/ broadcasting
+        a2 = np.array([[1., 2., 3.], [4., 5., 6.]])
+        a2m = array(a2, mask=[[1, 0, 0], [0, 0, 1]])
+        b1 = np.array([2, 4, 3])
+        b2 = np.array([b1, b1])
+        b2m = array(b2, mask=[[0, 1, 0], [0, 1, 0]])
+
+        ctrl = array([[1 ** 2, 2 ** 4, 3 ** 3], [4 ** 2, 5 ** 4, 6 ** 3]],
+                     mask=[[1, 1, 0], [0, 1, 1]])
+        # No broadcasting, base & exp w/ mask
+        test = a2m ** b2m
+        assert_equal(test, ctrl)
+        assert_equal(test.mask, ctrl.mask)
+        # No broadcasting, base w/ mask, exp w/o mask
+        test = a2m ** b2
+        assert_equal(test, ctrl)
+        assert_equal(test.mask, a2m.mask)
+        # No broadcasting, base w/o mask, exp w/ mask
+        test = a2 ** b2m
+        assert_equal(test, ctrl)
+        assert_equal(test.mask, b2m.mask)
+
+        ctrl = array([[2 ** 2, 4 ** 4, 3 ** 3], [2 ** 2, 4 ** 4, 3 ** 3]],
+                     mask=[[0, 1, 0], [0, 1, 0]])
+        test = b1 ** b2m
+        assert_equal(test, ctrl)
+        assert_equal(test.mask, ctrl.mask)
+        test = b2m ** b1
+        assert_equal(test, ctrl)
+        assert_equal(test.mask, ctrl.mask)
+
+    @pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
+    def test_where(self):
+        # Test the where function
+        x = np.array([1., 1., 1., -2., pi/2.0, 4., 5., -10., 10., 1., 2., 3.])
+        y = np.array([5., 0., 3., 2., -1., -4., 0., -10., 10., 1., 0., 3.])
+        m1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        m2 = [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1]
+        xm = masked_array(x, mask=m1)
+        ym = masked_array(y, mask=m2)
+        xm.set_fill_value(1e+20)
+
+        d = where(xm > 2, xm, -9)
+        assert_equal(d, [-9., -9., -9., -9., -9., 4.,
+                         -9., -9., 10., -9., -9., 3.])
+        assert_equal(d._mask, xm._mask)
+        d = where(xm > 2, -9, ym)
+        assert_equal(d, [5., 0., 3., 2., -1., -9.,
+                         -9., -10., -9., 1., 0., -9.])
+        assert_equal(d._mask, [1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0])
+        d = where(xm > 2, xm, masked)
+        assert_equal(d, [-9., -9., -9., -9., -9., 4.,
+                         -9., -9., 10., -9., -9., 3.])
+        tmp = xm._mask.copy()
+        tmp[(xm <= 2).filled(True)] = True
+        assert_equal(d._mask, tmp)
+
+        with np.errstate(invalid="warn"):
+            # The fill value is 1e20, it cannot be converted to `int`:
+            with pytest.warns(RuntimeWarning, match="invalid value"):
+                ixm = xm.astype(int)
+        d = where(ixm > 2, ixm, masked)
+        assert_equal(d, [-9, -9, -9, -9, -9, 4, -9, -9, 10, -9, -9, 3])
+        assert_equal(d.dtype, ixm.dtype)
+
+    def test_where_object(self):
+        a = np.array(None)
+        b = masked_array(None)
+        r = b.copy()
+        assert_equal(np.ma.where(True, a, a), r)
+        assert_equal(np.ma.where(True, b, b), r)
+
+    def test_where_with_masked_choice(self):
+        x = arange(10)
+        x[3] = masked
+        c = x >= 8
+        # Set False to masked
+        z = where(c, x, masked)
+        assert_(z.dtype is x.dtype)
+        assert_(z[3] is masked)
+        assert_(z[4] is masked)
+        assert_(z[7] is masked)
+        assert_(z[8] is not masked)
+        assert_(z[9] is not masked)
+        assert_equal(x, z)
+        # Set True to masked
+        z = where(c, masked, x)
+        assert_(z.dtype is x.dtype)
+        assert_(z[3] is masked)
+        assert_(z[4] is not masked)
+        assert_(z[7] is not masked)
+        assert_(z[8] is masked)
+        assert_(z[9] is masked)
+
+    def test_where_with_masked_condition(self):
+        x = array([1., 2., 3., 4., 5.])
+        c = array([1, 1, 1, 0, 0])
+        x[2] = masked
+        z = where(c, x, -x)
+        assert_equal(z, [1., 2., 0., -4., -5])
+        c[0] = masked
+        z = where(c, x, -x)
+        assert_equal(z, [1., 2., 0., -4., -5])
+        assert_(z[0] is masked)
+        assert_(z[1] is not masked)
+        assert_(z[2] is masked)
+
+        x = arange(1, 6)
+        x[-1] = masked
+        y = arange(1, 6) * 10
+        y[2] = masked
+        c = array([1, 1, 1, 0, 0], mask=[1, 0, 0, 0, 0])
+        cm = c.filled(1)
+        z = where(c, x, y)
+        zm = where(cm, x, y)
+        assert_equal(z, zm)
+        assert_(getmask(zm) is nomask)
+        assert_equal(zm, [1, 2, 3, 40, 50])
+        z = where(c, masked, 1)
+        assert_equal(z, [99, 99, 99, 1, 1])
+        z = where(c, 1, masked)
+        assert_equal(z, [99, 1, 1, 99, 99])
+
+    def test_where_type(self):
+        # Test the type conservation with where
+        x = np.arange(4, dtype=np.int32)
+        y = np.arange(4, dtype=np.float32) * 2.2
+        test = where(x > 1.5, y, x).dtype
+        control = np.result_type(np.int32, np.float32)
+        assert_equal(test, control)
+
+    def test_where_broadcast(self):
+        # Issue 8599
+        x = np.arange(9).reshape(3, 3)
+        y = np.zeros(3)
+        core = np.where([1, 0, 1], x, y)
+        ma = where([1, 0, 1], x, y)
+
+        assert_equal(core, ma)
+        assert_equal(core.dtype, ma.dtype)
+
+    def test_where_structured(self):
+        # Issue 8600
+        dt = np.dtype([('a', int), ('b', int)])
+        x = np.array([(1, 2), (3, 4), (5, 6)], dtype=dt)
+        y = np.array((10, 20), dtype=dt)
+        core = np.where([0, 1, 1], x, y)
+        ma = np.where([0, 1, 1], x, y)
+
+        assert_equal(core, ma)
+        assert_equal(core.dtype, ma.dtype)
+
+    def test_where_structured_masked(self):
+        dt = np.dtype([('a', int), ('b', int)])
+        x = np.array([(1, 2), (3, 4), (5, 6)], dtype=dt)
+
+        ma = where([0, 1, 1], x, masked)
+        expected = masked_where([1, 0, 0], x)
+
+        assert_equal(ma.dtype, expected.dtype)
+        assert_equal(ma, expected)
+        assert_equal(ma.mask, expected.mask)
+
+    def test_masked_invalid_error(self):
+        a = np.arange(5, dtype=object)
+        a[3] = np.PINF
+        a[2] = np.NaN
+        with pytest.raises(TypeError,
+                           match="not supported for the input types"):
+            np.ma.masked_invalid(a)
+
+    def test_masked_invalid_pandas(self):
+        # getdata() used to be bad for pandas series due to its _data
+        # attribute.  This test is a regression test mainly and may be
+        # removed if getdata() is adjusted.
+        class Series():
+            _data = "nonsense"
+
+            def __array__(self):
+                return np.array([5, np.nan, np.inf])
+
+        arr = np.ma.masked_invalid(Series())
+        assert_array_equal(arr._data, np.array(Series()))
+        assert_array_equal(arr._mask, [False, True, True])
+
+    @pytest.mark.parametrize("copy", [True, False])
+    def test_masked_invalid_full_mask(self, copy):
+        # Matplotlib relied on masked_invalid always returning a full mask
+        # (Also astropy projects, but were ok with it gh-22720 and gh-22842)
+        a = np.ma.array([1, 2, 3, 4])
+        assert a._mask is nomask
+        res = np.ma.masked_invalid(a, copy=copy)
+        assert res.mask is not nomask
+        # mask of a should not be mutated
+        assert a.mask is nomask
+        assert np.may_share_memory(a._data, res._data) != copy
+
+    def test_choose(self):
+        # Test choose
+        choices = [[0, 1, 2, 3], [10, 11, 12, 13],
+                   [20, 21, 22, 23], [30, 31, 32, 33]]
+        chosen = choose([2, 3, 1, 0], choices)
+        assert_equal(chosen, array([20, 31, 12, 3]))
+        chosen = choose([2, 4, 1, 0], choices, mode='clip')
+        assert_equal(chosen, array([20, 31, 12, 3]))
+        chosen = choose([2, 4, 1, 0], choices, mode='wrap')
+        assert_equal(chosen, array([20, 1, 12, 3]))
+        # Check with some masked indices
+        indices_ = array([2, 4, 1, 0], mask=[1, 0, 0, 1])
+        chosen = choose(indices_, choices, mode='wrap')
+        assert_equal(chosen, array([99, 1, 12, 99]))
+        assert_equal(chosen.mask, [1, 0, 0, 1])
+        # Check with some masked choices
+        choices = array(choices, mask=[[0, 0, 0, 1], [1, 1, 0, 1],
+                                       [1, 0, 0, 0], [0, 0, 0, 0]])
+        indices_ = [2, 3, 1, 0]
+        chosen = choose(indices_, choices, mode='wrap')
+        assert_equal(chosen, array([20, 31, 12, 3]))
+        assert_equal(chosen.mask, [1, 0, 0, 1])
+
+    def test_choose_with_out(self):
+        # Test choose with an explicit out keyword
+        choices = [[0, 1, 2, 3], [10, 11, 12, 13],
+                   [20, 21, 22, 23], [30, 31, 32, 33]]
+        store = empty(4, dtype=int)
+        chosen = choose([2, 3, 1, 0], choices, out=store)
+        assert_equal(store, array([20, 31, 12, 3]))
+        assert_(store is chosen)
+        # Check with some masked indices + out
+        store = empty(4, dtype=int)
+        indices_ = array([2, 3, 1, 0], mask=[1, 0, 0, 1])
+        chosen = choose(indices_, choices, mode='wrap', out=store)
+        assert_equal(store, array([99, 31, 12, 99]))
+        assert_equal(store.mask, [1, 0, 0, 1])
+        # Check with some masked choices + out ina ndarray !
+        choices = array(choices, mask=[[0, 0, 0, 1], [1, 1, 0, 1],
+                                       [1, 0, 0, 0], [0, 0, 0, 0]])
+        indices_ = [2, 3, 1, 0]
+        store = empty(4, dtype=int).view(ndarray)
+        chosen = choose(indices_, choices, mode='wrap', out=store)
+        assert_equal(store, array([999999, 31, 12, 999999]))
+
+    def test_reshape(self):
+        a = arange(10)
+        a[0] = masked
+        # Try the default
+        b = a.reshape((5, 2))
+        assert_equal(b.shape, (5, 2))
+        assert_(b.flags['C'])
+        # Try w/ arguments as list instead of tuple
+        b = a.reshape(5, 2)
+        assert_equal(b.shape, (5, 2))
+        assert_(b.flags['C'])
+        # Try w/ order
+        b = a.reshape((5, 2), order='F')
+        assert_equal(b.shape, (5, 2))
+        assert_(b.flags['F'])
+        # Try w/ order
+        b = a.reshape(5, 2, order='F')
+        assert_equal(b.shape, (5, 2))
+        assert_(b.flags['F'])
+
+        c = np.reshape(a, (2, 5))
+        assert_(isinstance(c, MaskedArray))
+        assert_equal(c.shape, (2, 5))
+        assert_(c[0, 0] is masked)
+        assert_(c.flags['C'])
+
+    def test_make_mask_descr(self):
+        # Flexible
+        ntype = [('a', float), ('b', float)]
+        test = make_mask_descr(ntype)
+        assert_equal(test, [('a', bool), ('b', bool)])
+        assert_(test is make_mask_descr(test))
+
+        # Standard w/ shape
+        ntype = (float, 2)
+        test = make_mask_descr(ntype)
+        assert_equal(test, (bool, 2))
+        assert_(test is make_mask_descr(test))
+
+        # Standard standard
+        ntype = float
+        test = make_mask_descr(ntype)
+        assert_equal(test, np.dtype(bool))
+        assert_(test is make_mask_descr(test))
+
+        # Nested
+        ntype = [('a', float), ('b', [('ba', float), ('bb', float)])]
+        test = make_mask_descr(ntype)
+        control = np.dtype([('a', 'b1'), ('b', [('ba', 'b1'), ('bb', 'b1')])])
+        assert_equal(test, control)
+        assert_(test is make_mask_descr(test))
+
+        # Named+ shape
+        ntype = [('a', (float, 2))]
+        test = make_mask_descr(ntype)
+        assert_equal(test, np.dtype([('a', (bool, 2))]))
+        assert_(test is make_mask_descr(test))
+
+        # 2 names
+        ntype = [(('A', 'a'), float)]
+        test = make_mask_descr(ntype)
+        assert_equal(test, np.dtype([(('A', 'a'), bool)]))
+        assert_(test is make_mask_descr(test))
+
+        # nested boolean types should preserve identity
+        base_type = np.dtype([('a', int, 3)])
+        base_mtype = make_mask_descr(base_type)
+        sub_type = np.dtype([('a', int), ('b', base_mtype)])
+        test = make_mask_descr(sub_type)
+        assert_equal(test, np.dtype([('a', bool), ('b', [('a', bool, 3)])]))
+        assert_(test.fields['b'][0] is base_mtype)
+
+    def test_make_mask(self):
+        # Test make_mask
+        # w/ a list as an input
+        mask = [0, 1]
+        test = make_mask(mask)
+        assert_equal(test.dtype, MaskType)
+        assert_equal(test, [0, 1])
+        # w/ a ndarray as an input
+        mask = np.array([0, 1], dtype=bool)
+        test = make_mask(mask)
+        assert_equal(test.dtype, MaskType)
+        assert_equal(test, [0, 1])
+        # w/ a flexible-type ndarray as an input - use default
+        mdtype = [('a', bool), ('b', bool)]
+        mask = np.array([(0, 0), (0, 1)], dtype=mdtype)
+        test = make_mask(mask)
+        assert_equal(test.dtype, MaskType)
+        assert_equal(test, [1, 1])
+        # w/ a flexible-type ndarray as an input - use input dtype
+        mdtype = [('a', bool), ('b', bool)]
+        mask = np.array([(0, 0), (0, 1)], dtype=mdtype)
+        test = make_mask(mask, dtype=mask.dtype)
+        assert_equal(test.dtype, mdtype)
+        assert_equal(test, mask)
+        # w/ a flexible-type ndarray as an input - use input dtype
+        mdtype = [('a', float), ('b', float)]
+        bdtype = [('a', bool), ('b', bool)]
+        mask = np.array([(0, 0), (0, 1)], dtype=mdtype)
+        test = make_mask(mask, dtype=mask.dtype)
+        assert_equal(test.dtype, bdtype)
+        assert_equal(test, np.array([(0, 0), (0, 1)], dtype=bdtype))
+        # Ensure this also works for void
+        mask = np.array((False, True), dtype='?,?')[()]
+        assert_(isinstance(mask, np.void))
+        test = make_mask(mask, dtype=mask.dtype)
+        assert_equal(test, mask)
+        assert_(test is not mask)
+        mask = np.array((0, 1), dtype='i4,i4')[()]
+        test2 = make_mask(mask, dtype=mask.dtype)
+        assert_equal(test2, test)
+        # test that nomask is returned when m is nomask.
+        bools = [True, False]
+        dtypes = [MaskType, float]
+        msgformat = 'copy=%s, shrink=%s, dtype=%s'
+        for cpy, shr, dt in itertools.product(bools, bools, dtypes):
+            res = make_mask(nomask, copy=cpy, shrink=shr, dtype=dt)
+            assert_(res is nomask, msgformat % (cpy, shr, dt))
+
+    def test_mask_or(self):
+        # Initialize
+        mtype = [('a', bool), ('b', bool)]
+        mask = np.array([(0, 0), (0, 1), (1, 0), (0, 0)], dtype=mtype)
+        # Test using nomask as input
+        test = mask_or(mask, nomask)
+        assert_equal(test, mask)
+        test = mask_or(nomask, mask)
+        assert_equal(test, mask)
+        # Using False as input
+        test = mask_or(mask, False)
+        assert_equal(test, mask)
+        # Using another array w / the same dtype
+        other = np.array([(0, 1), (0, 1), (0, 1), (0, 1)], dtype=mtype)
+        test = mask_or(mask, other)
+        control = np.array([(0, 1), (0, 1), (1, 1), (0, 1)], dtype=mtype)
+        assert_equal(test, control)
+        # Using another array w / a different dtype
+        othertype = [('A', bool), ('B', bool)]
+        other = np.array([(0, 1), (0, 1), (0, 1), (0, 1)], dtype=othertype)
+        try:
+            test = mask_or(mask, other)
+        except ValueError:
+            pass
+        # Using nested arrays
+        dtype = [('a', bool), ('b', [('ba', bool), ('bb', bool)])]
+        amask = np.array([(0, (1, 0)), (0, (1, 0))], dtype=dtype)
+        bmask = np.array([(1, (0, 1)), (0, (0, 0))], dtype=dtype)
+        cntrl = np.array([(1, (1, 1)), (0, (1, 0))], dtype=dtype)
+        assert_equal(mask_or(amask, bmask), cntrl)
+
+    def test_flatten_mask(self):
+        # Tests flatten mask
+        # Standard dtype
+        mask = np.array([0, 0, 1], dtype=bool)
+        assert_equal(flatten_mask(mask), mask)
+        # Flexible dtype
+        mask = np.array([(0, 0), (0, 1)], dtype=[('a', bool), ('b', bool)])
+        test = flatten_mask(mask)
+        control = np.array([0, 0, 0, 1], dtype=bool)
+        assert_equal(test, control)
+
+        mdtype = [('a', bool), ('b', [('ba', bool), ('bb', bool)])]
+        data = [(0, (0, 0)), (0, (0, 1))]
+        mask = np.array(data, dtype=mdtype)
+        test = flatten_mask(mask)
+        control = np.array([0, 0, 0, 0, 0, 1], dtype=bool)
+        assert_equal(test, control)
+
+    def test_on_ndarray(self):
+        # Test functions on ndarrays
+        a = np.array([1, 2, 3, 4])
+        m = array(a, mask=False)
+        test = anom(a)
+        assert_equal(test, m.anom())
+        test = reshape(a, (2, 2))
+        assert_equal(test, m.reshape(2, 2))
+
+    def test_compress(self):
+        # Test compress function on ndarray and masked array
+        # Address Github #2495.
+        arr = np.arange(8)
+        arr.shape = 4, 2
+        cond = np.array([True, False, True, True])
+        control = arr[[0, 2, 3]]
+        test = np.ma.compress(cond, arr, axis=0)
+        assert_equal(test, control)
+        marr = np.ma.array(arr)
+        test = np.ma.compress(cond, marr, axis=0)
+        assert_equal(test, control)
+
+    def test_compressed(self):
+        # Test ma.compressed function.
+        # Address gh-4026
+        a = np.ma.array([1, 2])
+        test = np.ma.compressed(a)
+        assert_(type(test) is np.ndarray)
+
+        # Test case when input data is ndarray subclass
+        class A(np.ndarray):
+            pass
+
+        a = np.ma.array(A(shape=0))
+        test = np.ma.compressed(a)
+        assert_(type(test) is A)
+
+        # Test that compress flattens
+        test = np.ma.compressed([[1],[2]])
+        assert_equal(test.ndim, 1)
+        test = np.ma.compressed([[[[[1]]]]])
+        assert_equal(test.ndim, 1)
+
+        # Test case when input is MaskedArray subclass
+        class M(MaskedArray):
+            pass
+
+        test = np.ma.compressed(M([[[]], [[]]]))
+        assert_equal(test.ndim, 1)
+
+        # with .compressed() overridden
+        class M(MaskedArray):
+            def compressed(self):
+                return 42
+
+        test = np.ma.compressed(M([[[]], [[]]]))
+        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, 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:
+
+    def setup_method(self):
+        ilist = [1, 2, 3, 4, 5]
+        flist = [1.1, 2.2, 3.3, 4.4, 5.5]
+        slist = ['one', 'two', 'three', 'four', 'five']
+        ddtype = [('a', int), ('b', float), ('c', '|S8')]
+        mdtype = [('a', bool), ('b', bool), ('c', bool)]
+        mask = [0, 1, 0, 0, 1]
+        base = array(list(zip(ilist, flist, slist)), mask=mask, dtype=ddtype)
+        self.data = dict(base=base, mask=mask, ddtype=ddtype, mdtype=mdtype)
+
+    def test_set_records_masks(self):
+        base = self.data['base']
+        mdtype = self.data['mdtype']
+        # Set w/ nomask or masked
+        base.mask = nomask
+        assert_equal_records(base._mask, np.zeros(base.shape, dtype=mdtype))
+        base.mask = masked
+        assert_equal_records(base._mask, np.ones(base.shape, dtype=mdtype))
+        # Set w/ simple boolean
+        base.mask = False
+        assert_equal_records(base._mask, np.zeros(base.shape, dtype=mdtype))
+        base.mask = True
+        assert_equal_records(base._mask, np.ones(base.shape, dtype=mdtype))
+        # Set w/ list
+        base.mask = [0, 0, 0, 1, 1]
+        assert_equal_records(base._mask,
+                             np.array([(x, x, x) for x in [0, 0, 0, 1, 1]],
+                                      dtype=mdtype))
+
+    def test_set_record_element(self):
+        # Check setting an element of a record)
+        base = self.data['base']
+        (base_a, base_b, base_c) = (base['a'], base['b'], base['c'])
+        base[0] = (pi, pi, 'pi')
+
+        assert_equal(base_a.dtype, int)
+        assert_equal(base_a._data, [3, 2, 3, 4, 5])
+
+        assert_equal(base_b.dtype, float)
+        assert_equal(base_b._data, [pi, 2.2, 3.3, 4.4, 5.5])
+
+        assert_equal(base_c.dtype, '|S8')
+        assert_equal(base_c._data,
+                     [b'pi', b'two', b'three', b'four', b'five'])
+
+    def test_set_record_slice(self):
+        base = self.data['base']
+        (base_a, base_b, base_c) = (base['a'], base['b'], base['c'])
+        base[:3] = (pi, pi, 'pi')
+
+        assert_equal(base_a.dtype, int)
+        assert_equal(base_a._data, [3, 3, 3, 4, 5])
+
+        assert_equal(base_b.dtype, float)
+        assert_equal(base_b._data, [pi, pi, pi, 4.4, 5.5])
+
+        assert_equal(base_c.dtype, '|S8')
+        assert_equal(base_c._data,
+                     [b'pi', b'pi', b'pi', b'four', b'five'])
+
+    def test_mask_element(self):
+        "Check record access"
+        base = self.data['base']
+        base[0] = masked
+
+        for n in ('a', 'b', 'c'):
+            assert_equal(base[n].mask, [1, 1, 0, 0, 1])
+            assert_equal(base[n]._data, base._data[n])
+
+    def test_getmaskarray(self):
+        # Test getmaskarray on flexible dtype
+        ndtype = [('a', int), ('b', float)]
+        test = empty(3, dtype=ndtype)
+        assert_equal(getmaskarray(test),
+                     np.array([(0, 0), (0, 0), (0, 0)],
+                              dtype=[('a', '|b1'), ('b', '|b1')]))
+        test[:] = masked
+        assert_equal(getmaskarray(test),
+                     np.array([(1, 1), (1, 1), (1, 1)],
+                              dtype=[('a', '|b1'), ('b', '|b1')]))
+
+    def test_view(self):
+        # Test view w/ flexible dtype
+        iterator = list(zip(np.arange(10), np.random.rand(10)))
+        data = np.array(iterator)
+        a = array(iterator, dtype=[('a', float), ('b', float)])
+        a.mask[0] = (1, 0)
+        controlmask = np.array([1] + 19 * [0], dtype=bool)
+        # Transform globally to simple dtype
+        test = a.view(float)
+        assert_equal(test, data.ravel())
+        assert_equal(test.mask, controlmask)
+        # Transform globally to dty
+        test = a.view((float, 2))
+        assert_equal(test, data)
+        assert_equal(test.mask, controlmask.reshape(-1, 2))
+
+    def test_getitem(self):
+        ndtype = [('a', float), ('b', float)]
+        a = array(list(zip(np.random.rand(10), np.arange(10))), dtype=ndtype)
+        a.mask = np.array(list(zip([0, 0, 0, 0, 0, 0, 0, 0, 1, 1],
+                                   [1, 0, 0, 0, 0, 0, 0, 0, 1, 0])),
+                          dtype=[('a', bool), ('b', bool)])
+
+        def _test_index(i):
+            assert_equal(type(a[i]), mvoid)
+            assert_equal_records(a[i]._data, a._data[i])
+            assert_equal_records(a[i]._mask, a._mask[i])
+
+            assert_equal(type(a[i, ...]), MaskedArray)
+            assert_equal_records(a[i,...]._data, a._data[i,...])
+            assert_equal_records(a[i,...]._mask, a._mask[i,...])
+
+        _test_index(1)   # No mask
+        _test_index(0)   # One element masked
+        _test_index(-2)  # All element masked
+
+    def test_setitem(self):
+        # Issue 4866: check that one can set individual items in [record][col]
+        # and [col][record] order
+        ndtype = np.dtype([('a', float), ('b', int)])
+        ma = np.ma.MaskedArray([(1.0, 1), (2.0, 2)], dtype=ndtype)
+        ma['a'][1] = 3.0
+        assert_equal(ma['a'], np.array([1.0, 3.0]))
+        ma[1]['a'] = 4.0
+        assert_equal(ma['a'], np.array([1.0, 4.0]))
+        # Issue 2403
+        mdtype = np.dtype([('a', bool), ('b', bool)])
+        # soft mask
+        control = np.array([(False, True), (True, True)], dtype=mdtype)
+        a = np.ma.masked_all((2,), dtype=ndtype)
+        a['a'][0] = 2
+        assert_equal(a.mask, control)
+        a = np.ma.masked_all((2,), dtype=ndtype)
+        a[0]['a'] = 2
+        assert_equal(a.mask, control)
+        # hard mask
+        control = np.array([(True, True), (True, True)], dtype=mdtype)
+        a = np.ma.masked_all((2,), dtype=ndtype)
+        a.harden_mask()
+        a['a'][0] = 2
+        assert_equal(a.mask, control)
+        a = np.ma.masked_all((2,), dtype=ndtype)
+        a.harden_mask()
+        a[0]['a'] = 2
+        assert_equal(a.mask, control)
+
+    def test_setitem_scalar(self):
+        # 8510
+        mask_0d = np.ma.masked_array(1, mask=True)
+        arr = np.ma.arange(3)
+        arr[0] = mask_0d
+        assert_array_equal(arr.mask, [True, False, False])
+
+    def test_element_len(self):
+        # check that len() works for mvoid (Github issue #576)
+        for rec in self.data['base']:
+            assert_equal(len(rec), len(self.data['ddtype']))
+
+
+class TestMaskedObjectArray:
+
+    def test_getitem(self):
+        arr = np.ma.array([None, None])
+        for dt in [float, object]:
+            a0 = np.eye(2).astype(dt)
+            a1 = np.eye(3).astype(dt)
+            arr[0] = a0
+            arr[1] = a1
+
+            assert_(arr[0] is a0)
+            assert_(arr[1] is a1)
+            assert_(isinstance(arr[0,...], MaskedArray))
+            assert_(isinstance(arr[1,...], MaskedArray))
+            assert_(arr[0,...][()] is a0)
+            assert_(arr[1,...][()] is a1)
+
+            arr[0] = np.ma.masked
+
+            assert_(arr[1] is a1)
+            assert_(isinstance(arr[0,...], MaskedArray))
+            assert_(isinstance(arr[1,...], MaskedArray))
+            assert_equal(arr[0,...].mask, True)
+            assert_(arr[1,...][()] is a1)
+
+            # gh-5962 - object arrays of arrays do something special
+            assert_equal(arr[0].data, a0)
+            assert_equal(arr[0].mask, True)
+            assert_equal(arr[0,...][()].data, a0)
+            assert_equal(arr[0,...][()].mask, True)
+
+    def test_nested_ma(self):
+
+        arr = np.ma.array([None, None])
+        # set the first object to be an unmasked masked constant. A little fiddly
+        arr[0,...] = np.array([np.ma.masked], object)[0,...]
+
+        # check the above line did what we were aiming for
+        assert_(arr.data[0] is np.ma.masked)
+
+        # test that getitem returned the value by identity
+        assert_(arr[0] is np.ma.masked)
+
+        # now mask the masked value!
+        arr[0] = np.ma.masked
+        assert_(arr[0] is np.ma.masked)
+
+
+class TestMaskedView:
+
+    def setup_method(self):
+        iterator = list(zip(np.arange(10), np.random.rand(10)))
+        data = np.array(iterator)
+        a = array(iterator, dtype=[('a', float), ('b', float)])
+        a.mask[0] = (1, 0)
+        controlmask = np.array([1] + 19 * [0], dtype=bool)
+        self.data = (data, a, controlmask)
+
+    def test_view_to_nothing(self):
+        (data, a, controlmask) = self.data
+        test = a.view()
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test._data, a._data)
+        assert_equal(test._mask, a._mask)
+
+    def test_view_to_type(self):
+        (data, a, controlmask) = self.data
+        test = a.view(np.ndarray)
+        assert_(not isinstance(test, MaskedArray))
+        assert_equal(test, a._data)
+        assert_equal_records(test, data.view(a.dtype).squeeze())
+
+    def test_view_to_simple_dtype(self):
+        (data, a, controlmask) = self.data
+        # View globally
+        test = a.view(float)
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test, data.ravel())
+        assert_equal(test.mask, controlmask)
+
+    def test_view_to_flexible_dtype(self):
+        (data, a, controlmask) = self.data
+
+        test = a.view([('A', float), ('B', float)])
+        assert_equal(test.mask.dtype.names, ('A', 'B'))
+        assert_equal(test['A'], a['a'])
+        assert_equal(test['B'], a['b'])
+
+        test = a[0].view([('A', float), ('B', float)])
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test.mask.dtype.names, ('A', 'B'))
+        assert_equal(test['A'], a['a'][0])
+        assert_equal(test['B'], a['b'][0])
+
+        test = a[-1].view([('A', float), ('B', float)])
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test.dtype.names, ('A', 'B'))
+        assert_equal(test['A'], a['a'][-1])
+        assert_equal(test['B'], a['b'][-1])
+
+    def test_view_to_subdtype(self):
+        (data, a, controlmask) = self.data
+        # View globally
+        test = a.view((float, 2))
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test, data)
+        assert_equal(test.mask, controlmask.reshape(-1, 2))
+        # View on 1 masked element
+        test = a[0].view((float, 2))
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test, data[0])
+        assert_equal(test.mask, (1, 0))
+        # View on 1 unmasked element
+        test = a[-1].view((float, 2))
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test, data[-1])
+
+    def test_view_to_dtype_and_type(self):
+        (data, a, controlmask) = self.data
+
+        test = a.view((float, 2), np.recarray)
+        assert_equal(test, data)
+        assert_(isinstance(test, np.recarray))
+        assert_(not isinstance(test, MaskedArray))
+
+
+class TestOptionalArgs:
+    def test_ndarrayfuncs(self):
+        # test axis arg behaves the same as ndarray (including multiple axes)
+
+        d = np.arange(24.0).reshape((2,3,4))
+        m = np.zeros(24, dtype=bool).reshape((2,3,4))
+        # mask out last element of last dimension
+        m[:,:,-1] = True
+        a = np.ma.array(d, mask=m)
+
+        def testaxis(f, a, d):
+            numpy_f = numpy.__getattribute__(f)
+            ma_f = np.ma.__getattribute__(f)
+
+            # test axis arg
+            assert_equal(ma_f(a, axis=1)[...,:-1], numpy_f(d[...,:-1], axis=1))
+            assert_equal(ma_f(a, axis=(0,1))[...,:-1],
+                         numpy_f(d[...,:-1], axis=(0,1)))
+
+        def testkeepdims(f, a, d):
+            numpy_f = numpy.__getattribute__(f)
+            ma_f = np.ma.__getattribute__(f)
+
+            # test keepdims arg
+            assert_equal(ma_f(a, keepdims=True).shape,
+                         numpy_f(d, keepdims=True).shape)
+            assert_equal(ma_f(a, keepdims=False).shape,
+                         numpy_f(d, keepdims=False).shape)
+
+            # test both at once
+            assert_equal(ma_f(a, axis=1, keepdims=True)[...,:-1],
+                         numpy_f(d[...,:-1], axis=1, keepdims=True))
+            assert_equal(ma_f(a, axis=(0,1), keepdims=True)[...,:-1],
+                         numpy_f(d[...,:-1], axis=(0,1), keepdims=True))
+
+        for f in ['sum', 'prod', 'mean', 'var', 'std']:
+            testaxis(f, a, d)
+            testkeepdims(f, a, d)
+
+        for f in ['min', 'max']:
+            testaxis(f, a, d)
+
+        d = (np.arange(24).reshape((2,3,4))%2 == 0)
+        a = np.ma.array(d, mask=m)
+        for f in ['all', 'any']:
+            testaxis(f, a, d)
+            testkeepdims(f, a, d)
+
+    def test_count(self):
+        # test np.ma.count specially
+
+        d = np.arange(24.0).reshape((2,3,4))
+        m = np.zeros(24, dtype=bool).reshape((2,3,4))
+        m[:,0,:] = True
+        a = np.ma.array(d, mask=m)
+
+        assert_equal(count(a), 16)
+        assert_equal(count(a, axis=1), 2*ones((2,4)))
+        assert_equal(count(a, axis=(0,1)), 4*ones((4,)))
+        assert_equal(count(a, keepdims=True), 16*ones((1,1,1)))
+        assert_equal(count(a, axis=1, keepdims=True), 2*ones((2,1,4)))
+        assert_equal(count(a, axis=(0,1), keepdims=True), 4*ones((1,1,4)))
+        assert_equal(count(a, axis=-2), 2*ones((2,4)))
+        assert_raises(ValueError, count, a, axis=(1,1))
+        assert_raises(np.AxisError, count, a, axis=3)
+
+        # check the 'nomask' path
+        a = np.ma.array(d, mask=nomask)
+
+        assert_equal(count(a), 24)
+        assert_equal(count(a, axis=1), 3*ones((2,4)))
+        assert_equal(count(a, axis=(0,1)), 6*ones((4,)))
+        assert_equal(count(a, keepdims=True), 24*ones((1,1,1)))
+        assert_equal(np.ndim(count(a, keepdims=True)), 3)
+        assert_equal(count(a, axis=1, keepdims=True), 3*ones((2,1,4)))
+        assert_equal(count(a, axis=(0,1), keepdims=True), 6*ones((1,1,4)))
+        assert_equal(count(a, axis=-2), 3*ones((2,4)))
+        assert_raises(ValueError, count, a, axis=(1,1))
+        assert_raises(np.AxisError, count, a, axis=3)
+
+        # check the 'masked' singleton
+        assert_equal(count(np.ma.masked), 0)
+
+        # check 0-d arrays do not allow axis > 0
+        assert_raises(np.AxisError, count, np.ma.array(1), axis=1)
+
+
+class TestMaskedConstant:
+    def _do_add_test(self, add):
+        # sanity check
+        assert_(add(np.ma.masked, 1) is np.ma.masked)
+
+        # now try with a vector
+        vector = np.array([1, 2, 3])
+        result = add(np.ma.masked, vector)
+
+        # lots of things could go wrong here
+        assert_(result is not np.ma.masked)
+        assert_(not isinstance(result, np.ma.core.MaskedConstant))
+        assert_equal(result.shape, vector.shape)
+        assert_equal(np.ma.getmask(result), np.ones(vector.shape, dtype=bool))
+
+    def test_ufunc(self):
+        self._do_add_test(np.add)
+
+    def test_operator(self):
+        self._do_add_test(lambda a, b: a + b)
+
+    def test_ctor(self):
+        m = np.ma.array(np.ma.masked)
+
+        # most importantly, we do not want to create a new MaskedConstant
+        # instance
+        assert_(not isinstance(m, np.ma.core.MaskedConstant))
+        assert_(m is not np.ma.masked)
+
+    def test_repr(self):
+        # copies should not exist, but if they do, it should be obvious that
+        # something is wrong
+        assert_equal(repr(np.ma.masked), 'masked')
+
+        # create a new instance in a weird way
+        masked2 = np.ma.MaskedArray.__new__(np.ma.core.MaskedConstant)
+        assert_not_equal(repr(masked2), 'masked')
+
+    def test_pickle(self):
+        from io import BytesIO
+
+        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
+            with BytesIO() as f:
+                pickle.dump(np.ma.masked, f, protocol=proto)
+                f.seek(0)
+                res = pickle.load(f)
+            assert_(res is np.ma.masked)
+
+    def test_copy(self):
+        # gh-9328
+        # copy is a no-op, like it is with np.True_
+        assert_equal(
+            np.ma.masked.copy() is np.ma.masked,
+            np.True_.copy() is np.True_)
+
+    def test__copy(self):
+        import copy
+        assert_(
+            copy.copy(np.ma.masked) is np.ma.masked)
+
+    def test_deepcopy(self):
+        import copy
+        assert_(
+            copy.deepcopy(np.ma.masked) is np.ma.masked)
+
+    def test_immutable(self):
+        orig = np.ma.masked
+        assert_raises(np.ma.core.MaskError, operator.setitem, orig, (), 1)
+        assert_raises(ValueError,operator.setitem, orig.data, (), 1)
+        assert_raises(ValueError, operator.setitem, orig.mask, (), False)
+
+        view = np.ma.masked.view(np.ma.MaskedArray)
+        assert_raises(ValueError, operator.setitem, view, (), 1)
+        assert_raises(ValueError, operator.setitem, view.data, (), 1)
+        assert_raises(ValueError, operator.setitem, view.mask, (), False)
+
+    def test_coercion_int(self):
+        a_i = np.zeros((), int)
+        assert_raises(MaskError, operator.setitem, a_i, (), np.ma.masked)
+        assert_raises(MaskError, int, np.ma.masked)
+
+    def test_coercion_float(self):
+        a_f = np.zeros((), float)
+        assert_warns(UserWarning, operator.setitem, a_f, (), np.ma.masked)
+        assert_(np.isnan(a_f[()]))
+
+    @pytest.mark.xfail(reason="See gh-9750")
+    def test_coercion_unicode(self):
+        a_u = np.zeros((), 'U10')
+        a_u[()] = np.ma.masked
+        assert_equal(a_u[()], '--')
+
+    @pytest.mark.xfail(reason="See gh-9750")
+    def test_coercion_bytes(self):
+        a_b = np.zeros((), 'S10')
+        a_b[()] = np.ma.masked
+        assert_equal(a_b[()], b'--')
+
+    def test_subclass(self):
+        # https://github.com/astropy/astropy/issues/6645
+        class Sub(type(np.ma.masked)): pass
+
+        a = Sub()
+        assert_(a is Sub())
+        assert_(a is not np.ma.masked)
+        assert_not_equal(repr(a), 'masked')
+
+    def test_attributes_readonly(self):
+        assert_raises(AttributeError, setattr, np.ma.masked, 'shape', (1,))
+        assert_raises(AttributeError, setattr, np.ma.masked, 'dtype', np.int64)
+
+
+class TestMaskedWhereAliases:
+
+    # TODO: Test masked_object, masked_equal, ...
+
+    def test_masked_values(self):
+        res = masked_values(np.array([-32768.0]), np.int16(-32768))
+        assert_equal(res.mask, [True])
+
+        res = masked_values(np.inf, np.inf)
+        assert_equal(res.mask, True)
+
+        res = np.ma.masked_values(np.inf, -np.inf)
+        assert_equal(res.mask, False)
+
+        res = np.ma.masked_values([1, 2, 3, 4], 5, shrink=True)
+        assert_(res.mask is np.ma.nomask)
+
+        res = np.ma.masked_values([1, 2, 3, 4], 5, shrink=False)
+        assert_equal(res.mask, [False] * 4)
+
+
+def test_masked_array():
+    a = np.ma.array([0, 1, 2, 3], mask=[0, 0, 1, 0])
+    assert_equal(np.argwhere(a), [[1], [3]])
+
+def test_masked_array_no_copy():
+    # check nomask array is updated in place
+    a = np.ma.array([1, 2, 3, 4])
+    _ = np.ma.masked_where(a == 3, a, copy=False)
+    assert_array_equal(a.mask, [False, False, True, False])
+    # check masked array is updated in place
+    a = np.ma.array([1, 2, 3, 4], mask=[1, 0, 0, 0])
+    _ = np.ma.masked_where(a == 3, a, copy=False)
+    assert_array_equal(a.mask, [True, False, True, False])
+    # check masked array with masked_invalid is updated in place
+    a = np.ma.array([np.inf, 1, 2, 3, 4])
+    _ = np.ma.masked_invalid(a, copy=False)
+    assert_array_equal(a.mask, [True, False, False, False, False])
+
+def test_append_masked_array():
+    a = np.ma.masked_equal([1,2,3], value=2)
+    b = np.ma.masked_equal([4,3,2], value=2)
+
+    result = np.ma.append(a, b)
+    expected_data = [1, 2, 3, 4, 3, 2]
+    expected_mask = [False, True, False, False, False, True]
+    assert_array_equal(result.data, expected_data)
+    assert_array_equal(result.mask, expected_mask)
+
+    a = np.ma.masked_all((2,2))
+    b = np.ma.ones((3,1))
+
+    result = np.ma.append(a, b)
+    expected_data = [1] * 3
+    expected_mask = [True] * 4 + [False] * 3
+    assert_array_equal(result.data[-3], expected_data)
+    assert_array_equal(result.mask, expected_mask)
+
+    result = np.ma.append(a, b, axis=None)
+    assert_array_equal(result.data[-3], expected_data)
+    assert_array_equal(result.mask, expected_mask)
+
+
+def test_append_masked_array_along_axis():
+    a = np.ma.masked_equal([1,2,3], value=2)
+    b = np.ma.masked_values([[4, 5, 6], [7, 8, 9]], 7)
+
+    # When `axis` is specified, `values` must have the correct shape.
+    assert_raises(ValueError, np.ma.append, a, b, axis=0)
+
+    result = np.ma.append(a[np.newaxis,:], b, axis=0)
+    expected = np.ma.arange(1, 10)
+    expected[[1, 6]] = np.ma.masked
+    expected = expected.reshape((3,3))
+    assert_array_equal(result.data, expected.data)
+    assert_array_equal(result.mask, expected.mask)
+
+def test_default_fill_value_complex():
+    # regression test for Python 3, where 'unicode' was not defined
+    assert_(default_fill_value(1 + 1j) == 1.e20 + 0.0j)
+
+
+def test_ufunc_with_output():
+    # check that giving an output argument always returns that output.
+    # Regression test for gh-8416.
+    x = array([1., 2., 3.], mask=[0, 0, 1])
+    y = np.add(x, 1., out=x)
+    assert_(y is x)
+
+
+def test_ufunc_with_out_varied():
+    """ Test that masked arrays are immune to gh-10459 """
+    # the mask of the output should not affect the result, however it is passed
+    a        = array([ 1,  2,  3], mask=[1, 0, 0])
+    b        = array([10, 20, 30], mask=[1, 0, 0])
+    out      = array([ 0,  0,  0], mask=[0, 0, 1])
+    expected = array([11, 22, 33], mask=[1, 0, 0])
+
+    out_pos = out.copy()
+    res_pos = np.add(a, b, out_pos)
+
+    out_kw = out.copy()
+    res_kw = np.add(a, b, out=out_kw)
+
+    out_tup = out.copy()
+    res_tup = np.add(a, b, out=(out_tup,))
+
+    assert_equal(res_kw.mask,  expected.mask)
+    assert_equal(res_kw.data,  expected.data)
+    assert_equal(res_tup.mask, expected.mask)
+    assert_equal(res_tup.data, expected.data)
+    assert_equal(res_pos.mask, expected.mask)
+    assert_equal(res_pos.data, expected.data)
+
+
+def test_astype_mask_ordering():
+    descr = np.dtype([('v', int, 3), ('x', [('y', float)])])
+    x = array([
+        [([1, 2, 3], (1.0,)),  ([1, 2, 3], (2.0,))],
+        [([1, 2, 3], (3.0,)),  ([1, 2, 3], (4.0,))]], dtype=descr)
+    x[0]['v'][0] = np.ma.masked
+
+    x_a = x.astype(descr)
+    assert x_a.dtype.names == np.dtype(descr).names
+    assert x_a.mask.dtype.names == np.dtype(descr).names
+    assert_equal(x, x_a)
+
+    assert_(x is x.astype(x.dtype, copy=False))
+    assert_equal(type(x.astype(x.dtype, subok=False)), np.ndarray)
+
+    x_f = x.astype(x.dtype, order='F')
+    assert_(x_f.flags.f_contiguous)
+    assert_(x_f.mask.flags.f_contiguous)
+
+    # Also test the same indirectly, via np.array
+    x_a2 = np.array(x, dtype=descr, subok=True)
+    assert x_a2.dtype.names == np.dtype(descr).names
+    assert x_a2.mask.dtype.names == np.dtype(descr).names
+    assert_equal(x, x_a2)
+
+    assert_(x is np.array(x, dtype=descr, copy=False, subok=True))
+
+    x_f2 = np.array(x, dtype=x.dtype, order='F', subok=True)
+    assert_(x_f2.flags.f_contiguous)
+    assert_(x_f2.mask.flags.f_contiguous)
+
+
+@pytest.mark.parametrize('dt1', num_dts, ids=num_ids)
+@pytest.mark.parametrize('dt2', num_dts, ids=num_ids)
+@pytest.mark.filterwarnings('ignore::numpy.ComplexWarning')
+def test_astype_basic(dt1, dt2):
+    # See gh-12070
+    src = np.ma.array(ones(3, dt1), fill_value=1)
+    dst = src.astype(dt2)
+
+    assert_(src.fill_value == 1)
+    assert_(src.dtype == dt1)
+    assert_(src.fill_value.dtype == dt1)
+
+    assert_(dst.fill_value == 1)
+    assert_(dst.dtype == dt2)
+    assert_(dst.fill_value.dtype == dt2)
+
+    assert_equal(src, dst)
+
+
+def test_fieldless_void():
+    dt = np.dtype([])  # a void dtype with no fields
+    x = np.empty(4, dt)
+
+    # these arrays contain no values, so there's little to test - but this
+    # shouldn't crash
+    mx = np.ma.array(x)
+    assert_equal(mx.dtype, x.dtype)
+    assert_equal(mx.shape, x.shape)
+
+    mx = np.ma.array(x, mask=x)
+    assert_equal(mx.dtype, x.dtype)
+    assert_equal(mx.shape, x.shape)
+
+
+def test_mask_shape_assignment_does_not_break_masked():
+    a = np.ma.masked
+    b = np.ma.array(1, mask=a.mask)
+    b.shape = (1,)
+    assert_equal(a.mask.shape, ())
+
+@pytest.mark.skipif(sys.flags.optimize > 1,
+                    reason="no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1")
+def test_doc_note():
+    def method(self):
+        """This docstring
+
+        Has multiple lines
+
+        And notes
+
+        Notes
+        -----
+        original note
+        """
+        pass
+
+    expected_doc = """This docstring
+
+Has multiple lines
+
+And notes
+
+Notes
+-----
+note
+
+original note"""
+
+    assert_equal(np.ma.core.doc_note(method.__doc__, "note"), expected_doc)
+
+
+def test_gh_22556():
+    source = np.ma.array([0, [0, 1, 2]], dtype=object)
+    deepcopy = copy.deepcopy(source)
+    deepcopy[1].append('this should not appear in source')
+    assert len(source[1]) == 3
+
+
+def test_gh_21022():
+    # testing for absence of reported error
+    source = np.ma.masked_array(data=[-1, -1], mask=True, dtype=np.float64)
+    axis = np.array(0)
+    result = np.prod(source, axis=axis, keepdims=False)
+    result = np.ma.masked_array(result,
+                                mask=np.ones(result.shape, dtype=np.bool_))
+    array = np.ma.masked_array(data=-1, mask=True, dtype=np.float64)
+    copy.deepcopy(array)
+    copy.deepcopy(result)
+
+
+def test_deepcopy_2d_obj():
+    source = np.ma.array([[0, "dog"],
+                          [1, 1],
+                          [[1, 2], "cat"]],
+                        mask=[[0, 1],
+                              [0, 0],
+                              [0, 0]],
+                        dtype=object)
+    deepcopy = copy.deepcopy(source)
+    deepcopy[2, 0].extend(['this should not appear in source', 3])
+    assert len(source[2, 0]) == 2
+    assert len(deepcopy[2, 0]) == 4
+    assert_equal(deepcopy._mask, source._mask)
+    deepcopy._mask[0, 0] = 1
+    assert source._mask[0, 0] == 0
+
+
+def test_deepcopy_0d_obj():
+    source = np.ma.array(0, mask=[0], dtype=object)
+    deepcopy = copy.deepcopy(source)
+    deepcopy[...] = 17
+    assert_equal(source, 0)
+    assert_equal(deepcopy, 17)
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_deprecations.py b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_deprecations.py
new file mode 100644
index 00000000..40c8418f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_deprecations.py
@@ -0,0 +1,84 @@
+"""Test deprecation and future warnings.
+
+"""
+import pytest
+import numpy as np
+from numpy.testing import assert_warns
+from numpy.ma.testutils import assert_equal
+from numpy.ma.core import MaskedArrayFutureWarning
+import io
+import textwrap
+
+class TestArgsort:
+    """ gh-8701 """
+    def _test_base(self, argsort, cls):
+        arr_0d = np.array(1).view(cls)
+        argsort(arr_0d)
+
+        arr_1d = np.array([1, 2, 3]).view(cls)
+        argsort(arr_1d)
+
+        # argsort has a bad default for >1d arrays
+        arr_2d = np.array([[1, 2], [3, 4]]).view(cls)
+        result = assert_warns(
+            np.ma.core.MaskedArrayFutureWarning, argsort, arr_2d)
+        assert_equal(result, argsort(arr_2d, axis=None))
+
+        # should be no warnings for explicitly specifying it
+        argsort(arr_2d, axis=None)
+        argsort(arr_2d, axis=-1)
+
+    def test_function_ndarray(self):
+        return self._test_base(np.ma.argsort, np.ndarray)
+
+    def test_function_maskedarray(self):
+        return self._test_base(np.ma.argsort, np.ma.MaskedArray)
+
+    def test_method(self):
+        return self._test_base(np.ma.MaskedArray.argsort, np.ma.MaskedArray)
+
+
+class TestMinimumMaximum:
+
+    def test_axis_default(self):
+        # NumPy 1.13, 2017-05-06
+
+        data1d = np.ma.arange(6)
+        data2d = data1d.reshape(2, 3)
+
+        ma_min = np.ma.minimum.reduce
+        ma_max = np.ma.maximum.reduce
+
+        # check that the default axis is still None, but warns on 2d arrays
+        result = assert_warns(MaskedArrayFutureWarning, ma_max, data2d)
+        assert_equal(result, ma_max(data2d, axis=None))
+
+        result = assert_warns(MaskedArrayFutureWarning, ma_min, data2d)
+        assert_equal(result, ma_min(data2d, axis=None))
+
+        # no warnings on 1d, as both new and old defaults are equivalent
+        result = ma_min(data1d)
+        assert_equal(result, ma_min(data1d, axis=None))
+        assert_equal(result, ma_min(data1d, axis=0))
+
+        result = ma_max(data1d)
+        assert_equal(result, ma_max(data1d, axis=None))
+        assert_equal(result, ma_max(data1d, axis=0))
+
+
+class TestFromtextfile:
+    def test_fromtextfile_delimitor(self):
+        # NumPy 1.22.0, 2021-09-23
+
+        textfile = io.StringIO(textwrap.dedent(
+            """
+            A,B,C,D
+            'string 1';1;1.0;'mixed column'
+            'string 2';2;2.0;
+            'string 3';3;3.0;123
+            'string 4';4;4.0;3.14
+            """
+        ))
+
+        with pytest.warns(DeprecationWarning):
+            result = np.ma.mrecords.fromtextfile(textfile, delimitor=';')
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_extras.py b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_extras.py
new file mode 100644
index 00000000..d09a50fe
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_extras.py
@@ -0,0 +1,1870 @@
+# pylint: disable-msg=W0611, W0612, W0511
+"""Tests suite for MaskedArray.
+Adapted from the original test_ma by Pierre Gerard-Marchant
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: test_extras.py 3473 2007-10-29 15:18:13Z jarrod.millman $
+
+"""
+import warnings
+import itertools
+import pytest
+
+import numpy as np
+from numpy.core.numeric import normalize_axis_tuple
+from numpy.testing import (
+    assert_warns, suppress_warnings
+    )
+from numpy.ma.testutils import (
+    assert_, assert_array_equal, assert_equal, assert_almost_equal
+    )
+from numpy.ma.core import (
+    array, arange, masked, MaskedArray, masked_array, getmaskarray, shape,
+    nomask, ones, zeros, count
+    )
+from numpy.ma.extras import (
+    atleast_1d, atleast_2d, atleast_3d, mr_, dot, polyfit, cov, corrcoef,
+    median, average, unique, setxor1d, setdiff1d, union1d, intersect1d, in1d,
+    ediff1d, apply_over_axes, apply_along_axis, compress_nd, compress_rowcols,
+    mask_rowcols, clump_masked, clump_unmasked, flatnotmasked_contiguous,
+    notmasked_contiguous, notmasked_edges, masked_all, masked_all_like, isin,
+    diagflat, ndenumerate, stack, vstack
+    )
+
+
+class TestGeneric:
+    #
+    def test_masked_all(self):
+        # Tests masked_all
+        # Standard dtype
+        test = masked_all((2,), dtype=float)
+        control = array([1, 1], mask=[1, 1], dtype=float)
+        assert_equal(test, control)
+        # Flexible dtype
+        dt = np.dtype({'names': ['a', 'b'], 'formats': ['f', 'f']})
+        test = masked_all((2,), dtype=dt)
+        control = array([(0, 0), (0, 0)], mask=[(1, 1), (1, 1)], dtype=dt)
+        assert_equal(test, control)
+        test = masked_all((2, 2), dtype=dt)
+        control = array([[(0, 0), (0, 0)], [(0, 0), (0, 0)]],
+                        mask=[[(1, 1), (1, 1)], [(1, 1), (1, 1)]],
+                        dtype=dt)
+        assert_equal(test, control)
+        # Nested dtype
+        dt = np.dtype([('a', 'f'), ('b', [('ba', 'f'), ('bb', 'f')])])
+        test = masked_all((2,), dtype=dt)
+        control = array([(1, (1, 1)), (1, (1, 1))],
+                        mask=[(1, (1, 1)), (1, (1, 1))], dtype=dt)
+        assert_equal(test, control)
+        test = masked_all((2,), dtype=dt)
+        control = array([(1, (1, 1)), (1, (1, 1))],
+                        mask=[(1, (1, 1)), (1, (1, 1))], dtype=dt)
+        assert_equal(test, control)
+        test = masked_all((1, 1), dtype=dt)
+        control = array([[(1, (1, 1))]], mask=[[(1, (1, 1))]], dtype=dt)
+        assert_equal(test, control)
+
+    def test_masked_all_with_object_nested(self):
+        # Test masked_all works with nested array with dtype of an 'object'
+        # refers to issue #15895
+        my_dtype = np.dtype([('b', ([('c', object)], (1,)))])
+        masked_arr = np.ma.masked_all((1,), my_dtype)
+
+        assert_equal(type(masked_arr['b']), np.ma.core.MaskedArray)
+        assert_equal(type(masked_arr['b']['c']), np.ma.core.MaskedArray)
+        assert_equal(len(masked_arr['b']['c']), 1)
+        assert_equal(masked_arr['b']['c'].shape, (1, 1))
+        assert_equal(masked_arr['b']['c']._fill_value.shape, ())
+
+    def test_masked_all_with_object(self):
+        # same as above except that the array is not nested
+        my_dtype = np.dtype([('b', (object, (1,)))])
+        masked_arr = np.ma.masked_all((1,), my_dtype)
+
+        assert_equal(type(masked_arr['b']), np.ma.core.MaskedArray)
+        assert_equal(len(masked_arr['b']), 1)
+        assert_equal(masked_arr['b'].shape, (1, 1))
+        assert_equal(masked_arr['b']._fill_value.shape, ())
+
+    def test_masked_all_like(self):
+        # Tests masked_all
+        # Standard dtype
+        base = array([1, 2], dtype=float)
+        test = masked_all_like(base)
+        control = array([1, 1], mask=[1, 1], dtype=float)
+        assert_equal(test, control)
+        # Flexible dtype
+        dt = np.dtype({'names': ['a', 'b'], 'formats': ['f', 'f']})
+        base = array([(0, 0), (0, 0)], mask=[(1, 1), (1, 1)], dtype=dt)
+        test = masked_all_like(base)
+        control = array([(10, 10), (10, 10)], mask=[(1, 1), (1, 1)], dtype=dt)
+        assert_equal(test, control)
+        # Nested dtype
+        dt = np.dtype([('a', 'f'), ('b', [('ba', 'f'), ('bb', 'f')])])
+        control = array([(1, (1, 1)), (1, (1, 1))],
+                        mask=[(1, (1, 1)), (1, (1, 1))], dtype=dt)
+        test = masked_all_like(control)
+        assert_equal(test, control)
+
+    def check_clump(self, f):
+        for i in range(1, 7):
+            for j in range(2**i):
+                k = np.arange(i, dtype=int)
+                ja = np.full(i, j, dtype=int)
+                a = masked_array(2**k)
+                a.mask = (ja & (2**k)) != 0
+                s = 0
+                for sl in f(a):
+                    s += a.data[sl].sum()
+                if f == clump_unmasked:
+                    assert_equal(a.compressed().sum(), s)
+                else:
+                    a.mask = ~a.mask
+                    assert_equal(a.compressed().sum(), s)
+
+    def test_clump_masked(self):
+        # Test clump_masked
+        a = masked_array(np.arange(10))
+        a[[0, 1, 2, 6, 8, 9]] = masked
+        #
+        test = clump_masked(a)
+        control = [slice(0, 3), slice(6, 7), slice(8, 10)]
+        assert_equal(test, control)
+
+        self.check_clump(clump_masked)
+
+    def test_clump_unmasked(self):
+        # Test clump_unmasked
+        a = masked_array(np.arange(10))
+        a[[0, 1, 2, 6, 8, 9]] = masked
+        test = clump_unmasked(a)
+        control = [slice(3, 6), slice(7, 8), ]
+        assert_equal(test, control)
+
+        self.check_clump(clump_unmasked)
+
+    def test_flatnotmasked_contiguous(self):
+        # Test flatnotmasked_contiguous
+        a = arange(10)
+        # No mask
+        test = flatnotmasked_contiguous(a)
+        assert_equal(test, [slice(0, a.size)])
+        # mask of all false
+        a.mask = np.zeros(10, dtype=bool)
+        assert_equal(test, [slice(0, a.size)])
+        # Some mask
+        a[(a < 3) | (a > 8) | (a == 5)] = masked
+        test = flatnotmasked_contiguous(a)
+        assert_equal(test, [slice(3, 5), slice(6, 9)])
+        #
+        a[:] = masked
+        test = flatnotmasked_contiguous(a)
+        assert_equal(test, [])
+
+
+class TestAverage:
+    # Several tests of average. Why so many ? Good point...
+    def test_testAverage1(self):
+        # Test of average.
+        ott = array([0., 1., 2., 3.], mask=[True, False, False, False])
+        assert_equal(2.0, average(ott, axis=0))
+        assert_equal(2.0, average(ott, weights=[1., 1., 2., 1.]))
+        result, wts = average(ott, weights=[1., 1., 2., 1.], returned=True)
+        assert_equal(2.0, result)
+        assert_(wts == 4.0)
+        ott[:] = masked
+        assert_equal(average(ott, axis=0).mask, [True])
+        ott = array([0., 1., 2., 3.], mask=[True, False, False, False])
+        ott = ott.reshape(2, 2)
+        ott[:, 1] = masked
+        assert_equal(average(ott, axis=0), [2.0, 0.0])
+        assert_equal(average(ott, axis=1).mask[0], [True])
+        assert_equal([2., 0.], average(ott, axis=0))
+        result, wts = average(ott, axis=0, returned=True)
+        assert_equal(wts, [1., 0.])
+
+    def test_testAverage2(self):
+        # More tests of average.
+        w1 = [0, 1, 1, 1, 1, 0]
+        w2 = [[0, 1, 1, 1, 1, 0], [1, 0, 0, 0, 0, 1]]
+        x = arange(6, dtype=np.float_)
+        assert_equal(average(x, axis=0), 2.5)
+        assert_equal(average(x, axis=0, weights=w1), 2.5)
+        y = array([arange(6, dtype=np.float_), 2.0 * arange(6)])
+        assert_equal(average(y, None), np.add.reduce(np.arange(6)) * 3. / 12.)
+        assert_equal(average(y, axis=0), np.arange(6) * 3. / 2.)
+        assert_equal(average(y, axis=1),
+                     [average(x, axis=0), average(x, axis=0) * 2.0])
+        assert_equal(average(y, None, weights=w2), 20. / 6.)
+        assert_equal(average(y, axis=0, weights=w2),
+                     [0., 1., 2., 3., 4., 10.])
+        assert_equal(average(y, axis=1),
+                     [average(x, axis=0), average(x, axis=0) * 2.0])
+        m1 = zeros(6)
+        m2 = [0, 0, 1, 1, 0, 0]
+        m3 = [[0, 0, 1, 1, 0, 0], [0, 1, 1, 1, 1, 0]]
+        m4 = ones(6)
+        m5 = [0, 1, 1, 1, 1, 1]
+        assert_equal(average(masked_array(x, m1), axis=0), 2.5)
+        assert_equal(average(masked_array(x, m2), axis=0), 2.5)
+        assert_equal(average(masked_array(x, m4), axis=0).mask, [True])
+        assert_equal(average(masked_array(x, m5), axis=0), 0.0)
+        assert_equal(count(average(masked_array(x, m4), axis=0)), 0)
+        z = masked_array(y, m3)
+        assert_equal(average(z, None), 20. / 6.)
+        assert_equal(average(z, axis=0), [0., 1., 99., 99., 4.0, 7.5])
+        assert_equal(average(z, axis=1), [2.5, 5.0])
+        assert_equal(average(z, axis=0, weights=w2),
+                     [0., 1., 99., 99., 4.0, 10.0])
+
+    def test_testAverage3(self):
+        # Yet more tests of average!
+        a = arange(6)
+        b = arange(6) * 3
+        r1, w1 = average([[a, b], [b, a]], axis=1, returned=True)
+        assert_equal(shape(r1), shape(w1))
+        assert_equal(r1.shape, w1.shape)
+        r2, w2 = average(ones((2, 2, 3)), axis=0, weights=[3, 1], returned=True)
+        assert_equal(shape(w2), shape(r2))
+        r2, w2 = average(ones((2, 2, 3)), returned=True)
+        assert_equal(shape(w2), shape(r2))
+        r2, w2 = average(ones((2, 2, 3)), weights=ones((2, 2, 3)), returned=True)
+        assert_equal(shape(w2), shape(r2))
+        a2d = array([[1, 2], [0, 4]], float)
+        a2dm = masked_array(a2d, [[False, False], [True, False]])
+        a2da = average(a2d, axis=0)
+        assert_equal(a2da, [0.5, 3.0])
+        a2dma = average(a2dm, axis=0)
+        assert_equal(a2dma, [1.0, 3.0])
+        a2dma = average(a2dm, axis=None)
+        assert_equal(a2dma, 7. / 3.)
+        a2dma = average(a2dm, axis=1)
+        assert_equal(a2dma, [1.5, 4.0])
+
+    def test_testAverage4(self):
+        # Test that `keepdims` works with average
+        x = np.array([2, 3, 4]).reshape(3, 1)
+        b = np.ma.array(x, mask=[[False], [False], [True]])
+        w = np.array([4, 5, 6]).reshape(3, 1)
+        actual = average(b, weights=w, axis=1, keepdims=True)
+        desired = masked_array([[2.], [3.], [4.]], [[False], [False], [True]])
+        assert_equal(actual, desired)
+
+    def test_onintegers_with_mask(self):
+        # Test average on integers with mask
+        a = average(array([1, 2]))
+        assert_equal(a, 1.5)
+        a = average(array([1, 2, 3, 4], mask=[False, False, True, True]))
+        assert_equal(a, 1.5)
+
+    def test_complex(self):
+        # Test with complex data.
+        # (Regression test for https://github.com/numpy/numpy/issues/2684)
+        mask = np.array([[0, 0, 0, 1, 0],
+                         [0, 1, 0, 0, 0]], dtype=bool)
+        a = masked_array([[0, 1+2j, 3+4j, 5+6j, 7+8j],
+                          [9j, 0+1j, 2+3j, 4+5j, 7+7j]],
+                         mask=mask)
+
+        av = average(a)
+        expected = np.average(a.compressed())
+        assert_almost_equal(av.real, expected.real)
+        assert_almost_equal(av.imag, expected.imag)
+
+        av0 = average(a, axis=0)
+        expected0 = average(a.real, axis=0) + average(a.imag, axis=0)*1j
+        assert_almost_equal(av0.real, expected0.real)
+        assert_almost_equal(av0.imag, expected0.imag)
+
+        av1 = average(a, axis=1)
+        expected1 = average(a.real, axis=1) + average(a.imag, axis=1)*1j
+        assert_almost_equal(av1.real, expected1.real)
+        assert_almost_equal(av1.imag, expected1.imag)
+
+        # Test with the 'weights' argument.
+        wts = np.array([[0.5, 1.0, 2.0, 1.0, 0.5],
+                        [1.0, 1.0, 1.0, 1.0, 1.0]])
+        wav = average(a, weights=wts)
+        expected = np.average(a.compressed(), weights=wts[~mask])
+        assert_almost_equal(wav.real, expected.real)
+        assert_almost_equal(wav.imag, expected.imag)
+
+        wav0 = average(a, weights=wts, axis=0)
+        expected0 = (average(a.real, weights=wts, axis=0) +
+                     average(a.imag, weights=wts, axis=0)*1j)
+        assert_almost_equal(wav0.real, expected0.real)
+        assert_almost_equal(wav0.imag, expected0.imag)
+
+        wav1 = average(a, weights=wts, axis=1)
+        expected1 = (average(a.real, weights=wts, axis=1) +
+                     average(a.imag, weights=wts, axis=1)*1j)
+        assert_almost_equal(wav1.real, expected1.real)
+        assert_almost_equal(wav1.imag, expected1.imag)
+
+    @pytest.mark.parametrize(
+        'x, axis, expected_avg, weights, expected_wavg, expected_wsum',
+        [([1, 2, 3], None, [2.0], [3, 4, 1], [1.75], [8.0]),
+         ([[1, 2, 5], [1, 6, 11]], 0, [[1.0, 4.0, 8.0]],
+          [1, 3], [[1.0, 5.0, 9.5]], [[4, 4, 4]])],
+    )
+    def test_basic_keepdims(self, x, axis, expected_avg,
+                            weights, expected_wavg, expected_wsum):
+        avg = np.ma.average(x, axis=axis, keepdims=True)
+        assert avg.shape == np.shape(expected_avg)
+        assert_array_equal(avg, expected_avg)
+
+        wavg = np.ma.average(x, axis=axis, weights=weights, keepdims=True)
+        assert wavg.shape == np.shape(expected_wavg)
+        assert_array_equal(wavg, expected_wavg)
+
+        wavg, wsum = np.ma.average(x, axis=axis, weights=weights,
+                                   returned=True, keepdims=True)
+        assert wavg.shape == np.shape(expected_wavg)
+        assert_array_equal(wavg, expected_wavg)
+        assert wsum.shape == np.shape(expected_wsum)
+        assert_array_equal(wsum, expected_wsum)
+
+    def test_masked_weights(self):
+        # Test with masked weights.
+        # (Regression test for https://github.com/numpy/numpy/issues/10438)
+        a = np.ma.array(np.arange(9).reshape(3, 3),
+                        mask=[[1, 0, 0], [1, 0, 0], [0, 0, 0]])
+        weights_unmasked = masked_array([5, 28, 31], mask=False)
+        weights_masked = masked_array([5, 28, 31], mask=[1, 0, 0])
+
+        avg_unmasked = average(a, axis=0,
+                               weights=weights_unmasked, returned=False)
+        expected_unmasked = np.array([6.0, 5.21875, 6.21875])
+        assert_almost_equal(avg_unmasked, expected_unmasked)
+
+        avg_masked = average(a, axis=0, weights=weights_masked, returned=False)
+        expected_masked = np.array([6.0, 5.576271186440678, 6.576271186440678])
+        assert_almost_equal(avg_masked, expected_masked)
+
+        # weights should be masked if needed
+        # depending on the array mask. This is to avoid summing
+        # masked nan or other values that are not cancelled by a zero
+        a = np.ma.array([1.0,   2.0,   3.0,  4.0],
+                   mask=[False, False, True, True])
+        avg_unmasked = average(a, weights=[1, 1, 1, np.nan])
+
+        assert_almost_equal(avg_unmasked, 1.5)
+
+        a = np.ma.array([
+            [1.0, 2.0, 3.0, 4.0],
+            [5.0, 6.0, 7.0, 8.0],
+            [9.0, 1.0, 2.0, 3.0],
+        ], mask=[
+            [False, True, True, False],
+            [True, False, True, True],
+            [True, False, True, False],
+        ])
+
+        avg_masked = np.ma.average(a, weights=[1, np.nan, 1], axis=0)
+        avg_expected = np.ma.array([1.0, np.nan, np.nan, 3.5],
+                              mask=[False, True, True, False])
+
+        assert_almost_equal(avg_masked, avg_expected)
+        assert_equal(avg_masked.mask, avg_expected.mask)
+
+
+class TestConcatenator:
+    # Tests for mr_, the equivalent of r_ for masked arrays.
+
+    def test_1d(self):
+        # Tests mr_ on 1D arrays.
+        assert_array_equal(mr_[1, 2, 3, 4, 5, 6], array([1, 2, 3, 4, 5, 6]))
+        b = ones(5)
+        m = [1, 0, 0, 0, 0]
+        d = masked_array(b, mask=m)
+        c = mr_[d, 0, 0, d]
+        assert_(isinstance(c, MaskedArray))
+        assert_array_equal(c, [1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1])
+        assert_array_equal(c.mask, mr_[m, 0, 0, m])
+
+    def test_2d(self):
+        # Tests mr_ on 2D arrays.
+        a_1 = np.random.rand(5, 5)
+        a_2 = np.random.rand(5, 5)
+        m_1 = np.round(np.random.rand(5, 5), 0)
+        m_2 = np.round(np.random.rand(5, 5), 0)
+        b_1 = masked_array(a_1, mask=m_1)
+        b_2 = masked_array(a_2, mask=m_2)
+        # append columns
+        d = mr_['1', b_1, b_2]
+        assert_(d.shape == (5, 10))
+        assert_array_equal(d[:, :5], b_1)
+        assert_array_equal(d[:, 5:], b_2)
+        assert_array_equal(d.mask, np.r_['1', m_1, m_2])
+        d = mr_[b_1, b_2]
+        assert_(d.shape == (10, 5))
+        assert_array_equal(d[:5,:], b_1)
+        assert_array_equal(d[5:,:], b_2)
+        assert_array_equal(d.mask, np.r_[m_1, m_2])
+
+    def test_masked_constant(self):
+        actual = mr_[np.ma.masked, 1]
+        assert_equal(actual.mask, [True, False])
+        assert_equal(actual.data[1], 1)
+
+        actual = mr_[[1, 2], np.ma.masked]
+        assert_equal(actual.mask, [False, False, True])
+        assert_equal(actual.data[:2], [1, 2])
+
+
+class TestNotMasked:
+    # Tests notmasked_edges and notmasked_contiguous.
+
+    def test_edges(self):
+        # Tests unmasked_edges
+        data = masked_array(np.arange(25).reshape(5, 5),
+                            mask=[[0, 0, 1, 0, 0],
+                                  [0, 0, 0, 1, 1],
+                                  [1, 1, 0, 0, 0],
+                                  [0, 0, 0, 0, 0],
+                                  [1, 1, 1, 0, 0]],)
+        test = notmasked_edges(data, None)
+        assert_equal(test, [0, 24])
+        test = notmasked_edges(data, 0)
+        assert_equal(test[0], [(0, 0, 1, 0, 0), (0, 1, 2, 3, 4)])
+        assert_equal(test[1], [(3, 3, 3, 4, 4), (0, 1, 2, 3, 4)])
+        test = notmasked_edges(data, 1)
+        assert_equal(test[0], [(0, 1, 2, 3, 4), (0, 0, 2, 0, 3)])
+        assert_equal(test[1], [(0, 1, 2, 3, 4), (4, 2, 4, 4, 4)])
+        #
+        test = notmasked_edges(data.data, None)
+        assert_equal(test, [0, 24])
+        test = notmasked_edges(data.data, 0)
+        assert_equal(test[0], [(0, 0, 0, 0, 0), (0, 1, 2, 3, 4)])
+        assert_equal(test[1], [(4, 4, 4, 4, 4), (0, 1, 2, 3, 4)])
+        test = notmasked_edges(data.data, -1)
+        assert_equal(test[0], [(0, 1, 2, 3, 4), (0, 0, 0, 0, 0)])
+        assert_equal(test[1], [(0, 1, 2, 3, 4), (4, 4, 4, 4, 4)])
+        #
+        data[-2] = masked
+        test = notmasked_edges(data, 0)
+        assert_equal(test[0], [(0, 0, 1, 0, 0), (0, 1, 2, 3, 4)])
+        assert_equal(test[1], [(1, 1, 2, 4, 4), (0, 1, 2, 3, 4)])
+        test = notmasked_edges(data, -1)
+        assert_equal(test[0], [(0, 1, 2, 4), (0, 0, 2, 3)])
+        assert_equal(test[1], [(0, 1, 2, 4), (4, 2, 4, 4)])
+
+    def test_contiguous(self):
+        # Tests notmasked_contiguous
+        a = masked_array(np.arange(24).reshape(3, 8),
+                         mask=[[0, 0, 0, 0, 1, 1, 1, 1],
+                               [1, 1, 1, 1, 1, 1, 1, 1],
+                               [0, 0, 0, 0, 0, 0, 1, 0]])
+        tmp = notmasked_contiguous(a, None)
+        assert_equal(tmp, [
+            slice(0, 4, None),
+            slice(16, 22, None),
+            slice(23, 24, None)
+        ])
+
+        tmp = notmasked_contiguous(a, 0)
+        assert_equal(tmp, [
+            [slice(0, 1, None), slice(2, 3, None)],
+            [slice(0, 1, None), slice(2, 3, None)],
+            [slice(0, 1, None), slice(2, 3, None)],
+            [slice(0, 1, None), slice(2, 3, None)],
+            [slice(2, 3, None)],
+            [slice(2, 3, None)],
+            [],
+            [slice(2, 3, None)]
+        ])
+        #
+        tmp = notmasked_contiguous(a, 1)
+        assert_equal(tmp, [
+            [slice(0, 4, None)],
+            [],
+            [slice(0, 6, None), slice(7, 8, None)]
+        ])
+
+
+class TestCompressFunctions:
+
+    def test_compress_nd(self):
+        # Tests compress_nd
+        x = np.array(list(range(3*4*5))).reshape(3, 4, 5)
+        m = np.zeros((3,4,5)).astype(bool)
+        m[1,1,1] = True
+        x = array(x, mask=m)
+
+        # axis=None
+        a = compress_nd(x)
+        assert_equal(a, [[[ 0,  2,  3,  4],
+                          [10, 12, 13, 14],
+                          [15, 17, 18, 19]],
+                         [[40, 42, 43, 44],
+                          [50, 52, 53, 54],
+                          [55, 57, 58, 59]]])
+
+        # axis=0
+        a = compress_nd(x, 0)
+        assert_equal(a, [[[ 0,  1,  2,  3,  4],
+                          [ 5,  6,  7,  8,  9],
+                          [10, 11, 12, 13, 14],
+                          [15, 16, 17, 18, 19]],
+                         [[40, 41, 42, 43, 44],
+                          [45, 46, 47, 48, 49],
+                          [50, 51, 52, 53, 54],
+                          [55, 56, 57, 58, 59]]])
+
+        # axis=1
+        a = compress_nd(x, 1)
+        assert_equal(a, [[[ 0,  1,  2,  3,  4],
+                          [10, 11, 12, 13, 14],
+                          [15, 16, 17, 18, 19]],
+                         [[20, 21, 22, 23, 24],
+                          [30, 31, 32, 33, 34],
+                          [35, 36, 37, 38, 39]],
+                         [[40, 41, 42, 43, 44],
+                          [50, 51, 52, 53, 54],
+                          [55, 56, 57, 58, 59]]])
+
+        a2 = compress_nd(x, (1,))
+        a3 = compress_nd(x, -2)
+        a4 = compress_nd(x, (-2,))
+        assert_equal(a, a2)
+        assert_equal(a, a3)
+        assert_equal(a, a4)
+
+        # axis=2
+        a = compress_nd(x, 2)
+        assert_equal(a, [[[ 0, 2,  3,  4],
+                          [ 5, 7,  8,  9],
+                          [10, 12, 13, 14],
+                          [15, 17, 18, 19]],
+                         [[20, 22, 23, 24],
+                          [25, 27, 28, 29],
+                          [30, 32, 33, 34],
+                          [35, 37, 38, 39]],
+                         [[40, 42, 43, 44],
+                          [45, 47, 48, 49],
+                          [50, 52, 53, 54],
+                          [55, 57, 58, 59]]])
+
+        a2 = compress_nd(x, (2,))
+        a3 = compress_nd(x, -1)
+        a4 = compress_nd(x, (-1,))
+        assert_equal(a, a2)
+        assert_equal(a, a3)
+        assert_equal(a, a4)
+
+        # axis=(0, 1)
+        a = compress_nd(x, (0, 1))
+        assert_equal(a, [[[ 0,  1,  2,  3,  4],
+                          [10, 11, 12, 13, 14],
+                          [15, 16, 17, 18, 19]],
+                         [[40, 41, 42, 43, 44],
+                          [50, 51, 52, 53, 54],
+                          [55, 56, 57, 58, 59]]])
+        a2 = compress_nd(x, (0, -2))
+        assert_equal(a, a2)
+
+        # axis=(1, 2)
+        a = compress_nd(x, (1, 2))
+        assert_equal(a, [[[ 0,  2,  3,  4],
+                          [10, 12, 13, 14],
+                          [15, 17, 18, 19]],
+                         [[20, 22, 23, 24],
+                          [30, 32, 33, 34],
+                          [35, 37, 38, 39]],
+                         [[40, 42, 43, 44],
+                          [50, 52, 53, 54],
+                          [55, 57, 58, 59]]])
+
+        a2 = compress_nd(x, (-2, 2))
+        a3 = compress_nd(x, (1, -1))
+        a4 = compress_nd(x, (-2, -1))
+        assert_equal(a, a2)
+        assert_equal(a, a3)
+        assert_equal(a, a4)
+
+        # axis=(0, 2)
+        a = compress_nd(x, (0, 2))
+        assert_equal(a, [[[ 0,  2,  3,  4],
+                          [ 5,  7,  8,  9],
+                          [10, 12, 13, 14],
+                          [15, 17, 18, 19]],
+                         [[40, 42, 43, 44],
+                          [45, 47, 48, 49],
+                          [50, 52, 53, 54],
+                          [55, 57, 58, 59]]])
+
+        a2 = compress_nd(x, (0, -1))
+        assert_equal(a, a2)
+
+    def test_compress_rowcols(self):
+        # Tests compress_rowcols
+        x = array(np.arange(9).reshape(3, 3),
+                  mask=[[1, 0, 0], [0, 0, 0], [0, 0, 0]])
+        assert_equal(compress_rowcols(x), [[4, 5], [7, 8]])
+        assert_equal(compress_rowcols(x, 0), [[3, 4, 5], [6, 7, 8]])
+        assert_equal(compress_rowcols(x, 1), [[1, 2], [4, 5], [7, 8]])
+        x = array(x._data, mask=[[0, 0, 0], [0, 1, 0], [0, 0, 0]])
+        assert_equal(compress_rowcols(x), [[0, 2], [6, 8]])
+        assert_equal(compress_rowcols(x, 0), [[0, 1, 2], [6, 7, 8]])
+        assert_equal(compress_rowcols(x, 1), [[0, 2], [3, 5], [6, 8]])
+        x = array(x._data, mask=[[1, 0, 0], [0, 1, 0], [0, 0, 0]])
+        assert_equal(compress_rowcols(x), [[8]])
+        assert_equal(compress_rowcols(x, 0), [[6, 7, 8]])
+        assert_equal(compress_rowcols(x, 1,), [[2], [5], [8]])
+        x = array(x._data, mask=[[1, 0, 0], [0, 1, 0], [0, 0, 1]])
+        assert_equal(compress_rowcols(x).size, 0)
+        assert_equal(compress_rowcols(x, 0).size, 0)
+        assert_equal(compress_rowcols(x, 1).size, 0)
+
+    def test_mask_rowcols(self):
+        # Tests mask_rowcols.
+        x = array(np.arange(9).reshape(3, 3),
+                  mask=[[1, 0, 0], [0, 0, 0], [0, 0, 0]])
+        assert_equal(mask_rowcols(x).mask,
+                     [[1, 1, 1], [1, 0, 0], [1, 0, 0]])
+        assert_equal(mask_rowcols(x, 0).mask,
+                     [[1, 1, 1], [0, 0, 0], [0, 0, 0]])
+        assert_equal(mask_rowcols(x, 1).mask,
+                     [[1, 0, 0], [1, 0, 0], [1, 0, 0]])
+        x = array(x._data, mask=[[0, 0, 0], [0, 1, 0], [0, 0, 0]])
+        assert_equal(mask_rowcols(x).mask,
+                     [[0, 1, 0], [1, 1, 1], [0, 1, 0]])
+        assert_equal(mask_rowcols(x, 0).mask,
+                     [[0, 0, 0], [1, 1, 1], [0, 0, 0]])
+        assert_equal(mask_rowcols(x, 1).mask,
+                     [[0, 1, 0], [0, 1, 0], [0, 1, 0]])
+        x = array(x._data, mask=[[1, 0, 0], [0, 1, 0], [0, 0, 0]])
+        assert_equal(mask_rowcols(x).mask,
+                     [[1, 1, 1], [1, 1, 1], [1, 1, 0]])
+        assert_equal(mask_rowcols(x, 0).mask,
+                     [[1, 1, 1], [1, 1, 1], [0, 0, 0]])
+        assert_equal(mask_rowcols(x, 1,).mask,
+                     [[1, 1, 0], [1, 1, 0], [1, 1, 0]])
+        x = array(x._data, mask=[[1, 0, 0], [0, 1, 0], [0, 0, 1]])
+        assert_(mask_rowcols(x).all() is masked)
+        assert_(mask_rowcols(x, 0).all() is masked)
+        assert_(mask_rowcols(x, 1).all() is masked)
+        assert_(mask_rowcols(x).mask.all())
+        assert_(mask_rowcols(x, 0).mask.all())
+        assert_(mask_rowcols(x, 1).mask.all())
+
+    @pytest.mark.parametrize("axis", [None, 0, 1])
+    @pytest.mark.parametrize(["func", "rowcols_axis"],
+                             [(np.ma.mask_rows, 0), (np.ma.mask_cols, 1)])
+    def test_mask_row_cols_axis_deprecation(self, axis, func, rowcols_axis):
+        # Test deprecation of the axis argument to `mask_rows` and `mask_cols`
+        x = array(np.arange(9).reshape(3, 3),
+                  mask=[[1, 0, 0], [0, 0, 0], [0, 0, 0]])
+
+        with assert_warns(DeprecationWarning):
+            res = func(x, axis=axis)
+            assert_equal(res, mask_rowcols(x, rowcols_axis))
+
+    def test_dot(self):
+        # Tests dot product
+        n = np.arange(1, 7)
+        #
+        m = [1, 0, 0, 0, 0, 0]
+        a = masked_array(n, mask=m).reshape(2, 3)
+        b = masked_array(n, mask=m).reshape(3, 2)
+        c = dot(a, b, strict=True)
+        assert_equal(c.mask, [[1, 1], [1, 0]])
+        c = dot(b, a, strict=True)
+        assert_equal(c.mask, [[1, 1, 1], [1, 0, 0], [1, 0, 0]])
+        c = dot(a, b, strict=False)
+        assert_equal(c, np.dot(a.filled(0), b.filled(0)))
+        c = dot(b, a, strict=False)
+        assert_equal(c, np.dot(b.filled(0), a.filled(0)))
+        #
+        m = [0, 0, 0, 0, 0, 1]
+        a = masked_array(n, mask=m).reshape(2, 3)
+        b = masked_array(n, mask=m).reshape(3, 2)
+        c = dot(a, b, strict=True)
+        assert_equal(c.mask, [[0, 1], [1, 1]])
+        c = dot(b, a, strict=True)
+        assert_equal(c.mask, [[0, 0, 1], [0, 0, 1], [1, 1, 1]])
+        c = dot(a, b, strict=False)
+        assert_equal(c, np.dot(a.filled(0), b.filled(0)))
+        assert_equal(c, dot(a, b))
+        c = dot(b, a, strict=False)
+        assert_equal(c, np.dot(b.filled(0), a.filled(0)))
+        #
+        m = [0, 0, 0, 0, 0, 0]
+        a = masked_array(n, mask=m).reshape(2, 3)
+        b = masked_array(n, mask=m).reshape(3, 2)
+        c = dot(a, b)
+        assert_equal(c.mask, nomask)
+        c = dot(b, a)
+        assert_equal(c.mask, nomask)
+        #
+        a = masked_array(n, mask=[1, 0, 0, 0, 0, 0]).reshape(2, 3)
+        b = masked_array(n, mask=[0, 0, 0, 0, 0, 0]).reshape(3, 2)
+        c = dot(a, b, strict=True)
+        assert_equal(c.mask, [[1, 1], [0, 0]])
+        c = dot(a, b, strict=False)
+        assert_equal(c, np.dot(a.filled(0), b.filled(0)))
+        c = dot(b, a, strict=True)
+        assert_equal(c.mask, [[1, 0, 0], [1, 0, 0], [1, 0, 0]])
+        c = dot(b, a, strict=False)
+        assert_equal(c, np.dot(b.filled(0), a.filled(0)))
+        #
+        a = masked_array(n, mask=[0, 0, 0, 0, 0, 1]).reshape(2, 3)
+        b = masked_array(n, mask=[0, 0, 0, 0, 0, 0]).reshape(3, 2)
+        c = dot(a, b, strict=True)
+        assert_equal(c.mask, [[0, 0], [1, 1]])
+        c = dot(a, b)
+        assert_equal(c, np.dot(a.filled(0), b.filled(0)))
+        c = dot(b, a, strict=True)
+        assert_equal(c.mask, [[0, 0, 1], [0, 0, 1], [0, 0, 1]])
+        c = dot(b, a, strict=False)
+        assert_equal(c, np.dot(b.filled(0), a.filled(0)))
+        #
+        a = masked_array(n, mask=[0, 0, 0, 0, 0, 1]).reshape(2, 3)
+        b = masked_array(n, mask=[0, 0, 1, 0, 0, 0]).reshape(3, 2)
+        c = dot(a, b, strict=True)
+        assert_equal(c.mask, [[1, 0], [1, 1]])
+        c = dot(a, b, strict=False)
+        assert_equal(c, np.dot(a.filled(0), b.filled(0)))
+        c = dot(b, a, strict=True)
+        assert_equal(c.mask, [[0, 0, 1], [1, 1, 1], [0, 0, 1]])
+        c = dot(b, a, strict=False)
+        assert_equal(c, np.dot(b.filled(0), a.filled(0)))
+        #
+        a = masked_array(np.arange(8).reshape(2, 2, 2),
+                         mask=[[[1, 0], [0, 0]], [[0, 0], [0, 0]]])
+        b = masked_array(np.arange(8).reshape(2, 2, 2),
+                         mask=[[[0, 0], [0, 0]], [[0, 0], [0, 1]]])
+        c = dot(a, b, strict=True)
+        assert_equal(c.mask,
+                     [[[[1, 1], [1, 1]], [[0, 0], [0, 1]]],
+                      [[[0, 0], [0, 1]], [[0, 0], [0, 1]]]])
+        c = dot(a, b, strict=False)
+        assert_equal(c.mask,
+                     [[[[0, 0], [0, 1]], [[0, 0], [0, 0]]],
+                      [[[0, 0], [0, 0]], [[0, 0], [0, 0]]]])
+        c = dot(b, a, strict=True)
+        assert_equal(c.mask,
+                     [[[[1, 0], [0, 0]], [[1, 0], [0, 0]]],
+                      [[[1, 0], [0, 0]], [[1, 1], [1, 1]]]])
+        c = dot(b, a, strict=False)
+        assert_equal(c.mask,
+                     [[[[0, 0], [0, 0]], [[0, 0], [0, 0]]],
+                      [[[0, 0], [0, 0]], [[1, 0], [0, 0]]]])
+        #
+        a = masked_array(np.arange(8).reshape(2, 2, 2),
+                         mask=[[[1, 0], [0, 0]], [[0, 0], [0, 0]]])
+        b = 5.
+        c = dot(a, b, strict=True)
+        assert_equal(c.mask, [[[1, 0], [0, 0]], [[0, 0], [0, 0]]])
+        c = dot(a, b, strict=False)
+        assert_equal(c.mask, [[[1, 0], [0, 0]], [[0, 0], [0, 0]]])
+        c = dot(b, a, strict=True)
+        assert_equal(c.mask, [[[1, 0], [0, 0]], [[0, 0], [0, 0]]])
+        c = dot(b, a, strict=False)
+        assert_equal(c.mask, [[[1, 0], [0, 0]], [[0, 0], [0, 0]]])
+        #
+        a = masked_array(np.arange(8).reshape(2, 2, 2),
+                         mask=[[[1, 0], [0, 0]], [[0, 0], [0, 0]]])
+        b = masked_array(np.arange(2), mask=[0, 1])
+        c = dot(a, b, strict=True)
+        assert_equal(c.mask, [[1, 1], [1, 1]])
+        c = dot(a, b, strict=False)
+        assert_equal(c.mask, [[1, 0], [0, 0]])
+
+    def test_dot_returns_maskedarray(self):
+        # See gh-6611
+        a = np.eye(3)
+        b = array(a)
+        assert_(type(dot(a, a)) is MaskedArray)
+        assert_(type(dot(a, b)) is MaskedArray)
+        assert_(type(dot(b, a)) is MaskedArray)
+        assert_(type(dot(b, b)) is MaskedArray)
+
+    def test_dot_out(self):
+        a = array(np.eye(3))
+        out = array(np.zeros((3, 3)))
+        res = dot(a, a, out=out)
+        assert_(res is out)
+        assert_equal(a, res)
+
+
+class TestApplyAlongAxis:
+    # Tests 2D functions
+    def test_3d(self):
+        a = arange(12.).reshape(2, 2, 3)
+
+        def myfunc(b):
+            return b[1]
+
+        xa = apply_along_axis(myfunc, 2, a)
+        assert_equal(xa, [[1, 4], [7, 10]])
+
+    # Tests kwargs functions
+    def test_3d_kwargs(self):
+        a = arange(12).reshape(2, 2, 3)
+
+        def myfunc(b, offset=0):
+            return b[1+offset]
+
+        xa = apply_along_axis(myfunc, 2, a, offset=1)
+        assert_equal(xa, [[2, 5], [8, 11]])
+
+
+class TestApplyOverAxes:
+    # Tests apply_over_axes
+    def test_basic(self):
+        a = arange(24).reshape(2, 3, 4)
+        test = apply_over_axes(np.sum, a, [0, 2])
+        ctrl = np.array([[[60], [92], [124]]])
+        assert_equal(test, ctrl)
+        a[(a % 2).astype(bool)] = masked
+        test = apply_over_axes(np.sum, a, [0, 2])
+        ctrl = np.array([[[28], [44], [60]]])
+        assert_equal(test, ctrl)
+
+
+class TestMedian:
+    def test_pytype(self):
+        r = np.ma.median([[np.inf, np.inf], [np.inf, np.inf]], axis=-1)
+        assert_equal(r, np.inf)
+
+    def test_inf(self):
+        # test that even which computes handles inf / x = masked
+        r = np.ma.median(np.ma.masked_array([[np.inf, np.inf],
+                                             [np.inf, np.inf]]), axis=-1)
+        assert_equal(r, np.inf)
+        r = np.ma.median(np.ma.masked_array([[np.inf, np.inf],
+                                             [np.inf, np.inf]]), axis=None)
+        assert_equal(r, np.inf)
+        # all masked
+        r = np.ma.median(np.ma.masked_array([[np.inf, np.inf],
+                                             [np.inf, np.inf]], mask=True),
+                         axis=-1)
+        assert_equal(r.mask, True)
+        r = np.ma.median(np.ma.masked_array([[np.inf, np.inf],
+                                             [np.inf, np.inf]], mask=True),
+                         axis=None)
+        assert_equal(r.mask, True)
+
+    def test_non_masked(self):
+        x = np.arange(9)
+        assert_equal(np.ma.median(x), 4.)
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        x = range(8)
+        assert_equal(np.ma.median(x), 3.5)
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        x = 5
+        assert_equal(np.ma.median(x), 5.)
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        # integer
+        x = np.arange(9 * 8).reshape(9, 8)
+        assert_equal(np.ma.median(x, axis=0), np.median(x, axis=0))
+        assert_equal(np.ma.median(x, axis=1), np.median(x, axis=1))
+        assert_(np.ma.median(x, axis=1) is not MaskedArray)
+        # float
+        x = np.arange(9 * 8.).reshape(9, 8)
+        assert_equal(np.ma.median(x, axis=0), np.median(x, axis=0))
+        assert_equal(np.ma.median(x, axis=1), np.median(x, axis=1))
+        assert_(np.ma.median(x, axis=1) is not MaskedArray)
+
+    def test_docstring_examples(self):
+        "test the examples given in the docstring of ma.median"
+        x = array(np.arange(8), mask=[0]*4 + [1]*4)
+        assert_equal(np.ma.median(x), 1.5)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        x = array(np.arange(10).reshape(2, 5), mask=[0]*6 + [1]*4)
+        assert_equal(np.ma.median(x), 2.5)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        ma_x = np.ma.median(x, axis=-1, overwrite_input=True)
+        assert_equal(ma_x, [2., 5.])
+        assert_equal(ma_x.shape, (2,), "shape mismatch")
+        assert_(type(ma_x) is MaskedArray)
+
+    def test_axis_argument_errors(self):
+        msg = "mask = %s, ndim = %s, axis = %s, overwrite_input = %s"
+        for ndmin in range(5):
+            for mask in [False, True]:
+                x = array(1, ndmin=ndmin, mask=mask)
+
+                # Valid axis values should not raise exception
+                args = itertools.product(range(-ndmin, ndmin), [False, True])
+                for axis, over in args:
+                    try:
+                        np.ma.median(x, axis=axis, overwrite_input=over)
+                    except Exception:
+                        raise AssertionError(msg % (mask, ndmin, axis, over))
+
+                # Invalid axis values should raise exception
+                args = itertools.product([-(ndmin + 1), ndmin], [False, True])
+                for axis, over in args:
+                    try:
+                        np.ma.median(x, axis=axis, overwrite_input=over)
+                    except np.AxisError:
+                        pass
+                    else:
+                        raise AssertionError(msg % (mask, ndmin, axis, over))
+
+    def test_masked_0d(self):
+        # Check values
+        x = array(1, mask=False)
+        assert_equal(np.ma.median(x), 1)
+        x = array(1, mask=True)
+        assert_equal(np.ma.median(x), np.ma.masked)
+
+    def test_masked_1d(self):
+        x = array(np.arange(5), mask=True)
+        assert_equal(np.ma.median(x), np.ma.masked)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is np.ma.core.MaskedConstant)
+        x = array(np.arange(5), mask=False)
+        assert_equal(np.ma.median(x), 2.)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        x = array(np.arange(5), mask=[0,1,0,0,0])
+        assert_equal(np.ma.median(x), 2.5)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        x = array(np.arange(5), mask=[0,1,1,1,1])
+        assert_equal(np.ma.median(x), 0.)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        # integer
+        x = array(np.arange(5), mask=[0,1,1,0,0])
+        assert_equal(np.ma.median(x), 3.)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        # float
+        x = array(np.arange(5.), mask=[0,1,1,0,0])
+        assert_equal(np.ma.median(x), 3.)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        # integer
+        x = array(np.arange(6), mask=[0,1,1,1,1,0])
+        assert_equal(np.ma.median(x), 2.5)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        # float
+        x = array(np.arange(6.), mask=[0,1,1,1,1,0])
+        assert_equal(np.ma.median(x), 2.5)
+        assert_equal(np.ma.median(x).shape, (), "shape mismatch")
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+
+    def test_1d_shape_consistency(self):
+        assert_equal(np.ma.median(array([1,2,3],mask=[0,0,0])).shape,
+                     np.ma.median(array([1,2,3],mask=[0,1,0])).shape )
+
+    def test_2d(self):
+        # Tests median w/ 2D
+        (n, p) = (101, 30)
+        x = masked_array(np.linspace(-1., 1., n),)
+        x[:10] = x[-10:] = masked
+        z = masked_array(np.empty((n, p), dtype=float))
+        z[:, 0] = x[:]
+        idx = np.arange(len(x))
+        for i in range(1, p):
+            np.random.shuffle(idx)
+            z[:, i] = x[idx]
+        assert_equal(median(z[:, 0]), 0)
+        assert_equal(median(z), 0)
+        assert_equal(median(z, axis=0), np.zeros(p))
+        assert_equal(median(z.T, axis=1), np.zeros(p))
+
+    def test_2d_waxis(self):
+        # Tests median w/ 2D arrays and different axis.
+        x = masked_array(np.arange(30).reshape(10, 3))
+        x[:3] = x[-3:] = masked
+        assert_equal(median(x), 14.5)
+        assert_(type(np.ma.median(x)) is not MaskedArray)
+        assert_equal(median(x, axis=0), [13.5, 14.5, 15.5])
+        assert_(type(np.ma.median(x, axis=0)) is MaskedArray)
+        assert_equal(median(x, axis=1), [0, 0, 0, 10, 13, 16, 19, 0, 0, 0])
+        assert_(type(np.ma.median(x, axis=1)) is MaskedArray)
+        assert_equal(median(x, axis=1).mask, [1, 1, 1, 0, 0, 0, 0, 1, 1, 1])
+
+    def test_3d(self):
+        # Tests median w/ 3D
+        x = np.ma.arange(24).reshape(3, 4, 2)
+        x[x % 3 == 0] = masked
+        assert_equal(median(x, 0), [[12, 9], [6, 15], [12, 9], [18, 15]])
+        x.shape = (4, 3, 2)
+        assert_equal(median(x, 0), [[99, 10], [11, 99], [13, 14]])
+        x = np.ma.arange(24).reshape(4, 3, 2)
+        x[x % 5 == 0] = masked
+        assert_equal(median(x, 0), [[12, 10], [8, 9], [16, 17]])
+
+    def test_neg_axis(self):
+        x = masked_array(np.arange(30).reshape(10, 3))
+        x[:3] = x[-3:] = masked
+        assert_equal(median(x, axis=-1), median(x, axis=1))
+
+    def test_out_1d(self):
+        # integer float even odd
+        for v in (30, 30., 31, 31.):
+            x = masked_array(np.arange(v))
+            x[:3] = x[-3:] = masked
+            out = masked_array(np.ones(()))
+            r = median(x, out=out)
+            if v == 30:
+                assert_equal(out, 14.5)
+            else:
+                assert_equal(out, 15.)
+            assert_(r is out)
+            assert_(type(r) is MaskedArray)
+
+    def test_out(self):
+        # integer float even odd
+        for v in (40, 40., 30, 30.):
+            x = masked_array(np.arange(v).reshape(10, -1))
+            x[:3] = x[-3:] = masked
+            out = masked_array(np.ones(10))
+            r = median(x, axis=1, out=out)
+            if v == 30:
+                e = masked_array([0.]*3 + [10, 13, 16, 19] + [0.]*3,
+                                 mask=[True] * 3 + [False] * 4 + [True] * 3)
+            else:
+                e = masked_array([0.]*3 + [13.5, 17.5, 21.5, 25.5] + [0.]*3,
+                                 mask=[True]*3 + [False]*4 + [True]*3)
+            assert_equal(r, e)
+            assert_(r is out)
+            assert_(type(r) is MaskedArray)
+
+    @pytest.mark.parametrize(
+        argnames='axis',
+        argvalues=[
+            None,
+            1,
+            (1, ),
+            (0, 1),
+            (-3, -1),
+        ]
+    )
+    def test_keepdims_out(self, axis):
+        mask = np.zeros((3, 5, 7, 11), dtype=bool)
+        # Randomly set some elements to True:
+        w = np.random.random((4, 200)) * np.array(mask.shape)[:, None]
+        w = w.astype(np.intp)
+        mask[tuple(w)] = np.nan
+        d = masked_array(np.ones(mask.shape), mask=mask)
+        if axis is None:
+            shape_out = (1,) * d.ndim
+        else:
+            axis_norm = normalize_axis_tuple(axis, d.ndim)
+            shape_out = tuple(
+                1 if i in axis_norm else d.shape[i] for i in range(d.ndim))
+        out = masked_array(np.empty(shape_out))
+        result = median(d, axis=axis, keepdims=True, out=out)
+        assert result is out
+        assert_equal(result.shape, shape_out)
+
+    def test_single_non_masked_value_on_axis(self):
+        data = [[1., 0.],
+                [0., 3.],
+                [0., 0.]]
+        masked_arr = np.ma.masked_equal(data, 0)
+        expected = [1., 3.]
+        assert_array_equal(np.ma.median(masked_arr, axis=0),
+                           expected)
+
+    def test_nan(self):
+        for mask in (False, np.zeros(6, dtype=bool)):
+            dm = np.ma.array([[1, np.nan, 3], [1, 2, 3]])
+            dm.mask = mask
+
+            # scalar result
+            r = np.ma.median(dm, axis=None)
+            assert_(np.isscalar(r))
+            assert_array_equal(r, np.nan)
+            r = np.ma.median(dm.ravel(), axis=0)
+            assert_(np.isscalar(r))
+            assert_array_equal(r, np.nan)
+
+            r = np.ma.median(dm, axis=0)
+            assert_equal(type(r), MaskedArray)
+            assert_array_equal(r, [1, np.nan, 3])
+            r = np.ma.median(dm, axis=1)
+            assert_equal(type(r), MaskedArray)
+            assert_array_equal(r, [np.nan, 2])
+            r = np.ma.median(dm, axis=-1)
+            assert_equal(type(r), MaskedArray)
+            assert_array_equal(r, [np.nan, 2])
+
+        dm = np.ma.array([[1, np.nan, 3], [1, 2, 3]])
+        dm[:, 2] = np.ma.masked
+        assert_array_equal(np.ma.median(dm, axis=None), np.nan)
+        assert_array_equal(np.ma.median(dm, axis=0), [1, np.nan, 3])
+        assert_array_equal(np.ma.median(dm, axis=1), [np.nan, 1.5])
+
+    def test_out_nan(self):
+        o = np.ma.masked_array(np.zeros((4,)))
+        d = np.ma.masked_array(np.ones((3, 4)))
+        d[2, 1] = np.nan
+        d[2, 2] = np.ma.masked
+        assert_equal(np.ma.median(d, 0, out=o), o)
+        o = np.ma.masked_array(np.zeros((3,)))
+        assert_equal(np.ma.median(d, 1, out=o), o)
+        o = np.ma.masked_array(np.zeros(()))
+        assert_equal(np.ma.median(d, out=o), o)
+
+    def test_nan_behavior(self):
+        a = np.ma.masked_array(np.arange(24, dtype=float))
+        a[::3] = np.ma.masked
+        a[2] = np.nan
+        assert_array_equal(np.ma.median(a), np.nan)
+        assert_array_equal(np.ma.median(a, axis=0), np.nan)
+
+        a = np.ma.masked_array(np.arange(24, dtype=float).reshape(2, 3, 4))
+        a.mask = np.arange(a.size) % 2 == 1
+        aorig = a.copy()
+        a[1, 2, 3] = np.nan
+        a[1, 1, 2] = np.nan
+
+        # no axis
+        assert_array_equal(np.ma.median(a), np.nan)
+        assert_(np.isscalar(np.ma.median(a)))
+
+        # axis0
+        b = np.ma.median(aorig, axis=0)
+        b[2, 3] = np.nan
+        b[1, 2] = np.nan
+        assert_equal(np.ma.median(a, 0), b)
+
+        # axis1
+        b = np.ma.median(aorig, axis=1)
+        b[1, 3] = np.nan
+        b[1, 2] = np.nan
+        assert_equal(np.ma.median(a, 1), b)
+
+        # axis02
+        b = np.ma.median(aorig, axis=(0, 2))
+        b[1] = np.nan
+        b[2] = np.nan
+        assert_equal(np.ma.median(a, (0, 2)), b)
+
+    def test_ambigous_fill(self):
+        # 255 is max value, used as filler for sort
+        a = np.array([[3, 3, 255], [3, 3, 255]], dtype=np.uint8)
+        a = np.ma.masked_array(a, mask=a == 3)
+        assert_array_equal(np.ma.median(a, axis=1), 255)
+        assert_array_equal(np.ma.median(a, axis=1).mask, False)
+        assert_array_equal(np.ma.median(a, axis=0), a[0])
+        assert_array_equal(np.ma.median(a), 255)
+
+    def test_special(self):
+        for inf in [np.inf, -np.inf]:
+            a = np.array([[inf,  np.nan], [np.nan, np.nan]])
+            a = np.ma.masked_array(a, mask=np.isnan(a))
+            assert_equal(np.ma.median(a, axis=0), [inf,  np.nan])
+            assert_equal(np.ma.median(a, axis=1), [inf,  np.nan])
+            assert_equal(np.ma.median(a), inf)
+
+            a = np.array([[np.nan, np.nan, inf], [np.nan, np.nan, inf]])
+            a = np.ma.masked_array(a, mask=np.isnan(a))
+            assert_array_equal(np.ma.median(a, axis=1), inf)
+            assert_array_equal(np.ma.median(a, axis=1).mask, False)
+            assert_array_equal(np.ma.median(a, axis=0), a[0])
+            assert_array_equal(np.ma.median(a), inf)
+
+            # no mask
+            a = np.array([[inf, inf], [inf, inf]])
+            assert_equal(np.ma.median(a), inf)
+            assert_equal(np.ma.median(a, axis=0), inf)
+            assert_equal(np.ma.median(a, axis=1), inf)
+
+            a = np.array([[inf, 7, -inf, -9],
+                          [-10, np.nan, np.nan, 5],
+                          [4, np.nan, np.nan, inf]],
+                          dtype=np.float32)
+            a = np.ma.masked_array(a, mask=np.isnan(a))
+            if inf > 0:
+                assert_equal(np.ma.median(a, axis=0), [4., 7., -inf, 5.])
+                assert_equal(np.ma.median(a), 4.5)
+            else:
+                assert_equal(np.ma.median(a, axis=0), [-10., 7., -inf, -9.])
+                assert_equal(np.ma.median(a), -2.5)
+            assert_equal(np.ma.median(a, axis=1), [-1., -2.5, inf])
+
+            for i in range(0, 10):
+                for j in range(1, 10):
+                    a = np.array([([np.nan] * i) + ([inf] * j)] * 2)
+                    a = np.ma.masked_array(a, mask=np.isnan(a))
+                    assert_equal(np.ma.median(a), inf)
+                    assert_equal(np.ma.median(a, axis=1), inf)
+                    assert_equal(np.ma.median(a, axis=0),
+                                 ([np.nan] * i) + [inf] * j)
+
+    def test_empty(self):
+        # empty arrays
+        a = np.ma.masked_array(np.array([], dtype=float))
+        with suppress_warnings() as w:
+            w.record(RuntimeWarning)
+            assert_array_equal(np.ma.median(a), np.nan)
+            assert_(w.log[0].category is RuntimeWarning)
+
+        # multiple dimensions
+        a = np.ma.masked_array(np.array([], dtype=float, ndmin=3))
+        # no axis
+        with suppress_warnings() as w:
+            w.record(RuntimeWarning)
+            warnings.filterwarnings('always', '', RuntimeWarning)
+            assert_array_equal(np.ma.median(a), np.nan)
+            assert_(w.log[0].category is RuntimeWarning)
+
+        # axis 0 and 1
+        b = np.ma.masked_array(np.array([], dtype=float, ndmin=2))
+        assert_equal(np.ma.median(a, axis=0), b)
+        assert_equal(np.ma.median(a, axis=1), b)
+
+        # axis 2
+        b = np.ma.masked_array(np.array(np.nan, dtype=float, ndmin=2))
+        with warnings.catch_warnings(record=True) as w:
+            warnings.filterwarnings('always', '', RuntimeWarning)
+            assert_equal(np.ma.median(a, axis=2), b)
+            assert_(w[0].category is RuntimeWarning)
+
+    def test_object(self):
+        o = np.ma.masked_array(np.arange(7.))
+        assert_(type(np.ma.median(o.astype(object))), float)
+        o[2] = np.nan
+        assert_(type(np.ma.median(o.astype(object))), float)
+
+
+class TestCov:
+
+    def setup_method(self):
+        self.data = array(np.random.rand(12))
+
+    def test_1d_without_missing(self):
+        # Test cov on 1D variable w/o missing values
+        x = self.data
+        assert_almost_equal(np.cov(x), cov(x))
+        assert_almost_equal(np.cov(x, rowvar=False), cov(x, rowvar=False))
+        assert_almost_equal(np.cov(x, rowvar=False, bias=True),
+                            cov(x, rowvar=False, bias=True))
+
+    def test_2d_without_missing(self):
+        # Test cov on 1 2D variable w/o missing values
+        x = self.data.reshape(3, 4)
+        assert_almost_equal(np.cov(x), cov(x))
+        assert_almost_equal(np.cov(x, rowvar=False), cov(x, rowvar=False))
+        assert_almost_equal(np.cov(x, rowvar=False, bias=True),
+                            cov(x, rowvar=False, bias=True))
+
+    def test_1d_with_missing(self):
+        # Test cov 1 1D variable w/missing values
+        x = self.data
+        x[-1] = masked
+        x -= x.mean()
+        nx = x.compressed()
+        assert_almost_equal(np.cov(nx), cov(x))
+        assert_almost_equal(np.cov(nx, rowvar=False), cov(x, rowvar=False))
+        assert_almost_equal(np.cov(nx, rowvar=False, bias=True),
+                            cov(x, rowvar=False, bias=True))
+        #
+        try:
+            cov(x, allow_masked=False)
+        except ValueError:
+            pass
+        #
+        # 2 1D variables w/ missing values
+        nx = x[1:-1]
+        assert_almost_equal(np.cov(nx, nx[::-1]), cov(x, x[::-1]))
+        assert_almost_equal(np.cov(nx, nx[::-1], rowvar=False),
+                            cov(x, x[::-1], rowvar=False))
+        assert_almost_equal(np.cov(nx, nx[::-1], rowvar=False, bias=True),
+                            cov(x, x[::-1], rowvar=False, bias=True))
+
+    def test_2d_with_missing(self):
+        # Test cov on 2D variable w/ missing value
+        x = self.data
+        x[-1] = masked
+        x = x.reshape(3, 4)
+        valid = np.logical_not(getmaskarray(x)).astype(int)
+        frac = np.dot(valid, valid.T)
+        xf = (x - x.mean(1)[:, None]).filled(0)
+        assert_almost_equal(cov(x),
+                            np.cov(xf) * (x.shape[1] - 1) / (frac - 1.))
+        assert_almost_equal(cov(x, bias=True),
+                            np.cov(xf, bias=True) * x.shape[1] / frac)
+        frac = np.dot(valid.T, valid)
+        xf = (x - x.mean(0)).filled(0)
+        assert_almost_equal(cov(x, rowvar=False),
+                            (np.cov(xf, rowvar=False) *
+                             (x.shape[0] - 1) / (frac - 1.)))
+        assert_almost_equal(cov(x, rowvar=False, bias=True),
+                            (np.cov(xf, rowvar=False, bias=True) *
+                             x.shape[0] / frac))
+
+
+class TestCorrcoef:
+
+    def setup_method(self):
+        self.data = array(np.random.rand(12))
+        self.data2 = array(np.random.rand(12))
+
+    def test_ddof(self):
+        # ddof raises DeprecationWarning
+        x, y = self.data, self.data2
+        expected = np.corrcoef(x)
+        expected2 = np.corrcoef(x, y)
+        with suppress_warnings() as sup:
+            warnings.simplefilter("always")
+            assert_warns(DeprecationWarning, corrcoef, x, ddof=-1)
+            sup.filter(DeprecationWarning, "bias and ddof have no effect")
+            # ddof has no or negligible effect on the function
+            assert_almost_equal(np.corrcoef(x, ddof=0), corrcoef(x, ddof=0))
+            assert_almost_equal(corrcoef(x, ddof=-1), expected)
+            assert_almost_equal(corrcoef(x, y, ddof=-1), expected2)
+            assert_almost_equal(corrcoef(x, ddof=3), expected)
+            assert_almost_equal(corrcoef(x, y, ddof=3), expected2)
+
+    def test_bias(self):
+        x, y = self.data, self.data2
+        expected = np.corrcoef(x)
+        # bias raises DeprecationWarning
+        with suppress_warnings() as sup:
+            warnings.simplefilter("always")
+            assert_warns(DeprecationWarning, corrcoef, x, y, True, False)
+            assert_warns(DeprecationWarning, corrcoef, x, y, True, True)
+            assert_warns(DeprecationWarning, corrcoef, x, bias=False)
+            sup.filter(DeprecationWarning, "bias and ddof have no effect")
+            # bias has no or negligible effect on the function
+            assert_almost_equal(corrcoef(x, bias=1), expected)
+
+    def test_1d_without_missing(self):
+        # Test cov on 1D variable w/o missing values
+        x = self.data
+        assert_almost_equal(np.corrcoef(x), corrcoef(x))
+        assert_almost_equal(np.corrcoef(x, rowvar=False),
+                            corrcoef(x, rowvar=False))
+        with suppress_warnings() as sup:
+            sup.filter(DeprecationWarning, "bias and ddof have no effect")
+            assert_almost_equal(np.corrcoef(x, rowvar=False, bias=True),
+                                corrcoef(x, rowvar=False, bias=True))
+
+    def test_2d_without_missing(self):
+        # Test corrcoef on 1 2D variable w/o missing values
+        x = self.data.reshape(3, 4)
+        assert_almost_equal(np.corrcoef(x), corrcoef(x))
+        assert_almost_equal(np.corrcoef(x, rowvar=False),
+                            corrcoef(x, rowvar=False))
+        with suppress_warnings() as sup:
+            sup.filter(DeprecationWarning, "bias and ddof have no effect")
+            assert_almost_equal(np.corrcoef(x, rowvar=False, bias=True),
+                                corrcoef(x, rowvar=False, bias=True))
+
+    def test_1d_with_missing(self):
+        # Test corrcoef 1 1D variable w/missing values
+        x = self.data
+        x[-1] = masked
+        x -= x.mean()
+        nx = x.compressed()
+        assert_almost_equal(np.corrcoef(nx), corrcoef(x))
+        assert_almost_equal(np.corrcoef(nx, rowvar=False),
+                            corrcoef(x, rowvar=False))
+        with suppress_warnings() as sup:
+            sup.filter(DeprecationWarning, "bias and ddof have no effect")
+            assert_almost_equal(np.corrcoef(nx, rowvar=False, bias=True),
+                                corrcoef(x, rowvar=False, bias=True))
+        try:
+            corrcoef(x, allow_masked=False)
+        except ValueError:
+            pass
+        # 2 1D variables w/ missing values
+        nx = x[1:-1]
+        assert_almost_equal(np.corrcoef(nx, nx[::-1]), corrcoef(x, x[::-1]))
+        assert_almost_equal(np.corrcoef(nx, nx[::-1], rowvar=False),
+                            corrcoef(x, x[::-1], rowvar=False))
+        with suppress_warnings() as sup:
+            sup.filter(DeprecationWarning, "bias and ddof have no effect")
+            # ddof and bias have no or negligible effect on the function
+            assert_almost_equal(np.corrcoef(nx, nx[::-1]),
+                                corrcoef(x, x[::-1], bias=1))
+            assert_almost_equal(np.corrcoef(nx, nx[::-1]),
+                                corrcoef(x, x[::-1], ddof=2))
+
+    def test_2d_with_missing(self):
+        # Test corrcoef on 2D variable w/ missing value
+        x = self.data
+        x[-1] = masked
+        x = x.reshape(3, 4)
+
+        test = corrcoef(x)
+        control = np.corrcoef(x)
+        assert_almost_equal(test[:-1, :-1], control[:-1, :-1])
+        with suppress_warnings() as sup:
+            sup.filter(DeprecationWarning, "bias and ddof have no effect")
+            # ddof and bias have no or negligible effect on the function
+            assert_almost_equal(corrcoef(x, ddof=-2)[:-1, :-1],
+                                control[:-1, :-1])
+            assert_almost_equal(corrcoef(x, ddof=3)[:-1, :-1],
+                                control[:-1, :-1])
+            assert_almost_equal(corrcoef(x, bias=1)[:-1, :-1],
+                                control[:-1, :-1])
+
+
+class TestPolynomial:
+    #
+    def test_polyfit(self):
+        # Tests polyfit
+        # On ndarrays
+        x = np.random.rand(10)
+        y = np.random.rand(20).reshape(-1, 2)
+        assert_almost_equal(polyfit(x, y, 3), np.polyfit(x, y, 3))
+        # ON 1D maskedarrays
+        x = x.view(MaskedArray)
+        x[0] = masked
+        y = y.view(MaskedArray)
+        y[0, 0] = y[-1, -1] = masked
+        #
+        (C, R, K, S, D) = polyfit(x, y[:, 0], 3, full=True)
+        (c, r, k, s, d) = np.polyfit(x[1:], y[1:, 0].compressed(), 3,
+                                     full=True)
+        for (a, a_) in zip((C, R, K, S, D), (c, r, k, s, d)):
+            assert_almost_equal(a, a_)
+        #
+        (C, R, K, S, D) = polyfit(x, y[:, -1], 3, full=True)
+        (c, r, k, s, d) = np.polyfit(x[1:-1], y[1:-1, -1], 3, full=True)
+        for (a, a_) in zip((C, R, K, S, D), (c, r, k, s, d)):
+            assert_almost_equal(a, a_)
+        #
+        (C, R, K, S, D) = polyfit(x, y, 3, full=True)
+        (c, r, k, s, d) = np.polyfit(x[1:-1], y[1:-1,:], 3, full=True)
+        for (a, a_) in zip((C, R, K, S, D), (c, r, k, s, d)):
+            assert_almost_equal(a, a_)
+        #
+        w = np.random.rand(10) + 1
+        wo = w.copy()
+        xs = x[1:-1]
+        ys = y[1:-1]
+        ws = w[1:-1]
+        (C, R, K, S, D) = polyfit(x, y, 3, full=True, w=w)
+        (c, r, k, s, d) = np.polyfit(xs, ys, 3, full=True, w=ws)
+        assert_equal(w, wo)
+        for (a, a_) in zip((C, R, K, S, D), (c, r, k, s, d)):
+            assert_almost_equal(a, a_)
+
+    def test_polyfit_with_masked_NaNs(self):
+        x = np.random.rand(10)
+        y = np.random.rand(20).reshape(-1, 2)
+
+        x[0] = np.nan
+        y[-1,-1] = np.nan
+        x = x.view(MaskedArray)
+        y = y.view(MaskedArray)
+        x[0] = masked
+        y[-1,-1] = masked
+
+        (C, R, K, S, D) = polyfit(x, y, 3, full=True)
+        (c, r, k, s, d) = np.polyfit(x[1:-1], y[1:-1,:], 3, full=True)
+        for (a, a_) in zip((C, R, K, S, D), (c, r, k, s, d)):
+            assert_almost_equal(a, a_)
+
+
+class TestArraySetOps:
+
+    def test_unique_onlist(self):
+        # Test unique on list
+        data = [1, 1, 1, 2, 2, 3]
+        test = unique(data, return_index=True, return_inverse=True)
+        assert_(isinstance(test[0], MaskedArray))
+        assert_equal(test[0], masked_array([1, 2, 3], mask=[0, 0, 0]))
+        assert_equal(test[1], [0, 3, 5])
+        assert_equal(test[2], [0, 0, 0, 1, 1, 2])
+
+    def test_unique_onmaskedarray(self):
+        # Test unique on masked data w/use_mask=True
+        data = masked_array([1, 1, 1, 2, 2, 3], mask=[0, 0, 1, 0, 1, 0])
+        test = unique(data, return_index=True, return_inverse=True)
+        assert_equal(test[0], masked_array([1, 2, 3, -1], mask=[0, 0, 0, 1]))
+        assert_equal(test[1], [0, 3, 5, 2])
+        assert_equal(test[2], [0, 0, 3, 1, 3, 2])
+        #
+        data.fill_value = 3
+        data = masked_array(data=[1, 1, 1, 2, 2, 3],
+                            mask=[0, 0, 1, 0, 1, 0], fill_value=3)
+        test = unique(data, return_index=True, return_inverse=True)
+        assert_equal(test[0], masked_array([1, 2, 3, -1], mask=[0, 0, 0, 1]))
+        assert_equal(test[1], [0, 3, 5, 2])
+        assert_equal(test[2], [0, 0, 3, 1, 3, 2])
+
+    def test_unique_allmasked(self):
+        # Test all masked
+        data = masked_array([1, 1, 1], mask=True)
+        test = unique(data, return_index=True, return_inverse=True)
+        assert_equal(test[0], masked_array([1, ], mask=[True]))
+        assert_equal(test[1], [0])
+        assert_equal(test[2], [0, 0, 0])
+        #
+        # Test masked
+        data = masked
+        test = unique(data, return_index=True, return_inverse=True)
+        assert_equal(test[0], masked_array(masked))
+        assert_equal(test[1], [0])
+        assert_equal(test[2], [0])
+
+    def test_ediff1d(self):
+        # Tests mediff1d
+        x = masked_array(np.arange(5), mask=[1, 0, 0, 0, 1])
+        control = array([1, 1, 1, 4], mask=[1, 0, 0, 1])
+        test = ediff1d(x)
+        assert_equal(test, control)
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+
+    def test_ediff1d_tobegin(self):
+        # Test ediff1d w/ to_begin
+        x = masked_array(np.arange(5), mask=[1, 0, 0, 0, 1])
+        test = ediff1d(x, to_begin=masked)
+        control = array([0, 1, 1, 1, 4], mask=[1, 1, 0, 0, 1])
+        assert_equal(test, control)
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+        #
+        test = ediff1d(x, to_begin=[1, 2, 3])
+        control = array([1, 2, 3, 1, 1, 1, 4], mask=[0, 0, 0, 1, 0, 0, 1])
+        assert_equal(test, control)
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+
+    def test_ediff1d_toend(self):
+        # Test ediff1d w/ to_end
+        x = masked_array(np.arange(5), mask=[1, 0, 0, 0, 1])
+        test = ediff1d(x, to_end=masked)
+        control = array([1, 1, 1, 4, 0], mask=[1, 0, 0, 1, 1])
+        assert_equal(test, control)
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+        #
+        test = ediff1d(x, to_end=[1, 2, 3])
+        control = array([1, 1, 1, 4, 1, 2, 3], mask=[1, 0, 0, 1, 0, 0, 0])
+        assert_equal(test, control)
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+
+    def test_ediff1d_tobegin_toend(self):
+        # Test ediff1d w/ to_begin and to_end
+        x = masked_array(np.arange(5), mask=[1, 0, 0, 0, 1])
+        test = ediff1d(x, to_end=masked, to_begin=masked)
+        control = array([0, 1, 1, 1, 4, 0], mask=[1, 1, 0, 0, 1, 1])
+        assert_equal(test, control)
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+        #
+        test = ediff1d(x, to_end=[1, 2, 3], to_begin=masked)
+        control = array([0, 1, 1, 1, 4, 1, 2, 3],
+                        mask=[1, 1, 0, 0, 1, 0, 0, 0])
+        assert_equal(test, control)
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+
+    def test_ediff1d_ndarray(self):
+        # Test ediff1d w/ a ndarray
+        x = np.arange(5)
+        test = ediff1d(x)
+        control = array([1, 1, 1, 1], mask=[0, 0, 0, 0])
+        assert_equal(test, control)
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+        #
+        test = ediff1d(x, to_end=masked, to_begin=masked)
+        control = array([0, 1, 1, 1, 1, 0], mask=[1, 0, 0, 0, 0, 1])
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test.filled(0), control.filled(0))
+        assert_equal(test.mask, control.mask)
+
+    def test_intersect1d(self):
+        # Test intersect1d
+        x = array([1, 3, 3, 3], mask=[0, 0, 0, 1])
+        y = array([3, 1, 1, 1], mask=[0, 0, 0, 1])
+        test = intersect1d(x, y)
+        control = array([1, 3, -1], mask=[0, 0, 1])
+        assert_equal(test, control)
+
+    def test_setxor1d(self):
+        # Test setxor1d
+        a = array([1, 2, 5, 7, -1], mask=[0, 0, 0, 0, 1])
+        b = array([1, 2, 3, 4, 5, -1], mask=[0, 0, 0, 0, 0, 1])
+        test = setxor1d(a, b)
+        assert_equal(test, array([3, 4, 7]))
+        #
+        a = array([1, 2, 5, 7, -1], mask=[0, 0, 0, 0, 1])
+        b = [1, 2, 3, 4, 5]
+        test = setxor1d(a, b)
+        assert_equal(test, array([3, 4, 7, -1], mask=[0, 0, 0, 1]))
+        #
+        a = array([1, 2, 3])
+        b = array([6, 5, 4])
+        test = setxor1d(a, b)
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test, [1, 2, 3, 4, 5, 6])
+        #
+        a = array([1, 8, 2, 3], mask=[0, 1, 0, 0])
+        b = array([6, 5, 4, 8], mask=[0, 0, 0, 1])
+        test = setxor1d(a, b)
+        assert_(isinstance(test, MaskedArray))
+        assert_equal(test, [1, 2, 3, 4, 5, 6])
+        #
+        assert_array_equal([], setxor1d([], []))
+
+    def test_isin(self):
+        # the tests for in1d cover most of isin's behavior
+        # if in1d is removed, would need to change those tests to test
+        # isin instead.
+        a = np.arange(24).reshape([2, 3, 4])
+        mask = np.zeros([2, 3, 4])
+        mask[1, 2, 0] = 1
+        a = array(a, mask=mask)
+        b = array(data=[0, 10, 20, 30,  1,  3, 11, 22, 33],
+                  mask=[0,  1,  0,  1,  0,  1,  0,  1,  0])
+        ec = zeros((2, 3, 4), dtype=bool)
+        ec[0, 0, 0] = True
+        ec[0, 0, 1] = True
+        ec[0, 2, 3] = True
+        c = isin(a, b)
+        assert_(isinstance(c, MaskedArray))
+        assert_array_equal(c, ec)
+        #compare results of np.isin to ma.isin
+        d = np.isin(a, b[~b.mask]) & ~a.mask
+        assert_array_equal(c, d)
+
+    def test_in1d(self):
+        # Test in1d
+        a = array([1, 2, 5, 7, -1], mask=[0, 0, 0, 0, 1])
+        b = array([1, 2, 3, 4, 5, -1], mask=[0, 0, 0, 0, 0, 1])
+        test = in1d(a, b)
+        assert_equal(test, [True, True, True, False, True])
+        #
+        a = array([5, 5, 2, 1, -1], mask=[0, 0, 0, 0, 1])
+        b = array([1, 5, -1], mask=[0, 0, 1])
+        test = in1d(a, b)
+        assert_equal(test, [True, True, False, True, True])
+        #
+        assert_array_equal([], in1d([], []))
+
+    def test_in1d_invert(self):
+        # Test in1d's invert parameter
+        a = array([1, 2, 5, 7, -1], mask=[0, 0, 0, 0, 1])
+        b = array([1, 2, 3, 4, 5, -1], mask=[0, 0, 0, 0, 0, 1])
+        assert_equal(np.invert(in1d(a, b)), in1d(a, b, invert=True))
+
+        a = array([5, 5, 2, 1, -1], mask=[0, 0, 0, 0, 1])
+        b = array([1, 5, -1], mask=[0, 0, 1])
+        assert_equal(np.invert(in1d(a, b)), in1d(a, b, invert=True))
+
+        assert_array_equal([], in1d([], [], invert=True))
+
+    def test_union1d(self):
+        # Test union1d
+        a = array([1, 2, 5, 7, 5, -1], mask=[0, 0, 0, 0, 0, 1])
+        b = array([1, 2, 3, 4, 5, -1], mask=[0, 0, 0, 0, 0, 1])
+        test = union1d(a, b)
+        control = array([1, 2, 3, 4, 5, 7, -1], mask=[0, 0, 0, 0, 0, 0, 1])
+        assert_equal(test, control)
+
+        # Tests gh-10340, arguments to union1d should be
+        # flattened if they are not already 1D
+        x = array([[0, 1, 2], [3, 4, 5]], mask=[[0, 0, 0], [0, 0, 1]])
+        y = array([0, 1, 2, 3, 4], mask=[0, 0, 0, 0, 1])
+        ez = array([0, 1, 2, 3, 4, 5], mask=[0, 0, 0, 0, 0, 1])
+        z = union1d(x, y)
+        assert_equal(z, ez)
+        #
+        assert_array_equal([], union1d([], []))
+
+    def test_setdiff1d(self):
+        # Test setdiff1d
+        a = array([6, 5, 4, 7, 7, 1, 2, 1], mask=[0, 0, 0, 0, 0, 0, 0, 1])
+        b = array([2, 4, 3, 3, 2, 1, 5])
+        test = setdiff1d(a, b)
+        assert_equal(test, array([6, 7, -1], mask=[0, 0, 1]))
+        #
+        a = arange(10)
+        b = arange(8)
+        assert_equal(setdiff1d(a, b), array([8, 9]))
+        a = array([], np.uint32, mask=[])
+        assert_equal(setdiff1d(a, []).dtype, np.uint32)
+
+    def test_setdiff1d_char_array(self):
+        # Test setdiff1d_charray
+        a = np.array(['a', 'b', 'c'])
+        b = np.array(['a', 'b', 's'])
+        assert_array_equal(setdiff1d(a, b), np.array(['c']))
+
+
+class TestShapeBase:
+
+    def test_atleast_2d(self):
+        # Test atleast_2d
+        a = masked_array([0, 1, 2], mask=[0, 1, 0])
+        b = atleast_2d(a)
+        assert_equal(b.shape, (1, 3))
+        assert_equal(b.mask.shape, b.data.shape)
+        assert_equal(a.shape, (3,))
+        assert_equal(a.mask.shape, a.data.shape)
+        assert_equal(b.mask.shape, b.data.shape)
+
+    def test_shape_scalar(self):
+        # the atleast and diagflat function should work with scalars
+        # GitHub issue #3367
+        # Additionally, the atleast functions should accept multiple scalars
+        # correctly
+        b = atleast_1d(1.0)
+        assert_equal(b.shape, (1,))
+        assert_equal(b.mask.shape, b.shape)
+        assert_equal(b.data.shape, b.shape)
+
+        b = atleast_1d(1.0, 2.0)
+        for a in b:
+            assert_equal(a.shape, (1,))
+            assert_equal(a.mask.shape, a.shape)
+            assert_equal(a.data.shape, a.shape)
+
+        b = atleast_2d(1.0)
+        assert_equal(b.shape, (1, 1))
+        assert_equal(b.mask.shape, b.shape)
+        assert_equal(b.data.shape, b.shape)
+
+        b = atleast_2d(1.0, 2.0)
+        for a in b:
+            assert_equal(a.shape, (1, 1))
+            assert_equal(a.mask.shape, a.shape)
+            assert_equal(a.data.shape, a.shape)
+
+        b = atleast_3d(1.0)
+        assert_equal(b.shape, (1, 1, 1))
+        assert_equal(b.mask.shape, b.shape)
+        assert_equal(b.data.shape, b.shape)
+
+        b = atleast_3d(1.0, 2.0)
+        for a in b:
+            assert_equal(a.shape, (1, 1, 1))
+            assert_equal(a.mask.shape, a.shape)
+            assert_equal(a.data.shape, a.shape)
+
+        b = diagflat(1.0)
+        assert_equal(b.shape, (1, 1))
+        assert_equal(b.mask.shape, b.data.shape)
+
+
+class TestNDEnumerate:
+
+    def test_ndenumerate_nomasked(self):
+        ordinary = np.arange(6.).reshape((1, 3, 2))
+        empty_mask = np.zeros_like(ordinary, dtype=bool)
+        with_mask = masked_array(ordinary, mask=empty_mask)
+        assert_equal(list(np.ndenumerate(ordinary)),
+                     list(ndenumerate(ordinary)))
+        assert_equal(list(ndenumerate(ordinary)),
+                     list(ndenumerate(with_mask)))
+        assert_equal(list(ndenumerate(with_mask)),
+                     list(ndenumerate(with_mask, compressed=False)))
+
+    def test_ndenumerate_allmasked(self):
+        a = masked_all(())
+        b = masked_all((100,))
+        c = masked_all((2, 3, 4))
+        assert_equal(list(ndenumerate(a)), [])
+        assert_equal(list(ndenumerate(b)), [])
+        assert_equal(list(ndenumerate(b, compressed=False)),
+                     list(zip(np.ndindex((100,)), 100 * [masked])))
+        assert_equal(list(ndenumerate(c)), [])
+        assert_equal(list(ndenumerate(c, compressed=False)),
+                     list(zip(np.ndindex((2, 3, 4)), 2 * 3 * 4 * [masked])))
+
+    def test_ndenumerate_mixedmasked(self):
+        a = masked_array(np.arange(12).reshape((3, 4)),
+                         mask=[[1, 1, 1, 1],
+                               [1, 1, 0, 1],
+                               [0, 0, 0, 0]])
+        items = [((1, 2), 6),
+                 ((2, 0), 8), ((2, 1), 9), ((2, 2), 10), ((2, 3), 11)]
+        assert_equal(list(ndenumerate(a)), items)
+        assert_equal(len(list(ndenumerate(a, compressed=False))), a.size)
+        for coordinate, value in ndenumerate(a, compressed=False):
+            assert_equal(a[coordinate], value)
+
+
+class TestStack:
+
+    def test_stack_1d(self):
+        a = masked_array([0, 1, 2], mask=[0, 1, 0])
+        b = masked_array([9, 8, 7], mask=[1, 0, 0])
+
+        c = stack([a, b], axis=0)
+        assert_equal(c.shape, (2, 3))
+        assert_array_equal(a.mask, c[0].mask)
+        assert_array_equal(b.mask, c[1].mask)
+
+        d = vstack([a, b])
+        assert_array_equal(c.data, d.data)
+        assert_array_equal(c.mask, d.mask)
+
+        c = stack([a, b], axis=1)
+        assert_equal(c.shape, (3, 2))
+        assert_array_equal(a.mask, c[:, 0].mask)
+        assert_array_equal(b.mask, c[:, 1].mask)
+
+    def test_stack_masks(self):
+        a = masked_array([0, 1, 2], mask=True)
+        b = masked_array([9, 8, 7], mask=False)
+
+        c = stack([a, b], axis=0)
+        assert_equal(c.shape, (2, 3))
+        assert_array_equal(a.mask, c[0].mask)
+        assert_array_equal(b.mask, c[1].mask)
+
+        d = vstack([a, b])
+        assert_array_equal(c.data, d.data)
+        assert_array_equal(c.mask, d.mask)
+
+        c = stack([a, b], axis=1)
+        assert_equal(c.shape, (3, 2))
+        assert_array_equal(a.mask, c[:, 0].mask)
+        assert_array_equal(b.mask, c[:, 1].mask)
+
+    def test_stack_nd(self):
+        # 2D
+        shp = (3, 2)
+        d1 = np.random.randint(0, 10, shp)
+        d2 = np.random.randint(0, 10, shp)
+        m1 = np.random.randint(0, 2, shp).astype(bool)
+        m2 = np.random.randint(0, 2, shp).astype(bool)
+        a1 = masked_array(d1, mask=m1)
+        a2 = masked_array(d2, mask=m2)
+
+        c = stack([a1, a2], axis=0)
+        c_shp = (2,) + shp
+        assert_equal(c.shape, c_shp)
+        assert_array_equal(a1.mask, c[0].mask)
+        assert_array_equal(a2.mask, c[1].mask)
+
+        c = stack([a1, a2], axis=-1)
+        c_shp = shp + (2,)
+        assert_equal(c.shape, c_shp)
+        assert_array_equal(a1.mask, c[..., 0].mask)
+        assert_array_equal(a2.mask, c[..., 1].mask)
+
+        # 4D
+        shp = (3, 2, 4, 5,)
+        d1 = np.random.randint(0, 10, shp)
+        d2 = np.random.randint(0, 10, shp)
+        m1 = np.random.randint(0, 2, shp).astype(bool)
+        m2 = np.random.randint(0, 2, shp).astype(bool)
+        a1 = masked_array(d1, mask=m1)
+        a2 = masked_array(d2, mask=m2)
+
+        c = stack([a1, a2], axis=0)
+        c_shp = (2,) + shp
+        assert_equal(c.shape, c_shp)
+        assert_array_equal(a1.mask, c[0].mask)
+        assert_array_equal(a2.mask, c[1].mask)
+
+        c = stack([a1, a2], axis=-1)
+        c_shp = shp + (2,)
+        assert_equal(c.shape, c_shp)
+        assert_array_equal(a1.mask, c[..., 0].mask)
+        assert_array_equal(a2.mask, c[..., 1].mask)
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_mrecords.py b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_mrecords.py
new file mode 100644
index 00000000..77123c3c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_mrecords.py
@@ -0,0 +1,493 @@
+# pylint: disable-msg=W0611, W0612, W0511,R0201
+"""Tests suite for mrecords.
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+
+"""
+import numpy as np
+import numpy.ma as ma
+from numpy import recarray
+from numpy.ma import masked, nomask
+from numpy.testing import temppath
+from numpy.core.records import (
+    fromrecords as recfromrecords, fromarrays as recfromarrays
+    )
+from numpy.ma.mrecords import (
+    MaskedRecords, mrecarray, fromarrays, fromtextfile, fromrecords,
+    addfield
+    )
+from numpy.ma.testutils import (
+    assert_, assert_equal,
+    assert_equal_records,
+    )
+from numpy.compat import pickle
+
+
+class TestMRecords:
+
+    ilist = [1, 2, 3, 4, 5]
+    flist = [1.1, 2.2, 3.3, 4.4, 5.5]
+    slist = [b'one', b'two', b'three', b'four', b'five']
+    ddtype = [('a', int), ('b', float), ('c', '|S8')]
+    mask = [0, 1, 0, 0, 1]
+    base = ma.array(list(zip(ilist, flist, slist)), mask=mask, dtype=ddtype)
+
+    def test_byview(self):
+        # Test creation by view
+        base = self.base
+        mbase = base.view(mrecarray)
+        assert_equal(mbase.recordmask, base.recordmask)
+        assert_equal_records(mbase._mask, base._mask)
+        assert_(isinstance(mbase._data, recarray))
+        assert_equal_records(mbase._data, base._data.view(recarray))
+        for field in ('a', 'b', 'c'):
+            assert_equal(base[field], mbase[field])
+        assert_equal_records(mbase.view(mrecarray), mbase)
+
+    def test_get(self):
+        # Tests fields retrieval
+        base = self.base.copy()
+        mbase = base.view(mrecarray)
+        # As fields..........
+        for field in ('a', 'b', 'c'):
+            assert_equal(getattr(mbase, field), mbase[field])
+            assert_equal(base[field], mbase[field])
+        # as elements .......
+        mbase_first = mbase[0]
+        assert_(isinstance(mbase_first, mrecarray))
+        assert_equal(mbase_first.dtype, mbase.dtype)
+        assert_equal(mbase_first.tolist(), (1, 1.1, b'one'))
+        # Used to be mask, now it's recordmask
+        assert_equal(mbase_first.recordmask, nomask)
+        assert_equal(mbase_first._mask.item(), (False, False, False))
+        assert_equal(mbase_first['a'], mbase['a'][0])
+        mbase_last = mbase[-1]
+        assert_(isinstance(mbase_last, mrecarray))
+        assert_equal(mbase_last.dtype, mbase.dtype)
+        assert_equal(mbase_last.tolist(), (None, None, None))
+        # Used to be mask, now it's recordmask
+        assert_equal(mbase_last.recordmask, True)
+        assert_equal(mbase_last._mask.item(), (True, True, True))
+        assert_equal(mbase_last['a'], mbase['a'][-1])
+        assert_((mbase_last['a'] is masked))
+        # as slice ..........
+        mbase_sl = mbase[:2]
+        assert_(isinstance(mbase_sl, mrecarray))
+        assert_equal(mbase_sl.dtype, mbase.dtype)
+        # Used to be mask, now it's recordmask
+        assert_equal(mbase_sl.recordmask, [0, 1])
+        assert_equal_records(mbase_sl.mask,
+                             np.array([(False, False, False),
+                                       (True, True, True)],
+                                      dtype=mbase._mask.dtype))
+        assert_equal_records(mbase_sl, base[:2].view(mrecarray))
+        for field in ('a', 'b', 'c'):
+            assert_equal(getattr(mbase_sl, field), base[:2][field])
+
+    def test_set_fields(self):
+        # Tests setting fields.
+        base = self.base.copy()
+        mbase = base.view(mrecarray)
+        mbase = mbase.copy()
+        mbase.fill_value = (999999, 1e20, 'N/A')
+        # Change the data, the mask should be conserved
+        mbase.a._data[:] = 5
+        assert_equal(mbase['a']._data, [5, 5, 5, 5, 5])
+        assert_equal(mbase['a']._mask, [0, 1, 0, 0, 1])
+        # Change the elements, and the mask will follow
+        mbase.a = 1
+        assert_equal(mbase['a']._data, [1]*5)
+        assert_equal(ma.getmaskarray(mbase['a']), [0]*5)
+        # Use to be _mask, now it's recordmask
+        assert_equal(mbase.recordmask, [False]*5)
+        assert_equal(mbase._mask.tolist(),
+                     np.array([(0, 0, 0),
+                               (0, 1, 1),
+                               (0, 0, 0),
+                               (0, 0, 0),
+                               (0, 1, 1)],
+                              dtype=bool))
+        # Set a field to mask ........................
+        mbase.c = masked
+        # Use to be mask, and now it's still mask !
+        assert_equal(mbase.c.mask, [1]*5)
+        assert_equal(mbase.c.recordmask, [1]*5)
+        assert_equal(ma.getmaskarray(mbase['c']), [1]*5)
+        assert_equal(ma.getdata(mbase['c']), [b'N/A']*5)
+        assert_equal(mbase._mask.tolist(),
+                     np.array([(0, 0, 1),
+                               (0, 1, 1),
+                               (0, 0, 1),
+                               (0, 0, 1),
+                               (0, 1, 1)],
+                              dtype=bool))
+        # Set fields by slices .......................
+        mbase = base.view(mrecarray).copy()
+        mbase.a[3:] = 5
+        assert_equal(mbase.a, [1, 2, 3, 5, 5])
+        assert_equal(mbase.a._mask, [0, 1, 0, 0, 0])
+        mbase.b[3:] = masked
+        assert_equal(mbase.b, base['b'])
+        assert_equal(mbase.b._mask, [0, 1, 0, 1, 1])
+        # Set fields globally..........................
+        ndtype = [('alpha', '|S1'), ('num', int)]
+        data = ma.array([('a', 1), ('b', 2), ('c', 3)], dtype=ndtype)
+        rdata = data.view(MaskedRecords)
+        val = ma.array([10, 20, 30], mask=[1, 0, 0])
+
+        rdata['num'] = val
+        assert_equal(rdata.num, val)
+        assert_equal(rdata.num.mask, [1, 0, 0])
+
+    def test_set_fields_mask(self):
+        # Tests setting the mask of a field.
+        base = self.base.copy()
+        # This one has already a mask....
+        mbase = base.view(mrecarray)
+        mbase['a'][-2] = masked
+        assert_equal(mbase.a, [1, 2, 3, 4, 5])
+        assert_equal(mbase.a._mask, [0, 1, 0, 1, 1])
+        # This one has not yet
+        mbase = fromarrays([np.arange(5), np.random.rand(5)],
+                           dtype=[('a', int), ('b', float)])
+        mbase['a'][-2] = masked
+        assert_equal(mbase.a, [0, 1, 2, 3, 4])
+        assert_equal(mbase.a._mask, [0, 0, 0, 1, 0])
+
+    def test_set_mask(self):
+        base = self.base.copy()
+        mbase = base.view(mrecarray)
+        # Set the mask to True .......................
+        mbase.mask = masked
+        assert_equal(ma.getmaskarray(mbase['b']), [1]*5)
+        assert_equal(mbase['a']._mask, mbase['b']._mask)
+        assert_equal(mbase['a']._mask, mbase['c']._mask)
+        assert_equal(mbase._mask.tolist(),
+                     np.array([(1, 1, 1)]*5, dtype=bool))
+        # Delete the mask ............................
+        mbase.mask = nomask
+        assert_equal(ma.getmaskarray(mbase['c']), [0]*5)
+        assert_equal(mbase._mask.tolist(),
+                     np.array([(0, 0, 0)]*5, dtype=bool))
+
+    def test_set_mask_fromarray(self):
+        base = self.base.copy()
+        mbase = base.view(mrecarray)
+        # Sets the mask w/ an array
+        mbase.mask = [1, 0, 0, 0, 1]
+        assert_equal(mbase.a.mask, [1, 0, 0, 0, 1])
+        assert_equal(mbase.b.mask, [1, 0, 0, 0, 1])
+        assert_equal(mbase.c.mask, [1, 0, 0, 0, 1])
+        # Yay, once more !
+        mbase.mask = [0, 0, 0, 0, 1]
+        assert_equal(mbase.a.mask, [0, 0, 0, 0, 1])
+        assert_equal(mbase.b.mask, [0, 0, 0, 0, 1])
+        assert_equal(mbase.c.mask, [0, 0, 0, 0, 1])
+
+    def test_set_mask_fromfields(self):
+        mbase = self.base.copy().view(mrecarray)
+
+        nmask = np.array(
+            [(0, 1, 0), (0, 1, 0), (1, 0, 1), (1, 0, 1), (0, 0, 0)],
+            dtype=[('a', bool), ('b', bool), ('c', bool)])
+        mbase.mask = nmask
+        assert_equal(mbase.a.mask, [0, 0, 1, 1, 0])
+        assert_equal(mbase.b.mask, [1, 1, 0, 0, 0])
+        assert_equal(mbase.c.mask, [0, 0, 1, 1, 0])
+        # Reinitialize and redo
+        mbase.mask = False
+        mbase.fieldmask = nmask
+        assert_equal(mbase.a.mask, [0, 0, 1, 1, 0])
+        assert_equal(mbase.b.mask, [1, 1, 0, 0, 0])
+        assert_equal(mbase.c.mask, [0, 0, 1, 1, 0])
+
+    def test_set_elements(self):
+        base = self.base.copy()
+        # Set an element to mask .....................
+        mbase = base.view(mrecarray).copy()
+        mbase[-2] = masked
+        assert_equal(
+            mbase._mask.tolist(),
+            np.array([(0, 0, 0), (1, 1, 1), (0, 0, 0), (1, 1, 1), (1, 1, 1)],
+                     dtype=bool))
+        # Used to be mask, now it's recordmask!
+        assert_equal(mbase.recordmask, [0, 1, 0, 1, 1])
+        # Set slices .................................
+        mbase = base.view(mrecarray).copy()
+        mbase[:2] = (5, 5, 5)
+        assert_equal(mbase.a._data, [5, 5, 3, 4, 5])
+        assert_equal(mbase.a._mask, [0, 0, 0, 0, 1])
+        assert_equal(mbase.b._data, [5., 5., 3.3, 4.4, 5.5])
+        assert_equal(mbase.b._mask, [0, 0, 0, 0, 1])
+        assert_equal(mbase.c._data,
+                     [b'5', b'5', b'three', b'four', b'five'])
+        assert_equal(mbase.b._mask, [0, 0, 0, 0, 1])
+
+        mbase = base.view(mrecarray).copy()
+        mbase[:2] = masked
+        assert_equal(mbase.a._data, [1, 2, 3, 4, 5])
+        assert_equal(mbase.a._mask, [1, 1, 0, 0, 1])
+        assert_equal(mbase.b._data, [1.1, 2.2, 3.3, 4.4, 5.5])
+        assert_equal(mbase.b._mask, [1, 1, 0, 0, 1])
+        assert_equal(mbase.c._data,
+                     [b'one', b'two', b'three', b'four', b'five'])
+        assert_equal(mbase.b._mask, [1, 1, 0, 0, 1])
+
+    def test_setslices_hardmask(self):
+        # Tests setting slices w/ hardmask.
+        base = self.base.copy()
+        mbase = base.view(mrecarray)
+        mbase.harden_mask()
+        try:
+            mbase[-2:] = (5, 5, 5)
+            assert_equal(mbase.a._data, [1, 2, 3, 5, 5])
+            assert_equal(mbase.b._data, [1.1, 2.2, 3.3, 5, 5.5])
+            assert_equal(mbase.c._data,
+                         [b'one', b'two', b'three', b'5', b'five'])
+            assert_equal(mbase.a._mask, [0, 1, 0, 0, 1])
+            assert_equal(mbase.b._mask, mbase.a._mask)
+            assert_equal(mbase.b._mask, mbase.c._mask)
+        except NotImplementedError:
+            # OK, not implemented yet...
+            pass
+        except AssertionError:
+            raise
+        else:
+            raise Exception("Flexible hard masks should be supported !")
+        # Not using a tuple should crash
+        try:
+            mbase[-2:] = 3
+        except (NotImplementedError, TypeError):
+            pass
+        else:
+            raise TypeError("Should have expected a readable buffer object!")
+
+    def test_hardmask(self):
+        # Test hardmask
+        base = self.base.copy()
+        mbase = base.view(mrecarray)
+        mbase.harden_mask()
+        assert_(mbase._hardmask)
+        mbase.mask = nomask
+        assert_equal_records(mbase._mask, base._mask)
+        mbase.soften_mask()
+        assert_(not mbase._hardmask)
+        mbase.mask = nomask
+        # So, the mask of a field is no longer set to nomask...
+        assert_equal_records(mbase._mask,
+                             ma.make_mask_none(base.shape, base.dtype))
+        assert_(ma.make_mask(mbase['b']._mask) is nomask)
+        assert_equal(mbase['a']._mask, mbase['b']._mask)
+
+    def test_pickling(self):
+        # Test pickling
+        base = self.base.copy()
+        mrec = base.view(mrecarray)
+        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
+            _ = pickle.dumps(mrec, protocol=proto)
+            mrec_ = pickle.loads(_)
+            assert_equal(mrec_.dtype, mrec.dtype)
+            assert_equal_records(mrec_._data, mrec._data)
+            assert_equal(mrec_._mask, mrec._mask)
+            assert_equal_records(mrec_._mask, mrec._mask)
+
+    def test_filled(self):
+        # Test filling the array
+        _a = ma.array([1, 2, 3], mask=[0, 0, 1], dtype=int)
+        _b = ma.array([1.1, 2.2, 3.3], mask=[0, 0, 1], dtype=float)
+        _c = ma.array(['one', 'two', 'three'], mask=[0, 0, 1], dtype='|S8')
+        ddtype = [('a', int), ('b', float), ('c', '|S8')]
+        mrec = fromarrays([_a, _b, _c], dtype=ddtype,
+                          fill_value=(99999, 99999., 'N/A'))
+        mrecfilled = mrec.filled()
+        assert_equal(mrecfilled['a'], np.array((1, 2, 99999), dtype=int))
+        assert_equal(mrecfilled['b'], np.array((1.1, 2.2, 99999.),
+                                               dtype=float))
+        assert_equal(mrecfilled['c'], np.array(('one', 'two', 'N/A'),
+                                               dtype='|S8'))
+
+    def test_tolist(self):
+        # Test tolist.
+        _a = ma.array([1, 2, 3], mask=[0, 0, 1], dtype=int)
+        _b = ma.array([1.1, 2.2, 3.3], mask=[0, 0, 1], dtype=float)
+        _c = ma.array(['one', 'two', 'three'], mask=[1, 0, 0], dtype='|S8')
+        ddtype = [('a', int), ('b', float), ('c', '|S8')]
+        mrec = fromarrays([_a, _b, _c], dtype=ddtype,
+                          fill_value=(99999, 99999., 'N/A'))
+
+        assert_equal(mrec.tolist(),
+                     [(1, 1.1, None), (2, 2.2, b'two'),
+                      (None, None, b'three')])
+
+    def test_withnames(self):
+        # Test the creation w/ format and names
+        x = mrecarray(1, formats=float, names='base')
+        x[0]['base'] = 10
+        assert_equal(x['base'][0], 10)
+
+    def test_exotic_formats(self):
+        # Test that 'exotic' formats are processed properly
+        easy = mrecarray(1, dtype=[('i', int), ('s', '|S8'), ('f', float)])
+        easy[0] = masked
+        assert_equal(easy.filled(1).item(), (1, b'1', 1.))
+
+        solo = mrecarray(1, dtype=[('f0', '<f8', (2, 2))])
+        solo[0] = masked
+        assert_equal(solo.filled(1).item(),
+                     np.array((1,), dtype=solo.dtype).item())
+
+        mult = mrecarray(2, dtype="i4, (2,3)float, float")
+        mult[0] = masked
+        mult[1] = (1, 1, 1)
+        mult.filled(0)
+        assert_equal_records(mult.filled(0),
+                             np.array([(0, 0, 0), (1, 1, 1)],
+                                      dtype=mult.dtype))
+
+
+class TestView:
+
+    def setup_method(self):
+        (a, b) = (np.arange(10), np.random.rand(10))
+        ndtype = [('a', float), ('b', float)]
+        arr = np.array(list(zip(a, b)), dtype=ndtype)
+
+        mrec = fromarrays([a, b], dtype=ndtype, fill_value=(-9., -99.))
+        mrec.mask[3] = (False, True)
+        self.data = (mrec, a, b, arr)
+
+    def test_view_by_itself(self):
+        (mrec, a, b, arr) = self.data
+        test = mrec.view()
+        assert_(isinstance(test, MaskedRecords))
+        assert_equal_records(test, mrec)
+        assert_equal_records(test._mask, mrec._mask)
+
+    def test_view_simple_dtype(self):
+        (mrec, a, b, arr) = self.data
+        ntype = (float, 2)
+        test = mrec.view(ntype)
+        assert_(isinstance(test, ma.MaskedArray))
+        assert_equal(test, np.array(list(zip(a, b)), dtype=float))
+        assert_(test[3, 1] is ma.masked)
+
+    def test_view_flexible_type(self):
+        (mrec, a, b, arr) = self.data
+        alttype = [('A', float), ('B', float)]
+        test = mrec.view(alttype)
+        assert_(isinstance(test, MaskedRecords))
+        assert_equal_records(test, arr.view(alttype))
+        assert_(test['B'][3] is masked)
+        assert_equal(test.dtype, np.dtype(alttype))
+        assert_(test._fill_value is None)
+
+
+##############################################################################
+class TestMRecordsImport:
+
+    _a = ma.array([1, 2, 3], mask=[0, 0, 1], dtype=int)
+    _b = ma.array([1.1, 2.2, 3.3], mask=[0, 0, 1], dtype=float)
+    _c = ma.array([b'one', b'two', b'three'],
+                  mask=[0, 0, 1], dtype='|S8')
+    ddtype = [('a', int), ('b', float), ('c', '|S8')]
+    mrec = fromarrays([_a, _b, _c], dtype=ddtype,
+                      fill_value=(b'99999', b'99999.',
+                                  b'N/A'))
+    nrec = recfromarrays((_a._data, _b._data, _c._data), dtype=ddtype)
+    data = (mrec, nrec, ddtype)
+
+    def test_fromarrays(self):
+        _a = ma.array([1, 2, 3], mask=[0, 0, 1], dtype=int)
+        _b = ma.array([1.1, 2.2, 3.3], mask=[0, 0, 1], dtype=float)
+        _c = ma.array(['one', 'two', 'three'], mask=[0, 0, 1], dtype='|S8')
+        (mrec, nrec, _) = self.data
+        for (f, l) in zip(('a', 'b', 'c'), (_a, _b, _c)):
+            assert_equal(getattr(mrec, f)._mask, l._mask)
+        # One record only
+        _x = ma.array([1, 1.1, 'one'], mask=[1, 0, 0], dtype=object)
+        assert_equal_records(fromarrays(_x, dtype=mrec.dtype), mrec[0])
+
+    def test_fromrecords(self):
+        # Test construction from records.
+        (mrec, nrec, ddtype) = self.data
+        #......
+        palist = [(1, 'abc', 3.7000002861022949, 0),
+                  (2, 'xy', 6.6999998092651367, 1),
+                  (0, ' ', 0.40000000596046448, 0)]
+        pa = recfromrecords(palist, names='c1, c2, c3, c4')
+        mpa = fromrecords(palist, names='c1, c2, c3, c4')
+        assert_equal_records(pa, mpa)
+        #.....
+        _mrec = fromrecords(nrec)
+        assert_equal(_mrec.dtype, mrec.dtype)
+        for field in _mrec.dtype.names:
+            assert_equal(getattr(_mrec, field), getattr(mrec._data, field))
+
+        _mrec = fromrecords(nrec.tolist(), names='c1,c2,c3')
+        assert_equal(_mrec.dtype, [('c1', int), ('c2', float), ('c3', '|S5')])
+        for (f, n) in zip(('c1', 'c2', 'c3'), ('a', 'b', 'c')):
+            assert_equal(getattr(_mrec, f), getattr(mrec._data, n))
+
+        _mrec = fromrecords(mrec)
+        assert_equal(_mrec.dtype, mrec.dtype)
+        assert_equal_records(_mrec._data, mrec.filled())
+        assert_equal_records(_mrec._mask, mrec._mask)
+
+    def test_fromrecords_wmask(self):
+        # Tests construction from records w/ mask.
+        (mrec, nrec, ddtype) = self.data
+
+        _mrec = fromrecords(nrec.tolist(), dtype=ddtype, mask=[0, 1, 0,])
+        assert_equal_records(_mrec._data, mrec._data)
+        assert_equal(_mrec._mask.tolist(), [(0, 0, 0), (1, 1, 1), (0, 0, 0)])
+
+        _mrec = fromrecords(nrec.tolist(), dtype=ddtype, mask=True)
+        assert_equal_records(_mrec._data, mrec._data)
+        assert_equal(_mrec._mask.tolist(), [(1, 1, 1), (1, 1, 1), (1, 1, 1)])
+
+        _mrec = fromrecords(nrec.tolist(), dtype=ddtype, mask=mrec._mask)
+        assert_equal_records(_mrec._data, mrec._data)
+        assert_equal(_mrec._mask.tolist(), mrec._mask.tolist())
+
+        _mrec = fromrecords(nrec.tolist(), dtype=ddtype,
+                            mask=mrec._mask.tolist())
+        assert_equal_records(_mrec._data, mrec._data)
+        assert_equal(_mrec._mask.tolist(), mrec._mask.tolist())
+
+    def test_fromtextfile(self):
+        # Tests reading from a text file.
+        fcontent = (
+"""#
+'One (S)','Two (I)','Three (F)','Four (M)','Five (-)','Six (C)'
+'strings',1,1.0,'mixed column',,1
+'with embedded "double quotes"',2,2.0,1.0,,1
+'strings',3,3.0E5,3,,1
+'strings',4,-1e-10,,,1
+""")
+        with temppath() as path:
+            with open(path, 'w') as f:
+                f.write(fcontent)
+            mrectxt = fromtextfile(path, delimiter=',', varnames='ABCDEFG')
+        assert_(isinstance(mrectxt, MaskedRecords))
+        assert_equal(mrectxt.F, [1, 1, 1, 1])
+        assert_equal(mrectxt.E._mask, [1, 1, 1, 1])
+        assert_equal(mrectxt.C, [1, 2, 3.e+5, -1e-10])
+
+    def test_addfield(self):
+        # Tests addfield
+        (mrec, nrec, ddtype) = self.data
+        (d, m) = ([100, 200, 300], [1, 0, 0])
+        mrec = addfield(mrec, ma.array(d, mask=m))
+        assert_equal(mrec.f3, d)
+        assert_equal(mrec.f3._mask, m)
+
+
+def test_record_array_with_object_field():
+    # Trac #1839
+    y = ma.masked_array(
+        [(1, '2'), (3, '4')],
+        mask=[(0, 0), (0, 1)],
+        dtype=[('a', int), ('b', object)])
+    # getting an item used to fail
+    y[1]
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_old_ma.py b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_old_ma.py
new file mode 100644
index 00000000..7b892ad2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_old_ma.py
@@ -0,0 +1,874 @@
+from functools import reduce
+
+import pytest
+
+import numpy as np
+import numpy.core.umath as umath
+import numpy.core.fromnumeric as fromnumeric
+from numpy.testing import (
+    assert_, assert_raises, assert_equal,
+    )
+from numpy.ma import (
+    MaskType, MaskedArray, absolute, add, all, allclose, allequal, alltrue,
+    arange, arccos, arcsin, arctan, arctan2, array, average, choose,
+    concatenate, conjugate, cos, cosh, count, divide, equal, exp, filled,
+    getmask, greater, greater_equal, inner, isMaskedArray, less,
+    less_equal, log, log10, make_mask, masked, masked_array, masked_equal,
+    masked_greater, masked_greater_equal, masked_inside, masked_less,
+    masked_less_equal, masked_not_equal, masked_outside,
+    masked_print_option, masked_values, masked_where, maximum, minimum,
+    multiply, nomask, nonzero, not_equal, ones, outer, product, put, ravel,
+    repeat, resize, shape, sin, sinh, sometrue, sort, sqrt, subtract, sum,
+    take, tan, tanh, transpose, where, zeros,
+    )
+from numpy.compat import pickle
+
+pi = np.pi
+
+
+def eq(v, w, msg=''):
+    result = allclose(v, w)
+    if not result:
+        print(f'Not eq:{msg}\n{v}\n----{w}')
+    return result
+
+
+class TestMa:
+
+    def setup_method(self):
+        x = np.array([1., 1., 1., -2., pi/2.0, 4., 5., -10., 10., 1., 2., 3.])
+        y = np.array([5., 0., 3., 2., -1., -4., 0., -10., 10., 1., 0., 3.])
+        a10 = 10.
+        m1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        m2 = [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1]
+        xm = array(x, mask=m1)
+        ym = array(y, mask=m2)
+        z = np.array([-.5, 0., .5, .8])
+        zm = array(z, mask=[0, 1, 0, 0])
+        xf = np.where(m1, 1e+20, x)
+        s = x.shape
+        xm.set_fill_value(1e+20)
+        self.d = (x, y, a10, m1, m2, xm, ym, z, zm, xf, s)
+
+    def test_testBasic1d(self):
+        # Test of basic array creation and properties in 1 dimension.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf, s) = self.d
+        assert_(not isMaskedArray(x))
+        assert_(isMaskedArray(xm))
+        assert_equal(shape(xm), s)
+        assert_equal(xm.shape, s)
+        assert_equal(xm.dtype, x.dtype)
+        assert_equal(xm.size, reduce(lambda x, y:x * y, s))
+        assert_equal(count(xm), len(m1) - reduce(lambda x, y:x + y, m1))
+        assert_(eq(xm, xf))
+        assert_(eq(filled(xm, 1.e20), xf))
+        assert_(eq(x, xm))
+
+    @pytest.mark.parametrize("s", [(4, 3), (6, 2)])
+    def test_testBasic2d(self, s):
+        # Test of basic array creation and properties in 2 dimensions.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf, s) = self.d
+        x.shape = s
+        y.shape = s
+        xm.shape = s
+        ym.shape = s
+        xf.shape = s
+
+        assert_(not isMaskedArray(x))
+        assert_(isMaskedArray(xm))
+        assert_equal(shape(xm), s)
+        assert_equal(xm.shape, s)
+        assert_equal(xm.size, reduce(lambda x, y: x * y, s))
+        assert_equal(count(xm), len(m1) - reduce(lambda x, y: x + y, m1))
+        assert_(eq(xm, xf))
+        assert_(eq(filled(xm, 1.e20), xf))
+        assert_(eq(x, xm))
+
+    def test_testArithmetic(self):
+        # Test of basic arithmetic.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf, s) = self.d
+        a2d = array([[1, 2], [0, 4]])
+        a2dm = masked_array(a2d, [[0, 0], [1, 0]])
+        assert_(eq(a2d * a2d, a2d * a2dm))
+        assert_(eq(a2d + a2d, a2d + a2dm))
+        assert_(eq(a2d - a2d, a2d - a2dm))
+        for s in [(12,), (4, 3), (2, 6)]:
+            x = x.reshape(s)
+            y = y.reshape(s)
+            xm = xm.reshape(s)
+            ym = ym.reshape(s)
+            xf = xf.reshape(s)
+            assert_(eq(-x, -xm))
+            assert_(eq(x + y, xm + ym))
+            assert_(eq(x - y, xm - ym))
+            assert_(eq(x * y, xm * ym))
+            with np.errstate(divide='ignore', invalid='ignore'):
+                assert_(eq(x / y, xm / ym))
+            assert_(eq(a10 + y, a10 + ym))
+            assert_(eq(a10 - y, a10 - ym))
+            assert_(eq(a10 * y, a10 * ym))
+            with np.errstate(divide='ignore', invalid='ignore'):
+                assert_(eq(a10 / y, a10 / ym))
+            assert_(eq(x + a10, xm + a10))
+            assert_(eq(x - a10, xm - a10))
+            assert_(eq(x * a10, xm * a10))
+            assert_(eq(x / a10, xm / a10))
+            assert_(eq(x ** 2, xm ** 2))
+            assert_(eq(abs(x) ** 2.5, abs(xm) ** 2.5))
+            assert_(eq(x ** y, xm ** ym))
+            assert_(eq(np.add(x, y), add(xm, ym)))
+            assert_(eq(np.subtract(x, y), subtract(xm, ym)))
+            assert_(eq(np.multiply(x, y), multiply(xm, ym)))
+            with np.errstate(divide='ignore', invalid='ignore'):
+                assert_(eq(np.divide(x, y), divide(xm, ym)))
+
+    def test_testMixedArithmetic(self):
+        na = np.array([1])
+        ma = array([1])
+        assert_(isinstance(na + ma, MaskedArray))
+        assert_(isinstance(ma + na, MaskedArray))
+
+    def test_testUfuncs1(self):
+        # Test various functions such as sin, cos.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf, s) = self.d
+        assert_(eq(np.cos(x), cos(xm)))
+        assert_(eq(np.cosh(x), cosh(xm)))
+        assert_(eq(np.sin(x), sin(xm)))
+        assert_(eq(np.sinh(x), sinh(xm)))
+        assert_(eq(np.tan(x), tan(xm)))
+        assert_(eq(np.tanh(x), tanh(xm)))
+        with np.errstate(divide='ignore', invalid='ignore'):
+            assert_(eq(np.sqrt(abs(x)), sqrt(xm)))
+            assert_(eq(np.log(abs(x)), log(xm)))
+            assert_(eq(np.log10(abs(x)), log10(xm)))
+        assert_(eq(np.exp(x), exp(xm)))
+        assert_(eq(np.arcsin(z), arcsin(zm)))
+        assert_(eq(np.arccos(z), arccos(zm)))
+        assert_(eq(np.arctan(z), arctan(zm)))
+        assert_(eq(np.arctan2(x, y), arctan2(xm, ym)))
+        assert_(eq(np.absolute(x), absolute(xm)))
+        assert_(eq(np.equal(x, y), equal(xm, ym)))
+        assert_(eq(np.not_equal(x, y), not_equal(xm, ym)))
+        assert_(eq(np.less(x, y), less(xm, ym)))
+        assert_(eq(np.greater(x, y), greater(xm, ym)))
+        assert_(eq(np.less_equal(x, y), less_equal(xm, ym)))
+        assert_(eq(np.greater_equal(x, y), greater_equal(xm, ym)))
+        assert_(eq(np.conjugate(x), conjugate(xm)))
+        assert_(eq(np.concatenate((x, y)), concatenate((xm, ym))))
+        assert_(eq(np.concatenate((x, y)), concatenate((x, y))))
+        assert_(eq(np.concatenate((x, y)), concatenate((xm, y))))
+        assert_(eq(np.concatenate((x, y, x)), concatenate((x, ym, x))))
+
+    def test_xtestCount(self):
+        # Test count
+        ott = array([0., 1., 2., 3.], mask=[1, 0, 0, 0])
+        assert_(count(ott).dtype.type is np.intp)
+        assert_equal(3, count(ott))
+        assert_equal(1, count(1))
+        assert_(eq(0, array(1, mask=[1])))
+        ott = ott.reshape((2, 2))
+        assert_(count(ott).dtype.type is np.intp)
+        assert_(isinstance(count(ott, 0), np.ndarray))
+        assert_(count(ott).dtype.type is np.intp)
+        assert_(eq(3, count(ott)))
+        assert_(getmask(count(ott, 0)) is nomask)
+        assert_(eq([1, 2], count(ott, 0)))
+
+    def test_testMinMax(self):
+        # Test minimum and maximum.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf, s) = self.d
+        xr = np.ravel(x)  # max doesn't work if shaped
+        xmr = ravel(xm)
+
+        # true because of careful selection of data
+        assert_(eq(max(xr), maximum.reduce(xmr)))
+        assert_(eq(min(xr), minimum.reduce(xmr)))
+
+    def test_testAddSumProd(self):
+        # Test add, sum, product.
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf, s) = self.d
+        assert_(eq(np.add.reduce(x), add.reduce(x)))
+        assert_(eq(np.add.accumulate(x), add.accumulate(x)))
+        assert_(eq(4, sum(array(4), axis=0)))
+        assert_(eq(4, sum(array(4), axis=0)))
+        assert_(eq(np.sum(x, axis=0), sum(x, axis=0)))
+        assert_(eq(np.sum(filled(xm, 0), axis=0), sum(xm, axis=0)))
+        assert_(eq(np.sum(x, 0), sum(x, 0)))
+        assert_(eq(np.prod(x, axis=0), product(x, axis=0)))
+        assert_(eq(np.prod(x, 0), product(x, 0)))
+        assert_(eq(np.prod(filled(xm, 1), axis=0),
+                           product(xm, axis=0)))
+        if len(s) > 1:
+            assert_(eq(np.concatenate((x, y), 1),
+                               concatenate((xm, ym), 1)))
+            assert_(eq(np.add.reduce(x, 1), add.reduce(x, 1)))
+            assert_(eq(np.sum(x, 1), sum(x, 1)))
+            assert_(eq(np.prod(x, 1), product(x, 1)))
+
+    def test_testCI(self):
+        # Test of conversions and indexing
+        x1 = np.array([1, 2, 4, 3])
+        x2 = array(x1, mask=[1, 0, 0, 0])
+        x3 = array(x1, mask=[0, 1, 0, 1])
+        x4 = array(x1)
+        # test conversion to strings
+        str(x2)  # raises?
+        repr(x2)  # raises?
+        assert_(eq(np.sort(x1), sort(x2, fill_value=0)))
+        # tests of indexing
+        assert_(type(x2[1]) is type(x1[1]))
+        assert_(x1[1] == x2[1])
+        assert_(x2[0] is masked)
+        assert_(eq(x1[2], x2[2]))
+        assert_(eq(x1[2:5], x2[2:5]))
+        assert_(eq(x1[:], x2[:]))
+        assert_(eq(x1[1:], x3[1:]))
+        x1[2] = 9
+        x2[2] = 9
+        assert_(eq(x1, x2))
+        x1[1:3] = 99
+        x2[1:3] = 99
+        assert_(eq(x1, x2))
+        x2[1] = masked
+        assert_(eq(x1, x2))
+        x2[1:3] = masked
+        assert_(eq(x1, x2))
+        x2[:] = x1
+        x2[1] = masked
+        assert_(allequal(getmask(x2), array([0, 1, 0, 0])))
+        x3[:] = masked_array([1, 2, 3, 4], [0, 1, 1, 0])
+        assert_(allequal(getmask(x3), array([0, 1, 1, 0])))
+        x4[:] = masked_array([1, 2, 3, 4], [0, 1, 1, 0])
+        assert_(allequal(getmask(x4), array([0, 1, 1, 0])))
+        assert_(allequal(x4, array([1, 2, 3, 4])))
+        x1 = np.arange(5) * 1.0
+        x2 = masked_values(x1, 3.0)
+        assert_(eq(x1, x2))
+        assert_(allequal(array([0, 0, 0, 1, 0], MaskType), x2.mask))
+        assert_(eq(3.0, x2.fill_value))
+        x1 = array([1, 'hello', 2, 3], object)
+        x2 = np.array([1, 'hello', 2, 3], object)
+        s1 = x1[1]
+        s2 = x2[1]
+        assert_equal(type(s2), str)
+        assert_equal(type(s1), str)
+        assert_equal(s1, s2)
+        assert_(x1[1:1].shape == (0,))
+
+    def test_testCopySize(self):
+        # Tests of some subtle points of copying and sizing.
+        n = [0, 0, 1, 0, 0]
+        m = make_mask(n)
+        m2 = make_mask(m)
+        assert_(m is m2)
+        m3 = make_mask(m, copy=True)
+        assert_(m is not m3)
+
+        x1 = np.arange(5)
+        y1 = array(x1, mask=m)
+        assert_(y1._data is not x1)
+        assert_(allequal(x1, y1._data))
+        assert_(y1._mask is m)
+
+        y1a = array(y1, copy=0)
+        # For copy=False, one might expect that the array would just
+        # passed on, i.e., that it would be "is" instead of "==".
+        # See gh-4043 for discussion.
+        assert_(y1a._mask.__array_interface__ ==
+                y1._mask.__array_interface__)
+
+        y2 = array(x1, mask=m3, copy=0)
+        assert_(y2._mask is m3)
+        assert_(y2[2] is masked)
+        y2[2] = 9
+        assert_(y2[2] is not masked)
+        assert_(y2._mask is m3)
+        assert_(allequal(y2.mask, 0))
+
+        y2a = array(x1, mask=m, copy=1)
+        assert_(y2a._mask is not m)
+        assert_(y2a[2] is masked)
+        y2a[2] = 9
+        assert_(y2a[2] is not masked)
+        assert_(y2a._mask is not m)
+        assert_(allequal(y2a.mask, 0))
+
+        y3 = array(x1 * 1.0, mask=m)
+        assert_(filled(y3).dtype is (x1 * 1.0).dtype)
+
+        x4 = arange(4)
+        x4[2] = masked
+        y4 = resize(x4, (8,))
+        assert_(eq(concatenate([x4, x4]), y4))
+        assert_(eq(getmask(y4), [0, 0, 1, 0, 0, 0, 1, 0]))
+        y5 = repeat(x4, (2, 2, 2, 2), axis=0)
+        assert_(eq(y5, [0, 0, 1, 1, 2, 2, 3, 3]))
+        y6 = repeat(x4, 2, axis=0)
+        assert_(eq(y5, y6))
+
+    def test_testPut(self):
+        # Test of put
+        d = arange(5)
+        n = [0, 0, 0, 1, 1]
+        m = make_mask(n)
+        m2 = m.copy()
+        x = array(d, mask=m)
+        assert_(x[3] is masked)
+        assert_(x[4] is masked)
+        x[[1, 4]] = [10, 40]
+        assert_(x._mask is m)
+        assert_(x[3] is masked)
+        assert_(x[4] is not masked)
+        assert_(eq(x, [0, 10, 2, -1, 40]))
+
+        x = array(d, mask=m2, copy=True)
+        x.put([0, 1, 2], [-1, 100, 200])
+        assert_(x._mask is not m2)
+        assert_(x[3] is masked)
+        assert_(x[4] is masked)
+        assert_(eq(x, [-1, 100, 200, 0, 0]))
+
+    def test_testPut2(self):
+        # Test of put
+        d = arange(5)
+        x = array(d, mask=[0, 0, 0, 0, 0])
+        z = array([10, 40], mask=[1, 0])
+        assert_(x[2] is not masked)
+        assert_(x[3] is not masked)
+        x[2:4] = z
+        assert_(x[2] is masked)
+        assert_(x[3] is not masked)
+        assert_(eq(x, [0, 1, 10, 40, 4]))
+
+        d = arange(5)
+        x = array(d, mask=[0, 0, 0, 0, 0])
+        y = x[2:4]
+        z = array([10, 40], mask=[1, 0])
+        assert_(x[2] is not masked)
+        assert_(x[3] is not masked)
+        y[:] = z
+        assert_(y[0] is masked)
+        assert_(y[1] is not masked)
+        assert_(eq(y, [10, 40]))
+        assert_(x[2] is masked)
+        assert_(x[3] is not masked)
+        assert_(eq(x, [0, 1, 10, 40, 4]))
+
+    def test_testMaPut(self):
+        (x, y, a10, m1, m2, xm, ym, z, zm, xf, s) = self.d
+        m = [1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1]
+        i = np.nonzero(m)[0]
+        put(ym, i, zm)
+        assert_(all(take(ym, i, axis=0) == zm))
+
+    def test_testOddFeatures(self):
+        # Test of other odd features
+        x = arange(20)
+        x = x.reshape(4, 5)
+        x.flat[5] = 12
+        assert_(x[1, 0] == 12)
+        z = x + 10j * x
+        assert_(eq(z.real, x))
+        assert_(eq(z.imag, 10 * x))
+        assert_(eq((z * conjugate(z)).real, 101 * x * x))
+        z.imag[...] = 0.0
+
+        x = arange(10)
+        x[3] = masked
+        assert_(str(x[3]) == str(masked))
+        c = x >= 8
+        assert_(count(where(c, masked, masked)) == 0)
+        assert_(shape(where(c, masked, masked)) == c.shape)
+        z = where(c, x, masked)
+        assert_(z.dtype is x.dtype)
+        assert_(z[3] is masked)
+        assert_(z[4] is masked)
+        assert_(z[7] is masked)
+        assert_(z[8] is not masked)
+        assert_(z[9] is not masked)
+        assert_(eq(x, z))
+        z = where(c, masked, x)
+        assert_(z.dtype is x.dtype)
+        assert_(z[3] is masked)
+        assert_(z[4] is not masked)
+        assert_(z[7] is not masked)
+        assert_(z[8] is masked)
+        assert_(z[9] is masked)
+        z = masked_where(c, x)
+        assert_(z.dtype is x.dtype)
+        assert_(z[3] is masked)
+        assert_(z[4] is not masked)
+        assert_(z[7] is not masked)
+        assert_(z[8] is masked)
+        assert_(z[9] is masked)
+        assert_(eq(x, z))
+        x = array([1., 2., 3., 4., 5.])
+        c = array([1, 1, 1, 0, 0])
+        x[2] = masked
+        z = where(c, x, -x)
+        assert_(eq(z, [1., 2., 0., -4., -5]))
+        c[0] = masked
+        z = where(c, x, -x)
+        assert_(eq(z, [1., 2., 0., -4., -5]))
+        assert_(z[0] is masked)
+        assert_(z[1] is not masked)
+        assert_(z[2] is masked)
+        assert_(eq(masked_where(greater(x, 2), x), masked_greater(x, 2)))
+        assert_(eq(masked_where(greater_equal(x, 2), x),
+                   masked_greater_equal(x, 2)))
+        assert_(eq(masked_where(less(x, 2), x), masked_less(x, 2)))
+        assert_(eq(masked_where(less_equal(x, 2), x), masked_less_equal(x, 2)))
+        assert_(eq(masked_where(not_equal(x, 2), x), masked_not_equal(x, 2)))
+        assert_(eq(masked_where(equal(x, 2), x), masked_equal(x, 2)))
+        assert_(eq(masked_where(not_equal(x, 2), x), masked_not_equal(x, 2)))
+        assert_(eq(masked_inside(list(range(5)), 1, 3), [0, 199, 199, 199, 4]))
+        assert_(eq(masked_outside(list(range(5)), 1, 3), [199, 1, 2, 3, 199]))
+        assert_(eq(masked_inside(array(list(range(5)),
+                                       mask=[1, 0, 0, 0, 0]), 1, 3).mask,
+                   [1, 1, 1, 1, 0]))
+        assert_(eq(masked_outside(array(list(range(5)),
+                                        mask=[0, 1, 0, 0, 0]), 1, 3).mask,
+                   [1, 1, 0, 0, 1]))
+        assert_(eq(masked_equal(array(list(range(5)),
+                                      mask=[1, 0, 0, 0, 0]), 2).mask,
+                   [1, 0, 1, 0, 0]))
+        assert_(eq(masked_not_equal(array([2, 2, 1, 2, 1],
+                                          mask=[1, 0, 0, 0, 0]), 2).mask,
+                   [1, 0, 1, 0, 1]))
+        assert_(eq(masked_where([1, 1, 0, 0, 0], [1, 2, 3, 4, 5]),
+                   [99, 99, 3, 4, 5]))
+        atest = ones((10, 10, 10), dtype=np.float32)
+        btest = zeros(atest.shape, MaskType)
+        ctest = masked_where(btest, atest)
+        assert_(eq(atest, ctest))
+        z = choose(c, (-x, x))
+        assert_(eq(z, [1., 2., 0., -4., -5]))
+        assert_(z[0] is masked)
+        assert_(z[1] is not masked)
+        assert_(z[2] is masked)
+        x = arange(6)
+        x[5] = masked
+        y = arange(6) * 10
+        y[2] = masked
+        c = array([1, 1, 1, 0, 0, 0], mask=[1, 0, 0, 0, 0, 0])
+        cm = c.filled(1)
+        z = where(c, x, y)
+        zm = where(cm, x, y)
+        assert_(eq(z, zm))
+        assert_(getmask(zm) is nomask)
+        assert_(eq(zm, [0, 1, 2, 30, 40, 50]))
+        z = where(c, masked, 1)
+        assert_(eq(z, [99, 99, 99, 1, 1, 1]))
+        z = where(c, 1, masked)
+        assert_(eq(z, [99, 1, 1, 99, 99, 99]))
+
+    def test_testMinMax2(self):
+        # Test of minimum, maximum.
+        assert_(eq(minimum([1, 2, 3], [4, 0, 9]), [1, 0, 3]))
+        assert_(eq(maximum([1, 2, 3], [4, 0, 9]), [4, 2, 9]))
+        x = arange(5)
+        y = arange(5) - 2
+        x[3] = masked
+        y[0] = masked
+        assert_(eq(minimum(x, y), where(less(x, y), x, y)))
+        assert_(eq(maximum(x, y), where(greater(x, y), x, y)))
+        assert_(minimum.reduce(x) == 0)
+        assert_(maximum.reduce(x) == 4)
+
+    def test_testTakeTransposeInnerOuter(self):
+        # Test of take, transpose, inner, outer products
+        x = arange(24)
+        y = np.arange(24)
+        x[5:6] = masked
+        x = x.reshape(2, 3, 4)
+        y = y.reshape(2, 3, 4)
+        assert_(eq(np.transpose(y, (2, 0, 1)), transpose(x, (2, 0, 1))))
+        assert_(eq(np.take(y, (2, 0, 1), 1), take(x, (2, 0, 1), 1)))
+        assert_(eq(np.inner(filled(x, 0), filled(y, 0)),
+                   inner(x, y)))
+        assert_(eq(np.outer(filled(x, 0), filled(y, 0)),
+                   outer(x, y)))
+        y = array(['abc', 1, 'def', 2, 3], object)
+        y[2] = masked
+        t = take(y, [0, 3, 4])
+        assert_(t[0] == 'abc')
+        assert_(t[1] == 2)
+        assert_(t[2] == 3)
+
+    def test_testInplace(self):
+        # Test of inplace operations and rich comparisons
+        y = arange(10)
+
+        x = arange(10)
+        xm = arange(10)
+        xm[2] = masked
+        x += 1
+        assert_(eq(x, y + 1))
+        xm += 1
+        assert_(eq(x, y + 1))
+
+        x = arange(10)
+        xm = arange(10)
+        xm[2] = masked
+        x -= 1
+        assert_(eq(x, y - 1))
+        xm -= 1
+        assert_(eq(xm, y - 1))
+
+        x = arange(10) * 1.0
+        xm = arange(10) * 1.0
+        xm[2] = masked
+        x *= 2.0
+        assert_(eq(x, y * 2))
+        xm *= 2.0
+        assert_(eq(xm, y * 2))
+
+        x = arange(10) * 2
+        xm = arange(10)
+        xm[2] = masked
+        x //= 2
+        assert_(eq(x, y))
+        xm //= 2
+        assert_(eq(x, y))
+
+        x = arange(10) * 1.0
+        xm = arange(10) * 1.0
+        xm[2] = masked
+        x /= 2.0
+        assert_(eq(x, y / 2.0))
+        xm /= arange(10)
+        assert_(eq(xm, ones((10,))))
+
+        x = arange(10).astype(np.float32)
+        xm = arange(10)
+        xm[2] = masked
+        x += 1.
+        assert_(eq(x, y + 1.))
+
+    def test_testPickle(self):
+        # Test of pickling
+        x = arange(12)
+        x[4:10:2] = masked
+        x = x.reshape(4, 3)
+        for proto in range(2, pickle.HIGHEST_PROTOCOL + 1):
+            s = pickle.dumps(x, protocol=proto)
+            y = pickle.loads(s)
+            assert_(eq(x, y))
+
+    def test_testMasked(self):
+        # Test of masked element
+        xx = arange(6)
+        xx[1] = masked
+        assert_(str(masked) == '--')
+        assert_(xx[1] is masked)
+        assert_equal(filled(xx[1], 0), 0)
+
+    def test_testAverage1(self):
+        # Test of average.
+        ott = array([0., 1., 2., 3.], mask=[1, 0, 0, 0])
+        assert_(eq(2.0, average(ott, axis=0)))
+        assert_(eq(2.0, average(ott, weights=[1., 1., 2., 1.])))
+        result, wts = average(ott, weights=[1., 1., 2., 1.], returned=True)
+        assert_(eq(2.0, result))
+        assert_(wts == 4.0)
+        ott[:] = masked
+        assert_(average(ott, axis=0) is masked)
+        ott = array([0., 1., 2., 3.], mask=[1, 0, 0, 0])
+        ott = ott.reshape(2, 2)
+        ott[:, 1] = masked
+        assert_(eq(average(ott, axis=0), [2.0, 0.0]))
+        assert_(average(ott, axis=1)[0] is masked)
+        assert_(eq([2., 0.], average(ott, axis=0)))
+        result, wts = average(ott, axis=0, returned=True)
+        assert_(eq(wts, [1., 0.]))
+
+    def test_testAverage2(self):
+        # More tests of average.
+        w1 = [0, 1, 1, 1, 1, 0]
+        w2 = [[0, 1, 1, 1, 1, 0], [1, 0, 0, 0, 0, 1]]
+        x = arange(6)
+        assert_(allclose(average(x, axis=0), 2.5))
+        assert_(allclose(average(x, axis=0, weights=w1), 2.5))
+        y = array([arange(6), 2.0 * arange(6)])
+        assert_(allclose(average(y, None),
+                                 np.add.reduce(np.arange(6)) * 3. / 12.))
+        assert_(allclose(average(y, axis=0), np.arange(6) * 3. / 2.))
+        assert_(allclose(average(y, axis=1),
+                                 [average(x, axis=0), average(x, axis=0)*2.0]))
+        assert_(allclose(average(y, None, weights=w2), 20. / 6.))
+        assert_(allclose(average(y, axis=0, weights=w2),
+                                 [0., 1., 2., 3., 4., 10.]))
+        assert_(allclose(average(y, axis=1),
+                                 [average(x, axis=0), average(x, axis=0)*2.0]))
+        m1 = zeros(6)
+        m2 = [0, 0, 1, 1, 0, 0]
+        m3 = [[0, 0, 1, 1, 0, 0], [0, 1, 1, 1, 1, 0]]
+        m4 = ones(6)
+        m5 = [0, 1, 1, 1, 1, 1]
+        assert_(allclose(average(masked_array(x, m1), axis=0), 2.5))
+        assert_(allclose(average(masked_array(x, m2), axis=0), 2.5))
+        assert_(average(masked_array(x, m4), axis=0) is masked)
+        assert_equal(average(masked_array(x, m5), axis=0), 0.0)
+        assert_equal(count(average(masked_array(x, m4), axis=0)), 0)
+        z = masked_array(y, m3)
+        assert_(allclose(average(z, None), 20. / 6.))
+        assert_(allclose(average(z, axis=0),
+                                 [0., 1., 99., 99., 4.0, 7.5]))
+        assert_(allclose(average(z, axis=1), [2.5, 5.0]))
+        assert_(allclose(average(z, axis=0, weights=w2),
+                                 [0., 1., 99., 99., 4.0, 10.0]))
+
+        a = arange(6)
+        b = arange(6) * 3
+        r1, w1 = average([[a, b], [b, a]], axis=1, returned=True)
+        assert_equal(shape(r1), shape(w1))
+        assert_equal(r1.shape, w1.shape)
+        r2, w2 = average(ones((2, 2, 3)), axis=0, weights=[3, 1], returned=True)
+        assert_equal(shape(w2), shape(r2))
+        r2, w2 = average(ones((2, 2, 3)), returned=True)
+        assert_equal(shape(w2), shape(r2))
+        r2, w2 = average(ones((2, 2, 3)), weights=ones((2, 2, 3)), returned=True)
+        assert_(shape(w2) == shape(r2))
+        a2d = array([[1, 2], [0, 4]], float)
+        a2dm = masked_array(a2d, [[0, 0], [1, 0]])
+        a2da = average(a2d, axis=0)
+        assert_(eq(a2da, [0.5, 3.0]))
+        a2dma = average(a2dm, axis=0)
+        assert_(eq(a2dma, [1.0, 3.0]))
+        a2dma = average(a2dm, axis=None)
+        assert_(eq(a2dma, 7. / 3.))
+        a2dma = average(a2dm, axis=1)
+        assert_(eq(a2dma, [1.5, 4.0]))
+
+    def test_testToPython(self):
+        assert_equal(1, int(array(1)))
+        assert_equal(1.0, float(array(1)))
+        assert_equal(1, int(array([[[1]]])))
+        assert_equal(1.0, float(array([[1]])))
+        assert_raises(TypeError, float, array([1, 1]))
+        assert_raises(ValueError, bool, array([0, 1]))
+        assert_raises(ValueError, bool, array([0, 0], mask=[0, 1]))
+
+    def test_testScalarArithmetic(self):
+        xm = array(0, mask=1)
+        #TODO FIXME: Find out what the following raises a warning in r8247
+        with np.errstate(divide='ignore'):
+            assert_((1 / array(0)).mask)
+        assert_((1 + xm).mask)
+        assert_((-xm).mask)
+        assert_((-xm).mask)
+        assert_(maximum(xm, xm).mask)
+        assert_(minimum(xm, xm).mask)
+        assert_(xm.filled().dtype is xm._data.dtype)
+        x = array(0, mask=0)
+        assert_(x.filled() == x._data)
+        assert_equal(str(xm), str(masked_print_option))
+
+    def test_testArrayMethods(self):
+        a = array([1, 3, 2])
+        assert_(eq(a.any(), a._data.any()))
+        assert_(eq(a.all(), a._data.all()))
+        assert_(eq(a.argmax(), a._data.argmax()))
+        assert_(eq(a.argmin(), a._data.argmin()))
+        assert_(eq(a.choose(0, 1, 2, 3, 4),
+                           a._data.choose(0, 1, 2, 3, 4)))
+        assert_(eq(a.compress([1, 0, 1]), a._data.compress([1, 0, 1])))
+        assert_(eq(a.conj(), a._data.conj()))
+        assert_(eq(a.conjugate(), a._data.conjugate()))
+        m = array([[1, 2], [3, 4]])
+        assert_(eq(m.diagonal(), m._data.diagonal()))
+        assert_(eq(a.sum(), a._data.sum()))
+        assert_(eq(a.take([1, 2]), a._data.take([1, 2])))
+        assert_(eq(m.transpose(), m._data.transpose()))
+
+    def test_testArrayAttributes(self):
+        a = array([1, 3, 2])
+        assert_equal(a.ndim, 1)
+
+    def test_testAPI(self):
+        assert_(not [m for m in dir(np.ndarray)
+                     if m not in dir(MaskedArray) and
+                     not m.startswith('_')])
+
+    def test_testSingleElementSubscript(self):
+        a = array([1, 3, 2])
+        b = array([1, 3, 2], mask=[1, 0, 1])
+        assert_equal(a[0].shape, ())
+        assert_equal(b[0].shape, ())
+        assert_equal(b[1].shape, ())
+
+    def test_assignment_by_condition(self):
+        # Test for gh-18951
+        a = array([1, 2, 3, 4], mask=[1, 0, 1, 0])
+        c = a >= 3
+        a[c] = 5
+        assert_(a[2] is masked)
+
+    def test_assignment_by_condition_2(self):
+        # gh-19721
+        a = masked_array([0, 1], mask=[False, False])
+        b = masked_array([0, 1], mask=[True, True])
+        mask = a < 1
+        b[mask] = a[mask]
+        expected_mask = [False, True]
+        assert_equal(b.mask, expected_mask)
+
+
+class TestUfuncs:
+    def setup_method(self):
+        self.d = (array([1.0, 0, -1, pi / 2] * 2, mask=[0, 1] + [0] * 6),
+                  array([1.0, 0, -1, pi / 2] * 2, mask=[1, 0] + [0] * 6),)
+
+    def test_testUfuncRegression(self):
+        f_invalid_ignore = [
+            'sqrt', 'arctanh', 'arcsin', 'arccos',
+            'arccosh', 'arctanh', 'log', 'log10', 'divide',
+            'true_divide', 'floor_divide', 'remainder', 'fmod']
+        for f in ['sqrt', 'log', 'log10', 'exp', 'conjugate',
+                  'sin', 'cos', 'tan',
+                  'arcsin', 'arccos', 'arctan',
+                  'sinh', 'cosh', 'tanh',
+                  'arcsinh',
+                  'arccosh',
+                  'arctanh',
+                  'absolute', 'fabs', 'negative',
+                  'floor', 'ceil',
+                  'logical_not',
+                  'add', 'subtract', 'multiply',
+                  'divide', 'true_divide', 'floor_divide',
+                  'remainder', 'fmod', 'hypot', 'arctan2',
+                  'equal', 'not_equal', 'less_equal', 'greater_equal',
+                  'less', 'greater',
+                  'logical_and', 'logical_or', 'logical_xor']:
+            try:
+                uf = getattr(umath, f)
+            except AttributeError:
+                uf = getattr(fromnumeric, f)
+            mf = getattr(np.ma, f)
+            args = self.d[:uf.nin]
+            with np.errstate():
+                if f in f_invalid_ignore:
+                    np.seterr(invalid='ignore')
+                if f in ['arctanh', 'log', 'log10']:
+                    np.seterr(divide='ignore')
+                ur = uf(*args)
+                mr = mf(*args)
+            assert_(eq(ur.filled(0), mr.filled(0), f))
+            assert_(eqmask(ur.mask, mr.mask))
+
+    def test_reduce(self):
+        a = self.d[0]
+        assert_(not alltrue(a, axis=0))
+        assert_(sometrue(a, axis=0))
+        assert_equal(sum(a[:3], axis=0), 0)
+        assert_equal(product(a, axis=0), 0)
+
+    def test_minmax(self):
+        a = arange(1, 13).reshape(3, 4)
+        amask = masked_where(a < 5, a)
+        assert_equal(amask.max(), a.max())
+        assert_equal(amask.min(), 5)
+        assert_((amask.max(0) == a.max(0)).all())
+        assert_((amask.min(0) == [5, 6, 7, 8]).all())
+        assert_(amask.max(1)[0].mask)
+        assert_(amask.min(1)[0].mask)
+
+    def test_nonzero(self):
+        for t in "?bhilqpBHILQPfdgFDGO":
+            x = array([1, 0, 2, 0], mask=[0, 0, 1, 1])
+            assert_(eq(nonzero(x), [0]))
+
+
+class TestArrayMethods:
+
+    def setup_method(self):
+        x = np.array([8.375, 7.545, 8.828, 8.5, 1.757, 5.928,
+                      8.43, 7.78, 9.865, 5.878, 8.979, 4.732,
+                      3.012, 6.022, 5.095, 3.116, 5.238, 3.957,
+                      6.04, 9.63, 7.712, 3.382, 4.489, 6.479,
+                      7.189, 9.645, 5.395, 4.961, 9.894, 2.893,
+                      7.357, 9.828, 6.272, 3.758, 6.693, 0.993])
+        X = x.reshape(6, 6)
+        XX = x.reshape(3, 2, 2, 3)
+
+        m = np.array([0, 1, 0, 1, 0, 0,
+                      1, 0, 1, 1, 0, 1,
+                      0, 0, 0, 1, 0, 1,
+                      0, 0, 0, 1, 1, 1,
+                      1, 0, 0, 1, 0, 0,
+                      0, 0, 1, 0, 1, 0])
+        mx = array(data=x, mask=m)
+        mX = array(data=X, mask=m.reshape(X.shape))
+        mXX = array(data=XX, mask=m.reshape(XX.shape))
+
+        self.d = (x, X, XX, m, mx, mX, mXX)
+
+    def test_trace(self):
+        (x, X, XX, m, mx, mX, mXX,) = self.d
+        mXdiag = mX.diagonal()
+        assert_equal(mX.trace(), mX.diagonal().compressed().sum())
+        assert_(eq(mX.trace(),
+                           X.trace() - sum(mXdiag.mask * X.diagonal(),
+                                           axis=0)))
+
+    def test_clip(self):
+        (x, X, XX, m, mx, mX, mXX,) = self.d
+        clipped = mx.clip(2, 8)
+        assert_(eq(clipped.mask, mx.mask))
+        assert_(eq(clipped._data, x.clip(2, 8)))
+        assert_(eq(clipped._data, mx._data.clip(2, 8)))
+
+    def test_ptp(self):
+        (x, X, XX, m, mx, mX, mXX,) = self.d
+        (n, m) = X.shape
+        assert_equal(mx.ptp(), mx.compressed().ptp())
+        rows = np.zeros(n, np.float_)
+        cols = np.zeros(m, np.float_)
+        for k in range(m):
+            cols[k] = mX[:, k].compressed().ptp()
+        for k in range(n):
+            rows[k] = mX[k].compressed().ptp()
+        assert_(eq(mX.ptp(0), cols))
+        assert_(eq(mX.ptp(1), rows))
+
+    def test_swapaxes(self):
+        (x, X, XX, m, mx, mX, mXX,) = self.d
+        mXswapped = mX.swapaxes(0, 1)
+        assert_(eq(mXswapped[-1], mX[:, -1]))
+        mXXswapped = mXX.swapaxes(0, 2)
+        assert_equal(mXXswapped.shape, (2, 2, 3, 3))
+
+    def test_cumprod(self):
+        (x, X, XX, m, mx, mX, mXX,) = self.d
+        mXcp = mX.cumprod(0)
+        assert_(eq(mXcp._data, mX.filled(1).cumprod(0)))
+        mXcp = mX.cumprod(1)
+        assert_(eq(mXcp._data, mX.filled(1).cumprod(1)))
+
+    def test_cumsum(self):
+        (x, X, XX, m, mx, mX, mXX,) = self.d
+        mXcp = mX.cumsum(0)
+        assert_(eq(mXcp._data, mX.filled(0).cumsum(0)))
+        mXcp = mX.cumsum(1)
+        assert_(eq(mXcp._data, mX.filled(0).cumsum(1)))
+
+    def test_varstd(self):
+        (x, X, XX, m, mx, mX, mXX,) = self.d
+        assert_(eq(mX.var(axis=None), mX.compressed().var()))
+        assert_(eq(mX.std(axis=None), mX.compressed().std()))
+        assert_(eq(mXX.var(axis=3).shape, XX.var(axis=3).shape))
+        assert_(eq(mX.var().shape, X.var().shape))
+        (mXvar0, mXvar1) = (mX.var(axis=0), mX.var(axis=1))
+        for k in range(6):
+            assert_(eq(mXvar1[k], mX[k].compressed().var()))
+            assert_(eq(mXvar0[k], mX[:, k].compressed().var()))
+            assert_(eq(np.sqrt(mXvar0[k]),
+                               mX[:, k].compressed().std()))
+
+
+def eqmask(m1, m2):
+    if m1 is nomask:
+        return m2 is nomask
+    if m2 is nomask:
+        return m1 is nomask
+    return (m1 == m2).all()
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_regression.py b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_regression.py
new file mode 100644
index 00000000..f4f32cc7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_regression.py
@@ -0,0 +1,97 @@
+import numpy as np
+from numpy.testing import (
+    assert_, assert_array_equal, assert_allclose, suppress_warnings
+    )
+
+
+class TestRegression:
+    def test_masked_array_create(self):
+        # Ticket #17
+        x = np.ma.masked_array([0, 1, 2, 3, 0, 4, 5, 6],
+                               mask=[0, 0, 0, 1, 1, 1, 0, 0])
+        assert_array_equal(np.ma.nonzero(x), [[1, 2, 6, 7]])
+
+    def test_masked_array(self):
+        # Ticket #61
+        np.ma.array(1, mask=[1])
+
+    def test_mem_masked_where(self):
+        # Ticket #62
+        from numpy.ma import masked_where, MaskType
+        a = np.zeros((1, 1))
+        b = np.zeros(a.shape, MaskType)
+        c = masked_where(b, a)
+        a-c
+
+    def test_masked_array_multiply(self):
+        # Ticket #254
+        a = np.ma.zeros((4, 1))
+        a[2, 0] = np.ma.masked
+        b = np.zeros((4, 2))
+        a*b
+        b*a
+
+    def test_masked_array_repeat(self):
+        # Ticket #271
+        np.ma.array([1], mask=False).repeat(10)
+
+    def test_masked_array_repr_unicode(self):
+        # Ticket #1256
+        repr(np.ma.array("Unicode"))
+
+    def test_atleast_2d(self):
+        # Ticket #1559
+        a = np.ma.masked_array([0.0, 1.2, 3.5], mask=[False, True, False])
+        b = np.atleast_2d(a)
+        assert_(a.mask.ndim == 1)
+        assert_(b.mask.ndim == 2)
+
+    def test_set_fill_value_unicode_py3(self):
+        # Ticket #2733
+        a = np.ma.masked_array(['a', 'b', 'c'], mask=[1, 0, 0])
+        a.fill_value = 'X'
+        assert_(a.fill_value == 'X')
+
+    def test_var_sets_maskedarray_scalar(self):
+        # Issue gh-2757
+        a = np.ma.array(np.arange(5), mask=True)
+        mout = np.ma.array(-1, dtype=float)
+        a.var(out=mout)
+        assert_(mout._data == 0)
+
+    def test_ddof_corrcoef(self):
+        # See gh-3336
+        x = np.ma.masked_equal([1, 2, 3, 4, 5], 4)
+        y = np.array([2, 2.5, 3.1, 3, 5])
+        # this test can be removed after deprecation.
+        with suppress_warnings() as sup:
+            sup.filter(DeprecationWarning, "bias and ddof have no effect")
+            r0 = np.ma.corrcoef(x, y, ddof=0)
+            r1 = np.ma.corrcoef(x, y, ddof=1)
+            # ddof should not have an effect (it gets cancelled out)
+            assert_allclose(r0.data, r1.data)
+
+    def test_mask_not_backmangled(self):
+        # See gh-10314.  Test case taken from gh-3140.
+        a = np.ma.MaskedArray([1., 2.], mask=[False, False])
+        assert_(a.mask.shape == (2,))
+        b = np.tile(a, (2, 1))
+        # Check that the above no longer changes a.shape to (1, 2)
+        assert_(a.mask.shape == (2,))
+        assert_(b.shape == (2, 2))
+        assert_(b.mask.shape == (2, 2))
+
+    def test_empty_list_on_structured(self):
+        # See gh-12464. Indexing with empty list should give empty result.
+        ma = np.ma.MaskedArray([(1, 1.), (2, 2.), (3, 3.)], dtype='i4,f4')
+        assert_array_equal(ma[[]], ma[:0])
+
+    def test_masked_array_tobytes_fortran(self):
+        ma = np.ma.arange(4).reshape((2,2))
+        assert_array_equal(ma.tobytes(order='F'), ma.T.tobytes())
+
+    def test_structured_array(self):
+        # see gh-22041
+        np.ma.array((1, (b"", b"")),
+                    dtype=[("x", np.int_),
+                          ("y", [("i", np.void), ("j", np.void)])])
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_subclassing.py b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_subclassing.py
new file mode 100644
index 00000000..e3c88525
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/tests/test_subclassing.py
@@ -0,0 +1,460 @@
+# pylint: disable-msg=W0611, W0612, W0511,R0201
+"""Tests suite for MaskedArray & subclassing.
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: test_subclassing.py 3473 2007-10-29 15:18:13Z jarrod.millman $
+
+"""
+import numpy as np
+from numpy.lib.mixins import NDArrayOperatorsMixin
+from numpy.testing import assert_, assert_raises
+from numpy.ma.testutils import assert_equal
+from numpy.ma.core import (
+    array, arange, masked, MaskedArray, masked_array, log, add, hypot,
+    divide, asarray, asanyarray, nomask
+    )
+# from numpy.ma.core import (
+
+def assert_startswith(a, b):
+    # produces a better error message than assert_(a.startswith(b))
+    assert_equal(a[:len(b)], b)
+
+class SubArray(np.ndarray):
+    # Defines a generic np.ndarray subclass, that stores some metadata
+    # in the  dictionary `info`.
+    def __new__(cls,arr,info={}):
+        x = np.asanyarray(arr).view(cls)
+        x.info = info.copy()
+        return x
+
+    def __array_finalize__(self, obj):
+        super().__array_finalize__(obj)
+        self.info = getattr(obj, 'info', {}).copy()
+        return
+
+    def __add__(self, other):
+        result = super().__add__(other)
+        result.info['added'] = result.info.get('added', 0) + 1
+        return result
+
+    def __iadd__(self, other):
+        result = super().__iadd__(other)
+        result.info['iadded'] = result.info.get('iadded', 0) + 1
+        return result
+
+
+subarray = SubArray
+
+
+class SubMaskedArray(MaskedArray):
+    """Pure subclass of MaskedArray, keeping some info on subclass."""
+    def __new__(cls, info=None, **kwargs):
+        obj = super().__new__(cls, **kwargs)
+        obj._optinfo['info'] = info
+        return obj
+
+
+class MSubArray(SubArray, MaskedArray):
+
+    def __new__(cls, data, info={}, mask=nomask):
+        subarr = SubArray(data, info)
+        _data = MaskedArray.__new__(cls, data=subarr, mask=mask)
+        _data.info = subarr.info
+        return _data
+
+    @property
+    def _series(self):
+        _view = self.view(MaskedArray)
+        _view._sharedmask = False
+        return _view
+
+msubarray = MSubArray
+
+
+# Also a subclass that overrides __str__, __repr__ and __setitem__, disallowing
+# setting to non-class values (and thus np.ma.core.masked_print_option)
+# and overrides __array_wrap__, updating the info dict, to check that this
+# doesn't get destroyed by MaskedArray._update_from.  But this one also needs
+# its own iterator...
+class CSAIterator:
+    """
+    Flat iterator object that uses its own setter/getter
+    (works around ndarray.flat not propagating subclass setters/getters
+    see https://github.com/numpy/numpy/issues/4564)
+    roughly following MaskedIterator
+    """
+    def __init__(self, a):
+        self._original = a
+        self._dataiter = a.view(np.ndarray).flat
+
+    def __iter__(self):
+        return self
+
+    def __getitem__(self, indx):
+        out = self._dataiter.__getitem__(indx)
+        if not isinstance(out, np.ndarray):
+            out = out.__array__()
+        out = out.view(type(self._original))
+        return out
+
+    def __setitem__(self, index, value):
+        self._dataiter[index] = self._original._validate_input(value)
+
+    def __next__(self):
+        return next(self._dataiter).__array__().view(type(self._original))
+
+
+class ComplicatedSubArray(SubArray):
+
+    def __str__(self):
+        return f'myprefix {self.view(SubArray)} mypostfix'
+
+    def __repr__(self):
+        # Return a repr that does not start with 'name('
+        return f'<{self.__class__.__name__} {self}>'
+
+    def _validate_input(self, value):
+        if not isinstance(value, ComplicatedSubArray):
+            raise ValueError("Can only set to MySubArray values")
+        return value
+
+    def __setitem__(self, item, value):
+        # validation ensures direct assignment with ndarray or
+        # masked_print_option will fail
+        super().__setitem__(item, self._validate_input(value))
+
+    def __getitem__(self, item):
+        # ensure getter returns our own class also for scalars
+        value = super().__getitem__(item)
+        if not isinstance(value, np.ndarray):  # scalar
+            value = value.__array__().view(ComplicatedSubArray)
+        return value
+
+    @property
+    def flat(self):
+        return CSAIterator(self)
+
+    @flat.setter
+    def flat(self, value):
+        y = self.ravel()
+        y[:] = value
+
+    def __array_wrap__(self, obj, context=None):
+        obj = super().__array_wrap__(obj, context)
+        if context is not None and context[0] is np.multiply:
+            obj.info['multiplied'] = obj.info.get('multiplied', 0) + 1
+
+        return obj
+
+
+class WrappedArray(NDArrayOperatorsMixin):
+    """
+    Wrapping a MaskedArray rather than subclassing to test that
+    ufunc deferrals are commutative.
+    See: https://github.com/numpy/numpy/issues/15200)
+    """
+    __slots__ = ('_array', 'attrs')
+    __array_priority__ = 20
+
+    def __init__(self, array, **attrs):
+        self._array = array
+        self.attrs = attrs
+
+    def __repr__(self):
+        return f"{self.__class__.__name__}(\n{self._array}\n{self.attrs}\n)"
+
+    def __array__(self):
+        return np.asarray(self._array)
+
+    def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
+        if method == '__call__':
+            inputs = [arg._array if isinstance(arg, self.__class__) else arg
+                      for arg in inputs]
+            return self.__class__(ufunc(*inputs, **kwargs), **self.attrs)
+        else:
+            return NotImplemented
+
+
+class TestSubclassing:
+    # Test suite for masked subclasses of ndarray.
+
+    def setup_method(self):
+        x = np.arange(5, dtype='float')
+        mx = msubarray(x, mask=[0, 1, 0, 0, 0])
+        self.data = (x, mx)
+
+    def test_data_subclassing(self):
+        # Tests whether the subclass is kept.
+        x = np.arange(5)
+        m = [0, 0, 1, 0, 0]
+        xsub = SubArray(x)
+        xmsub = masked_array(xsub, mask=m)
+        assert_(isinstance(xmsub, MaskedArray))
+        assert_equal(xmsub._data, xsub)
+        assert_(isinstance(xmsub._data, SubArray))
+
+    def test_maskedarray_subclassing(self):
+        # Tests subclassing MaskedArray
+        (x, mx) = self.data
+        assert_(isinstance(mx._data, subarray))
+
+    def test_masked_unary_operations(self):
+        # Tests masked_unary_operation
+        (x, mx) = self.data
+        with np.errstate(divide='ignore'):
+            assert_(isinstance(log(mx), msubarray))
+            assert_equal(log(x), np.log(x))
+
+    def test_masked_binary_operations(self):
+        # Tests masked_binary_operation
+        (x, mx) = self.data
+        # Result should be a msubarray
+        assert_(isinstance(add(mx, mx), msubarray))
+        assert_(isinstance(add(mx, x), msubarray))
+        # Result should work
+        assert_equal(add(mx, x), mx+x)
+        assert_(isinstance(add(mx, mx)._data, subarray))
+        assert_(isinstance(add.outer(mx, mx), msubarray))
+        assert_(isinstance(hypot(mx, mx), msubarray))
+        assert_(isinstance(hypot(mx, x), msubarray))
+
+    def test_masked_binary_operations2(self):
+        # Tests domained_masked_binary_operation
+        (x, mx) = self.data
+        xmx = masked_array(mx.data.__array__(), mask=mx.mask)
+        assert_(isinstance(divide(mx, mx), msubarray))
+        assert_(isinstance(divide(mx, x), msubarray))
+        assert_equal(divide(mx, mx), divide(xmx, xmx))
+
+    def test_attributepropagation(self):
+        x = array(arange(5), mask=[0]+[1]*4)
+        my = masked_array(subarray(x))
+        ym = msubarray(x)
+        #
+        z = (my+1)
+        assert_(isinstance(z, MaskedArray))
+        assert_(not isinstance(z, MSubArray))
+        assert_(isinstance(z._data, SubArray))
+        assert_equal(z._data.info, {})
+        #
+        z = (ym+1)
+        assert_(isinstance(z, MaskedArray))
+        assert_(isinstance(z, MSubArray))
+        assert_(isinstance(z._data, SubArray))
+        assert_(z._data.info['added'] > 0)
+        # Test that inplace methods from data get used (gh-4617)
+        ym += 1
+        assert_(isinstance(ym, MaskedArray))
+        assert_(isinstance(ym, MSubArray))
+        assert_(isinstance(ym._data, SubArray))
+        assert_(ym._data.info['iadded'] > 0)
+        #
+        ym._set_mask([1, 0, 0, 0, 1])
+        assert_equal(ym._mask, [1, 0, 0, 0, 1])
+        ym._series._set_mask([0, 0, 0, 0, 1])
+        assert_equal(ym._mask, [0, 0, 0, 0, 1])
+        #
+        xsub = subarray(x, info={'name':'x'})
+        mxsub = masked_array(xsub)
+        assert_(hasattr(mxsub, 'info'))
+        assert_equal(mxsub.info, xsub.info)
+
+    def test_subclasspreservation(self):
+        # Checks that masked_array(...,subok=True) preserves the class.
+        x = np.arange(5)
+        m = [0, 0, 1, 0, 0]
+        xinfo = [(i, j) for (i, j) in zip(x, m)]
+        xsub = MSubArray(x, mask=m, info={'xsub':xinfo})
+        #
+        mxsub = masked_array(xsub, subok=False)
+        assert_(not isinstance(mxsub, MSubArray))
+        assert_(isinstance(mxsub, MaskedArray))
+        assert_equal(mxsub._mask, m)
+        #
+        mxsub = asarray(xsub)
+        assert_(not isinstance(mxsub, MSubArray))
+        assert_(isinstance(mxsub, MaskedArray))
+        assert_equal(mxsub._mask, m)
+        #
+        mxsub = masked_array(xsub, subok=True)
+        assert_(isinstance(mxsub, MSubArray))
+        assert_equal(mxsub.info, xsub.info)
+        assert_equal(mxsub._mask, xsub._mask)
+        #
+        mxsub = asanyarray(xsub)
+        assert_(isinstance(mxsub, MSubArray))
+        assert_equal(mxsub.info, xsub.info)
+        assert_equal(mxsub._mask, m)
+
+    def test_subclass_items(self):
+        """test that getter and setter go via baseclass"""
+        x = np.arange(5)
+        xcsub = ComplicatedSubArray(x)
+        mxcsub = masked_array(xcsub, mask=[True, False, True, False, False])
+        # getter should  return a ComplicatedSubArray, even for single item
+        # first check we wrote ComplicatedSubArray correctly
+        assert_(isinstance(xcsub[1], ComplicatedSubArray))
+        assert_(isinstance(xcsub[1,...], ComplicatedSubArray))
+        assert_(isinstance(xcsub[1:4], ComplicatedSubArray))
+
+        # now that it propagates inside the MaskedArray
+        assert_(isinstance(mxcsub[1], ComplicatedSubArray))
+        assert_(isinstance(mxcsub[1,...].data, ComplicatedSubArray))
+        assert_(mxcsub[0] is masked)
+        assert_(isinstance(mxcsub[0,...].data, ComplicatedSubArray))
+        assert_(isinstance(mxcsub[1:4].data, ComplicatedSubArray))
+
+        # also for flattened version (which goes via MaskedIterator)
+        assert_(isinstance(mxcsub.flat[1].data, ComplicatedSubArray))
+        assert_(mxcsub.flat[0] is masked)
+        assert_(isinstance(mxcsub.flat[1:4].base, ComplicatedSubArray))
+
+        # setter should only work with ComplicatedSubArray input
+        # first check we wrote ComplicatedSubArray correctly
+        assert_raises(ValueError, xcsub.__setitem__, 1, x[4])
+        # now that it propagates inside the MaskedArray
+        assert_raises(ValueError, mxcsub.__setitem__, 1, x[4])
+        assert_raises(ValueError, mxcsub.__setitem__, slice(1, 4), x[1:4])
+        mxcsub[1] = xcsub[4]
+        mxcsub[1:4] = xcsub[1:4]
+        # also for flattened version (which goes via MaskedIterator)
+        assert_raises(ValueError, mxcsub.flat.__setitem__, 1, x[4])
+        assert_raises(ValueError, mxcsub.flat.__setitem__, slice(1, 4), x[1:4])
+        mxcsub.flat[1] = xcsub[4]
+        mxcsub.flat[1:4] = xcsub[1:4]
+
+    def test_subclass_nomask_items(self):
+        x = np.arange(5)
+        xcsub = ComplicatedSubArray(x)
+        mxcsub_nomask = masked_array(xcsub)
+
+        assert_(isinstance(mxcsub_nomask[1,...].data, ComplicatedSubArray))
+        assert_(isinstance(mxcsub_nomask[0,...].data, ComplicatedSubArray))
+
+        assert_(isinstance(mxcsub_nomask[1], ComplicatedSubArray))
+        assert_(isinstance(mxcsub_nomask[0], ComplicatedSubArray))
+
+    def test_subclass_repr(self):
+        """test that repr uses the name of the subclass
+        and 'array' for np.ndarray"""
+        x = np.arange(5)
+        mx = masked_array(x, mask=[True, False, True, False, False])
+        assert_startswith(repr(mx), 'masked_array')
+        xsub = SubArray(x)
+        mxsub = masked_array(xsub, mask=[True, False, True, False, False])
+        assert_startswith(repr(mxsub),
+            f'masked_{SubArray.__name__}(data=[--, 1, --, 3, 4]')
+
+    def test_subclass_str(self):
+        """test str with subclass that has overridden str, setitem"""
+        # first without override
+        x = np.arange(5)
+        xsub = SubArray(x)
+        mxsub = masked_array(xsub, mask=[True, False, True, False, False])
+        assert_equal(str(mxsub), '[-- 1 -- 3 4]')
+
+        xcsub = ComplicatedSubArray(x)
+        assert_raises(ValueError, xcsub.__setitem__, 0,
+                      np.ma.core.masked_print_option)
+        mxcsub = masked_array(xcsub, mask=[True, False, True, False, False])
+        assert_equal(str(mxcsub), 'myprefix [-- 1 -- 3 4] mypostfix')
+
+    def test_pure_subclass_info_preservation(self):
+        # Test that ufuncs and methods conserve extra information consistently;
+        # see gh-7122.
+        arr1 = SubMaskedArray('test', data=[1,2,3,4,5,6])
+        arr2 = SubMaskedArray(data=[0,1,2,3,4,5])
+        diff1 = np.subtract(arr1, arr2)
+        assert_('info' in diff1._optinfo)
+        assert_(diff1._optinfo['info'] == 'test')
+        diff2 = arr1 - arr2
+        assert_('info' in diff2._optinfo)
+        assert_(diff2._optinfo['info'] == 'test')
+
+
+class ArrayNoInheritance:
+    """Quantity-like class that does not inherit from ndarray"""
+    def __init__(self, data, units):
+        self.magnitude = data
+        self.units = units
+
+    def __getattr__(self, attr):
+        return getattr(self.magnitude, attr)
+
+
+def test_array_no_inheritance():
+    data_masked = np.ma.array([1, 2, 3], mask=[True, False, True])
+    data_masked_units = ArrayNoInheritance(data_masked, 'meters')
+
+    # Get the masked representation of the Quantity-like class
+    new_array = np.ma.array(data_masked_units)
+    assert_equal(data_masked.data, new_array.data)
+    assert_equal(data_masked.mask, new_array.mask)
+    # Test sharing the mask
+    data_masked.mask = [True, False, False]
+    assert_equal(data_masked.mask, new_array.mask)
+    assert_(new_array.sharedmask)
+
+    # Get the masked representation of the Quantity-like class
+    new_array = np.ma.array(data_masked_units, copy=True)
+    assert_equal(data_masked.data, new_array.data)
+    assert_equal(data_masked.mask, new_array.mask)
+    # Test that the mask is not shared when copy=True
+    data_masked.mask = [True, False, True]
+    assert_equal([True, False, False], new_array.mask)
+    assert_(not new_array.sharedmask)
+
+    # Get the masked representation of the Quantity-like class
+    new_array = np.ma.array(data_masked_units, keep_mask=False)
+    assert_equal(data_masked.data, new_array.data)
+    # The change did not affect the original mask
+    assert_equal(data_masked.mask, [True, False, True])
+    # Test that the mask is False and not shared when keep_mask=False
+    assert_(not new_array.mask)
+    assert_(not new_array.sharedmask)
+
+
+class TestClassWrapping:
+    # Test suite for classes that wrap MaskedArrays
+
+    def setup_method(self):
+        m = np.ma.masked_array([1, 3, 5], mask=[False, True, False])
+        wm = WrappedArray(m)
+        self.data = (m, wm)
+
+    def test_masked_unary_operations(self):
+        # Tests masked_unary_operation
+        (m, wm) = self.data
+        with np.errstate(divide='ignore'):
+            assert_(isinstance(np.log(wm), WrappedArray))
+
+    def test_masked_binary_operations(self):
+        # Tests masked_binary_operation
+        (m, wm) = self.data
+        # Result should be a WrappedArray
+        assert_(isinstance(np.add(wm, wm), WrappedArray))
+        assert_(isinstance(np.add(m, wm), WrappedArray))
+        assert_(isinstance(np.add(wm, m), WrappedArray))
+        # add and '+' should call the same ufunc
+        assert_equal(np.add(m, wm), m + wm)
+        assert_(isinstance(np.hypot(m, wm), WrappedArray))
+        assert_(isinstance(np.hypot(wm, m), WrappedArray))
+        # Test domained binary operations
+        assert_(isinstance(np.divide(wm, m), WrappedArray))
+        assert_(isinstance(np.divide(m, wm), WrappedArray))
+        assert_equal(np.divide(wm, m) * m, np.divide(m, m) * wm)
+        # Test broadcasting
+        m2 = np.stack([m, m])
+        assert_(isinstance(np.divide(wm, m2), WrappedArray))
+        assert_(isinstance(np.divide(m2, wm), WrappedArray))
+        assert_equal(np.divide(m2, wm), np.divide(wm, m2))
+
+    def test_mixins_have_slots(self):
+        mixin = NDArrayOperatorsMixin()
+        # Should raise an error
+        assert_raises(AttributeError, mixin.__setattr__, "not_a_real_attr", 1)
+
+        m = np.ma.masked_array([1, 3, 5], mask=[False, True, False])
+        wm = WrappedArray(m)
+        assert_raises(AttributeError, wm.__setattr__, "not_an_attr", 2)
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/testutils.py b/.venv/lib/python3.12/site-packages/numpy/ma/testutils.py
new file mode 100644
index 00000000..7a633906
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/testutils.py
@@ -0,0 +1,288 @@
+"""Miscellaneous functions for testing masked arrays and subclasses
+
+:author: Pierre Gerard-Marchant
+:contact: pierregm_at_uga_dot_edu
+:version: $Id: testutils.py 3529 2007-11-13 08:01:14Z jarrod.millman $
+
+"""
+import operator
+
+import numpy as np
+from numpy import ndarray, float_
+import numpy.core.umath as umath
+import numpy.testing
+from numpy.testing import (
+    assert_, assert_allclose, assert_array_almost_equal_nulp,
+    assert_raises, build_err_msg
+    )
+from .core import mask_or, getmask, masked_array, nomask, masked, filled
+
+__all__masked = [
+    'almost', 'approx', 'assert_almost_equal', 'assert_array_almost_equal',
+    'assert_array_approx_equal', 'assert_array_compare',
+    'assert_array_equal', 'assert_array_less', 'assert_close',
+    'assert_equal', 'assert_equal_records', 'assert_mask_equal',
+    'assert_not_equal', 'fail_if_array_equal',
+    ]
+
+# Include some normal test functions to avoid breaking other projects who
+# have mistakenly included them from this file. SciPy is one. That is
+# unfortunate, as some of these functions are not intended to work with
+# masked arrays. But there was no way to tell before.
+from unittest import TestCase
+__some__from_testing = [
+    'TestCase', 'assert_', 'assert_allclose', 'assert_array_almost_equal_nulp',
+    'assert_raises'
+    ]
+
+__all__ = __all__masked + __some__from_testing
+
+
+def approx(a, b, fill_value=True, rtol=1e-5, atol=1e-8):
+    """
+    Returns true if all components of a and b are equal to given tolerances.
+
+    If fill_value is True, masked values considered equal. Otherwise,
+    masked values are considered unequal.  The relative error rtol should
+    be positive and << 1.0 The absolute error atol comes into play for
+    those elements of b that are very small or zero; it says how small a
+    must be also.
+
+    """
+    m = mask_or(getmask(a), getmask(b))
+    d1 = filled(a)
+    d2 = filled(b)
+    if d1.dtype.char == "O" or d2.dtype.char == "O":
+        return np.equal(d1, d2).ravel()
+    x = filled(masked_array(d1, copy=False, mask=m), fill_value).astype(float_)
+    y = filled(masked_array(d2, copy=False, mask=m), 1).astype(float_)
+    d = np.less_equal(umath.absolute(x - y), atol + rtol * umath.absolute(y))
+    return d.ravel()
+
+
+def almost(a, b, decimal=6, fill_value=True):
+    """
+    Returns True if a and b are equal up to decimal places.
+
+    If fill_value is True, masked values considered equal. Otherwise,
+    masked values are considered unequal.
+
+    """
+    m = mask_or(getmask(a), getmask(b))
+    d1 = filled(a)
+    d2 = filled(b)
+    if d1.dtype.char == "O" or d2.dtype.char == "O":
+        return np.equal(d1, d2).ravel()
+    x = filled(masked_array(d1, copy=False, mask=m), fill_value).astype(float_)
+    y = filled(masked_array(d2, copy=False, mask=m), 1).astype(float_)
+    d = np.around(np.abs(x - y), decimal) <= 10.0 ** (-decimal)
+    return d.ravel()
+
+
+def _assert_equal_on_sequences(actual, desired, err_msg=''):
+    """
+    Asserts the equality of two non-array sequences.
+
+    """
+    assert_equal(len(actual), len(desired), err_msg)
+    for k in range(len(desired)):
+        assert_equal(actual[k], desired[k], f'item={k!r}\n{err_msg}')
+    return
+
+
+def assert_equal_records(a, b):
+    """
+    Asserts that two records are equal.
+
+    Pretty crude for now.
+
+    """
+    assert_equal(a.dtype, b.dtype)
+    for f in a.dtype.names:
+        (af, bf) = (operator.getitem(a, f), operator.getitem(b, f))
+        if not (af is masked) and not (bf is masked):
+            assert_equal(operator.getitem(a, f), operator.getitem(b, f))
+    return
+
+
+def assert_equal(actual, desired, err_msg=''):
+    """
+    Asserts that two items are equal.
+
+    """
+    # Case #1: dictionary .....
+    if isinstance(desired, dict):
+        if not isinstance(actual, dict):
+            raise AssertionError(repr(type(actual)))
+        assert_equal(len(actual), len(desired), err_msg)
+        for k, i in desired.items():
+            if k not in actual:
+                raise AssertionError(f"{k} not in {actual}")
+            assert_equal(actual[k], desired[k], f'key={k!r}\n{err_msg}')
+        return
+    # Case #2: lists .....
+    if isinstance(desired, (list, tuple)) and isinstance(actual, (list, tuple)):
+        return _assert_equal_on_sequences(actual, desired, err_msg='')
+    if not (isinstance(actual, ndarray) or isinstance(desired, ndarray)):
+        msg = build_err_msg([actual, desired], err_msg,)
+        if not desired == actual:
+            raise AssertionError(msg)
+        return
+    # Case #4. arrays or equivalent
+    if ((actual is masked) and not (desired is masked)) or \
+            ((desired is masked) and not (actual is masked)):
+        msg = build_err_msg([actual, desired],
+                            err_msg, header='', names=('x', 'y'))
+        raise ValueError(msg)
+    actual = np.asanyarray(actual)
+    desired = np.asanyarray(desired)
+    (actual_dtype, desired_dtype) = (actual.dtype, desired.dtype)
+    if actual_dtype.char == "S" and desired_dtype.char == "S":
+        return _assert_equal_on_sequences(actual.tolist(),
+                                          desired.tolist(),
+                                          err_msg='')
+    return assert_array_equal(actual, desired, err_msg)
+
+
+def fail_if_equal(actual, desired, err_msg='',):
+    """
+    Raises an assertion error if two items are equal.
+
+    """
+    if isinstance(desired, dict):
+        if not isinstance(actual, dict):
+            raise AssertionError(repr(type(actual)))
+        fail_if_equal(len(actual), len(desired), err_msg)
+        for k, i in desired.items():
+            if k not in actual:
+                raise AssertionError(repr(k))
+            fail_if_equal(actual[k], desired[k], f'key={k!r}\n{err_msg}')
+        return
+    if isinstance(desired, (list, tuple)) and isinstance(actual, (list, tuple)):
+        fail_if_equal(len(actual), len(desired), err_msg)
+        for k in range(len(desired)):
+            fail_if_equal(actual[k], desired[k], f'item={k!r}\n{err_msg}')
+        return
+    if isinstance(actual, np.ndarray) or isinstance(desired, np.ndarray):
+        return fail_if_array_equal(actual, desired, err_msg)
+    msg = build_err_msg([actual, desired], err_msg)
+    if not desired != actual:
+        raise AssertionError(msg)
+
+
+assert_not_equal = fail_if_equal
+
+
+def assert_almost_equal(actual, desired, decimal=7, err_msg='', verbose=True):
+    """
+    Asserts that two items are almost equal.
+
+    The test is equivalent to abs(desired-actual) < 0.5 * 10**(-decimal).
+
+    """
+    if isinstance(actual, np.ndarray) or isinstance(desired, np.ndarray):
+        return assert_array_almost_equal(actual, desired, decimal=decimal,
+                                         err_msg=err_msg, verbose=verbose)
+    msg = build_err_msg([actual, desired],
+                        err_msg=err_msg, verbose=verbose)
+    if not round(abs(desired - actual), decimal) == 0:
+        raise AssertionError(msg)
+
+
+assert_close = assert_almost_equal
+
+
+def assert_array_compare(comparison, x, y, err_msg='', verbose=True, header='',
+                         fill_value=True):
+    """
+    Asserts that comparison between two masked arrays is satisfied.
+
+    The comparison is elementwise.
+
+    """
+    # Allocate a common mask and refill
+    m = mask_or(getmask(x), getmask(y))
+    x = masked_array(x, copy=False, mask=m, keep_mask=False, subok=False)
+    y = masked_array(y, copy=False, mask=m, keep_mask=False, subok=False)
+    if ((x is masked) and not (y is masked)) or \
+            ((y is masked) and not (x is masked)):
+        msg = build_err_msg([x, y], err_msg=err_msg, verbose=verbose,
+                            header=header, names=('x', 'y'))
+        raise ValueError(msg)
+    # OK, now run the basic tests on filled versions
+    return np.testing.assert_array_compare(comparison,
+                                           x.filled(fill_value),
+                                           y.filled(fill_value),
+                                           err_msg=err_msg,
+                                           verbose=verbose, header=header)
+
+
+def assert_array_equal(x, y, err_msg='', verbose=True):
+    """
+    Checks the elementwise equality of two masked arrays.
+
+    """
+    assert_array_compare(operator.__eq__, x, y,
+                         err_msg=err_msg, verbose=verbose,
+                         header='Arrays are not equal')
+
+
+def fail_if_array_equal(x, y, err_msg='', verbose=True):
+    """
+    Raises an assertion error if two masked arrays are not equal elementwise.
+
+    """
+    def compare(x, y):
+        return (not np.all(approx(x, y)))
+    assert_array_compare(compare, x, y, err_msg=err_msg, verbose=verbose,
+                         header='Arrays are not equal')
+
+
+def assert_array_approx_equal(x, y, decimal=6, err_msg='', verbose=True):
+    """
+    Checks the equality of two masked arrays, up to given number odecimals.
+
+    The equality is checked elementwise.
+
+    """
+    def compare(x, y):
+        "Returns the result of the loose comparison between x and y)."
+        return approx(x, y, rtol=10. ** -decimal)
+    assert_array_compare(compare, x, y, err_msg=err_msg, verbose=verbose,
+                         header='Arrays are not almost equal')
+
+
+def assert_array_almost_equal(x, y, decimal=6, err_msg='', verbose=True):
+    """
+    Checks the equality of two masked arrays, up to given number odecimals.
+
+    The equality is checked elementwise.
+
+    """
+    def compare(x, y):
+        "Returns the result of the loose comparison between x and y)."
+        return almost(x, y, decimal)
+    assert_array_compare(compare, x, y, err_msg=err_msg, verbose=verbose,
+                         header='Arrays are not almost equal')
+
+
+def assert_array_less(x, y, err_msg='', verbose=True):
+    """
+    Checks that x is smaller than y elementwise.
+
+    """
+    assert_array_compare(operator.__lt__, x, y,
+                         err_msg=err_msg, verbose=verbose,
+                         header='Arrays are not less-ordered')
+
+
+def assert_mask_equal(m1, m2, err_msg=''):
+    """
+    Asserts the equality of two masks.
+
+    """
+    if m1 is nomask:
+        assert_(m2 is nomask)
+    if m2 is nomask:
+        assert_(m1 is nomask)
+    assert_array_equal(m1, m2, err_msg=err_msg)
diff --git a/.venv/lib/python3.12/site-packages/numpy/ma/timer_comparison.py b/.venv/lib/python3.12/site-packages/numpy/ma/timer_comparison.py
new file mode 100644
index 00000000..9eb1a23c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/numpy/ma/timer_comparison.py
@@ -0,0 +1,443 @@
+import timeit
+from functools import reduce
+
+import numpy as np
+from numpy import float_
+import numpy.core.fromnumeric as fromnumeric
+
+from numpy.testing import build_err_msg
+
+
+pi = np.pi
+
+class ModuleTester:
+    def __init__(self, module):
+        self.module = module
+        self.allequal = module.allequal
+        self.arange = module.arange
+        self.array = module.array
+        self.concatenate = module.concatenate
+        self.count = module.count
+        self.equal = module.equal
+        self.filled = module.filled
+        self.getmask = module.getmask
+        self.getmaskarray = module.getmaskarray
+        self.id = id
+        self.inner = module.inner
+        self.make_mask = module.make_mask
+        self.masked = module.masked
+        self.masked_array = module.masked_array
+        self.masked_values = module.masked_values
+        self.mask_or = module.mask_or
+        self.nomask = module.nomask
+        self.ones = module.ones
+        self.outer = module.outer
+        self.repeat = module.repeat
+        self.resize = module.resize
+        self.sort = module.sort
+        self.take = module.take
+        self.transpose = module.transpose
+        self.zeros = module.zeros
+        self.MaskType = module.MaskType
+        try:
+            self.umath = module.umath
+        except AttributeError:
+            self.umath = module.core.umath
+        self.testnames = []
+
+    def assert_array_compare(self, comparison, x, y, err_msg='', header='',
+                         fill_value=True):
+        """
+        Assert that a comparison of two masked arrays is satisfied elementwise.
+
+        """
+        xf = self.filled(x)
+        yf = self.filled(y)
+        m = self.mask_or(self.getmask(x), self.getmask(y))
+
+        x = self.filled(self.masked_array(xf, mask=m), fill_value)
+        y = self.filled(self.masked_array(yf, mask=m), fill_value)
+        if (x.dtype.char != "O"):
+            x = x.astype(float_)
+            if isinstance(x, np.ndarray) and x.size > 1:
+                x[np.isnan(x)] = 0
+            elif np.isnan(x):
+                x = 0
+        if (y.dtype.char != "O"):
+            y = y.astype(float_)
+            if isinstance(y, np.ndarray) and y.size > 1:
+                y[np.isnan(y)] = 0
+            elif np.isnan(y):
+                y = 0
+        try:
+            cond = (x.shape == () or y.shape == ()) or x.shape == y.shape
+            if not cond:
+                msg = build_err_msg([x, y],
+                                    err_msg
+                                    + f'\n(shapes {x.shape}, {y.shape} mismatch)',
+                                    header=header,
+                                    names=('x', 'y'))
+                assert cond, msg
+            val = comparison(x, y)
+            if m is not self.nomask and fill_value:
+                val = self.masked_array(val, mask=m)
+            if isinstance(val, bool):
+                cond = val
+                reduced = [0]
+            else:
+                reduced = val.ravel()
+                cond = reduced.all()
+                reduced = reduced.tolist()
+            if not cond:
+                match = 100-100.0*reduced.count(1)/len(reduced)
+                msg = build_err_msg([x, y],
+                                    err_msg
+                                    + '\n(mismatch %s%%)' % (match,),
+                                    header=header,
+                                    names=('x', 'y'))
+                assert cond, msg
+        except ValueError as e:
+            msg = build_err_msg([x, y], err_msg, header=header, names=('x', 'y'))
+            raise ValueError(msg) from e
+
+    def assert_array_equal(self, x, y, err_msg=''):
+        """
+        Checks the elementwise equality of two masked arrays.
+
+        """
+        self.assert_array_compare(self.equal, x, y, err_msg=err_msg,
+                                  header='Arrays are not equal')
+
+    @np.errstate(all='ignore')
+    def test_0(self):
+        """
+        Tests creation
+
+        """
+        x = np.array([1., 1., 1., -2., pi/2.0, 4., 5., -10., 10., 1., 2., 3.])
+        m = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        xm = self.masked_array(x, mask=m)
+        xm[0]
+
+    @np.errstate(all='ignore')
+    def test_1(self):
+        """
+        Tests creation
+
+        """
+        x = np.array([1., 1., 1., -2., pi/2.0, 4., 5., -10., 10., 1., 2., 3.])
+        y = np.array([5., 0., 3., 2., -1., -4., 0., -10., 10., 1., 0., 3.])
+        m1 = [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0]
+        m2 = [0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1]
+        xm = self.masked_array(x, mask=m1)
+        ym = self.masked_array(y, mask=m2)
+        xf = np.where(m1, 1.e+20, x)
+        xm.set_fill_value(1.e+20)
+
+        assert((xm-ym).filled(0).any())
+        s = x.shape
+        assert(xm.size == reduce(lambda x, y:x*y, s))
+        assert(self.count(xm) == len(m1) - reduce(lambda x, y:x+y, m1))
+
+        for s in [(4, 3), (6, 2)]:
+            x.shape = s
+            y.shape = s
+            xm.shape = s
+            ym.shape = s
+            xf.shape = s
+            assert(self.count(xm) == len(m1) - reduce(lambda x, y:x+y, m1))
+
+    @np.errstate(all='ignore')
+    def test_2(self):
+        """
+        Tests conversions and indexing.
+
+        """
+        x1 = np.array([1, 2, 4, 3])
+        x2 = self.array(x1, mask=[1, 0, 0, 0])
+        x3 = self.array(x1, mask=[0, 1, 0, 1])
+        x4 = self.array(x1)
+        # test conversion to strings, no errors
+        str(x2)
+        repr(x2)
+        # tests of indexing
+        assert type(x2[1]) is type(x1[1])
+        assert x1[1] == x2[1]
+        x1[2] = 9
+        x2[2] = 9
+        self.assert_array_equal(x1, x2)
+        x1[1:3] = 99
+        x2[1:3] = 99
+        x2[1] = self.masked
+        x2[1:3] = self.masked
+        x2[:] = x1
+        x2[1] = self.masked
+        x3[:] = self.masked_array([1, 2, 3, 4], [0, 1, 1, 0])
+        x4[:] = self.masked_array([1, 2, 3, 4], [0, 1, 1, 0])
+        x1 = np.arange(5)*1.0
+        x2 = self.masked_values(x1, 3.0)
+        x1 = self.array([1, 'hello', 2, 3], object)
+        x2 = np.array([1, 'hello', 2, 3], object)
+        # check that no error occurs.
+        x1[1]
+        x2[1]
+        assert x1[1:1].shape == (0,)
+        # Tests copy-size
+        n = [0, 0, 1, 0, 0]
+        m = self.make_mask(n)
+        m2 = self.make_mask(m)
+        assert(m is m2)
+        m3 = self.make_mask(m, copy=1)
+        assert(m is not m3)
+
+    @np.errstate(all='ignore')
+    def test_3(self):
+        """
+        Tests resize/repeat
+
+        """
+        x4 = self.arange(4)
+        x4[2] = self.masked
+        y4 = self.resize(x4, (8,))
+        assert self.allequal(self.concatenate([x4, x4]), y4)
+        assert self.allequal(self.getmask(y4), [0, 0, 1, 0, 0, 0, 1, 0])
+        y5 = self.repeat(x4, (2, 2, 2, 2), axis=0)
+        self.assert_array_equal(y5, [0, 0, 1, 1, 2, 2, 3, 3])
+        y6 = self.repeat(x4, 2, axis=0)
+        assert self.allequal(y5, y6)
+        y7 = x4.repeat((2, 2, 2, 2), axis=0)
+        assert self.allequal(y5, y7)
+        y8 = x4.repeat(2, 0)
+        assert self.allequal(y5, y8)
+
+    @np.errstate(all='ignore')
+    def test_4(self):
+        """
+        Test of take, transpose, inner, outer products.
+
+        """
+        x = self.arange(24)
+        y = np.arange(24)
+        x[5:6] = self.masked
+        x = x.reshape(2, 3, 4)
+        y = y.reshape(2, 3, 4)
+        assert self.allequal(np.transpose(y, (2, 0, 1)), self.transpose(x, (2, 0, 1)))
+        assert self.allequal(np.take(y, (2, 0, 1), 1), self.take(x, (2, 0, 1), 1))
+        assert self.allequal(np.inner(self.filled(x, 0), self.filled(y, 0)),
+                            self.inner(x, y))
+        assert self.allequal(np.outer(self.filled(x, 0), self.filled(y, 0)),
+                            self.outer(x, y))
+        y = self.array(['abc', 1, 'def', 2, 3], object)
+        y[2] = self.masked
+        t = self.take(y, [0, 3, 4])
+        assert t[0] == 'abc'
+        assert t[1] == 2
+        assert t[2] == 3
+
+    @np.errstate(all='ignore')
+    def test_5(self):
+        """
+        Tests inplace w/ scalar
+
+        """
+        x = self.arange(10)
+        y = self.arange(10)
+        xm = self.arange(10)
+        xm[2] = self.masked
+        x += 1
+        assert self.allequal(x, y+1)
+        xm += 1
+        assert self.allequal(xm, y+1)
+
+        x = self.arange(10)
+        xm = self.arange(10)
+        xm[2] = self.masked
+        x -= 1
+        assert self.allequal(x, y-1)
+        xm -= 1
+        assert self.allequal(xm, y-1)
+
+        x = self.arange(10)*1.0
+        xm = self.arange(10)*1.0
+        xm[2] = self.masked
+        x *= 2.0
+        assert self.allequal(x, y*2)
+        xm *= 2.0
+        assert self.allequal(xm, y*2)
+
+        x = self.arange(10)*2
+        xm = self.arange(10)*2
+        xm[2] = self.masked
+        x /= 2
+        assert self.allequal(x, y)
+        xm /= 2
+        assert self.allequal(xm, y)
+
+        x = self.arange(10)*1.0
+        xm = self.arange(10)*1.0
+        xm[2] = self.masked
+        x /= 2.0
+        assert self.allequal(x, y/2.0)
+        xm /= self.arange(10)
+        self.assert_array_equal(xm, self.ones((10,)))
+
+        x = self.arange(10).astype(float_)
+        xm = self.arange(10)
+        xm[2] = self.masked
+        x += 1.
+        assert self.allequal(x, y + 1.)
+
+    @np.errstate(all='ignore')
+    def test_6(self):
+        """
+        Tests inplace w/ array
+
+        """
+        x = self.arange(10, dtype=float_)
+        y = self.arange(10)
+        xm = self.arange(10, dtype=float_)
+        xm[2] = self.masked
+        m = xm.mask
+        a = self.arange(10, dtype=float_)
+        a[-1] = self.masked
+        x += a
+        xm += a
+        assert self.allequal(x, y+a)
+        assert self.allequal(xm, y+a)
+        assert self.allequal(xm.mask, self.mask_or(m, a.mask))
+
+        x = self.arange(10, dtype=float_)
+        xm = self.arange(10, dtype=float_)
+        xm[2] = self.masked
+        m = xm.mask
+        a = self.arange(10, dtype=float_)
+        a[-1] = self.masked
+        x -= a
+        xm -= a
+        assert self.allequal(x, y-a)
+        assert self.allequal(xm, y-a)
+        assert self.allequal(xm.mask, self.mask_or(m, a.mask))
+
+        x = self.arange(10, dtype=float_)
+        xm = self.arange(10, dtype=float_)
+        xm[2] = self.masked
+        m = xm.mask
+        a = self.arange(10, dtype=float_)
+        a[-1] = self.masked
+        x *= a
+        xm *= a
+        assert self.allequal(x, y*a)
+        assert self.allequal(xm, y*a)
+        assert self.allequal(xm.mask, self.mask_or(m, a.mask))
+
+        x = self.arange(10, dtype=float_)
+        xm = self.arange(10, dtype=float_)
+        xm[2] = self.masked
+        m = xm.mask
+        a = self.arange(10, dtype=float_)
+        a[-1] = self.masked
+        x /= a
+        xm /= a
+
+    @np.errstate(all='ignore')
+    def test_7(self):
+        "Tests ufunc"
+        d = (self.array([1.0, 0, -1, pi/2]*2, mask=[0, 1]+[0]*6),
+             self.array([1.0, 0, -1, pi/2]*2, mask=[1, 0]+[0]*6),)
+        for f in ['sqrt', 'log', 'log10', 'exp', 'conjugate',
+#                  'sin', 'cos', 'tan',
+#                  'arcsin', 'arccos', 'arctan',
+#                  'sinh', 'cosh', 'tanh',
+#                  'arcsinh',
+#                  'arccosh',
+#                  'arctanh',
+#                  'absolute', 'fabs', 'negative',
+#                  # 'nonzero', 'around',
+#                  'floor', 'ceil',
+#                  # 'sometrue', 'alltrue',
+#                  'logical_not',
+#                  'add', 'subtract', 'multiply',
+#                  'divide', 'true_divide', 'floor_divide',
+#                  'remainder', 'fmod', 'hypot', 'arctan2',
+#                  'equal', 'not_equal', 'less_equal', 'greater_equal',
+#                  'less', 'greater',
+#                  'logical_and', 'logical_or', 'logical_xor',
+                  ]:
+            try:
+                uf = getattr(self.umath, f)
+            except AttributeError:
+                uf = getattr(fromnumeric, f)
+            mf = getattr(self.module, f)
+            args = d[:uf.nin]
+            ur = uf(*args)
+            mr = mf(*args)
+            self.assert_array_equal(ur.filled(0), mr.filled(0), f)
+            self.assert_array_equal(ur._mask, mr._mask)
+
+    @np.errstate(all='ignore')
+    def test_99(self):
+        # test average
+        ott = self.array([0., 1., 2., 3.], mask=[1, 0, 0, 0])
+        self.assert_array_equal(2.0, self.average(ott, axis=0))
+        self.assert_array_equal(2.0, self.average(ott, weights=[1., 1., 2., 1.]))
+        result, wts = self.average(ott, weights=[1., 1., 2., 1.], returned=1)
+        self.assert_array_equal(2.0, result)
+        assert(wts == 4.0)
+        ott[:] = self.masked
+        assert(self.average(ott, axis=0) is self.masked)
+        ott = self.array([0., 1., 2., 3.], mask=[1, 0, 0, 0])
+        ott = ott.reshape(2, 2)
+        ott[:, 1] = self.masked
+        self.assert_array_equal(self.average(ott, axis=0), [2.0, 0.0])
+        assert(self.average(ott, axis=1)[0] is self.masked)
+        self.assert_array_equal([2., 0.], self.average(ott, axis=0))
+        result, wts = self.average(ott, axis=0, returned=1)
+        self.assert_array_equal(wts, [1., 0.])
+        w1 = [0, 1, 1, 1, 1, 0]
+        w2 = [[0, 1, 1, 1, 1, 0], [1, 0, 0, 0, 0, 1]]
+        x = self.arange(6)
+        self.assert_array_equal(self.average(x, axis=0), 2.5)
+        self.assert_array_equal(self.average(x, axis=0, weights=w1), 2.5)
+        y = self.array([self.arange(6), 2.0*self.arange(6)])
+        self.assert_array_equal(self.average(y, None), np.add.reduce(np.arange(6))*3./12.)
+        self.assert_array_equal(self.average(y, axis=0), np.arange(6) * 3./2.)
+        self.assert_array_equal(self.average(y, axis=1), [self.average(x, axis=0), self.average(x, axis=0) * 2.0])
+        self.assert_array_equal(self.average(y, None, weights=w2), 20./6.)
+        self.assert_array_equal(self.average(y, axis=0, weights=w2), [0., 1., 2., 3., 4., 10.])
+        self.assert_array_equal(self.average(y, axis=1), [self.average(x, axis=0), self.average(x, axis=0) * 2.0])
+        m1 = self.zeros(6)
+        m2 = [0, 0, 1, 1, 0, 0]
+        m3 = [[0, 0, 1, 1, 0, 0], [0, 1, 1, 1, 1, 0]]
+        m4 = self.ones(6)
+        m5 = [0, 1, 1, 1, 1, 1]
+        self.assert_array_equal(self.average(self.masked_array(x, m1), axis=0), 2.5)
+        self.assert_array_equal(self.average(self.masked_array(x, m2), axis=0), 2.5)
+        self.assert_array_equal(self.average(self.masked_array(x, m5), axis=0), 0.0)
+        self.assert_array_equal(self.count(self.average(self.masked_array(x, m4), axis=0)), 0)
+        z = self.masked_array(y, m3)
+        self.assert_array_equal(self.average(z, None), 20./6.)
+        self.assert_array_equal(self.average(z, axis=0), [0., 1., 99., 99., 4.0, 7.5])
+        self.assert_array_equal(self.average(z, axis=1), [2.5, 5.0])
+        self.assert_array_equal(self.average(z, axis=0, weights=w2), [0., 1., 99., 99., 4.0, 10.0])
+
+    @np.errstate(all='ignore')
+    def test_A(self):
+        x = self.arange(24)
+        x[5:6] = self.masked
+        x = x.reshape(2, 3, 4)
+
+
+if __name__ == '__main__':
+    setup_base = ("from __main__ import ModuleTester \n"
+                  "import numpy\n"
+                  "tester = ModuleTester(module)\n")
+    setup_cur = "import numpy.ma.core as module\n" + setup_base
+    (nrepeat, nloop) = (10, 10)
+
+    for i in range(1, 8):
+        func = 'tester.test_%i()' % i
+        cur = timeit.Timer(func, setup_cur).repeat(nrepeat, nloop*10)
+        cur = np.sort(cur)
+        print("#%i" % i + 50*'.')
+        print(eval("ModuleTester.test_%i.__doc__" % i))
+        print(f'core_current : {cur[0]:.3f} - {cur[1]:.3f}')