API Reference#

The ndindex API consists of classes representing the different types of index objects (integers, slices, etc.), as well as some helper functions for dealing with indices.

ndindex#

ndindex.ndindex(obj)#

Convert an object into an ndindex type.

Invalid indices will raise IndexError, TypeError, or ValueError (generally, the same error NumPy would raise if the index were used on an array).

Indices are created by calling the ndindex with raw index objects:

>>> from ndindex import ndindex
>>> ndindex(slice(0, 10))
Slice(0, 10, None)
>>> ndindex((slice(0, 10), 0))
Tuple(slice(0, 10, None), 0)

Indices can also be created by calling ndindex with getitem syntax.

>>> ndindex[1]
Integer(1)
>>> ndindex[0:10]
Slice(0, 10, None)
>>> ndindex(0:10)
Traceback (most recent call last):
...
    ndindex(0:10)
             ^
SyntaxError: invalid syntax

The ndindex[idx] form should generally be preferred when creating an index from a tuple or slice literal, since ndindex(a:b) is not syntactically valid and must be typed as ndindex(slice(a, b)). Additionally, the ndindex[idx] syntax does not require parentheses when creating a tuple index:

>>> ndindex[0, 1]
Tuple(0, 1)
>>> ndindex(0, 1) 
Traceback (most recent call last):
...
TypeError: NDIndexConstructor.__call__() takes 2 positional arguments but 3 were given
>>> ndindex((0, 1))
Tuple(0, 1)

Index Types#

The following classes represent different types of indices.

class ndindex.Integer(idx)#

Represents an integer index on an axis of an nd-array.

Any object that implements __index__ can be used as an integer index.

>>> from ndindex import Integer
>>> idx = Integer(1)
>>> [0, 1, 2][idx.raw]
1
>>> idx = Integer(-3)
>>> [0, 1, 2][idx.raw]
0

Note that Integer itself implements __index__, so it can be used as an index directly. However, it is still recommended to use raw for consistency, as this only works for Integer.

Note

Integer does not represent an integer, but rather an integer index. It does not have most methods that int has, and should not be used in non-indexing contexts. See the document on Type Confusion for more details.

__eq__(other)#

Return self==value.

__hash__()#

Return hash(self).

__len__()#

Returns the number of elements indexed by self

Since self is an integer index, this always returns 1. Note that integer indices always remove an axis.

as_subindex(index)#

i.as_subindex(j) produces an index k such that a[j][k] gives all of the elements of a[j] that are also in a[i].

If a[j] is a subset of a[i], then a[j][k] == a[i]. Otherwise, a[j][k] == a[i & j], where i & j is the intersection of i and j, that is, the elements of a that are indexed by both i and j.

For example, in the below diagram, i and j index a subset of the array a. k = i.as_subindex(j) is an index on a[j] that gives the subset of a[j] also included in a[i]:

    +------------ self ------------+
    |                              |
------------------- a -----------------------
       |                                 |
       +------------- index -------------+
       |                           |
       +- self.as_subindex(index) -+

i.as_subindex(j) is currently only implemented when j is a slice with positive steps and nonnegative start and stop, or a Tuple of the same. To use it with slices with negative start or stop, call reduce() with a shape first.

as_subindex can be seen as the left-inverse of composition, that is, if a[i] = a[j][k], then k = i.as_subindex(j), so that k "=" (j^-1)[i] (this only works as a true inverse if j is a subset of i).

Note that due to symmetry, a[j][i.as_subindex(j)] and a[i][j.as_subindex(i)] will give the same subarrays of a, which will be the array of elements indexed by both a[i] and a[j].

i.as_subindex(j) may raise ValueError in the case that the indices i and j do not intersect at all.

Examples

An example usage of as_subindex is to split an index up into subindices of chunks of an array. For example, say a 1-D array a is chunked up into chunks of size N, so that a[0:N], a[N:2*N], [2*N:3*N], etc. are stored separately. Then an index a[i] can be reindexed onto the chunks via i.as_subindex(Slice(0, N)), i.as_subindex(Slice(N, 2*N)), etc.

>>> from ndindex import Slice
>>> i = Slice(5, 15)
>>> j1 = Slice(0, 10)
>>> j2 = Slice(10, 20)
>>> a = list(range(20))
>>> a[i.raw]
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> a[j1.raw]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[j2.raw]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> k1 = i.as_subindex(j1)
>>> k1
Slice(5, 10, 1)
>>> k2 = i.as_subindex(j2)
>>> k2
Slice(0, 5, 1)
>>> a[j1.raw][k1.raw]
[5, 6, 7, 8, 9]
>>> a[j2.raw][k2.raw]
[10, 11, 12, 13, 14]

See also

ndindex.ChunkSize.as_subchunks

a high-level iterator that efficiently gives only those chunks that intersect with a given index

ndindex.ChunkSize.num_subchunks

isempty(shape=None)#

Returns whether self always indexes an empty array

An empty array is an array whose shape contains at least one 0. Note that scalars (arrays with shape ()) are not considered empty.

shape can be None (the default), or an array shape. If it is None, isempty() will return True when self is always empty for any array shape. However, if it gives False, it could still give an empty array for some array shapes, but not all. If you know the shape of the array that will be indexed, you can call idx.isempty(shape) first and the result will be correct for arrays of shape shape. If shape is given and self would raise an IndexError on an array of shape shape, isempty() also raises IndexError.

>>> from ndindex import Tuple, Slice
>>> Tuple(0, slice(0, 1)).isempty()
False
>>> Tuple(0, slice(0, 0)).isempty()
True
>>> Slice(5, 10).isempty()
False
>>> Slice(5, 10).isempty(4)
True
isvalid(shape, _axis=0)#

Check whether a given index is valid on an array of a given shape.

Returns True if an array of shape shape can be indexed by self and False if it would raise IndexError.

>>> from ndindex import ndindex
>>> ndindex(3).isvalid((4,))
True
>>> ndindex(3).isvalid((2,))
False

Note that some indices can never be valid and will raise a IndexError or TypeError if you attempt to construct them.

>>> ndindex((..., 0, ...))
Traceback (most recent call last):
  ...
IndexError: an index can only have a single ellipsis ('...')
>>> ndindex(slice(True))
Traceback (most recent call last):
  ...
TypeError: 'bool' object cannot be interpreted as an integer

See also

NDIndex.newshape

newshape(shape)#

Returns the shape of a[idx.raw], assuming a has shape shape.

shape should be a tuple of ints, or an int, which is equivalent to a 1-D shape.

Raises IndexError if self would be invalid for an array of shape shape.

>>> from ndindex import Slice, Integer, Tuple
>>> shape = (6, 7, 8)
>>> Integer(1).newshape(shape)
(7, 8)
>>> Integer(10).newshape(shape)
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 6
>>> Slice(2, 5).newshape(shape)
(3, 7, 8)
>>> Tuple(0, ..., Slice(1, 3)).newshape(shape)
(7, 2)

See also

NDIndex.isvalid

property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
reduce(shape=None, *, axis=0, negative_int=False, axiserror=False)#

Reduce an Integer index on an array of shape shape.

The result will either be IndexError if the index is invalid for the given shape, or an Integer index where the value is nonnegative.

If negative_int is True and a shape is provided, then the result will be an Integer index where the value is negative.

>>> from ndindex import Integer
>>> idx = Integer(-5)
>>> idx.reduce((3,))
Traceback (most recent call last):
...
IndexError: index -5 is out of bounds for axis 0 with size 3
>>> idx.reduce((9,))
Integer(4)
selected_indices(shape, axis=None)#

Return an iterator over all indices that are selected by self on an array of shape shape.

The result is a set of indices i such that [a[i] for i in idx.selected_indices(a.shape)] is all the elements of a[idx]. The indices are all iterated over in C (i.e., row major) order.

>>> from ndindex import Slice, Tuple
>>> idx = Slice(5, 10)
>>> list(idx.selected_indices(20))
[Integer(5), Integer(6), Integer(7), Integer(8), Integer(9)]
>>> idx = Tuple(Slice(5, 10), Slice(0, 2))
>>> list(idx.selected_indices((20, 3)))
[Tuple(5, 0), Tuple(5, 1),
 Tuple(6, 0), Tuple(6, 1),
 Tuple(7, 0), Tuple(7, 1),
 Tuple(8, 0), Tuple(8, 1),
 Tuple(9, 0), Tuple(9, 1)]

To correspond these indices to the elements of a[idx], you can use iter_indices(idx.newshape(shape)), since both iterators iterate the indices in C order.

>>> from ndindex import iter_indices
>>> idx = Tuple(Slice(3, 5), Slice(0, 2))
>>> shape = (5, 5)
>>> import numpy as np
>>> a = np.arange(25).reshape(shape)
>>> for a_idx, (new_idx,) in zip(
...    idx.selected_indices(shape),
...    iter_indices(idx.newshape(shape))):
...        print(a_idx, new_idx, a[a_idx.raw], a[idx.raw][new_idx.raw])
Tuple(3, 0) Tuple(0, 0) 15 15
Tuple(3, 1) Tuple(0, 1) 16 16
Tuple(4, 0) Tuple(1, 0) 20 20
Tuple(4, 1) Tuple(1, 1) 21 21

See also

ndindex.iter_indices

An iterator of indices to select every element for arrays of a given shape.

ndindex.ChunkSize.as_subchunks

A high-level iterator that efficiently gives only those chunks that intersect with a given index

class ndindex.Slice(start, stop=<class 'ndindex.slice.default'>, step=None)#

Represents a slice on an axis of an nd-array.

Slice(x) with one argument is equivalent to Slice(None, x). Slice(x, y) with two arguments is equivalent to Slice(x, y, None).

start and stop can be any integer, or None. step can be any nonzero integer or None.

Slice(a, b) is the same as the syntax a:b in an index and Slice(a, b, c) is the same as a:b:c. An argument being None is equivalent to the syntax where the item is omitted, for example, Slice(None, None, k) is the same as the syntax ::k.

Slice.args always has three arguments, and does not make any distinction between, for instance, Slice(x, y) and Slice(x, y, None). This is because Python itself does not make the distinction between x:y and x:y: syntactically.

See Slices for a description of the semantic meaning of slices on arrays.

Slice has attributes start, stop, and step to access the corresponding attributes.

>>> from ndindex import Slice
>>> s = Slice(10)
>>> s
Slice(None, 10, None)
>>> print(s.start)
None
>>> s.args
(None, 10, None)
>>> s.raw
slice(None, 10, None)

For most use-cases, it’s more convenient to create Slice objects using ndindex[slice], which allows using a:b slicing syntax:

>>> from ndindex import ndindex
>>> ndindex[0:10]
Slice(0, 10, None)
__eq__(other)#

Return self==value.

__hash__()#

Return hash(self).

__len__()#

len() gives the maximum size of an axis sliced with self.

An actual array may produce a smaller size if it is smaller than the bounds of the slice. For instance, [0, 1, 2][2:4] only has 1 element but the maximum length of the slice 2:4 is 2.

>>> from ndindex import Slice
>>> [0, 1, 2][2:4]
[2]
>>> len(Slice(2, 4))
2
>>> [0, 1, 2, 3][2:4]
[2, 3]

If there is no such maximum, it raises ValueError.

>>> # From the second element to the end, which could have any size
>>> len(Slice(1, None))
Traceback (most recent call last):
...
ValueError: Cannot determine max length of slice

The Slice.reduce() method with a shape argument returns a Slice that always has a correct len which doesn’t raise ValueError.

>>> Slice(2, 4).reduce(3)
Slice(2, 3, 1)
>>> len(_)
1

Be aware that len(Slice) only gives the size of the axis being sliced. It does not say anything about the total shape of the array. In particular, the array may be empty after slicing if one of its dimensions is 0, but the other dimensions may be nonzero. To check if an array will empty after indexing, use isempty().

See also

isempty

as_subindex(index)#

i.as_subindex(j) produces an index k such that a[j][k] gives all of the elements of a[j] that are also in a[i].

If a[j] is a subset of a[i], then a[j][k] == a[i]. Otherwise, a[j][k] == a[i & j], where i & j is the intersection of i and j, that is, the elements of a that are indexed by both i and j.

For example, in the below diagram, i and j index a subset of the array a. k = i.as_subindex(j) is an index on a[j] that gives the subset of a[j] also included in a[i]:

    +------------ self ------------+
    |                              |
------------------- a -----------------------
       |                                 |
       +------------- index -------------+
       |                           |
       +- self.as_subindex(index) -+

i.as_subindex(j) is currently only implemented when j is a slice with positive steps and nonnegative start and stop, or a Tuple of the same. To use it with slices with negative start or stop, call reduce() with a shape first.

as_subindex can be seen as the left-inverse of composition, that is, if a[i] = a[j][k], then k = i.as_subindex(j), so that k "=" (j^-1)[i] (this only works as a true inverse if j is a subset of i).

Note that due to symmetry, a[j][i.as_subindex(j)] and a[i][j.as_subindex(i)] will give the same subarrays of a, which will be the array of elements indexed by both a[i] and a[j].

