summaryrefslogtreecommitdiff
path: root/doc
diff options
context:
space:
mode:
Diffstat (limited to 'doc')
-rw-r--r--doc/neps/missing-data.rst209
-rw-r--r--doc/source/reference/c-api.array.rst27
2 files changed, 193 insertions, 43 deletions
diff --git a/doc/neps/missing-data.rst b/doc/neps/missing-data.rst
index 7a2c076cb..f983d0ce3 100644
--- a/doc/neps/missing-data.rst
+++ b/doc/neps/missing-data.rst
@@ -225,27 +225,30 @@ provides a starting point.
For example,::
- >>> np.array([1.0, 2.0, np.NA, 7.0], namasked=True)
- array([1., 2., NA, 7.], namasked=True)
- >>> np.array([1.0, 2.0, np.NA, 7.0], dtype='NA[f8]')
+ >>> np.array([1.0, 2.0, np.NA, 7.0], maskna=True)
+ array([1., 2., NA, 7.], maskna=True)
+ >>> np.array([1.0, 2.0, np.NA, 7.0], dtype='NA')
array([1., 2., NA, 7.], dtype='NA[<f8]')
+ >>> np.array([1.0, 2.0, np.NA, 7.0], dtype='NA[f4]')
+ array([1., 2., NA, 7.], dtype='NA[<f4]')
produce arrays with values [1.0, 2.0, <inaccessible>, 7.0] /
-mask [Unmasked, Unmasked, Masked, Unmasked], and
-values [1.0, 2.0, <NA bitpattern>, 7.0] respectively.
+mask [Exposed, Exposed, Hidden, Exposed], and
+values [1.0, 2.0, <NA bitpattern>, 7.0] for the masked and
+NA dtype versions respectively.
It may be worth overloading the np.NA __call__ method to accept a dtype,
returning a zero-dimensional array with a missing value of that dtype.
Without doing this, NA printouts would look like::
- >>> np.sum(np.array([1.0, 2.0, np.NA, 7.0], namasked=True))
- array(NA, dtype='float64', namasked=True)
+ >>> np.sum(np.array([1.0, 2.0, np.NA, 7.0], maskna=True))
+ array(NA, dtype='float64', maskna=True)
>>> np.sum(np.array([1.0, 2.0, np.NA, 7.0], dtype='NA[f8]'))
array(NA, dtype='NA[<f8]')
but with this, they could be printed as::
- >>> np.sum(np.array([1.0, 2.0, np.NA, 7.0], namasked=True))
+ >>> np.sum(np.array([1.0, 2.0, np.NA, 7.0], maskna=True))
NA('float64')
>>> np.sum(np.array([1.0, 2.0, np.NA, 7.0], dtype='NA[f8]'))
NA('NA[<f8]')
@@ -274,12 +277,12 @@ from another view which doesn't have them masked. For example::
>>> a = np.array([1,2])
>>> b = a.view()
- >>> b.flags.hasnamask = True
+ >>> b.flags.hasmaskna = True
>>> b
- array([1,2], namasked=True)
+ array([1,2], maskna=True)
>>> b[0] = np.NA
>>> b
- array([NA,2], namasked=True)
+ array([NA,2], maskna=True)
>>> a
array([1,2])
>>> # The underlying number 1 value in 'a[0]' was untouched
@@ -351,10 +354,10 @@ Creating Masked Arrays
There are two flags which indicate and control the nature of the mask
used in masked arrays.
-First is 'arr.flags.hasnamask', which is True for all masked arrays and
+First is 'arr.flags.hasmaskna', which is True for all masked arrays and
may be set to True to add a mask to an array which does not have one.
-Second is 'arr.flags.ownnamask', which is True if the array owns the
+Second is 'arr.flags.ownmaskna', which is True if the array owns the
memory to the mask, and False if the array has no mask, or has a view
into the mask of another array. If this is set to False in a masked
array, the array will create a copy of the mask so that further modifications
@@ -402,8 +405,16 @@ New functions added to the ndarray are::
array is unmasked and has the 'NA' part stripped from the
parameterized type ('NA[f8]' becomes just 'f8').
- arr.view(namasked=True)
- This is a shortcut for 'a = arr.view(); a.flags.hasnamask=True'.
+ arr.view(maskna=True)
+ This is a shortcut for
+ >>> a = arr.view()
+ >>> a.flags.hasmaskna = True
+
+ arr.view(ownmaskna=True)
+ This is a shortcut for
+ >>> a = arr.view()
+ >>> a.flags.hasmaskna = True
+ >>> a.flags.ownmaskna = True
Element-wise UFuncs With Missing Values
=======================================
@@ -461,9 +472,9 @@ will also use the unmasked value counts for their calculations if
Some examples::
- >>> a = np.array([1., 3., np.NA, 7.], namasked=True)
+ >>> a = np.array([1., 3., np.NA, 7.], maskna=True)
>>> np.sum(a)
- array(NA, dtype='<f8', masked=True)
+ array(NA, dtype='<f8', maskna=True)
>>> np.sum(a, skipna=True)
11.0
>>> np.mean(a)
@@ -471,11 +482,11 @@ Some examples::
>>> np.mean(a, skipna=True)
3.6666666666666665
- >>> a = np.array([np.NA, np.NA], dtype='f8', namasked=True)
+ >>> a = np.array([np.NA, np.NA], dtype='f8', maskna=True)
>>> np.sum(a, skipna=True)
0.0
>>> np.max(a, skipna=True)
- array(NA, dtype='<f8', namasked=True)
+ array(NA, dtype='<f8', maskna=True)
>>> np.mean(a)
NA('<f8')
>>> np.mean(a, skipna=True)
@@ -487,20 +498,24 @@ The functions 'np.any' and 'np.all' require some special consideration,
just as logical_and and logical_or do. Maybe the best way to describe
their behavior is through a series of examples::
- >>> np.any(np.array([False, False, False], namasked=True))
+ >>> np.any(np.array([False, False, False], maskna=True))
False
- >>> np.any(np.array([False, NA, False], namasked=True))
+ >>> np.any(np.array([False, np.NA, False], maskna=True))
NA
- >>> np.any(np.array([False, NA, True], namasked=True))
+ >>> np.any(np.array([False, np.NA, True], maskna=True))
True
- >>> np.all(np.array([True, True, True], namasked=True))
+ >>> np.all(np.array([True, True, True], maskna=True))
True
- >>> np.all(np.array([True, NA, True], namasked=True))
+ >>> np.all(np.array([True, np.NA, True], maskna=True))
NA
- >>> np.all(np.array([False, NA, True], namasked=True))
+ >>> np.all(np.array([False, np.NA, True], maskna=True))
False
+Since 'np.any' is the reduction for 'np.logical_or', and 'np.all'
+is the reduction for 'np.logical_and', it makes sense for them to
+have a 'skipna=' parameter like the other similar reduction functions.
+
Parameterized NA Data Types
===========================
@@ -609,14 +624,124 @@ The important part of future-proofing the design is making sure
the C ABI-level choices and the Python API-level choices have a natural
transition to multi-NA support. Here is one way multi-NA support could look::
- >>> a = np.array([np.NA(1), 3, np.NA(2)], namasked='multi')
+ >>> a = np.array([np.NA(1), 3, np.NA(2)], maskna='multi')
>>> np.sum(a)
- NA(1)
+ NA(1, dtype='<i4')
>>> np.sum(a[1:])
- NA(2)
- >>> b = np.array([np.NA, 2, 5], namasked=True)
+ NA(2, dtype='<i4')
+ >>> b = np.array([np.NA, 2, 5], maskna=True)
>>> a + b
- array([NA(0), 5, NA(2)], namasked='multi')
+ array([NA(0), 5, NA(2)], maskna='multi')
+
+The design of this NEP does not distinguish between NAs that come
+from an NA mask or NAs that come from an NA dtype. Both of these get
+treated equivalently in computations, with masks dominating over NA
+dtypes.::
+
+ >>> a = np.array([np.NA, 2, 5], maskna=True)
+ >>> b = np.array([1, np.NA, 7], dtype='NA')
+ >>> a + b
+ array([NA, NA, 12], maskna=True)
+
+The multi-NA approach allows one to distinguish between these NAs,
+through assigning different payloads to the different types. If we
+extend the 'skipna=' parameter to accept a list of payloads in addition
+to True/False, one could do this::
+
+ >>> a = np.array([np.NA(1), 2, 5], maskna='multi')
+ >>> b = np.array([1, np.NA(0), 7], dtype='NA[f4,multi]')
+ >>> a + b
+ array([NA(1), NA(0), 12], maskna='multi')
+ >>> np.sum(a, skipna=0)
+ NA(1, dtype='<i4')
+ >>> np.sum(a, skipna=1)
+ 7
+ >>> np.sum(b, skipna=0)
+ 8
+ >>> np.sum(b, skipna=1)
+ NA(0, dtype='<f4')
+ >>> np.sum(a+b, skipna=(0,1))
+ 12
+
+Differences with numpy.ma
+=========================
+
+The computational model that numpy.ma uses does not strictly adhere to
+either the NA or the IGNORE model. This section exhibits some examples
+of how these differences affect simple computations. This information
+will be very important for helping users navigate between the systems,
+so a summary probably should be put in a table in the documentation.::
+
+ >>> a = np.random.random((3, 2))
+ >>> mask = [[False, True], [True, True], [False, False]]
+ >>> b1 = np.ma.masked_array(a, mask=mask)
+ >>> b2 = a.view(maskna=True)
+ >>> b2[mask] = np.NA
+
+ >>> b1
+ masked_array(data =
+ [[0.110804969841 --]
+ [-- --]
+ [0.955128477746 0.440430735546]],
+ mask =
+ [[False True]
+ [ True True]
+ [False False]],
+ fill_value = 1e+20)
+ >>> b2
+ array([[0.110804969841, NA],
+ [NA, NA],
+ [0.955128477746, 0.440430735546]],
+ maskna=True)
+
+ >>> b1.mean(axis=0)
+ masked_array(data = [0.532966723794 0.440430735546],
+ mask = [False False],
+ fill_value = 1e+20)
+
+ >>> b2.mean(axis=0)
+ array([NA, NA], dtype='<f8', maskna=True)
+ >>> b2.mean(axis=0, skipna=True)
+ array([0.532966723794 0.440430735546], maskna=True)
+
+For functions like np.mean, when 'skipna=True', the behavior
+for all NAs is consistent with an empty array::
+
+ >>> b1.mean(axis=1)
+ masked_array(data = [0.110804969841 -- 0.697779606646],
+ mask = [False True False],
+ fill_value = 1e+20)
+
+ >>> b2.mean(axis=1)
+ array([NA, NA, 0.697779606646], maskna=True)
+ >>> b2.mean(axis=1, skipna=True)
+ RuntimeWarning: invalid value encountered in double_scalars
+ array([0.110804969841, nan, 0.697779606646], maskna=True)
+
+ >>> np.mean([])
+ RuntimeWarning: invalid value encountered in double_scalars
+ nan
+
+In particular, note that numpy.ma generally skips masked values,
+except returns masked when all the values are masked, while
+the 'skipna=' parameter returns zero when all the values are NA,
+to be consistent with the result of np.sum([])::
+
+ >>> b1[1]
+ masked_array(data = [-- --],
+ mask = [ True True],
+ fill_value = 1e+20)
+ >>> b2[1]
+ array([NA, NA], dtype='<f8', maskna=True)
+ >>> b1[1].sum()
+ masked
+ >>> b2[1].sum()
+ NA(dtype='<f8')
+ >>> b2[1].sum(skipna=True)
+ 0.0
+
+ >>> np.sum([])
+ 0.0
PEP 3118
========
@@ -696,28 +821,28 @@ This gives us the following additions to the PyArrayObject::
/*
* Descriptor for the mask dtype.
* If no mask: NULL
- * If mask : bool/structured dtype of bools
+ * If mask : bool/uint8/structured dtype of mask dtypes
*/
- PyArray_Descr *maskdescr;
+ PyArray_Descr *maskna_descr;
/*
* Raw data buffer for mask. If the array has the flag
- * NPY_ARRAY_OWNNAMASK enabled, it owns this memory and
+ * NPY_ARRAY_OWNMASKNA enabled, it owns this memory and
* must call PyArray_free on it when destroyed.
*/
- npy_uint8 *maskdata;
+ npy_mask *maskna_data;
/*
* Just like dimensions and strides point into the same memory
* buffer, we now just make the buffer 3x the nd instead of 2x
* and use the same buffer.
*/
- npy_intp *maskstrides;
+ npy_intp *maskna_strides;
There are 2 (or 3) flags which must be added to the array flags::
- NPY_ARRAY_HASNAMASK
- NPY_ARRAY_OWNNAMASK
+ NPY_ARRAY_HASMASKNA
+ NPY_ARRAY_OWNMASKNA
/* To possibly add in a later revision */
- NPY_ARRAY_HARDNAMASK
+ NPY_ARRAY_HARDMASKNA
To allow the easy detection of NA support, and whether an array
has any missing values, we add the following functions:
@@ -807,7 +932,7 @@ NPY_ITER_ARRAYMASK
can be only one such mask, and there cannot also be a virtual
mask.
- As a special case, if the flag NPY_ITER_USE_NAMASK is specified
+ As a special case, if the flag NPY_ITER_USE_MASKNA is specified
at the same time, the mask for the operand is used instead
of the operand itself. If the operand has no mask but is
based on an NA dtype, that mask exposed by the iterator converts
@@ -827,14 +952,14 @@ Iterator NA-array Features
We add several new per-operand flags:
-NPY_ITER_USE_NAMASK
+NPY_ITER_USE_MASKNA
If the operand has an NA dtype, an NA mask, or both, this adds a new
virtual operand to the end of the operand list which iterates
over the mask of the particular operand.
-NPY_ITER_IGNORE_NAMASK
+NPY_ITER_IGNORE_MASKNA
If an operand has an NA mask, by default the iterator will raise
- an exception unless NPY_ITER_USE_NAMASK is specified. This flag
+ an exception unless NPY_ITER_USE_MASKNA is specified. This flag
disables that check, and is intended for cases where one has first
checked that all the elements in the array are not NA using the
PyArray_ContainsNA function.
diff --git a/doc/source/reference/c-api.array.rst b/doc/source/reference/c-api.array.rst
index 22a7b46fa..4b57945e9 100644
--- a/doc/source/reference/c-api.array.rst
+++ b/doc/source/reference/c-api.array.rst
@@ -50,6 +50,18 @@ sub-types).
.. cfunction:: PyObject *PyArray_BASE(PyObject* arr)
+ This returns the base object of the array. In most cases, this
+ means the object which owns the memory the array is pointing at.
+
+ If you are constructing an array using the C API, and specifying
+ your own memory, you should use the function :cfunc:`PyArray_SetBaseObject`
+ to set the base to an object which owns the memory.
+
+ If the :cdata:`NPY_ARRAY_UPDATEIFCOPY` flag is set, it has a different
+ meaning, namely base is the array into which the current array will
+ be copied upon destruction. This overloading of the base property
+ for two functions is likely to change in a future version of NumPy.
+
.. cfunction:: PyArray_Descr *PyArray_DESCR(PyObject* arr)
.. cfunction:: int PyArray_FLAGS(PyObject* arr)
@@ -149,7 +161,7 @@ From scratch
is not ``NULL``, then it is assumed to point to the memory to be
used for the array and the *flags* argument is used as the new
flags for the array (except the state of :cdata:`NPY_OWNDATA` and
- :cdata:`UPDATEIFCOPY` flags of the new array will be reset). In
+ :cdata:`NPY_ARRAY_UPDATEIFCOPY` flags of the new array will be reset). In
addition, if *data* is non-NULL, then *strides* can also be
provided. If *strides* is ``NULL``, then the array strides are
computed as C-style contiguous (default) or Fortran-style
@@ -266,6 +278,19 @@ From scratch
increments of ``step``. Equivalent to arange( ``start``,
``stop``, ``step``, ``typenum`` ).
+.. cfunction:: int PyArray_SetBaseObject(PyArrayObject *arr, PyObject *obj)
+
+ If you construct an array by passing in your own memory buffer as
+ a parameter, you need to set the array's `base` property to ensure
+ the lifetime of the memory buffer is appropriate. This function
+ accomplishes the task.
+
+ The return value is 0 on success, -1 on failure.
+
+ If the object provided is an array, this function traverses the
+ chain of `base` pointers so that each array points to the owner
+ of the memory directly. Once the base is set, it may not be changed
+ to another value.
From other objects
^^^^^^^^^^^^^^^^^^