# Array, slices and indexing

## Array, Slices and Fancy indexing¶

In this small post we'll investigate (quickly) the difference between `slices`

(which are part of the Python standard library), and numpy array, and how these could be used for indexing. First let's create a matrix containing integers so that element at index `i,j`

has value `10i+j`

for convenience.

```
import numpy as np
from copy import copy
```

Let's create a single row, that is to say a matrix or height `1`

and width number of element.
We'll use `-1`

in reshape to mean "whatever is necessary". for 2d matrices and tensor it's not super useful, but for higher dimension object, it can be quite conveneient.

```
X = np.arange(0, 10).reshape(1,-1)
X
```

now a column, same trick.

```
Y = (10*np.arange(0, 8).reshape(-1, 1))
Y
```

By summing, and the rules of "broadcasting", we get a nice rectangular matrix.

```
R = np.arange(5*5*5*5*5).reshape(5,5,5,5,5)
```

```
M = X+Y
M
```

### Slicing¶

Quick intro about slicing. You have likely use it before if you've encoutered the `objet[12:34]`

`objet[42:96:3]`

notation. The `X:Y:Z`

part is a slice. This way of writing a slice is allowed only in between square bracket for indexing.

`X`

, `Y`

and `Z`

are optional and default to whatever is convenient, so `::3`

(every three), `:7`

and `:7:`

(until 7), `:`

and `::`

(everything) are valid slices.

A slice is an efficent object that (usually) represent "From X to Y by Every Z", it is not limitted to numbers.

```
class PhylosophicalArray:
def __getitem__(self, sl):
print(f"From `{sl.start}` to `{sl.stop}` every `{sl.step}`.")
arr = PhylosophicalArray()
arr['cow':'phone':'traffic jam']
```

You can construct a slice using the `slice`

builtin, this is (sometime) convenient, and use it in place of `x:y:z`

```
sl = slice('cow', 'phone', 'traffic jam')
```

```
arr[sl]
```

In multidimentional arrays, slice of `0`

or `1`

width, can be used to not drop dimensions, when comparing them to scalars.

```
M[:, 3] # third column, now a vector.
```

```
M[:, 3:4] # now a N,1 matrix.
```

This is convenient when indices represent various quatities, for example an athmospheric ensemble when dimension 1 is latitude, 2: longitude, 3: height, 4: temperature, 5: pressure, and you want to focus on `height==0`

, without having to shift temprature index from `4`

to `3`

, pressure from `5`

to `4`

...

Zero-width slices are mostly used to simplify algorythmes to avoid having to check for edge cases.

```
a = 3
b = 3
M[a:b]
```

```
M[a:b] = a-b
```

```
M # M is not modified !
```

When indexing an array, you will slice each dimention individually.
Here **we extract the center block** of the matrix **not the 3 diagonal elements**.

```
M[4:7, 4:7]
```

```
sl = slice(4,7)
sl
```

```
M[sl, sl]
```

Let's change the sign the **biggest square block** in the upper left of this matrix.

```
K = copy(M)
el = slice(0, min(K.shape))
el
```

```
K[el, el] = -K[el, el]
K
```

That's about for slices, it was already a lot.

In the next section we'll talk about arrays

### Fancy indexing¶

Array are more or less what you've seem in other languages. Finite Sequences of discrete values

```
ar = np.arange(4,7)
ar
```

When you slice with array, the elements of each arrays will be taken together.

```
M[ar,ar]
```

We now get *a partial diagonal* in out matrix. It does not have to be a diaonal:

```
M[ar, ar+1]
```

The result of this operation is a 1 dimentional array (which is a view – when possible – on the initial matrix memory), in the same way as we flipped the sign of the largest block in the previous section, we'll try indexing with the same value:

```
S = copy(M)
```

```
el = np.arange(min(S.shape))
el
```

```
S[el, el] = -S[el,el]
S
```

Here we flipped the value of **only the diagonal elements**. It of couse did not had to do the diagonal elements:

```
S[el, el+1]
```

```
S[el, el+1] = 0
S
```

Nor are we required to have the same elements only once:

```
el-1
```

```
sy = np.array([0, 1, 2, 0, 1, 2])
sx = np.array([1, 2, 3, 1, 2, 3])
ld = S[sx, sy] # select 3 elements of lower diagonal twice
ld
```

More in the scipy lectures notes, Numpy quickstart, Python DataScience Handbook

## Some experiments¶

```
S = copy(M)
S[0:10, 0:10] = 0
S
```

```
S = copy(M)
S[0:10:2, 0:10] = 0
S
```

```
S = copy(M)
S[0:10, 0:10:2] = 0
S
```

```
S = copy(M)
S[0:10:2, 0:10:2] = 0
S
```

```
S = copy(M)
S[0:10:2, 0:10] = 0
S[0:10, 0:10:2] = 0
S
```

```
S = copy(M)
S[0:8, 0:8] = 0
S
```

```
S = copy(M)
S[np.arange(0,8), np.arange(0,8)] = 0
S
```

```
S = copy(M)
S[range(0,8), range(0,8)] = 0
S
```

```
S = copy(M)
S[np.arange(0, 10), np.arange(0, 10)] = 0 ## will fail
S
```

```
```