i.as_subindex(j) may raise ValueError in the case that the indices i and j do not intersect at all.

Examples

An example usage of as_subindex is to split an index up into subindices of chunks of an array. For example, say a 1-D array a is chunked up into chunks of size N, so that a[0:N], a[N:2*N], [2*N:3*N], etc. are stored separately. Then an index a[i] can be reindexed onto the chunks via i.as_subindex(Slice(0, N)), i.as_subindex(Slice(N, 2*N)), etc.

>>> from ndindex import Slice
>>> i = Slice(5, 15)
>>> j1 = Slice(0, 10)
>>> j2 = Slice(10, 20)
>>> a = list(range(20))
>>> a[i.raw]
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> a[j1.raw]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[j2.raw]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> k1 = i.as_subindex(j1)
>>> k1
Slice(5, 10, 1)
>>> k2 = i.as_subindex(j2)
>>> k2
Slice(0, 5, 1)
>>> a[j1.raw][k1.raw]
[5, 6, 7, 8, 9]
>>> a[j2.raw][k2.raw]
[10, 11, 12, 13, 14]

See also

ndindex.ChunkSize.as_subchunks

a high-level iterator that efficiently gives only those chunks that intersect with a given index

ndindex.ChunkSize.num_subchunks

isempty(shape=None)#

Returns whether self always indexes an empty array

An empty array is an array whose shape contains at least one 0. Note that scalars (arrays with shape ()) are not considered empty.

shape can be None (the default), or an array shape. If it is None, isempty() will return True when self is always empty for any array shape. However, if it gives False, it could still give an empty array for some array shapes, but not all. If you know the shape of the array that will be indexed, you can call idx.isempty(shape) first and the result will be correct for arrays of shape shape. If shape is given and self would raise an IndexError on an array of shape shape, isempty() also raises IndexError.

>>> from ndindex import Tuple, Slice
>>> Tuple(0, slice(0, 1)).isempty()
False
>>> Tuple(0, slice(0, 0)).isempty()
True
>>> Slice(5, 10).isempty()
False
>>> Slice(5, 10).isempty(4)
True
isvalid(shape)#

Check whether a given index is valid on an array of a given shape.

Returns True if an array of shape shape can be indexed by self and False if it would raise IndexError.

>>> from ndindex import ndindex
>>> ndindex(3).isvalid((4,))
True
>>> ndindex(3).isvalid((2,))
False

Note that some indices can never be valid and will raise a IndexError or TypeError if you attempt to construct them.

>>> ndindex((..., 0, ...))
Traceback (most recent call last):
  ...
IndexError: an index can only have a single ellipsis ('...')
>>> ndindex(slice(True))
Traceback (most recent call last):
  ...
TypeError: 'bool' object cannot be interpreted as an integer

See also

NDIndex.newshape

newshape(shape)#

Returns the shape of a[idx.raw], assuming a has shape shape.

shape should be a tuple of ints, or an int, which is equivalent to a 1-D shape.

Raises IndexError if self would be invalid for an array of shape shape.

>>> from ndindex import Slice, Integer, Tuple
>>> shape = (6, 7, 8)
>>> Integer(1).newshape(shape)
(7, 8)
>>> Integer(10).newshape(shape)
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 6
>>> Slice(2, 5).newshape(shape)
(3, 7, 8)
>>> Tuple(0, ..., Slice(1, 3)).newshape(shape)
(7, 2)

See also

NDIndex.isvalid

property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
reduce(shape=None, *, axis=0, negative_int=False)#

Slice.reduce returns a slice that is canonicalized for an array of the given shape, or for any shape if shape is None (the default).

Slice.reduce is a perfect canonicalization, meaning that two slices are equal—for all array shapes if shape=None or for arrays of shape shape otherwise—if and only if they reduce to the same Slice object. Note that ndindex objects do not simplify automatically, and == only does exact equality comparison, so to test that two slices are equal, use slice1.reduce(shape) == slice2.reduce(shape).

  • If shape is None, the following properties hold after calling reduce():

    • start is not None.

    • stop is not None, when possible. The reduced stop can only be None if the original stop is.

    • step is not None.

    • step is as close to 0 as possible.

    • If the slice is always empty, the resulting slice will be Slice(0, 0, 1). However, one should prefer the isempty method to test if a slice is always empty.

    In particular, stop may be None, even after canonicalization with reduce() with no shape. This is because some slices are impossible to represent without None without making assumptions about the array shape. For example, Slice(0, None) cannot be equivalent to a slice with stop != None for all array shapes. To get a slice where the start, stop, and step are always integers, use reduce(shape) with an explicit array shape.

    Note that Slice objects that index a single element are not canonicalized to Integer, because integer indices always remove an axis whereas slices keep the axis. Furthermore, slices cannot raise IndexError except on arrays with shape equal to ().

    >>> from ndindex import Slice
    >>> Slice(10).reduce()
    Slice(0, 10, 1)
    >>> Slice(1, 3, 3).reduce()
    Slice(1, 2, 1)
    
  • If an explicit shape is given, the following properties are true after calling Slice.reduce(shape):

    • start, stop, and step are not None,

    • start is nonnegative.

    • stop is nonnegative whenever possible. In particular, stop is only negative when it has to be to represent the given slice, i.e., a slice with negative step that indexes more than 1 element and indexes the first (index 0) element (in this case, it will be -n - 1 where n is the size of the axis being sliced).

    • stop is as small as possible for positive step or large as possible for negative step.

    • step is as close to 0 as possible.

    • If the slice is empty for the given shape, the resulting slice will be Slice(0, 0, 1). However, one should prefer the isempty method to test if a slice is always empty.

    • If the slice indexes a single element, the resulting slice will be of the form Slice(i, i+1, 1). However, one should prefer using len(s.reduce(shape)) == 1 to test if a slice indexes exactly 1 element.

    • len() gives the true size of the axis for a sliced array of the given shape, and never raises ValueError.

    The axis argument can be used to specify an axis of the shape (by default, axis=0). For convenience, shape can be passed as an integer for a single dimension.

    >>> from ndindex import Slice
    >>> Slice(1, 10).reduce(3)
    Slice(1, 3, 1)
    >>> Slice(-1, 1, -2).reduce(4)
    Slice(3, 4, 1)
    >>> Slice(1, 10, 3).reduce((4, 5), axis=0)
    Slice(1, 2, 1)
    >>> Slice(1, 10, 3).reduce((4, 5), axis=1)
    Slice(1, 5, 3)
    
    >>> s = Slice(2, None)
    >>> len(s)
    Traceback (most recent call last):
    ...
    ValueError: Cannot determine max length of slice
    >>> s.reduce((5,))
    Slice(2, 5, 1)
    >>> len(_)
    3
    
selected_indices(shape, axis=None)#

Return an iterator over all indices that are selected by self on an array of shape shape.

The result is a set of indices i such that [a[i] for i in idx.selected_indices(a.shape)] is all the elements of a[idx]. The indices are all iterated over in C (i.e., row major) order.

>>> from ndindex import Slice, Tuple
>>> idx = Slice(5, 10)
>>> list(idx.selected_indices(20))
[Integer(5), Integer(6), Integer(7), Integer(8), Integer(9)]
>>> idx = Tuple(Slice(5, 10), Slice(0, 2))
>>> list(idx.selected_indices((20, 3)))
[Tuple(5, 0), Tuple(5, 1),
 Tuple(6, 0), Tuple(6, 1),
 Tuple(7, 0), Tuple(7, 1),
 Tuple(8, 0), Tuple(8, 1),
 Tuple(9, 0), Tuple(9, 1)]

To correspond these indices to the elements of a[idx], you can use iter_indices(idx.newshape(shape)), since both iterators iterate the indices in C order.

>>> from ndindex import iter_indices
>>> idx = Tuple(Slice(3, 5), Slice(0, 2))
>>> shape = (5, 5)
>>> import numpy as np
>>> a = np.arange(25).reshape(shape)
>>> for a_idx, (new_idx,) in zip(
...    idx.selected_indices(shape),
...    iter_indices(idx.newshape(shape))):
...        print(a_idx, new_idx, a[a_idx.raw], a[idx.raw][new_idx.raw])
Tuple(3, 0) Tuple(0, 0) 15 15
Tuple(3, 1) Tuple(0, 1) 16 16
Tuple(4, 0) Tuple(1, 0) 20 20
Tuple(4, 1) Tuple(1, 1) 21 21

See also

ndindex.iter_indices

An iterator of indices to select every element for arrays of a given shape.

ndindex.ChunkSize.as_subchunks

A high-level iterator that efficiently gives only those chunks that intersect with a given index

property start#

The start value of the slice.

Note that this may be an integer or None.

property step#

The step of the slice.

Note that this may be a nonzero integer or None.

property stop#

The stop of the slice.

Note that this may be an integer or None.

class ndindex.ellipsis#

Represents an ellipsis index, i.e., ... (or Ellipsis).

Ellipsis indices by themselves return the full array. Inside of a tuple index, an ellipsis skips 0 or more axes of the array so that everything after the ellipsis indexes the last axes of the array. A tuple index can have at most one ellipsis.

For example a[(0, ..., -2)] would index the first element on the first axis, the second-to-last element in the last axis, and include all the axes in between.

>>> from numpy import arange
>>> a = arange(2*3*4).reshape((2, 3, 4))
>>> a
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],
       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])
>>> a[0, ..., -2]
array([ 2,  6, 10])

An ellipsis can go at the beginning of end of a tuple index, and is allowed to match 0 axes.

Note

Unlike the standard Python Ellipsis, ellipsis is the type, not the object (the name is lowercase to avoid conflicting with the built-in). Use ellipsis() or ndindex(...) to create the object. In most ndindex contexts, ... can be used instead of ellipsis(), for instance, when creating a Tuple object. Also unlike Ellipsis, ellipsis() is not singletonized, so you should not use is to compare it. See the document on Type Confusion for more details.

as_subindex(index)#

i.as_subindex(j) produces an index k such that a[j][k] gives all of the elements of a[j] that are also in a[i].

If a[j] is a subset of a[i], then a[j][k] == a[i]. Otherwise, a[j][k] == a[i & j], where i & j is the intersection of i and j, that is, the elements of a that are indexed by both i and j.

For example, in the below diagram, i and j index a subset of the array a. k = i.as_subindex(j) is an index on a[j] that gives the subset of a[j] also included in a[i]:

    +------------ self ------------+
    |                              |
------------------- a -----------------------
       |                                 |
       +------------- index -------------+
       |                           |
       +- self.as_subindex(index) -+

i.as_subindex(j) is currently only implemented when j is a slice with positive steps and nonnegative start and stop, or a Tuple of the same. To use it with slices with negative start or stop, call reduce() with a shape first.

as_subindex can be seen as the left-inverse of composition, that is, if a[i] = a[j][k], then k = i.as_subindex(j), so that k "=" (j^-1)[i] (this only works as a true inverse if j is a subset of i).

Note that due to symmetry, a[j][i.as_subindex(j)] and a[i][j.as_subindex(i)] will give the same subarrays of a, which will be the array of elements indexed by both a[i] and a[j].

i.as_subindex(j) may raise ValueError in the case that the indices i and j do not intersect at all.

Examples

An example usage of as_subindex is to split an index up into subindices of chunks of an array. For example, say a 1-D array a is chunked up into chunks of size N, so that a[0:N], a[N:2*N], [2*N:3*N], etc. are stored separately. Then an index a[i] can be reindexed onto the chunks via i.as_subindex(Slice(0, N)), i.as_subindex(Slice(N, 2*N)), etc.

>>> from ndindex import Slice
>>> i = Slice(5, 15)
>>> j1 = Slice(0, 10)
>>> j2 = Slice(10, 20)
>>> a = list(range(20))
>>> a[i.raw]
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> a[j1.raw]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[j2.raw]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> k1 = i.as_subindex(j1)
>>> k1
Slice(5, 10, 1)
>>> k2 = i.as_subindex(j2)
>>> k2
Slice(0, 5, 1)
>>> a[j1.raw][k1.raw]
[5, 6, 7, 8, 9]
>>> a[j2.raw][k2.raw]
[10, 11, 12, 13, 14]

See also

ndindex.ChunkSize.as_subchunks

a high-level iterator that efficiently gives only those chunks that intersect with a given index

ndindex.ChunkSize.num_subchunks

isempty(shape=None)#

Returns whether self always indexes an empty array

An empty array is an array whose shape contains at least one 0. Note that scalars (arrays with shape ()) are not considered empty.

shape can be None (the default), or an array shape. If it is None, isempty() will return True when self is always empty for any array shape. However, if it gives False, it could still give an empty array for some array shapes, but not all. If you know the shape of the array that will be indexed, you can call idx.isempty(shape) first and the result will be correct for arrays of shape shape. If shape is given and self would raise an IndexError on an array of shape shape, isempty() also raises IndexError.

>>> from ndindex import Tuple, Slice
>>> Tuple(0, slice(0, 1)).isempty()
False
>>> Tuple(0, slice(0, 0)).isempty()
True
>>> Slice(5, 10).isempty()
False
>>> Slice(5, 10).isempty(4)
True
isvalid(shape)#

