In this article, I want to start from the beginning and tell you all about indexing.

I am going to use `ndarray`

s in my explanation. So, awesome if you are using it too. If not, hopefully, it will be similar enough to be helpful to you. All of what I am saying here works for lists as well, except for the 2-dimensional part.

### How arrays are indexed

Let’s imagine that you create an ndarray `x = np.array([5, 10, 15, 20])`

.

Then its indexes will range from 0 to the length of the array minus one, 3 in this case:

### Access an index value

You can access the value that `x`

has at index `i`

using `x[i]`

. Here are a few examples:

```
x = np.array([5, 10, 15, 20])
print(x[0])
print(x[3])
```

```
5
20
```

### Set an index value

You can set the value of `x`

at index `i`

to `v`

using `x[i] = v`

. Here are a few examples:

```
x = np.array([5, 10, 15, 20])
x[1] = 0
x[2] = 1
print(x)
```

```
[ 5 0 1 20]
```

### Slicing

We can obtain a sub-array from `start`

to `end`

from a ndarray using the same notation as we do with lists and strings. If `x`

is a ndarray then `x[start:end]`

will get us the sub-array from `start`

(inclusive) to `end`

(exclusive).

You can check it using Python:

```
x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(x[1:7])
```

```
[1 2 3 4 5 6]
```

If we omit `start`

then it will be considered as zero, meaning that it takes elements starting from the first one:

```
print(x[:4]) # Same as x[0:4]
```

```
[0 1 2 3]
```

If we omit `end`

then it will be considered as the length of the ndarray, meaning that it takes elements until the last one:

```
print(x[2:]) # Same as x[2:10]
```

```
[2 3 4 5 6 7 8 9]
```

Note that by omitting both, we’ll be selecting the whole ndarray since `start`

will default to 0 and `end`

to 10:

```
print(x[:]) # Same as x[0:10]
```

```
[0 1 2 3 4 5 6 7 8 9]
```

### Slicing Increment

We can provide a third parameter when slicing a ndarray. This is the **slicing increment** or **step** parameter. It determines how much indexes are incremented when taking elements in a slice. The general syntax for slicing is the following:

```
x[start:end:step]
```

By default, the step is equal to one, meaning that it takes every single element in the range. So this means that, for instance, `x[1:7]`

is the same as `x[1:7:1]`

. The following image illustrates this:

If we set the step to 2, we will take one element, then skip one, then take the next one, and so on, within the range defined by the first two arguments.

Here are few examples in Python:

```
x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(x[0:10:2]) # Takes elements at indexes 0, 2, 4, 6 and 8
print(x[::2]) # Same as previous, using default values of start and end
print(x[0:10:3]) # Takes elements at indexes 0, 3, 6 and 9
print(x[::3]) # Same as previous, using default values of start and end
print(x[1:9:4]) # Takes elements at indexes 1, 5 and 9
```

```
[0 2 4 6 8]
[0 2 4 6 8]
[0 3 6 9]
[0 3 6 9]
[1 5]
```

### Negative Indexes

We can also use negative indexes with nparrays. The index `-1`

corresponds to the last position, `-2`

to the second to last position, and so on.

The following figure illustrates this on a ndarray of length 4:

The most common use of negative indexes is to access the last value. As we see in the above figure, this can be accessed with index `-1`

:

```
x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(x[-1])
```

```
9
```

### Negative steps when slicing

We can also use negative values for the step. It works in the same way as positive indexes, but moving to the left at each step rather than the right.

In Python:

```
x = np.array([5, 6, 2, 8, 2, 7, 9, 3])
print(x[5:1:-2])
```

```
[7 8]
```

Note that since we move to the left now, for the range to make sense, we need to have the start be on the right of the end.

There is one major difference when using negative steps. When the step is negative, omitting the start index will make the range end at the first index; omitting the end index will make the range start at the last index.

In particular, this means that in `x[::-1]`

the range is from the last index to the first (both inclusive), but we move from the right to the left. The following figure illustrates this:

In order words, this will reverse the ndarray:

```
x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(x[::-1])
```

```
[9 8 7 6 5 4 3 2 1 0]
```

### 2D arrays

One way to create a 2-dimensional ndarray is to pass a list of lists to the `numpy.array()`

constructor:

```
array2d = np.array([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15]
])
print(array2d)
```

```
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]]
```

To access a specific value given its row and column indexes we use `array2d[row_index, col_index]`

:

```
print(array2d[1, 3])
```

```
9
```

In the same way, we can set the value at a specific position:

```
array2d[2, 1] = 42
```

Negative indexes also work with 2-dimensional nparrays. For example:

```
print(array2d[-1, -1])
```

```
15
```

### Slicing 2D Arrays

We can slice 2-dimensional arrays similarly as we did with 1-dimensional arrays. The difference is that now we need to specify both the rows and columns that we want. The general notation is the following:

```
array2d[row_start:row_end:row_step, col_start:col_end:col_step]
```

As with the 1-dimensional case, range ends ( `row_end`

and `col_end`

) are *exclusive* :

In the above example, we take all columns in the range because not step is provided. If we provide a step of 2 on the columns, NumPy will skip column 2:

Remember that if we don’t specify range starts, they will be considered as 0. If we don’t specify the range ends, they will be considered to be the number of rows and columns, respectively. Here is an example:

### Selecting rows and columns

To select values from a single row, we provide the single row index (rather than a range) in the row selection. We can still select a subset of columns by specifying a range if we want to:

To select values from a single row, we provide the single row index (rather than a range) in the row selection. We can still select a subset of columns by specifying a range if we want to:

The difference between giving a single index rather than a range with a single index is that, in the first case case, we get a 1-dimensional ndarray. In the latter case, we get a 2-dimensional ndarray with a single row or column.

Example for rows:

```
print(people_data[1,:]) # Get a 1D ndarray with row 1
print(people_data[1:2,:]) # Get a 2D ndarray with row 1
```

```
[35. 81. 1.84]
[[35. 81. 1.84]] # Note the double square brackets
```

Example for columns:

```
print(people_data[:,2]) # Get a 1D ndarray with col 2
print(people_data[:,2:3]) # Get a 2D ndarray with col 2
```

```
[1.65 1.84 1.6 1.79]
[[1.65]
[1.84]
[1.6 ]
[1.79]]
```

I first posted this article as a reply to this topic. I reposted it here to give it more visibility because I believe it can help more learners.