Numpy : Advance Indexing
Tags: python, numpy, array, indexing
Published: 2021-01-10 13:15:37



In this blog, we are talking about different ways of indexing array in NumPy which are amazing.

## Basic Slicing and Indexing

So here from basic slicing and indexing we are talking about pythons basic concept of slicing and indexing which is similar as python built-in data type `tuple`, `list` and `str`.

for accessing a single element from numpy array we can do it by passing passing each dimension’s index saperated by `,` in `[ ]`, because numpy support multi-dimension indexing.
for example:  `a[1,2] = a[1][2] `.
we can pass value of index either positive start with 0 (which is the first element) or negative where -1 mean the last element of the array.

For accessing more then one element , we can use python's standers slicing syntex per dimension basis all sapetared with `,` in square bracket.

>Basic slice syntex in `i:j:k` where i is starting index, j is stoping index and k is the steps.


```python
>>> import numpy as np
>>> ## Accessing a single element:
>>> # 1. from 1-D array:
>>> arr = np.arange(10)
>>> arr[0]
 0
>>> arr[-2]
 8
>>> #2. from 2-D array:
>>> nd_arr = np.array([[1,2,3],
...               [4,5,6],
...               [7,8,9]])
>>> nd_arr[1,2]
 6
>>> nd_arr[-1,2]
 9
>>> ## accessing more then one element:
>>> # for 1-D array:
>>> arr[1:5:2]
array([1, 3])
>>> # for 2-D array:
>>> nd_arr[0:2:1,:]
array([[1, 2, 3],
       [4, 5, 6]])
>>> #this can also be achieved by '...':
>>> nd_arr[0:2:1,...]
array([[1, 2, 3],
       [4, 5, 6]])
```
`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 `arr.ndim`. There may only be a single ellipsis present.

> `Ellipsis` is a python built-in Constant type which used mostly in conjunction with extended slicing syntax for user-defined container data types. 

## Advance Indexing in Numpy array

advance indexing mean when the selection object is non-tuple object , an ndarray of int or bool , or tuple with al least one sequence object with int or bool data types.
There are two types of advance indexing, one is integer and another is boolean.

> In numpy, `Advanced Indexing` always returns a copy of data while `basic slicing` returns a view.


>One most important thing is that 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



### 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.


```python
>>> #Example:
>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])
>>> #Example
>>> x = np.array([[ 0,  1,  2],
...           [ 3,  4,  5],
...           [ 6,  7,  8],
...           [ 9, 10, 11]])
>>> rows = np.array([[0, 0],[3, 3]], dtype=np.intp)
>>> columns = np.array([[0, 2],[0, 2]], dtype=np.intp)
>>> x[rows, columns]
array([[ 0,  2],
       [ 9, 11]])
```


> Advance Indexes always broadcasted and iterated as one. The result we get is similar dimention of broadcasted result.

in above example we broadcast in rows and column but in nympy there are one function `ix_` which help this broadcasting.


```python
>>> #Example
>>> x = np.array([[ 0,  1,  2],
...           [ 3,  4,  5],
...           [ 6,  7,  8],
...           [ 9, 10, 11]])

>>> rows = np.array([0, 3], dtype=np.intp)
>>> columns = np.array([0, 2], dtype=np.intp)

>>> x[np.ix_(rows, columns)]
array([[ 0,  2],
       [ 9, 11]])
>>> # if we don't use `np.ix_` then result:
>>> x[rows, columns]
array([ 0, 11])
```


#### Combining advanced and basic indexing

When there is at least one `slice (:)`, `ellipsis (...)` or `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


```python
>>> x[1:2,1:3]
array([[4, 5]])
>>> x[1:2,[1,2]]
array([[4, 5]])
```


### Boolean array Indexing

This types of indexing occurs when the selection object is the array of Boolean, such as returned from comparison operator.

if the dimension of selection onject is same as the array then it returns a 1-D array filled with all result corresponding to the `True` value . The search order will be `row-major` , *C-style* . 

>If obj has `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 `False`.


```python
>>> #Example:
>>> x = np.array([[1., 2.], [np.nan, 3.], [np.nan, np.nan]])
>>> x[~np.isnan(x)]
array([1., 2., 3.])
>>> #Example:
>>> x = np.array([1., -1., -2., 3])
>>> x[x < 0] += 20
>>> x
array([ 1., 19., 18.,  3.])
>>> #Example : use with basic indexing
>>> x = np.array([[0, 1], [1, 1], [2, 2]])
>>> rowsum = x.sum(-1)
>>> x[rowsum <= 2, :]
array([[0, 1],
       [1, 1]])
>>> #Example : use with np.ix_
>>> x = np.array([[ 0,  1,  2],
...           [ 3,  4,  5],
...           [ 6,  7,  8],
...           [ 9, 10, 11]])
>>> rows = (x.sum(-1) % 2) == 0
>>> columns = [0, 2]

>>> x[np.ix_(rows, columns)]
array([[ 3,  5],
       [ 9, 11]])
```


# Something More intresting then above:

## Field Access:

If the ndarray object is a structured array the fields of the array can be accessed by indexing the array with strings, dictionary-like.


Indexing `x['field-name']` returns a new view to the array.

If the accessed field is a sub-array, the dimensions of the sub-array are appended to the shape of the result.


```python
>>> x = np.zeros((2,2), dtype=[('a', np.int32), ('b', np.float64,(3,3))])

>>> x['a']
array([[0, 0],
       [0, 0]])
>>> x['b']
array([[[[0., 0., 0.],
         [0., 0., 0.],
         [0., 0., 0.]],
    
         [[0., 0., 0.],
         [0., 0., 0.],
         [0., 0., 0.]]],
   
    
       [[[0., 0., 0.],
         [0., 0., 0.],
         [0., 0., 0.]],
   
        [[0., 0., 0.],
         [0., 0., 0.],
         [0., 0., 0.]]]])
```


### Flat Iterator indexing

`x.flat` returns an iterator that will iterate over the entire array (in C-contiguous style with the last index varying the fastest). This iterator object can also be indexed using basic slicing or advanced indexing as long as the selection object is not a tuple. This should be clear from the fact that x.flat is a 1-dimensional view. It can be used for integer indexing with 1-dimensional `C-style-flat indices`. The shape of any returned array is therefore the shape of the integer indexing object.


```python
>>> x = np.arange(1, 7).reshape(2, 3)
>>> x
array([[1, 2, 3],
       [4, 5, 6]])
>>> x.flat[3]
4
>>> x.T
array([[1, 4],
       [2, 5],
       [3, 6]])
>>> x.T.flat[3]
5
>>> type(x.flat)
numpy.flatiter

>>> #assignment example
>>> x.flat = 3
>>> x
array([[3, 3, 3],
       [3, 3, 3]])
>>> #assignment example

>>> x.flat[[1,4]] = 1
>>> x
array([[3, 1, 3],
       [3, 1, 3]])

```

<p align="center">
<img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQBk7RSi8Ro0TREAQFNbAeyGbHIqZHWUMqieg&usqp=CAU">
</p>