Check whether a given index is valid on an array of a given shape.

Returns True if an array of shape shape can be indexed by self and False if it would raise IndexError.

>>> from ndindex import ndindex
>>> ndindex(3).isvalid((4,))
True
>>> ndindex(3).isvalid((2,))
False

Note that some indices can never be valid and will raise a IndexError or TypeError if you attempt to construct them.

>>> ndindex((..., 0, ...))
Traceback (most recent call last):
  ...
IndexError: an index can only have a single ellipsis ('...')
>>> ndindex(slice(True))
Traceback (most recent call last):
  ...
TypeError: 'bool' object cannot be interpreted as an integer

See also

NDIndex.newshape

newshape(shape)#

Returns the shape of a[idx.raw], assuming a has shape shape.

shape should be a tuple of ints, or an int, which is equivalent to a 1-D shape.

Raises IndexError if self would be invalid for an array of shape shape.

>>> from ndindex import Slice, Integer, Tuple
>>> shape = (6, 7, 8)
>>> Integer(1).newshape(shape)
(7, 8)
>>> Integer(10).newshape(shape)
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 6
>>> Slice(2, 5).newshape(shape)
(3, 7, 8)
>>> Tuple(0, ..., Slice(1, 3)).newshape(shape)
(7, 2)

See also

NDIndex.isvalid

property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
reduce(shape=None, *, negative_int=False)#

Reduce an ellipsis index

Since an ellipsis by itself always returns the full array unchanged, ellipsis().reduce() returns Tuple() as a canonical form (the index () also always returns an array unchanged).

>>> from ndindex import ellipsis
>>> ellipsis().reduce()
Tuple()
class ndindex.Newaxis#

Represents a np.newaxis (i.e., None) index.

Newaxis adds a shape 1 dimension to the array. If a Newaxis is inside of a tuple index, it adds a shape 1 dimension at that location in the index.

For example, if a has shape (2, 3), then a[newaxis] has shape (1, 2, 3), a[:, newaxis] has shape (2, 1, 3), and so on.

>>> from ndindex import Newaxis
>>> from numpy import arange
>>> a = arange(0,6).reshape(2,3)
>>> a[Newaxis().raw].shape
(1, 2, 3)
>>> a[:, Newaxis().raw, :].shape
(2, 1, 3)

Using Newaxis().raw as an index is equivalent to using numpy.newaxis.

Note

Unlike the NumPy newaxis, Newaxis is the type, not the object (the name is uppercase to avoid conflicting with the NumPy type). Use Newaxis(), ndindex(np.newaxis), or ndindex(None) to create the object. In most ndindex contexts, np.newaxis or None can be used instead of Newaxis(), for instance, when creating a Tuple object. Also unlike None, Newaxis() is not singletonized, so you should not use is to compare it. See the document on Type Confusion for more details.

isempty(shape=None)#

Returns whether self always indexes an empty array

An empty array is an array whose shape contains at least one 0. Note that scalars (arrays with shape ()) are not considered empty.

shape can be None (the default), or an array shape. If it is None, isempty() will return True when self is always empty for any array shape. However, if it gives False, it could still give an empty array for some array shapes, but not all. If you know the shape of the array that will be indexed, you can call idx.isempty(shape) first and the result will be correct for arrays of shape shape. If shape is given and self would raise an IndexError on an array of shape shape, isempty() also raises IndexError.

>>> from ndindex import Tuple, Slice
>>> Tuple(0, slice(0, 1)).isempty()
False
>>> Tuple(0, slice(0, 0)).isempty()
True
>>> Slice(5, 10).isempty()
False
>>> Slice(5, 10).isempty(4)
True
isvalid(shape)#

Check whether a given index is valid on an array of a given shape.

Returns True if an array of shape shape can be indexed by self and False if it would raise IndexError.

>>> from ndindex import ndindex
>>> ndindex(3).isvalid((4,))
True
>>> ndindex(3).isvalid((2,))
False

Note that some indices can never be valid and will raise a IndexError or TypeError if you attempt to construct them.

>>> ndindex((..., 0, ...))
Traceback (most recent call last):
  ...
IndexError: an index can only have a single ellipsis ('...')
>>> ndindex(slice(True))
Traceback (most recent call last):
  ...
TypeError: 'bool' object cannot be interpreted as an integer

See also

NDIndex.newshape

newshape(shape)#

Returns the shape of a[idx.raw], assuming a has shape shape.

shape should be a tuple of ints, or an int, which is equivalent to a 1-D shape.

Raises IndexError if self would be invalid for an array of shape shape.

>>> from ndindex import Slice, Integer, Tuple
>>> shape = (6, 7, 8)
>>> Integer(1).newshape(shape)
(7, 8)
>>> Integer(10).newshape(shape)
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 6
>>> Slice(2, 5).newshape(shape)
(3, 7, 8)
>>> Tuple(0, ..., Slice(1, 3)).newshape(shape)
(7, 2)

See also

NDIndex.isvalid

property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
reduce(shape=None, *, axis=0, negative_int=False)#

Reduce a Newaxis index

There is no other index that is equivalent to a newaxis index by itself, so Newaxis().reduce() always returns Newaxis() unchanged.

>>> from ndindex import Newaxis
>>> Newaxis().reduce()
Newaxis()
class ndindex.Tuple(*args)#

Represents a tuple of single-axis indices.

Valid single axis indices are

(some of the above are not yet implemented)

Tuple(x1, x2, …, xn) represents the index a[x1, x2, …, xn] or, equivalently, a[(x1, x2, …, xn)]. Tuple() with no arguments is the empty tuple index, a[()], which returns a unchanged.

>>> from ndindex import Tuple, Slice
>>> import numpy as np
>>> idx = Tuple(0, Slice(2, 4))
>>> a = np.arange(10).reshape((2, 5))
>>> a
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])
>>> a[0, 2:4]
array([2, 3])
>>> a[idx.raw]
array([2, 3])

Note

Tuple does not represent a tuple, but rather an tuple index. It does not have most methods that tuple has, and should not be used in non-indexing contexts. See the document on Type Confusion for more details.

as_subindex(index)#

i.as_subindex(j) produces an index k such that a[j][k] gives all of the elements of a[j] that are also in a[i].

If a[j] is a subset of a[i], then a[j][k] == a[i]. Otherwise, a[j][k] == a[i & j], where i & j is the intersection of i and j, that is, the elements of a that are indexed by both i and j.

For example, in the below diagram, i and j index a subset of the array a. k = i.as_subindex(j) is an index on a[j] that gives the subset of a[j] also included in a[i]:

    +------------ self ------------+
    |                              |
------------------- a -----------------------
       |                                 |
       +------------- index -------------+
       |                           |
       +- self.as_subindex(index) -+

i.as_subindex(j) is currently only implemented when j is a slice with positive steps and nonnegative start and stop, or a Tuple of the same. To use it with slices with negative start or stop, call reduce() with a shape first.

as_subindex can be seen as the left-inverse of composition, that is, if a[i] = a[j][k], then k = i.as_subindex(j), so that k "=" (j^-1)[i] (this only works as a true inverse if j is a subset of i).

Note that due to symmetry, a[j][i.as_subindex(j)] and a[i][j.as_subindex(i)] will give the same subarrays of a, which will be the array of elements indexed by both a[i] and a[j].

i.as_subindex(j) may raise ValueError in the case that the indices i and j do not intersect at all.

Examples

An example usage of as_subindex is to split an index up into subindices of chunks of an array. For example, say a 1-D array a is chunked up into chunks of size N, so that a[0:N], a[N:2*N], [2*N:3*N], etc. are stored separately. Then an index a[i] can be reindexed onto the chunks via i.as_subindex(Slice(0, N)), i.as_subindex(Slice(N, 2*N)), etc.

>>> from ndindex import Slice
>>> i = Slice(5, 15)
>>> j1 = Slice(0, 10)
>>> j2 = Slice(10, 20)
>>> a = list(range(20))
>>> a[i.raw]
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> a[j1.raw]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[j2.raw]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> k1 = i.as_subindex(j1)
>>> k1
Slice(5, 10, 1)
>>> k2 = i.as_subindex(j2)
>>> k2
Slice(0, 5, 1)
>>> a[j1.raw][k1.raw]
[5, 6, 7, 8, 9]
>>> a[j2.raw][k2.raw]
[10, 11, 12, 13, 14]

See also

ndindex.ChunkSize.as_subchunks

a high-level iterator that efficiently gives only those chunks that intersect with a given index

ndindex.ChunkSize.num_subchunks

broadcast_arrays()#

Broadcast all the array indices in self to a common shape and convert boolean array indices into integer array indices.

The resulting index is equivalent in all contexts where the original index is allowed. However, it is possible for the original index to give an IndexError but for the new index to not, since integer array indices have less stringent shape requirements than boolean array indices. There are also some instances for empty indices (isempty is True) where bounds would be checked before broadcasting but not after.

Any BooleanArray indices are converted to IntegerArray indices. Furthermore, if there are BooleanArray or IntegerArray indices, then any Integer indices are also converted into scalar IntegerArray indices and broadcast. Furthermore, if there are multiple boolean scalar indices (True or False), they are combined into a single one.

Note that array broadcastability is checked in the Tuple constructor, so this method will not raise any exceptions.

This is part of what is performed by expand, but unlike expand, this method does not do any other manipulations, and it does not require a shape.

>>> from ndindex import Tuple
>>> idx = Tuple([[False], [True], [True]], [[4], [5], [5]], -1)
>>> print(idx.broadcast_arrays())
Tuple(IntegerArray([[1 2] [1 2] [1 2]]),
      IntegerArray([[0 0] [0 0] [0 0]]),
      IntegerArray([[4 4] [5 5] [5 5]]),
      IntegerArray([[-1 -1] [-1 -1] [-1 -1]]))

See also

expand

property ellipsis_index#

Give the index i of self.args where the ellipsis is.

If self doesn’t have an ellipsis, it gives len(self.args), since tuple indices without an ellipsis always implicitly end in an ellipsis.

The resulting value i is such that self.args[:i] indexes the beginning axes of an array and self.args[i+1:] indexes the end axes of an array.

>>> from ndindex import Tuple
>>> idx = Tuple(0, 1, ..., 2, 3)
>>> i = idx.ellipsis_index
>>> i
2
>>> idx.args[:i]
(Integer(0), Integer(1))
>>> idx.args[i+1:]
(Integer(2), Integer(3))
>>> Tuple(0, 1).ellipsis_index
2
expand(shape)#

Expand a Tuple index on an array of shape shape

An expanded index is as explicit as possible. Unlike reduce, which tries to simplify an index and remove redundancies, expand() typically makes an index larger.

If self is invalid for the given shape, an IndexError is raised. Otherwise, the returned index satisfies the following:

  • It is always a Tuple.

  • All the elements of the Tuple are recursively reduced.

  • The length of the .args is equal to the length of the shape plus the number of Newaxis indices in self plus 1 if there is a scalar BooleanArray (True or False).

  • The resulting Tuple has no ellipses. If there are axes that would be matched by an ellipsis or an implicit ellipsis at the end of the tuple, Slice(0, n, 1) indices are inserted, where n is the corresponding axis of the shape.

  • Any array indices in self are broadcast together. If self contains array indices (IntegerArray or BooleanArray), then any Integer indices are converted into IntegerArray indices of shape () and broadcast. Note that broadcasting is done in a memory efficient way so that even if the broadcasted shape is large it will not take up more memory than the original.

  • Scalar BooleanArray arguments (True or False) are combined into a single term (the same as with Tuple.reduce()).

  • Non-scalar BooleanArrays are all converted into equivalent IntegerArrays via nonzero() and broadcast.

>>> from ndindex import Tuple, Slice
>>> Slice(None).expand((2, 3))
Tuple(slice(0, 2, 1), slice(0, 3, 1))
>>> idx = Tuple(slice(0, 10), ..., None, -3)
>>> idx.expand((5, 3))
Tuple(slice(0, 5, 1), None, 0)
>>> idx.expand((1, 2, 3))
Tuple(slice(0, 1, 1), slice(0, 2, 1), None, 0)
>>> idx.expand((5,))
Traceback (most recent call last):
...
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed
>>> idx.expand((5, 2))
Traceback (most recent call last):
...
IndexError: index -3 is out of bounds for axis 1 with size 2
>>> idx = Tuple(..., [0, 1], -1)
>>> idx.expand((1, 2, 3))
Tuple(slice(0, 1, 1), [0, 1], [2, 2])
property has_ellipsis#

Returns True if self has an ellipsis

isempty(shape=None)#

Returns whether self always indexes an empty array

An empty array is an array whose shape contains at least one 0. Note that scalars (arrays with shape ()) are not considered empty.

shape can be None (the default), or an array shape. If it is None, isempty() will return True when self is always empty for any array shape. However, if it gives False, it could still give an empty array for some array shapes, but not all. If you know the shape of the array that will be indexed, you can call idx.isempty(shape) first and the result will be correct for arrays of shape shape. If shape is given and self would raise an IndexError on an array of shape shape, isempty() also raises IndexError.

