A Complete Primer on Array Indexing in Numpy/Python

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

I am going to use ndarrays 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:

ndarray_indexing

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

slice_1d

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:

ndarray_1d_step_1

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.

ndarray_1d_step_2

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:

ndarray_negative_indexing

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.

ndarray_negative_step_1

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:

ndarray_negative_step_2

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

ndarray_2d

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

array2d[2, 1] = 42

ndarray_2d_update

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

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

ndarray_2d_negative

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 :

ndarray_2d_slice_1

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:

ndarray_2d_slice_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:

ndarray_2d_slice_4

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:

Selecting a row

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:

Selecting a column

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.

2 Likes