| .. Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| |
| .. _arrays.indexing: |
| |
| Indexing |
| ======== |
| |
| .. sectionauthor:: adapted from "Guide to NumPy" by Travis E. Oliphant |
| |
| .. currentmodule:: mxnet.np |
| |
| .. index:: indexing, slicing |
| |
| :class:`ndarrays <ndarray>` can be indexed using the standard Python |
| ``x[obj]`` syntax, where *x* is the array and *obj* the selection. |
| There are three kinds of indexing available: basic |
| slicing, advanced indexing, and boolean mask indexing. Which one occurs depends on *obj*. |
| |
| .. note:: |
| |
| In Python, ``x[(exp1, exp2, ..., expN)]`` is equivalent to |
| ``x[exp1, exp2, ..., expN]``; the latter is just syntactic sugar |
| for the former. |
| |
| |
| Basic Slicing and Indexing |
| -------------------------- |
| |
| Basic slicing extends Python's basic concept of slicing to N |
| dimensions. Basic slicing occurs when *obj* is a :class:`slice` object |
| (constructed by ``start:stop:step`` notation inside of brackets), an |
| integer, or a tuple of slice objects and integers. :const:`Ellipsis` |
| and :const:`newaxis` objects can be interspersed with these as |
| well. |
| |
| The simplest case of indexing with *N* integers returns an array |
| scalar representing the corresponding item. As in |
| Python, all indices are zero-based: for the *i*-th index :math:`n_i`, |
| the valid range is :math:`0 \le n_i < d_i` where :math:`d_i` is the |
| *i*-th element of the shape of the array. Negative indices are |
| interpreted as counting from the end of the array (*i.e.*, if |
| :math:`n_i < 0`, it means :math:`n_i + d_i`). |
| |
| All arrays generated by basic slicing are always views |
| of the original array if the fetched elements are contiguous in memory. |
| |
| The standard rules of sequence slicing apply to basic slicing on a |
| per-dimension basis (including using a step index). Some useful |
| concepts to remember include: |
| |
| - The basic slice syntax is ``i:j:k`` where *i* is the starting index, |
| *j* is the stopping index, and *k* is the step (:math:`k\neq0`). |
| This selects the *m* elements (in the corresponding dimension) with |
| index values *i*, *i + k*, ..., *i + (m - 1) k* where |
| :math:`m = q + (r\neq0)` and *q* and *r* are the quotient and remainder |
| obtained by dividing *j - i* by *k*: *j - i = q k + r*, so that |
| *i + (m - 1) k < j*. |
| |
| .. admonition:: Example |
| |
| >>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) |
| >>> x[1:7:2] |
| array([1, 3, 5]) |
| |
| - Negative *i* and *j* are interpreted as *n + i* and *n + j* where |
| *n* is the number of elements in the corresponding dimension. |
| Negative *k* makes stepping go towards smaller indices. |
| |
| .. admonition:: Example |
| |
| >>> x[-2:10] |
| array([8, 9]) |
| >>> x[-3:3:-1] |
| array([7, 6, 5, 4]) |
| |
| - Assume *n* is the number of elements in the dimension being |
| sliced. Then, if *i* is not given it defaults to 0 for *k > 0* and |
| *n - 1* for *k < 0* . If *j* is not given it defaults to *n* for *k > 0* |
| and *-n-1* for *k < 0* . If *k* is not given it defaults to 1. Note that |
| ``::`` is the same as ``:`` and means select all indices along this |
| axis. |
| |
| .. admonition:: Example |
| |
| >>> x[5:] |
| array([5, 6, 7, 8, 9]) |
| |
| - If the number of objects in the selection tuple is less than |
| *N* , then ``:`` is assumed for any subsequent dimensions. |
| |
| .. admonition:: Example |
| |
| >>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]]) |
| >>> x.shape |
| (2, 3, 1) |
| >>> x[1:2] |
| array([[[4], |
| [5], |
| [6]]]) |
| |
| - :const:`Ellipsis` expands to the number of ``:`` objects needed for the |
| selection tuple to index all dimensions. In most cases, this means that |
| length of the expanded selection tuple is ``x.ndim``. There may only be a |
| single ellipsis present. |
| |
| .. admonition:: Example |
| |
| >>> x[...,0] |
| array([[1, 2, 3], |
| [4, 5, 6]]) |
| |
| - Each :const:`newaxis` object in the selection tuple serves to expand |
| the dimensions of the resulting selection by one unit-length |
| dimension. The added dimension is the position of the :const:`newaxis` |
| object in the selection tuple. |
| |
| .. admonition:: Example |
| |
| >>> x[:,np.newaxis,:,:].shape |
| (2, 1, 3, 1) |
| |
| - An integer, *i*, returns the same values as ``i:i+1`` |
| **except** the dimensionality of the returned object is reduced by |
| 1. In particular, a selection tuple with the *p*-th |
| element an integer (and all other entries ``:``) returns the |
| corresponding sub-array with dimension *N - 1*. If *N = 1* |
| then the returned object is an scalar `ndarray` whose `ndim=0`. |
| |
| - If the selection tuple has all entries ``:`` except the |
| *p*-th entry which is a slice object ``i:j:k``, |
| then the returned array has dimension *N* formed by |
| concatenating the sub-arrays returned by integer indexing of |
| elements *i*, *i+k*, ..., *i + (m - 1) k < j*, |
| |
| - Basic slicing with more than one non-``:`` entry in the slicing |
| tuple, acts like repeated application of slicing using a single |
| non-``:`` entry, where the non-``:`` entries are successively taken |
| (with all other non-``:`` entries replaced by ``:``). Thus, |
| ``x[ind1,...,ind2,:]`` acts like ``x[ind1][...,ind2,:]`` under basic |
| slicing. |
| |
| .. warning:: The above is **not** true for advanced indexing. |
| |
| - You may use slicing to set values in the array, but (unlike lists) you |
| can never grow the array. The size of the value to be set in |
| ``x[obj] = value`` must be (broadcastable) to the same shape as |
| ``x[obj]``. |
| |
| .. note:: |
| |
| Remember that a slicing tuple can always be constructed as *obj* |
| and used in the ``x[obj]`` notation. Slice objects can be used in |
| the construction in place of the ``[start:stop:step]`` |
| notation. For example, ``x[1:10:5,::-1]`` can also be implemented |
| as ``obj = (slice(1,10,5), slice(None,None,-1)); x[obj]`` . This |
| can be useful for constructing generic code that works on arrays |
| of arbitrary dimension. |
| |
| .. data:: newaxis |
| :noindex: |
| |
| The :const:`newaxis` object can be used in all slicing operations to |
| create an axis of length one. :const:`newaxis` is an alias for |
| 'None', and 'None' can be used in place of this with the same result. |
| |
| |
| Advanced Indexing |
| ----------------- |
| |
| Advanced indexing is triggered when the selection object, *obj*, is a |
| non-tuple sequence object, an :class:`ndarray` (of data type integer or bool), |
| or a tuple with at least one sequence object or ndarray (of data type |
| integer or bool). There are two types of advanced indexing: integer |
| and Boolean. |
| |
| Advanced indexing always returns a *copy* of the data (contrast with |
| some cases in basic slicing that returns a view). |
| |
| .. warning:: |
| |
| The definition of advanced indexing means that ``x[(1,2,3),]`` is |
| fundamentally different than ``x[(1,2,3)]``. The latter is |
| equivalent to ``x[1,2,3]`` which will trigger basic selection while |
| the former will trigger advanced indexing. Be sure to understand |
| why this occurs. |
| |
| Also recognize that ``x[[1,2,3]]`` will trigger advanced indexing, |
| whereas due to the deprecated Numeric compatibility mentioned above, |
| ``x[[1,2,slice(None)]]`` will trigger basic slicing in the official NumPy |
| which is not currently supported in MXNet `numpy` module. |
| |
| Integer array indexing |
| ^^^^^^^^^^^^^^^^^^^^^^ |
| |
| Integer array indexing allows selection of arbitrary items in the array |
| based on their *N*-dimensional index. Each integer array represents a number |
| of indexes into that dimension. |
| |
| Purely integer array indexing |
| """"""""""""""""""""""""""""" |
| |
| When the index consists of as many integer arrays as the array being indexed |
| has dimensions, the indexing is straight forward, but different from slicing. |
| |
| Advanced indexes always are broadcasting and |
| iterated as *one*:: |
| |
| result[i_1, ..., i_M] == x[ind_1[i_1, ..., i_M], ind_2[i_1, ..., i_M], |
| ..., ind_N[i_1, ..., i_M]] |
| |
| Note that the result shape is identical to the (broadcast) indexing array |
| shapes ``ind_1, ..., ind_N``. |
| |
| .. admonition:: Example |
| |
| From each row, a specific element should be selected. The row index is just |
| ``[0, 1, 2]`` and the column index specifies the element to choose for the |
| corresponding row, here ``[0, 1, 0]``. Using both together the task |
| can be solved using advanced indexing: |
| |
| >>> x = np.array([[1, 2], [3, 4], [5, 6]]) |
| >>> x[[0, 1, 2], [0, 1, 0]] |
| array([1, 4, 5]) |
| |
| Combining advanced and basic indexing |
| """"""""""""""""""""""""""""""""""""" |
| |
| When there is at least one slice (``:``), ellipsis (``...``) or :const:`newaxis` |
| in the index (or the array has more dimensions than there are advanced indexes), |
| then the behaviour can be more complicated. It is like concatenating the |
| indexing result for each advanced index element |
| |
| In the simplest case, there is only a *single* advanced index. A single |
| advanced index can for example replace a slice and the result array will be |
| the same, however, it is a copy and may have a different memory layout. |
| A slice is preferable when it is possible. |
| |
| .. admonition:: Example |
| |
| >>> x[1:2, 1:3] |
| array([[4, 5]]) |
| >>> x[1:2, [1, 2]] |
| array([[4, 5]]) |
| |
| The easiest way to understand the situation may be to think in |
| terms of the result shape. There are two parts to the indexing operation, |
| the subspace defined by the basic indexing (excluding integers) and the |
| subspace from the advanced indexing part. Two cases of index combination |
| need to be distinguished: |
| |
| * The advanced indexes are separated by a slice, :const:`Ellipsis` or :const:`newaxis`. |
| For example ``x[arr1, :, arr2]``. |
| * The advanced indexes are all next to each other. |
| For example ``x[..., arr1, arr2, :]`` but *not* ``x[arr1, :, 1]`` |
| since ``1`` is an advanced index in this regard. |
| |
| In the first case, the dimensions resulting from the advanced indexing |
| operation come first in the result array, and the subspace dimensions after |
| that. |
| In the second case, the dimensions from the advanced indexing operations |
| are inserted into the result array at the same spot as they were in the |
| initial array (the latter logic is what makes simple advanced indexing |
| behave just like slicing). |
| |
| .. admonition:: Example |
| |
| Suppose ``x.shape`` is (10,20,30) and ``ind`` is a (2,3,4)-shaped |
| indexing :class:`intp` array, then ``result = x[...,ind,:]`` has |
| shape (10,2,3,4,30) because the (20,)-shaped subspace has been |
| replaced with a (2,3,4)-shaped broadcasted indexing subspace. If |
| we let *i, j, k* loop over the (2,3,4)-shaped subspace then |
| ``result[...,i,j,k,:] = x[...,ind[i,j,k],:]``. This example |
| produces the same result as :meth:`x.take(ind, axis=-2) <ndarray.take>`. |
| |
| .. admonition:: Example |
| |
| Let ``x.shape`` be (10,20,30,40,50) and suppose ``ind_1`` |
| and ``ind_2`` can be broadcast to the shape (2,3,4). Then |
| ``x[:,ind_1,ind_2]`` has shape (10,2,3,4,40,50) because the |
| (20,30)-shaped subspace from X has been replaced with the |
| (2,3,4) subspace from the indices. However, |
| ``x[:,ind_1,:,ind_2]`` has shape (2,3,4,10,30,50) because there |
| is no unambiguous place to drop in the indexing subspace, thus |
| it is tacked-on to the beginning. It is always possible to use |
| :meth:`.transpose() <ndarray.transpose>` to move the subspace |
| anywhere desired. Note that this example cannot be replicated |
| using :func:`take`. |
| |
| |
| Boolean array indexing |
| ^^^^^^^^^^^^^^^^^^^^^^ |
| |
| This advanced indexing occurs when obj is an array object of Boolean |
| type, such as may be returned from comparison operators. A single |
| boolean index array is practically identical to ``x[obj.nonzero()]`` where, |
| as described above, :meth:`obj.nonzero() <ndarray.nonzero>` returns a |
| tuple (of length :attr:`obj.ndim <ndarray.ndim>`) of integer index |
| arrays showing the :const:`True` elements of *obj*. However, it is |
| faster when ``obj.shape == x.shape``. |
| |
| If ``obj.ndim == x.ndim``, ``x[obj]`` returns a 1-dimensional array |
| filled with the elements of *x* corresponding to the :const:`True` |
| values of *obj*. The search order will be row-major, |
| C-style. If *obj* has :const:`True` values at entries that are outside |
| of the bounds of *x*, then an index error will be raised. If *obj* is |
| smaller than *x* it is identical to filling it with :const:`False`. |
| |
| .. note:: |
| |
| Boolean indexing currently only supports a single boolean ndarray as a index. |
| An composite index including a boolean array is not supported for now. |
| |
| If there is only one Boolean array and no integer indexing array present, |
| this is straight forward. Care must only be taken to make sure that the |
| boolean index has *exactly* as many dimensions as it is supposed to work |
| with. |
| |
| .. admonition:: Example |
| |
| From an array, select all rows which sum up to less or equal two: |
| |
| >>> x = np.array([[0, 1], [1, 1], [2, 2]], dtype=np.int32) |
| >>> rowsum = x.sum(-1) |
| >>> x[rowsum <= 2] |
| array([[0, 1], |
| [1, 1]], dtype=int32) |
| |
| But if ``rowsum`` would have two dimensions as well: |
| |
| >>> rowsum = x.sum(-1, keepdims=True) |
| >>> rowsum.shape |
| (3, 1) |
| >>> x[rowsum <= 2] # fail |
| IndexError: boolean index did not match indexed array along dimension 1 |
| |
| Detailed notes |
| -------------- |
| |
| These are some detailed notes, which are not of importance for day to day |
| indexing (in no particular order): |
| |
| * For advanced assignments, there is in general no guarantee for the |
| iteration order. This means that if an element is set more than once, |
| it is not possible to predict the final result. |
| * An empty (tuple) index is a full scalar index into a zero dimensional array. |
| ``x[()]`` returns a *scalar* `ndarray` if ``x`` has zero dimensions. |
| On the other hand ``x[...]`` always returns a view. |
| * If a zero dimensional array is present in the index *and* it is *not considered as* a full |
| integer index as in NumPy. Advanced indexing is not triggered. |
| * the ``nonzero`` equivalence for Boolean arrays does not hold for zero |
| dimensional boolean arrays. |
| * When the result of an advanced indexing operation has no elements but an |
| individual index is out of bounds, currently no ``IndexError`` is |
| raised as in NumPy. |
| |
| .. index:: |
| single: indexing |
| single: ndarray |