>>> from ndindex import Tuple, Slice
>>> Tuple(0, slice(0, 1)).isempty()
False
>>> Tuple(0, slice(0, 0)).isempty()
True
>>> Slice(5, 10).isempty()
False
>>> Slice(5, 10).isempty(4)
True
newshape(shape)#

Returns the shape of a[idx.raw], assuming a has shape shape.

shape should be a tuple of ints, or an int, which is equivalent to a 1-D shape.

Raises IndexError if self would be invalid for an array of shape shape.

>>> from ndindex import Slice, Integer, Tuple
>>> shape = (6, 7, 8)
>>> Integer(1).newshape(shape)
(7, 8)
>>> Integer(10).newshape(shape)
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 6
>>> Slice(2, 5).newshape(shape)
(3, 7, 8)
>>> Tuple(0, ..., Slice(1, 3)).newshape(shape)
(7, 2)

See also

NDIndex.isvalid

property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
reduce(shape=None, *, negative_int=False)#

Reduce a Tuple index on an array of shape shape

A Tuple with a single argument is always reduced to that single argument (because a[idx,] is the same as a[idx]).

>>> from ndindex import Tuple
>>> Tuple(slice(2, 4)).reduce()
Slice(2, 4, 1)

If an explicit array shape is given, the result will either be IndexError if the index is invalid for the given shape, or an index that is as simple as possible:

  • All the elements of the Tuple are recursively reduced.

  • Any axes that can be merged into an ellipsis are removed. This includes the implicit ellipsis at the end of a Tuple that doesn’t contain any explicit ellipses.

  • Ellipses that don’t match any axes are removed.

  • An ellipsis at the end of the Tuple is removed.

  • Scalar BooleanArray arguments (True or False) are combined into a single term (the first boolean scalar is replaced with the AND of all the boolean scalars).

  • If the resulting Tuple would have a single argument, that argument is returned.

>>> idx = Tuple(0, ..., slice(0, 3))
>>> idx.reduce((5, 4))
Tuple(0, slice(0, 3, 1))
>>> idx.reduce((5, 3))
Integer(0)
>>> idx = Tuple(slice(0, 10), -3)
>>> idx.reduce((5,))
Traceback (most recent call last):
...
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed
>>> idx.reduce((5, 2))
Traceback (most recent call last):
...
IndexError: index -3 is out of bounds for axis 1 with size 2

Note

ndindex presently does not distinguish between scalar objects and rank-0 arrays. It is possible for the original index to produce one and the reduced index to produce the other. In particular, the presence of a redundant ellipsis forces NumPy to return a rank-0 array instead of a scalar.

>>> import numpy as np
>>> a = np.array([0, 1])
>>> Tuple(..., 1).reduce(a.shape)
Integer(1)
>>> a[..., 1]
array(1)
>>> a[1] 
np.int64(1)

See https://github.com/Quansight-Labs/ndindex/issues/22.

selected_indices(shape)#

Return an iterator over all indices that are selected by self on an array of shape shape.

The result is a set of indices i such that [a[i] for i in idx.selected_indices(a.shape)] is all the elements of a[idx]. The indices are all iterated over in C (i.e., row major) order.

>>> from ndindex import Slice, Tuple
>>> idx = Slice(5, 10)
>>> list(idx.selected_indices(20))
[Integer(5), Integer(6), Integer(7), Integer(8), Integer(9)]
>>> idx = Tuple(Slice(5, 10), Slice(0, 2))
>>> list(idx.selected_indices((20, 3)))
[Tuple(5, 0), Tuple(5, 1),
 Tuple(6, 0), Tuple(6, 1),
 Tuple(7, 0), Tuple(7, 1),
 Tuple(8, 0), Tuple(8, 1),
 Tuple(9, 0), Tuple(9, 1)]

To correspond these indices to the elements of a[idx], you can use iter_indices(idx.newshape(shape)), since both iterators iterate the indices in C order.

>>> from ndindex import iter_indices
>>> idx = Tuple(Slice(3, 5), Slice(0, 2))
>>> shape = (5, 5)
>>> import numpy as np
>>> a = np.arange(25).reshape(shape)
>>> for a_idx, (new_idx,) in zip(
...    idx.selected_indices(shape),
...    iter_indices(idx.newshape(shape))):
...        print(a_idx, new_idx, a[a_idx.raw], a[idx.raw][new_idx.raw])
Tuple(3, 0) Tuple(0, 0) 15 15
Tuple(3, 1) Tuple(0, 1) 16 16
Tuple(4, 0) Tuple(1, 0) 20 20
Tuple(4, 1) Tuple(1, 1) 21 21

See also

ndindex.iter_indices

An iterator of indices to select every element for arrays of a given shape.

ndindex.ChunkSize.as_subchunks

A high-level iterator that efficiently gives only those chunks that intersect with a given index

class ndindex.IntegerArray(idx, shape=None, _copy=True)#

Represents an integer array index.

If idx is an n-dimensional integer array with shape s = (s1, ..., sn) and a is any array, a[idx] replaces the first dimension of a with dimensions of size s1, ..., sn, where each entry is indexed according to the entry in idx as an integer index.

Integer arrays can also appear as part of tuple indices. In that case, they replace the axis being indexed. If more than one integer array appears inside of a tuple index, they are broadcast together and iterated as one. Furthermore, if an integer array appears in a tuple index, all integer indices in the tuple are treated as scalar integer arrays and are also broadcast. In general, an Integer index semantically behaves the same as a scalar (shape=()) IntegerArray.

A list of integers may also be used in place of an integer array. Note that NumPy treats a direct list of integers as a tuple index, but this behavior is deprecated and will be replaced with integer array indexing in the future. ndindex always treats lists as arrays.

>>> from ndindex import IntegerArray
>>> import numpy as np
>>> idx = IntegerArray([[0, 1], [1, 2]])
>>> a = np.arange(10)
>>> a[idx.raw]
array([[0, 1],
       [1, 2]])

Note

IntegerArray does not represent an array, but rather an array index. It does not have most methods that numpy.ndarray has, and should not be used in array contexts. See the document on Type Confusion for more details.

dtype#

The dtype of IntegerArray is np.intp, which is typically either np.int32 or np.int64 depending on the platform.

args#

idx.args contains the arguments needed to create idx.

For an ndindex object idx, idx.args is always a tuple such that

type(idx)(*idx.args) == idx

For Tuple indices, the elements of .args are themselves ndindex types. For other types, .args contains raw Python types. Note that .args contains NumPy arrays for IntegerArray and BooleanArray types, so one should always do equality testing or hashing on the ndindex type itself, not its .args.

property array#

Return the NumPy array of self.

This is the same as self.args[0].

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([0, 1]).array
array([0, 1])
>>> BooleanArray([False, True]).array
array([False, True])
as_subindex(index)#

i.as_subindex(j) produces an index k such that a[j][k] gives all of the elements of a[j] that are also in a[i].

If a[j] is a subset of a[i], then a[j][k] == a[i]. Otherwise, a[j][k] == a[i & j], where i & j is the intersection of i and j, that is, the elements of a that are indexed by both i and j.

For example, in the below diagram, i and j index a subset of the array a. k = i.as_subindex(j) is an index on a[j] that gives the subset of a[j] also included in a[i]:

    +------------ self ------------+
    |                              |
------------------- a -----------------------
       |                                 |
       +------------- index -------------+
       |                           |
       +- self.as_subindex(index) -+

i.as_subindex(j) is currently only implemented when j is a slice with positive steps and nonnegative start and stop, or a Tuple of the same. To use it with slices with negative start or stop, call reduce() with a shape first.

as_subindex can be seen as the left-inverse of composition, that is, if a[i] = a[j][k], then k = i.as_subindex(j), so that k "=" (j^-1)[i] (this only works as a true inverse if j is a subset of i).

Note that due to symmetry, a[j][i.as_subindex(j)] and a[i][j.as_subindex(i)] will give the same subarrays of a, which will be the array of elements indexed by both a[i] and a[j].

i.as_subindex(j) may raise ValueError in the case that the indices i and j do not intersect at all.

Examples

An example usage of as_subindex is to split an index up into subindices of chunks of an array. For example, say a 1-D array a is chunked up into chunks of size N, so that a[0:N], a[N:2*N], [2*N:3*N], etc. are stored separately. Then an index a[i] can be reindexed onto the chunks via i.as_subindex(Slice(0, N)), i.as_subindex(Slice(N, 2*N)), etc.

>>> from ndindex import Slice
>>> i = Slice(5, 15)
>>> j1 = Slice(0, 10)
>>> j2 = Slice(10, 20)
>>> a = list(range(20))
>>> a[i.raw]
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> a[j1.raw]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[j2.raw]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> k1 = i.as_subindex(j1)
>>> k1
Slice(5, 10, 1)
>>> k2 = i.as_subindex(j2)
>>> k2
Slice(0, 5, 1)
>>> a[j1.raw][k1.raw]
[5, 6, 7, 8, 9]
>>> a[j2.raw][k2.raw]
[10, 11, 12, 13, 14]

See also

ndindex.ChunkSize.as_subchunks

a high-level iterator that efficiently gives only those chunks that intersect with a given index

ndindex.ChunkSize.num_subchunks

broadcast_arrays()#

Broadcast all the array indices in self to a common shape and convert boolean array indices into integer array indices.

The resulting index is equivalent in all contexts where the original index is allowed. However, it is possible for the original index to give an IndexError but for the new index to not, since integer array indices have less stringent shape requirements than boolean array indices. There are also some instances for empty indices (isempty is True) where bounds would be checked before broadcasting but not after.

Any BooleanArray indices are converted to IntegerArray indices. Furthermore, if there are BooleanArray or IntegerArray indices, then any Integer indices are also converted into scalar IntegerArray indices and broadcast. Furthermore, if there are multiple boolean scalar indices (True or False), they are combined into a single one.

Note that array broadcastability is checked in the Tuple constructor, so this method will not raise any exceptions.

This is part of what is performed by expand, but unlike expand, this method does not do any other manipulations, and it does not require a shape.

>>> from ndindex import Tuple
>>> idx = Tuple([[False], [True], [True]], [[4], [5], [5]], -1)
>>> print(idx.broadcast_arrays())
Tuple(IntegerArray([[1 2] [1 2] [1 2]]),
      IntegerArray([[0 0] [0 0] [0 0]]),
      IntegerArray([[4 4] [5 5] [5 5]]),
      IntegerArray([[-1 -1] [-1 -1] [-1 -1]]))

See also

expand

expand(shape)#

Expand a Tuple index on an array of shape shape

An expanded index is as explicit as possible. Unlike reduce, which tries to simplify an index and remove redundancies, expand() typically makes an index larger.

If self is invalid for the given shape, an IndexError is raised. Otherwise, the returned index satisfies the following:

  • It is always a Tuple.

  • All the elements of the Tuple are recursively reduced.

  • The length of the .args is equal to the length of the shape plus the number of Newaxis indices in self plus 1 if there is a scalar BooleanArray (True or False).

  • The resulting Tuple has no ellipses. If there are axes that would be matched by an ellipsis or an implicit ellipsis at the end of the tuple, Slice(0, n, 1) indices are inserted, where n is the corresponding axis of the shape.

  • Any array indices in self are broadcast together. If self contains array indices (IntegerArray or BooleanArray), then any Integer indices are converted into IntegerArray indices of shape () and broadcast. Note that broadcasting is done in a memory efficient way so that even if the broadcasted shape is large it will not take up more memory than the original.

  • Scalar BooleanArray arguments (True or False) are combined into a single term (the same as with Tuple.reduce()).

  • Non-scalar BooleanArrays are all converted into equivalent IntegerArrays via nonzero() and broadcast.

>>> from ndindex import Tuple, Slice
>>> Slice(None).expand((2, 3))
Tuple(slice(0, 2, 1), slice(0, 3, 1))
>>> idx = Tuple(slice(0, 10), ..., None, -3)
>>> idx.expand((5, 3))
Tuple(slice(0, 5, 1), None, 0)
>>> idx.expand((1, 2, 3))
Tuple(slice(0, 1, 1), slice(0, 2, 1), None, 0)
>>> idx.expand((5,))
Traceback (most recent call last):
...
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed
>>> idx.expand((5, 2))
Traceback (most recent call last):
...
IndexError: index -3 is out of bounds for axis 1 with size 2
>>> idx = Tuple(..., [0, 1], -1)
>>> idx.expand((1, 2, 3))
Tuple(slice(0, 1, 1), [0, 1], [2, 2])
isempty(shape=None)#

Returns whether self always indexes an empty array

An empty array is an array whose shape contains at least one 0. Note that scalars (arrays with shape ()) are not considered empty.

shape can be None (the default), or an array shape. If it is None, isempty() will return True when self is always empty for any array shape. However, if it gives False, it could still give an empty array for some array shapes, but not all. If you know the shape of the array that will be indexed, you can call idx.isempty(shape) first and the result will be correct for arrays of shape shape. If shape is given and self would raise an IndexError on an array of shape shape, isempty() also raises IndexError.

