For example, I have matrix=np.array([[1,2],[3,4]])
.
When I use boolean filtration in Numpy like: matrix[[True,False]]
, I understand how's it works - I'll get the first row. But when I use something like: matrix[True, False]
, I get empty parentheses. I guess, in this case boolean values means which dimension I want to use, because if I write: matrix[True, True]
, then I'll get the whole matrix, but if I use: matrix[True, True, False]
, then again I'll get empty parentheses, even though I have a 2D array.
How does this actually work?
This is a very weird case. It is in fact tested, but I don't think the behavior is documented anywhere.
If you index an array with any number of True
scalars, the result is equivalent to arr[np.newaxis]
: it contains all the original array's data, but with an extra length-1 axis at the start of its shape.
If you index an array with any number of boolean scalars, at least one of which is False
, you get an empty result. The result has the shape of the original array, except with an extra length-0 axis at the start of the shape.
The normal case for a boolean index is for the index array and the original array to have the same shape. The desired behavior in the normal case is to produce a 1D array containing all elements of the original array corresponding to True
cells in the index array.
For >=1D boolean index arrays, this is equivalent to substituting index.nonzero()
in place of the index array. But nonzero()
is a little weird for 0D arrays. Instead, 0D boolean arraylikes (including ordinary boolean scalars) have special handling.
A comment in the code describes the special handling as follows:
/*
* This can actually be well defined. A new axis is added,
* but at the same time no axis is "used". So if we have True,
* we add a new axis (a bit like with np.newaxis). If it is
* False, we add a new axis, but this axis has 0 entries.
*/
And when you index an array with a single boolean scalar index and nothing else, the comment accurately describes the behavior. In particular, it does the right thing in the case that motivates the special handling: array(5.0)[True]
produce array([5.0])
, and array(5.0)[False]
produce array([], dtype=float64)
.
But for two scalar boolean indices, the behavior doesn't quite line up with the comment. Reading the comment, you'd expect each one to add a new axis to the output, so if array arr
had shape (x, y)
, you'd expect arr[True, False]
to result in an (empty) output of shape (1, 0, x, y)
. But that's not what happens. What happens is what I described in the first section of the answer.
This is because the implementation doesn't have handling to add more than one axis for 0D boolean indices. It can do that for np.newaxis
, but the handling isn't there for 0D boolean indices. Instead, it sets a variable saying to add at least one "fancy indexing" dimension:
if (fancy_ndim < 1) {
fancy_ndim = 1;
}
and then later, for each boolean scalar index, it multiplies the length of the last "fancy indexing" dimension by 1 for True
, or 0 for False
:
else if (indices[i].type == HAS_0D_BOOL) {
mit->fancy_strides[j] = 0;
mit->fancy_dims[j] = 1;
/* Does not exist */
mit->iteraxes[j++] = -1;
if ((indices[i].value == 0) &&
(mit->dimensions[mit->nd_fancy - 1]) > 1) {
goto broadcast_error;
}
mit->dimensions[mit->nd_fancy-1] *= indices[i].value;
}
This handling is motivated by an analogy with broadcasting - it's as if we're broadcasting the fancy indexing dimensions against arrays of shape (1,)
or (0,)
. And in fact, the code actually creates those shape-(1,)
or shape-(0,)
arrays, even though I don't think it does anything with their contents - the values aren't used for indexing. These arrays are used for error messages, if a broadcasting failure happens, and I think they're used as dummies when creating an internal iterator later.
This handling also means that if you combine boolean scalars with other forms of "fancy indexing", such as integer arrays, you can see other odd behavior. For example, if we have the following arrays:
arr = np.array([10, 11, 12, 13])
index = np.array([[1, 2],
[0, 3]])
then arr[index]
would produce a 2-by-2 array whose elements are arr[1]
, arr[2]
, arr[0]
, and arr[3]
.
But if we do arr[index, True]
, or arr[index, False]
:
In [31]: arr[index, True].shape
Out[31]: (2, 2)
In [32]: arr[index, False].shape
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Input In [32], in <cell line: 1>()
----> 1 arr[index, False].shape
IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (2,2) (0,)
arr[index, True]
has the same shape as arr[index]
, and arr[index, False]
produces a broadcasting failure.