Prev: Array construction (cont'd) | Next: Array slicing

- Indexing in ndarrays works mostly as in lists. However, some powerful indexing possibilities are available that make programming of complex tasks a lot easier. We explore some of these further below.

- As in lists

In [1]:

```
import numpy as np
ar = np.linspace(0,10,8, False)
print(ar, '\n')
print(ar[0], ar[2], ar[len(ar)-1], ar[-1], ar[-len(ar)], sep=', ')
```

- As in lists but syntax is more flexible

In [2]:

```
import numpy as np
ar = np.arange(20).reshape(2,10)
print(ar)
print(ar[0][len(ar[0])-1])
print(ar[0,len(ar)]) # Remember that len(ar) equals '2' (number of list members)
# three syntactic forms are acceptable
print(ar[1,2], ar[0][5], ar[(1,0)])
```

- Note, however, that in occasions things might work counterintuitive and deeper investigation is needed to understand the computational representation of arrays.
- See the following example:

In [3]:

```
import numpy as np
ar = np.array([[1,2,3,4],[5,6,7,8,9,10]]) # ar is now a 'ragged' array due to uneven lists
print(ar.ndim) # strangely its dimension is 1. Why?
print(ar.dtype) # why is dtype 'object' and not integer?
```

*Answer*: because length of lists is different in array(0), numpy can not construct a homogeneous array object with integers. Instead the array 'ar' is constructed as 1D array with two members of dtype 'object'. Pointers are used in this case to point to the integer member items.- And
*what if we want to have 'ar' as a 1D array with 10 integer member items*? We can use the**concatenate()**function to flatten the array

In [4]:

```
ar = np.concatenate(ar)
print(ar)
print(ar.ndim, ar.dtype)
```

- Read more:
- about numpy.concatenate
- about array flattening: numpy.flatten and/or numpy.ravel

In [5]:

```
import numpy as np
ar = np.arange(1,17).reshape(2,2,4)
print(ar,'\n')
print(ar[0,1,2], ar[1,1,1], ar[0][1][3], ar[(1,1,3)])
print()
print(ar[0,1,len(ar)], ar[len(ar)-1,len(ar[1])-1,len(ar[0][1])-1],
ar[0][1][3], ar[(1,1,1)])
```

- Construct a two-dimensional array with 10 pseudo-random integers in [1,100] (5 in each of two rows). Then:
- (a) Print the rows one below the other (no indexing)
- (b) Similar to (a) but using single indexing for the rows
- (c) Print the array members (one by the other in a row; no indexing)
- (d) Similar to (c) but using double indices for the single array members
- (e) Using the .format() method.

In [6]:

```
import numpy as np
ar = np.array([[np.random.randint(1,101) for i in range(5)],
[np.random.randint(1,101) for i in range(5)]])
#(a)
for i in ar:
print(i)
```

In [7]:

```
#(b)
for i in [0,1]:
print(ar[i])
```

In [8]:

```
#(c)
for i in ar:
for k in i:
print(k, end=' ')
```

In [9]:

```
#(d)
for i in [0,1]:
for m in [0,1,2,3,4]:
print(ar[i][m], end=' ')
print()
```

In [10]:

```
#(e)
print('{:} {:}'.format(*(ar)))
```

- Assignment in arrays works much as expected (like in lists) but with few notable differences:

**Arrays are homogeneous constructs**(lists are heterogeneous) so they normally expect new assigned values to be of the same type object

In [11]:

```
import numpy as np
ar = np.arange(20).reshape(2,10)
ar[0][0] = 100
ar
#ar[0][0] = 'X' # but this is NOT valid in an array of integers
```

Out[11]:

In [12]:

```
ar[0][0] = '200' # this is accepted as digit-based string is trasformed to integer
ar
```

Out[12]:

**Array broadcasting**: assignment works if numpy can broadcast the array and array-like new values in the assignment- 'Broadcasting' is a term referring to compatibility of array shapes. For more explanations on array broadcasting see the section on array operations

In [13]:

```
import numpy as np
ar = np.arange(20).reshape(4,5)
print(ar,'\n')
ar[0] = 100 # a scalar is 'broadcast' across the 1D ar[0] array
print(ar)
```

In [14]:

```
import numpy as np
ar = np.arange(20).reshape(4,5)
print(ar,'\n')
# this will not work: numpy does not broadcast a 1D array with 3 members on the 5 member ar[0]
#ar[0] = [100,200,300]
# but this will work because shapes are compatible
ar[0] = [100,200,300,400,500]
print(ar)
```

- '
**Fancy indexing**' is a general term referring to*various non-standard ways of array indexing*. By 'standard' we mean the routinely used integer-based indexing. - Two interesting 'fancy indexing' methods are shown below.

In [15]:

```
import numpy as np
ar = np.random.random_integers(0, 100, 10) # See numpy.random documentation for this
print(ar)
mask = (ar%2==0) # mask is array constructed by applying a boolean expression on 'ar'
print(mask)
new_ar = ar[mask] # new_ar is constructed by using mask as array index
print('new_ar =',new_ar) # the new_ar contains the 'True' members of original ar
```

In [16]:

```
import numpy as np
ar = np.random.random_integers(0, 100, 10)
print(ar)
alist = [i if i%2==1 else i-1 for i in range(1,11)]
print(alist) # a list of integers is constructed with the required indices
new_ar = ar[alist] # new_ar is constructed by using alist as array index
print('new_ar =',new_ar)
```

. Free learning material

. See full copyright and disclaimer notice