>>> from ndindex import Tuple, Slice
>>> Tuple(0, slice(0, 1)).isempty()
False
>>> Tuple(0, slice(0, 0)).isempty()
True
>>> Slice(5, 10).isempty()
False
>>> Slice(5, 10).isempty(4)
True
isvalid(shape, _axis=0)#

Check whether a given index is valid on an array of a given shape.

Returns True if an array of shape shape can be indexed by self and False if it would raise IndexError.

>>> from ndindex import ndindex
>>> ndindex(3).isvalid((4,))
True
>>> ndindex(3).isvalid((2,))
False

Note that some indices can never be valid and will raise a IndexError or TypeError if you attempt to construct them.

>>> ndindex((..., 0, ...))
Traceback (most recent call last):
  ...
IndexError: an index can only have a single ellipsis ('...')
>>> ndindex(slice(True))
Traceback (most recent call last):
  ...
TypeError: 'bool' object cannot be interpreted as an integer

See also

NDIndex.newshape

property ndim#

Return the number of dimensions of the array of self.

This is the same as self.array.ndim. Note that this is not the same as the number of dimensions of an array that is indexed by self. Use len on newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).ndim
2
>>> BooleanArray([[False], [True]]).ndim
2
newshape(shape)#

Returns the shape of a[idx.raw], assuming a has shape shape.

shape should be a tuple of ints, or an int, which is equivalent to a 1-D shape.

Raises IndexError if self would be invalid for an array of shape shape.

>>> from ndindex import Slice, Integer, Tuple
>>> shape = (6, 7, 8)
>>> Integer(1).newshape(shape)
(7, 8)
>>> Integer(10).newshape(shape)
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 6
>>> Slice(2, 5).newshape(shape)
(3, 7, 8)
>>> Tuple(0, ..., Slice(1, 3)).newshape(shape)
(7, 2)

See also

NDIndex.isvalid

property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
reduce(shape=None, *, axis=0, negative_int=False)#

Reduce an IntegerArray index on an array of shape shape.

The result will either be IndexError if the index is invalid for the given shape, an IntegerArray index where the values are all nonnegative, or, if self is a scalar array index (self.shape == ()), an Integer whose value is nonnegative.

If negative_int is True and a shape is provided, the result will be an IntegerArray with negative entries instead of positive entries.

>>> from ndindex import IntegerArray
>>> idx = IntegerArray([-5, 2])
>>> idx.reduce((3,))
Traceback (most recent call last):
...
IndexError: index -5 is out of bounds for axis 0 with size 3
>>> idx.reduce((9,))
IntegerArray([4, 2])
>>> idx.reduce((9,), negative_int=True)
IntegerArray([-5, -7])
selected_indices(shape, axis=None)#

Return an iterator over all indices that are selected by self on an array of shape shape.

The result is a set of indices i such that [a[i] for i in idx.selected_indices(a.shape)] is all the elements of a[idx]. The indices are all iterated over in C (i.e., row major) order.

>>> from ndindex import Slice, Tuple
>>> idx = Slice(5, 10)
>>> list(idx.selected_indices(20))
[Integer(5), Integer(6), Integer(7), Integer(8), Integer(9)]
>>> idx = Tuple(Slice(5, 10), Slice(0, 2))
>>> list(idx.selected_indices((20, 3)))
[Tuple(5, 0), Tuple(5, 1),
 Tuple(6, 0), Tuple(6, 1),
 Tuple(7, 0), Tuple(7, 1),
 Tuple(8, 0), Tuple(8, 1),
 Tuple(9, 0), Tuple(9, 1)]

To correspond these indices to the elements of a[idx], you can use iter_indices(idx.newshape(shape)), since both iterators iterate the indices in C order.

>>> from ndindex import iter_indices
>>> idx = Tuple(Slice(3, 5), Slice(0, 2))
>>> shape = (5, 5)
>>> import numpy as np
>>> a = np.arange(25).reshape(shape)
>>> for a_idx, (new_idx,) in zip(
...    idx.selected_indices(shape),
...    iter_indices(idx.newshape(shape))):
...        print(a_idx, new_idx, a[a_idx.raw], a[idx.raw][new_idx.raw])
Tuple(3, 0) Tuple(0, 0) 15 15
Tuple(3, 1) Tuple(0, 1) 16 16
Tuple(4, 0) Tuple(1, 0) 20 20
Tuple(4, 1) Tuple(1, 1) 21 21

See also

ndindex.iter_indices

An iterator of indices to select every element for arrays of a given shape.

ndindex.ChunkSize.as_subchunks

A high-level iterator that efficiently gives only those chunks that intersect with a given index

property shape#

Return the shape of the array of self.

This is the same as self.array.shape. Note that this is not the same as the shape of an array that is indexed by self. Use newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).shape
(2, 1)
>>> BooleanArray([[False], [True]]).shape
(2, 1)
property size#

Return the number of elements of the array of self.

This is the same as self.array.size. Note that this is not the same as the number of elements of an array that is indexed by self. Use np.prod on newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).size
2
>>> BooleanArray([[False], [True]]).size
2
class ndindex.BooleanArray(idx, shape=None, _copy=True)#

Represents a boolean array index (also known as a mask).

If idx is an n-dimensional boolean array with shape s = (s1, ..., sn) and a is an array of shape s = (s1, ..., sn, ..., sm), a[idx] replaces the first n dimensions of a with a single dimensions of size np.nonzero(idx), where each entry is included if the corresponding element of idx is True. The axes in the index shape should match the corresponding axes in the array shape or be 0, or the index produces IndexError.

The typical way of creating a mask is to use boolean operations on an array, then index the array with that. For example, if a is an array of integers, a[a > 0] will produces a flat array of the elements of a that are positive.

Some important things to note about boolean array index semantics:

  1. A boolean array index will remove as many dimensions as the index has, and replace them with a single flat dimension which is the size of the number of True elements in the index.

  2. A boolean array index idx works the same as the integer array index np.nonzero(idx). In particular, the elements of the index are always iterated in row-major, C-style order. This does not apply to 0-dimensional boolean indices.

  3. A 0-dimensional boolean index (i.e., just the scalar True or False) can still be thought of as removing 0 dimensions and adding a single dimension of length 1 for True or 0 for False. Hence, if a has shape (s1, ..., sn), then a[True] has shape (1, s1, ..., sn), and a[False] has shape (0, s1, ..., sn).

  4. If a tuple index has multiple boolean arrays, they are broadcast together and iterated as a single array, similar to IntegerArray. If a boolean array index idx is mixed with an integer array index in a tuple index, it is treated like np.nonzero(idx).

A list of booleans may also be used in place of a boolean array. Note that NumPy treats a direct list of integers as a tuple index, but this behavior is deprecated and will be replaced with integer array indexing in the future. ndindex always treats lists as arrays.

>>> from ndindex import BooleanArray
>>> import numpy as np
>>> idx = BooleanArray([[ True,  True],
...                     [ True, False],
...                     [False, False],
...                     [False,  True],
...                     [False, False]])
>>> a = np.arange(10).reshape((5, 2))
>>> a[idx.raw]
array([0, 1, 2, 7])

Note

BooleanArray does not represent an array, but rather an array index. It does not have most methods that numpy.ndarray has, and should not be used in array contexts. See the document on Type Confusion for more details.

dtype#

The dtype of BooleanArray is np.bool_.

args#

idx.args contains the arguments needed to create idx.

For an ndindex object idx, idx.args is always a tuple such that

type(idx)(*idx.args) == idx

For Tuple indices, the elements of .args are themselves ndindex types. For other types, .args contains raw Python types. Note that .args contains NumPy arrays for IntegerArray and BooleanArray types, so one should always do equality testing or hashing on the ndindex type itself, not its .args.

property array#

Return the NumPy array of self.

This is the same as self.args[0].

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([0, 1]).array
array([0, 1])
>>> BooleanArray([False, True]).array
array([False, True])
as_subindex(index)#

i.as_subindex(j) produces an index k such that a[j][k] gives all of the elements of a[j] that are also in a[i].

If a[j] is a subset of a[i], then a[j][k] == a[i]. Otherwise, a[j][k] == a[i & j], where i & j is the intersection of i and j, that is, the elements of a that are indexed by both i and j.

For example, in the below diagram, i and j index a subset of the array a. k = i.as_subindex(j) is an index on a[j] that gives the subset of a[j] also included in a[i]:

    +------------ self ------------+
    |                              |
------------------- a -----------------------
       |                                 |
       +------------- index -------------+
       |                           |
       +- self.as_subindex(index) -+

i.as_subindex(j) is currently only implemented when j is a slice with positive steps and nonnegative start and stop, or a Tuple of the same. To use it with slices with negative start or stop, call reduce() with a shape first.

as_subindex can be seen as the left-inverse of composition, that is, if a[i] = a[j][k], then k = i.as_subindex(j), so that k "=" (j^-1)[i] (this only works as a true inverse if j is a subset of i).

Note that due to symmetry, a[j][i.as_subindex(j)] and a[i][j.as_subindex(i)] will give the same subarrays of a, which will be the array of elements indexed by both a[i] and a[j].

i.as_subindex(j) may raise ValueError in the case that the indices i and j do not intersect at all.

Examples

An example usage of as_subindex is to split an index up into subindices of chunks of an array. For example, say a 1-D array a is chunked up into chunks of size N, so that a[0:N], a[N:2*N], [2*N:3*N], etc. are stored separately. Then an index a[i] can be reindexed onto the chunks via i.as_subindex(Slice(0, N)), i.as_subindex(Slice(N, 2*N)), etc.

>>> from ndindex import Slice
>>> i = Slice(5, 15)
>>> j1 = Slice(0, 10)
>>> j2 = Slice(10, 20)
>>> a = list(range(20))
>>> a[i.raw]
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> a[j1.raw]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[j2.raw]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> k1 = i.as_subindex(j1)
>>> k1
Slice(5, 10, 1)
>>> k2 = i.as_subindex(j2)
>>> k2
Slice(0, 5, 1)
>>> a[j1.raw][k1.raw]
[5, 6, 7, 8, 9]
>>> a[j2.raw][k2.raw]
[10, 11, 12, 13, 14]

See also

ndindex.ChunkSize.as_subchunks

a high-level iterator that efficiently gives only those chunks that intersect with a given index

ndindex.ChunkSize.num_subchunks

broadcast_arrays()#

Broadcast all the array indices in self to a common shape and convert boolean array indices into integer array indices.

The resulting index is equivalent in all contexts where the original index is allowed. However, it is possible for the original index to give an IndexError but for the new index to not, since integer array indices have less stringent shape requirements than boolean array indices. There are also some instances for empty indices (isempty is True) where bounds would be checked before broadcasting but not after.

Any BooleanArray indices are converted to IntegerArray indices. Furthermore, if there are BooleanArray or IntegerArray indices, then any Integer indices are also converted into scalar IntegerArray indices and broadcast. Furthermore, if there are multiple boolean scalar indices (True or False), they are combined into a single one.

Note that array broadcastability is checked in the Tuple constructor, so this method will not raise any exceptions.

This is part of what is performed by expand, but unlike expand, this method does not do any other manipulations, and it does not require a shape.

>>> from ndindex import Tuple
>>> idx = Tuple([[False], [True], [True]], [[4], [5], [5]], -1)
>>> print(idx.broadcast_arrays())
Tuple(IntegerArray([[1 2] [1 2] [1 2]]),
      IntegerArray([[0 0] [0 0] [0 0]]),
      IntegerArray([[4 4] [5 5] [5 5]]),
      IntegerArray([[-1 -1] [-1 -1] [-1 -1]]))

See also

expand

property count_nonzero#

Returns the number of elements indexed by self.

In general, if shapes match, when indexed by self, the first n dimensions of an array are replaced with a single dimension of size count_nonzero, where n is self.shape.

This is the same as np.count_nonzero(self.array). Note, to get the shape of an array indexed by self, use newshape(), not this method.

>>> from ndindex import BooleanArray
>>> BooleanArray([True, False, True]).count_nonzero
2
expand(shape)#

Expand a Tuple index on an array of shape shape

An expanded index is as explicit as possible. Unlike reduce, which tries to simplify an index and remove redundancies, expand() typically makes an index larger.

If self is invalid for the given shape, an IndexError is raised. Otherwise, the returned index satisfies the following:

  • It is always a Tuple.

  • All the elements of the Tuple are recursively reduced.

  • The length of the .args is equal to the length of the shape plus the number of Newaxis indices in self plus 1 if there is a scalar BooleanArray (True or False).

  • The resulting Tuple has no ellipses. If there are axes that would be matched by an ellipsis or an implicit ellipsis at the end of the tuple, Slice(0, n, 1) indices are inserted, where n is the corresponding axis of the shape.

  • Any array indices in self are broadcast together. If self contains array indices (IntegerArray or BooleanArray), then any Integer indices are converted into IntegerArray indices of shape () and broadcast. Note that broadcasting is done in a memory efficient way so that even if the broadcasted shape is large it will not take up more memory than the original.

  • Scalar BooleanArray arguments (True or False) are combined into a single term (the same as with Tuple.reduce()).

  • Non-scalar BooleanArrays are all converted into equivalent IntegerArrays via nonzero() and broadcast.

>>> from ndindex import Tuple, Slice
>>> Slice(None).expand((2, 3))
Tuple(slice(0, 2, 1), slice(0, 3, 1))
>>> idx = Tuple(slice(0, 10), ..., None, -3)
>>> idx.expand((5, 3))
Tuple(slice(0, 5, 1), None, 0)
>>> idx.expand((1, 2, 3))
Tuple(slice(0, 1, 1), slice(0, 2, 1), None, 0)
>>> idx.expand((5,))
Traceback (most recent call last):
...
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed
>>> idx.expand((5, 2))
Traceback (most recent call last):
...
IndexError: index -3 is out of bounds for axis 1 with size 2
>>> idx = Tuple(..., [0, 1], -1)
>>> idx.expand((1, 2, 3))
Tuple(slice(0, 1, 1), [0, 1], [2, 2])
isempty(shape=None)#

Returns whether self always indexes an empty array

An empty array is an array whose shape contains at least one 0. Note that scalars (arrays with shape ()) are not considered empty.

shape can be None (the default), or an array shape. If it is None, isempty() will return True when self is always empty for any array shape. However, if it gives False, it could still give an empty array for some array shapes, but not all. If you know the shape of the array that will be indexed, you can call idx.isempty(shape) first and the result will be correct for arrays of shape shape. If shape is given and self would raise an IndexError on an array of shape shape, isempty() also raises IndexError.

>>> from ndindex import Tuple, Slice
>>> Tuple(0, slice(0, 1)).isempty()
False
>>> Tuple(0, slice(0, 0)).isempty()
True
>>> Slice(5, 10).isempty()
False
>>> Slice(5, 10).isempty(4)
True
isvalid(shape, _axis=0)#

Check whether a given index is valid on an array of a given shape.

Returns True if an array of shape shape can be indexed by self and False if it would raise IndexError.

>>> from ndindex import ndindex
>>> ndindex(3).isvalid((4,))
True
>>> ndindex(3).isvalid((2,))
False

Note that some indices can never be valid and will raise a IndexError or TypeError if you attempt to construct them.

>>> ndindex((..., 0, ...))
Traceback (most recent call last):
  ...
IndexError: an index can only have a single ellipsis ('...')
>>> ndindex(slice(True))
Traceback (most recent call last):
  ...
TypeError: 'bool' object cannot be interpreted as an integer

See also

NDIndex.newshape

property ndim#

Return the number of dimensions of the array of self.

This is the same as self.array.ndim. Note that this is not the same as the number of dimensions of an array that is indexed by self. Use len on newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).ndim
2
>>> BooleanArray([[False], [True]]).ndim
2
newshape(shape)#

Returns the shape of a[idx.raw], assuming a has shape shape.

shape should be a tuple of ints, or an int, which is equivalent to a 1-D shape.

Raises IndexError if self would be invalid for an array of shape shape.

>>> from ndindex import Slice, Integer, Tuple
>>> shape = (6, 7, 8)
>>> Integer(1).newshape(shape)
(7, 8)
>>> Integer(10).newshape(shape)
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 6
>>> Slice(2, 5).newshape(shape)
(3, 7, 8)
>>> Tuple(0, ..., Slice(1, 3)).newshape(shape)
(7, 2)

See also

NDIndex.isvalid

property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
reduce(shape=None, *, axis=0, negative_int=False)#

Reduce a BooleanArray index on an array of shape shape.

The result will either be IndexError if the index is invalid for the given shape, or a BooleanArray index. Presently, no simplifications are done for BooleanArray: if reduce() does not produce an IndexArray the index returned will be the same as self.

>>> from ndindex import BooleanArray
>>> idx = BooleanArray([True, False])
>>> idx.reduce((3,))
Traceback (most recent call last):
...
IndexError: boolean index did not match indexed array along axis 0; size of axis is 3 but size of corresponding boolean axis is 2
>>> idx.reduce((2,))
BooleanArray([True, False])
selected_indices(shape, axis=0)#

Return an iterator over all indices that are selected by self on an array of shape shape.

The result is a set of indices i such that [a[i] for i in idx.selected_indices(a.shape)] is all the elements of a[idx]. The indices are all iterated over in C (i.e., row major) order.

>>> from ndindex import Slice, Tuple
>>> idx = Slice(5, 10)
>>> list(idx.selected_indices(20))
[Integer(5), Integer(6), Integer(7), Integer(8), Integer(9)]
>>> idx = Tuple(Slice(5, 10), Slice(0, 2))
>>> list(idx.selected_indices((20, 3)))
[Tuple(5, 0), Tuple(5, 1),
 Tuple(6, 0), Tuple(6, 1),
 Tuple(7, 0), Tuple(7, 1),
 Tuple(8, 0), Tuple(8, 1),
 Tuple(9, 0), Tuple(9, 1)]

To correspond these indices to the elements of a[idx], you can use iter_indices(idx.newshape(shape)), since both iterators iterate the indices in C order.

>>> from ndindex import iter_indices
>>> idx = Tuple(Slice(3, 5), Slice(0, 2))
>>> shape = (5, 5)
>>> import numpy as np
>>> a = np.arange(25).reshape(shape)
>>> for a_idx, (new_idx,) in zip(
...    idx.selected_indices(shape),
...    iter_indices(idx.newshape(shape))):
...        print(a_idx, new_idx, a[a_idx.raw], a[idx.raw][new_idx.raw])
Tuple(3, 0) Tuple(0, 0) 15 15
Tuple(3, 1) Tuple(0, 1) 16 16
Tuple(4, 0) Tuple(1, 0) 20 20
Tuple(4, 1) Tuple(1, 1) 21 21

See also

ndindex.iter_indices

An iterator of indices to select every element for arrays of a given shape.

ndindex.ChunkSize.as_subchunks

A high-level iterator that efficiently gives only those chunks that intersect with a given index

property shape#

Return the shape of the array of self.

This is the same as self.array.shape. Note that this is not the same as the shape of an array that is indexed by self. Use newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).shape
(2, 1)
>>> BooleanArray([[False], [True]]).shape
(2, 1)
property size#

Return the number of elements of the array of self.

This is the same as self.array.size. Note that this is not the same as the number of elements of an array that is indexed by self. Use np.prod on newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).size
2
>>> BooleanArray([[False], [True]]).size
2

Index Helpers#

The functions here are helpers for working with indices that aren’t methods of the index objects.

ndindex.iter_indices(*shapes, skip_axes=(), _debug=False)#

Iterate indices for every element of an arrays of shape shapes.

Each shape in shapes should be a shape tuple, which are broadcast compatible along the non-skipped axes. Each iteration step will produce a tuple of indices, one for each shape, which would correspond to the same elements if the arrays of the given shapes were first broadcast together.

This is a generalization of the NumPy np.ndindex() function (which otherwise has no relation). np.ndindex() only iterates indices for a single shape, whereas iter_indices() supports generating indices for multiple broadcast compatible shapes at once. This is equivalent to first broadcasting the arrays then generating indices for the single broadcasted shape.

Additionally, this function supports the ability to skip axes of the shapes using skip_axes. These axes will be fully sliced in each index. The remaining axes will be indexed one element at a time with integer indices.

skip_axes should be a tuple of axes to skip or a list of tuples of axes to skip. If it is a single tuple, it applies to all shapes. Otherwise, each tuple applies to each shape respectively. It can use negative integers, e.g., skip_axes=(-1,) will skip the last axis. The order of the axes in skip_axes does not matter. Mixing negative and nonnegative skip axes is supported, but the skip axes must refer to unique dimensions for each shape.

The axes in skip_axes refer to the shapes before broadcasting (if you want to refer to the axes after broadcasting, either broadcast the shapes and arrays first, or refer to the axes using negative integers). For example, iter_indices((10, 2), (20, 1, 2), skip_axes=(0,)) will skip the size 10 axis of (10, 2) and the size 20 axis of (20, 1, 2). The result is two sets of indices, one for each element of the non-skipped dimensions:

>>> from ndindex import iter_indices
>>> for idx1, idx2 in iter_indices((10, 2), (20, 1, 2), skip_axes=(0,)):
...     print(idx1, idx2)
Tuple(slice(None, None, None), 0) Tuple(slice(None, None, None), 0, 0)
Tuple(slice(None, None, None), 1) Tuple(slice(None, None, None), 0, 1)

The skipped axes do not themselves need to be broadcast compatible, but the shapes with all the skipped axes removed should be broadcast compatible.

For example, suppose a is an array with shape (3, 2, 4, 4), which we wish to think of as a (3, 2) stack of 4 x 4 matrices. We can generate an iterator for each matrix in the “stack” with iter_indices((3, 2, 4, 4), skip_axes=(-1, -2)):

>>> for idx in iter_indices((3, 2, 4, 4), skip_axes=(-1, -2)):
...     print(idx)
(Tuple(0, 0, slice(None, None, None), slice(None, None, None)),)
(Tuple(0, 1, slice(None, None, None), slice(None, None, None)),)
(Tuple(1, 0, slice(None, None, None), slice(None, None, None)),)
(Tuple(1, 1, slice(None, None, None), slice(None, None, None)),)
(Tuple(2, 0, slice(None, None, None), slice(None, None, None)),)
(Tuple(2, 1, slice(None, None, None), slice(None, None, None)),)

Note

The iterates of iter_indices are always a tuple, even if only a single shape is provided (one could instead use for idx, in iter_indices(...) above).

As another example, say a is shape (1, 3) and b is shape (2, 1), and we want to generate indices for every value of the broadcasted operation a + b. We can do this by using a[idx1.raw] + b[idx2.raw] for every idx1 and idx2 as below:

>>> import numpy as np
>>> a = np.arange(3).reshape((1, 3))
>>> b = np.arange(100, 111, 10).reshape((2, 1))
>>> a
array([[0, 1, 2]])
>>> b
array([[100],
       [110]])
>>> for idx1, idx2 in iter_indices((1, 3), (2, 1)):
...     print(f"{idx1 = }; {idx2 = }; {(a[idx1.raw], b[idx2.raw]) = }") 
idx1 = Tuple(0, 0); idx2 = Tuple(0, 0); (a[idx1.raw], b[idx2.raw]) = (np.int64(0), np.int64(100))
idx1 = Tuple(0, 1); idx2 = Tuple(0, 0); (a[idx1.raw], b[idx2.raw]) = (np.int64(1), np.int64(100))
idx1 = Tuple(0, 2); idx2 = Tuple(0, 0); (a[idx1.raw], b[idx2.raw]) = (np.int64(2), np.int64(100))
idx1 = Tuple(0, 0); idx2 = Tuple(1, 0); (a[idx1.raw], b[idx2.raw]) = (np.int64(0), np.int64(110))
idx1 = Tuple(0, 1); idx2 = Tuple(1, 0); (a[idx1.raw], b[idx2.raw]) = (np.int64(1), np.int64(110))
idx1 = Tuple(0, 2); idx2 = Tuple(1, 0); (a[idx1.raw], b[idx2.raw]) = (np.int64(2), np.int64(110))
>>> a + b
array([[100, 101, 102],
       [110, 111, 112]])

To include an index into the final broadcasted array, you can simply include the final broadcasted shape as one of the shapes (the function broadcast_shapes() is useful here).

>>> np.broadcast_shapes((1, 3), (2, 1))
(2, 3)
>>> for idx1, idx2, broadcasted_idx in iter_indices((1, 3), (2, 1), (2, 3)):
...     print(broadcasted_idx)
Tuple(0, 0)
Tuple(0, 1)
Tuple(0, 2)
Tuple(1, 0)
Tuple(1, 1)
Tuple(1, 2)
ndindex.broadcast_shapes(*shapes, skip_axes=())#

Broadcast the input shapes shapes to a single shape.

This is the same as np.broadcast_shapes(), except is also supports skipping axes in the shape with skip_axes.

skip_axes can be a tuple of integers which apply to all shapes, or a list of tuples of integers, one for each shape, which apply to each respective shape. The skip_axes argument works the same as in iter_indices(). See its docstring for more details.

If the shapes are not broadcast compatible (excluding skip_axes), BroadcastError is raised.

>>> from ndindex import broadcast_shapes
>>> broadcast_shapes((2, 3), (3,), (4, 2, 1))
(4, 2, 3)
>>> broadcast_shapes((2, 3), (5,), (4, 2, 1))
Traceback (most recent call last):
...
ndindex.shapetools.BroadcastError: shape mismatch: objects cannot be broadcast to a single shape.  Mismatch is between arg 0 with shape (2, 3) and arg 1 with shape (5,).

Axes in skip_axes apply to each shape before being broadcasted. Each shape will be broadcasted together with these axes removed. The dimensions in skip_axes do not need to be equal or broadcast compatible with one another. The final broadcasted shape be the result of broadcasting all the non-skip axes.

>>> broadcast_shapes((10, 3, 2), (2, 20), skip_axes=[(0,), (1,)])
(3, 2)

Exceptions#

These are some custom exceptions that are raised by a few functions in ndindex. Note that most functions in ndindex will raise IndexError (if the index would be invalid), or TypeError or ValueError (if the input types or values are incorrect).

exception ndindex.BroadcastError(arg1, shape1, arg2, shape2)#

Exception raised by iter_indices() and broadcast_shapes() when the input shapes are not broadcast compatible.

exception ndindex.AxisError(axis, ndim)#

Exception raised by iter_indices() and broadcast_shapes() when the skip_axes argument is out-of-bounds.

This is used instead of the NumPy exception of the same name so that iter_indices does not need to depend on NumPy.

Chunking#

ndindex contains objects to represent chunking an array.

class ndindex.ChunkSize(chunk_size)#

Represents a chunk size tuple.

A chunk size is a tuple of length n where each element is either a positive integer or None. It represents a chunking of an array with n dimensions, where each corresponding dimension is chunked by the corresponding chunk size, or not chunked for None (note, None chunks are currently not yet implemented).

For example, given a 3 dimensional chunk size of (20, 20, None) and an array of shape (40, 30, 10), the array would be split into four chunks, corresponding to the indices 0:20,0:20,:, 0:20,20:30,:, 20:40,0:20,:, and 20:40,20:30,:. Note that the size of a chunk may be less than the total chunk size if the array shape is not a multiple of the chunk size in a given dimension.

ChunkSize behaves like a tuple. For example, chunk_size[0] gives the first chunk dimension, and len(chunk_size) gives the number of dimensions of a chunk. Also, the input to ChunkSize should be a tuple, just as with the tuple constructor, even for single dimensional chunk sizes.

>>> from ndindex import ChunkSize
>>> ChunkSize((20, 30, 40))
ChunkSize((20, 30, 40))
>>> ChunkSize((2**12,))
ChunkSize((4096,))
args#

idx.args contains the arguments needed to create idx.

For an ndindex object idx, idx.args is always a tuple such that

type(idx)(*idx.args) == idx

For Tuple indices, the elements of .args are themselves ndindex types. For other types, .args contains raw Python types. Note that .args contains NumPy arrays for IntegerArray and BooleanArray types, so one should always do equality testing or hashing on the ndindex type itself, not its .args.

as_subchunks(idx, shape, *, _force_slow=None)#

Split an index idx on an array of shape shape into subchunk indices.

Yields indices c, where c is an index for the chunk that should be sliced. Only those c for which idx includes at least one element are yielded.

That is to say, for each c index yielded, a[c][idx.as_subindex(c)] will give those elements of a[idx] that are part of the c chunk, and together they give all the elements of a[idx]. See also the docstring of as_subindex().

This method is roughly equivalent to

def as_subchunks(self, idx, shape):
    for c in self.indices(shape):
        try:
            index = idx.as_subindex(c)
        except ValueError:
            # as_subindex raises ValueError in some cases when the
            # indices do not intersect (see the docstring of
            # as_subindex())
            continue

        if not index.isempty(self):
            # Yield those c for which idx.as_subindex(c) is nonempty
            yield c

except it is more efficient.

>>> from ndindex import ChunkSize, Tuple
>>> idx = Tuple(slice(5, 15), 0)
>>> shape = (20, 20)
>>> chunk_size = ChunkSize((10, 10))
>>> for c in chunk_size.as_subchunks(idx, shape):
...     print(c)
...     print('    ', idx.as_subindex(c))
Tuple(slice(0, 10, 1), slice(0, 10, 1))
    Tuple(slice(5, 10, 1), 0)
Tuple(slice(10, 20, 1), slice(0, 10, 1))
    Tuple(slice(0, 5, 1), 0)
containing_block(idx, shape)#

Compute the index for the smallest contiguous block of chunks that contains idx on an array of shape shape.

A block is a subset of an array that is contiguous in all dimensions and is aligned along the chunk size. A block index is always of the form (Slice(k1, m1), Slice(k2, m2), …, Slice(kn, mn)) where n is the number of dimensions in the chunk size, and the ki and mi are multiples of the corresponding chunk dimension (the mi may be truncated to the shape).

For example, given a chunk size of (10, 15), an example block might be (Slice(0, 20), Slice(30, 45)). Such a block would be the smallest block that contains the index (Slice(0, 12), 40), for example.

>>> from ndindex import ChunkSize
>>> chunk_size = ChunkSize((10, 15))
>>> idx = (slice(0, 12), 40)
>>> shape = (100, 100)
>>> block = chunk_size.containing_block(idx, shape)
>>> block
Tuple(slice(0, 20, 1), slice(30, 45, 1))

The method as_subchunks() can be used on the block to determine which chunks are contained in it, and num_subchunks() to determine how many:

>>> chunk_size.num_subchunks(block, shape)
2
>>> for c in chunk_size.as_subchunks(block, shape):
...     print(c)
Tuple(slice(0, 10, 1), slice(30, 45, 1))
Tuple(slice(10, 20, 1), slice(30, 45, 1))

In this example, chunk_size.as_subchunk(block, shape) and chunk_size.as_subchunks(idx, shape) are the same, but in general, a block may overlap with more chunks than the original index because the block is contiguous.

indices(shape)#

Yield a set of ndindex indices for the chunks on an array of shape shape.

shape should have the same number of dimensions as self. If the shape is not a multiple of the chunk size, some chunks will be truncated, so that len(idx.args[i]) can be used to get the size of an indexed axis.

For example, if a has shape (10, 19) and is chunked into chunks of shape (5, 5):

>>> from ndindex import ChunkSize
>>> chunk_size = ChunkSize((5, 5))
>>> for idx in chunk_size.indices((10, 19)):
...     print(idx)
Tuple(slice(0, 5, 1), slice(0, 5, 1))
Tuple(slice(0, 5, 1), slice(5, 10, 1))
Tuple(slice(0, 5, 1), slice(10, 15, 1))
Tuple(slice(0, 5, 1), slice(15, 19, 1))
Tuple(slice(5, 10, 1), slice(0, 5, 1))
Tuple(slice(5, 10, 1), slice(5, 10, 1))
Tuple(slice(5, 10, 1), slice(10, 15, 1))
Tuple(slice(5, 10, 1), slice(15, 19, 1))
num_chunks(shape)#

Give the number of chunks for the given shape.

This is the same as len(list(self.indices(shape))), but much faster. shape must have the same number of dimensions as self.

>>> from ndindex import ChunkSize
>>> chunk_size = ChunkSize((10, 10, 10))
>>> shape = (10000, 10000, 10000)
>>> # len(list(chunk_size.indices(shape))) would be very slow, as
>>> # would have to iterate all 1 billion chunks
>>> chunk_size.num_chunks(shape)
1000000000
num_subchunks(idx, shape)#

Give the number of chunks indexed by idx on an array of shape shape.

This is equivalent to len(list(self.as_subindex(idx, shape))), but more efficient.

>>> from ndindex import ChunkSize, Tuple
>>> idx = Tuple(slice(5, 15), 0)
>>> shape = (20, 20)
>>> chunk_size = ChunkSize((10, 10))
>>> chunk_size.num_subchunks(idx, shape)
2

Internal API#

These classes are only intended for internal use in ndindex. They shouldn’t relied on as they may be removed or changed.

class ndindex.ndindex.ImmutableObject(*args, **kwargs)#

Base class for immutable objects.

Subclasses of this class are immutable objects. They all have the .args attribute, which gives the full necessary data to recreate the class, via,

type(obj)(*obj.args) == obj

Note: subclasses that specifically represent indices should subclass NDIndex instead.

All classes that subclass ImmutableObject should define the _typecheck method. _typecheck(self, *args) should do type checking and basic type canonicalization, and either return a tuple of the new arguments for the class or raise an exception. Type checking means it should raise exceptions for input types that are never semantically meaningful for numpy arrays, for example, floating point indices, using the same exceptions as numpy where possible. Basic type canonicalization means, for instance, converting integers into int using operator.index(). All other canonicalization should be done in the reduce() method. The ImmutableObject base constructor will automatically set .args to the arguments returned by this method. Classes should always be able to recreate themselves with .args, i.e., type(obj)(*obj.args) == obj should always hold.

See also

NDIndex

args#

idx.args contains the arguments needed to create idx.

For an ndindex object idx, idx.args is always a tuple such that

type(idx)(*idx.args) == idx

For Tuple indices, the elements of .args are themselves ndindex types. For other types, .args contains raw Python types. Note that .args contains NumPy arrays for IntegerArray and BooleanArray types, so one should always do equality testing or hashing on the ndindex type itself, not its .args.

class ndindex.ndindex.NDIndex(*args, **kwargs)#

Represents an index into an nd-array (i.e., a numpy array).

This is a base class for all ndindex types. All types that subclass this class should redefine the following methods

  • _typecheck(self, *args). See the docstring of ImmutableObject.

  • raw (a @property method) should return the raw index that can be passed as an index to a numpy array.

In addition other methods should be defined as necessary.

  • __len__ should return the largest possible shape of an axis sliced by the index (for single-axis indices), or raise ValueError if no such maximum exists.

  • reduce(shape=None) should reduce an index to an equivalent form for arrays of shape shape, or raise an IndexError. The error messages should match numpy as much as possible. The class of the equivalent index may be different. If shape is None, it should return a canonical form that is equivalent for all array shapes (assuming no IndexErrors).

The methods __init__ and __eq__ should not be overridden. Equality (and hashability) on NDIndex subclasses is determined by equality of types and .args. Equivalent indices should not attempt to redefine equality. Rather they should define canonicalization via reduce(). __hash__ is defined so that the hash matches the hash of .raw. If .raw is unhashable, __hash__ should be overridden to use hash(self.args).

See also

ImmutableObject

as_subindex(index)#

i.as_subindex(j) produces an index k such that a[j][k] gives all of the elements of a[j] that are also in a[i].

If a[j] is a subset of a[i], then a[j][k] == a[i]. Otherwise, a[j][k] == a[i & j], where i & j is the intersection of i and j, that is, the elements of a that are indexed by both i and j.

For example, in the below diagram, i and j index a subset of the array a. k = i.as_subindex(j) is an index on a[j] that gives the subset of a[j] also included in a[i]:

    +------------ self ------------+
    |                              |
------------------- a -----------------------
       |                                 |
       +------------- index -------------+
       |                           |
       +- self.as_subindex(index) -+

i.as_subindex(j) is currently only implemented when j is a slice with positive steps and nonnegative start and stop, or a Tuple of the same. To use it with slices with negative start or stop, call reduce() with a shape first.

as_subindex can be seen as the left-inverse of composition, that is, if a[i] = a[j][k], then k = i.as_subindex(j), so that k "=" (j^-1)[i] (this only works as a true inverse if j is a subset of i).

Note that due to symmetry, a[j][i.as_subindex(j)] and a[i][j.as_subindex(i)] will give the same subarrays of a, which will be the array of elements indexed by both a[i] and a[j].

i.as_subindex(j) may raise ValueError in the case that the indices i and j do not intersect at all.

Examples

An example usage of as_subindex is to split an index up into subindices of chunks of an array. For example, say a 1-D array a is chunked up into chunks of size N, so that a[0:N], a[N:2*N], [2*N:3*N], etc. are stored separately. Then an index a[i] can be reindexed onto the chunks via i.as_subindex(Slice(0, N)), i.as_subindex(Slice(N, 2*N)), etc.

>>> from ndindex import Slice
>>> i = Slice(5, 15)
>>> j1 = Slice(0, 10)
>>> j2 = Slice(10, 20)
>>> a = list(range(20))
>>> a[i.raw]
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> a[j1.raw]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> a[j2.raw]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> k1 = i.as_subindex(j1)
>>> k1
Slice(5, 10, 1)
>>> k2 = i.as_subindex(j2)
>>> k2
Slice(0, 5, 1)
>>> a[j1.raw][k1.raw]
[5, 6, 7, 8, 9]
>>> a[j2.raw][k2.raw]
[10, 11, 12, 13, 14]

See also

ndindex.ChunkSize.as_subchunks

a high-level iterator that efficiently gives only those chunks that intersect with a given index

ndindex.ChunkSize.num_subchunks

broadcast_arrays()#

Broadcast all the array indices in self to a common shape and convert boolean array indices into integer array indices.

The resulting index is equivalent in all contexts where the original index is allowed. However, it is possible for the original index to give an IndexError but for the new index to not, since integer array indices have less stringent shape requirements than boolean array indices. There are also some instances for empty indices (isempty is True) where bounds would be checked before broadcasting but not after.

Any BooleanArray indices are converted to IntegerArray indices. Furthermore, if there are BooleanArray or IntegerArray indices, then any Integer indices are also converted into scalar IntegerArray indices and broadcast. Furthermore, if there are multiple boolean scalar indices (True or False), they are combined into a single one.

Note that array broadcastability is checked in the Tuple constructor, so this method will not raise any exceptions.

This is part of what is performed by expand, but unlike expand, this method does not do any other manipulations, and it does not require a shape.

>>> from ndindex import Tuple
>>> idx = Tuple([[False], [True], [True]], [[4], [5], [5]], -1)
>>> print(idx.broadcast_arrays())
Tuple(IntegerArray([[1 2] [1 2] [1 2]]),
      IntegerArray([[0 0] [0 0] [0 0]]),
      IntegerArray([[4 4] [5 5] [5 5]]),
      IntegerArray([[-1 -1] [-1 -1] [-1 -1]]))

See also

expand

expand(shape)#

Expand a Tuple index on an array of shape shape

An expanded index is as explicit as possible. Unlike reduce, which tries to simplify an index and remove redundancies, expand() typically makes an index larger.

If self is invalid for the given shape, an IndexError is raised. Otherwise, the returned index satisfies the following:

  • It is always a Tuple.

  • All the elements of the Tuple are recursively reduced.

  • The length of the .args is equal to the length of the shape plus the number of Newaxis indices in self plus 1 if there is a scalar BooleanArray (True or False).

  • The resulting Tuple has no ellipses. If there are axes that would be matched by an ellipsis or an implicit ellipsis at the end of the tuple, Slice(0, n, 1) indices are inserted, where n is the corresponding axis of the shape.

  • Any array indices in self are broadcast together. If self contains array indices (IntegerArray or BooleanArray), then any Integer indices are converted into IntegerArray indices of shape () and broadcast. Note that broadcasting is done in a memory efficient way so that even if the broadcasted shape is large it will not take up more memory than the original.

  • Scalar BooleanArray arguments (True or False) are combined into a single term (the same as with Tuple.reduce()).

  • Non-scalar BooleanArrays are all converted into equivalent IntegerArrays via nonzero() and broadcast.

>>> from ndindex import Tuple, Slice
>>> Slice(None).expand((2, 3))
Tuple(slice(0, 2, 1), slice(0, 3, 1))
>>> idx = Tuple(slice(0, 10), ..., None, -3)
>>> idx.expand((5, 3))
Tuple(slice(0, 5, 1), None, 0)
>>> idx.expand((1, 2, 3))
Tuple(slice(0, 1, 1), slice(0, 2, 1), None, 0)
>>> idx.expand((5,))
Traceback (most recent call last):
...
IndexError: too many indices for array: array is 1-dimensional, but 2 were indexed
>>> idx.expand((5, 2))
Traceback (most recent call last):
...
IndexError: index -3 is out of bounds for axis 1 with size 2
>>> idx = Tuple(..., [0, 1], -1)
>>> idx.expand((1, 2, 3))
Tuple(slice(0, 1, 1), [0, 1], [2, 2])
isempty(shape=None)#

Returns whether self always indexes an empty array

An empty array is an array whose shape contains at least one 0. Note that scalars (arrays with shape ()) are not considered empty.

shape can be None (the default), or an array shape. If it is None, isempty() will return True when self is always empty for any array shape. However, if it gives False, it could still give an empty array for some array shapes, but not all. If you know the shape of the array that will be indexed, you can call idx.isempty(shape) first and the result will be correct for arrays of shape shape. If shape is given and self would raise an IndexError on an array of shape shape, isempty() also raises IndexError.

>>> from ndindex import Tuple, Slice
>>> Tuple(0, slice(0, 1)).isempty()
False
>>> Tuple(0, slice(0, 0)).isempty()
True
>>> Slice(5, 10).isempty()
False
>>> Slice(5, 10).isempty(4)
True
isvalid(shape)#

Check whether a given index is valid on an array of a given shape.

Returns True if an array of shape shape can be indexed by self and False if it would raise IndexError.

>>> from ndindex import ndindex
>>> ndindex(3).isvalid((4,))
True
>>> ndindex(3).isvalid((2,))
False

Note that some indices can never be valid and will raise a IndexError or TypeError if you attempt to construct them.

>>> ndindex((..., 0, ...))
Traceback (most recent call last):
  ...
IndexError: an index can only have a single ellipsis ('...')
>>> ndindex(slice(True))
Traceback (most recent call last):
  ...
TypeError: 'bool' object cannot be interpreted as an integer

See also

NDIndex.newshape

newshape(shape)#

Returns the shape of a[idx.raw], assuming a has shape shape.

shape should be a tuple of ints, or an int, which is equivalent to a 1-D shape.

Raises IndexError if self would be invalid for an array of shape shape.

>>> from ndindex import Slice, Integer, Tuple
>>> shape = (6, 7, 8)
>>> Integer(1).newshape(shape)
(7, 8)
>>> Integer(10).newshape(shape)
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 6
>>> Slice(2, 5).newshape(shape)
(3, 7, 8)
>>> Tuple(0, ..., Slice(1, 3)).newshape(shape)
(7, 2)

See also

NDIndex.isvalid

property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
reduce(shape=None, *, negative_int=False)#

Simplify an index given that it will be applied to an array of a given shape.

If shape is None (the default), the index will be canonicalized as much as possible while still staying equivalent for all array shapes that it does not raise IndexError for.

Either returns a new index type, which is equivalent on arrays of shape shape, or raises IndexError if the index would give an index error (for instance, out of bounds integer index or too many indices for array).

>>> from ndindex import Slice, Integer
>>> Slice(0, 10).reduce((5,))
Slice(0, 5, 1)
>>> Integer(10).reduce((5,))
Traceback (most recent call last):
...
IndexError: index 10 is out of bounds for axis 0 with size 5

For single axis indices such as Slice and Tuple, reduce takes an optional axis argument to specify the axis, defaulting to 0.

selected_indices(shape, axis=0)#

Return an iterator over all indices that are selected by self on an array of shape shape.

The result is a set of indices i such that [a[i] for i in idx.selected_indices(a.shape)] is all the elements of a[idx]. The indices are all iterated over in C (i.e., row major) order.

>>> from ndindex import Slice, Tuple
>>> idx = Slice(5, 10)
>>> list(idx.selected_indices(20))
[Integer(5), Integer(6), Integer(7), Integer(8), Integer(9)]
>>> idx = Tuple(Slice(5, 10), Slice(0, 2))
>>> list(idx.selected_indices((20, 3)))
[Tuple(5, 0), Tuple(5, 1),
 Tuple(6, 0), Tuple(6, 1),
 Tuple(7, 0), Tuple(7, 1),
 Tuple(8, 0), Tuple(8, 1),
 Tuple(9, 0), Tuple(9, 1)]

To correspond these indices to the elements of a[idx], you can use iter_indices(idx.newshape(shape)), since both iterators iterate the indices in C order.

>>> from ndindex import iter_indices
>>> idx = Tuple(Slice(3, 5), Slice(0, 2))
>>> shape = (5, 5)
>>> import numpy as np
>>> a = np.arange(25).reshape(shape)
>>> for a_idx, (new_idx,) in zip(
...    idx.selected_indices(shape),
...    iter_indices(idx.newshape(shape))):
...        print(a_idx, new_idx, a[a_idx.raw], a[idx.raw][new_idx.raw])
Tuple(3, 0) Tuple(0, 0) 15 15
Tuple(3, 1) Tuple(0, 1) 16 16
Tuple(4, 0) Tuple(1, 0) 20 20
Tuple(4, 1) Tuple(1, 1) 21 21

See also

ndindex.iter_indices

An iterator of indices to select every element for arrays of a given shape.

ndindex.ChunkSize.as_subchunks

A high-level iterator that efficiently gives only those chunks that intersect with a given index

class ndindex.array.ArrayIndex(idx, shape=None, _copy=True)#

Superclass for array indices

This class should not be instantiated directly. Rather, use one of its subclasses, IntegerArray or BooleanArray.

To subclass this, define the dtype attribute, as well as all the usual ndindex methods.

dtype Subclasses should redefine this#
property array#

Return the NumPy array of self.

This is the same as self.args[0].

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([0, 1]).array
array([0, 1])
>>> BooleanArray([False, True]).array
array([False, True])
isvalid(shape, _axis=0)#

Check whether a given index is valid on an array of a given shape.

Returns True if an array of shape shape can be indexed by self and False if it would raise IndexError.

>>> from ndindex import ndindex
>>> ndindex(3).isvalid((4,))
True
>>> ndindex(3).isvalid((2,))
False

Note that some indices can never be valid and will raise a IndexError or TypeError if you attempt to construct them.

>>> ndindex((..., 0, ...))
Traceback (most recent call last):
  ...
IndexError: an index can only have a single ellipsis ('...')
>>> ndindex(slice(True))
Traceback (most recent call last):
  ...
TypeError: 'bool' object cannot be interpreted as an integer

See also

NDIndex.newshape

property ndim#

Return the number of dimensions of the array of self.

This is the same as self.array.ndim. Note that this is not the same as the number of dimensions of an array that is indexed by self. Use len on newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).ndim
2
>>> BooleanArray([[False], [True]]).ndim
2
property raw#

Return the equivalent of self that can be used as an index

NumPy does not allow custom objects to be used as indices, with the exception of integer indices, so to use an ndindex object as an index, it is necessary to use raw.

>>> from ndindex import Slice
>>> import numpy as np
>>> a = np.arange(5)
>>> s = Slice(2, 4)
>>> a[s]
Traceback (most recent call last):
...
IndexError: only integers, slices (`:`), ellipsis (`...`), numpy.newaxis (`None`) and integer or boolean arrays are valid indices
>>> a[s.raw]
array([2, 3])
property shape#

Return the shape of the array of self.

This is the same as self.array.shape. Note that this is not the same as the shape of an array that is indexed by self. Use newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).shape
(2, 1)
>>> BooleanArray([[False], [True]]).shape
(2, 1)
property size#

Return the number of elements of the array of self.

This is the same as self.array.size. Note that this is not the same as the number of elements of an array that is indexed by self. Use np.prod on newshape() to get that.

>>> from ndindex import IntegerArray, BooleanArray
>>> IntegerArray([[0], [1]]).size
2
>>> BooleanArray([[False], [True]]).size
2
class ndindex.slice.default#

A default keyword argument value.

Used as the default value for keyword arguments where None is also a meaningful value but not the default.

ndindex.ndindex.operator_index(idx)#

Convert idx into an integer index using __index__() or raise TypeError.

This is the same as operator.index() except it disallows boolean types.

This is a slight break in NumPy compatibility, as NumPy allows bools in some contexts where __index__() is used, for instance, in slices. It does disallow it in others, such as in shapes. The main motivation for disallowing bools entirely is 1) numpy.bool_.__index__() is deprecated (currently it matches the built-in bool.__index__() and returns the object unchanged, but prints a deprecation warning), and 2) for raw indices, booleans and 0/1 are completely different, i.e., a[True] is not the same as a[1].

>>> from ndindex.ndindex import operator_index
>>> operator_index(1)
1
>>> operator_index(1.0)
Traceback (most recent call last):
...
TypeError: 'float' object cannot be interpreted as an integer
>>> operator_index(True)
Traceback (most recent call last):
...
TypeError: 'bool' object cannot be interpreted as an integer
ndindex.shapetools.asshape(shape, axis=None, *, allow_int=True, allow_negative=False)#

Cast shape as a valid NumPy shape.

The input can be an integer n (if allow_int=True), which is equivalent to (n,), or a tuple of integers.

If the axis argument is provided, an IndexError is raised if it is out of bounds for the shape.

The resulting shape is always a tuple of nonnegative integers. If allow_negative=True, negative integers are also allowed.

All ndindex functions that take a shape input should use:

shape = asshape(shape)

or:

shape = asshape(shape, axis=axis)
ndindex.shapetools.ncycles(iterable, n)#

Iterate iterable repeated n times.

This is based on a recipe from the Python itertools docs, but improved to give a repr, and to denest when it can. This makes debugging iter_indices() easier.

This is only intended for internal usage.

>>> from ndindex.shapetools import ncycles
>>> ncycles(range(3), 2)
ncycles(range(0, 3), 2)
>>> list(_)
[0, 1, 2, 0, 1, 2]
>>> ncycles(ncycles(range(3), 3), 2)
ncycles(range(0, 3), 6)
ndindex.shapetools.associated_axis(broadcasted_shape, i, skip_axes)#

Return the associated element of broadcasted_shape corresponding to shape[i] given skip_axes. If there is not such element (i.e., it’s out of bounds), returns None.

This function makes implicit assumptions about its input and is only designed for internal use.

ndindex.shapetools.remove_indices(x, idxes)#

Return x with the indices idxes removed.

This function is only intended for internal usage.

ndindex.shapetools.unremove_indices(x, idxes, *, val=None)#

Insert val in x so that it appears at idxes.

Note that idxes must be either all negative or all nonnegative.

This function is only intended for internal usage.

ndindex.shapetools.normalize_skip_axes(shapes, skip_axes)#

Return a canonical form of skip_axes corresponding to shapes.

A canonical form of skip_axes is a list of tuples of integers, one for each shape in shapes, which are a unique set of axes for each corresponding shape.

If skip_axes is an integer, this is basically [(skip_axes,) for s in shapes]. If skip_axes is a tuple, it is like [skip_axes for s in shapes].

The skip_axes must always refer to unique axes in each shape.

The returned skip_axes will always be negative integers and will be sorted.

This function is only intended for internal usage.