BrainsToBytes

Hands-on NumPy(III): Indexing and slicing

NumPy array indexing is a big topic, and there are many different ways of selecting elements from an array.

Let's start with the simplest case: selecting an entry from a 1-dimensional array.

import numpy as np

arr = np.arange(10)
print(arr)
[0 1 2 3 4 5 6 7 8 9]

You can access elements from a 1-dimensional array in NumPy using the same syntax you would use for regular Python lists. Both positive and negative indexing works as you would expect:

at_index_three = arr[3]
print(at_index_three)
3
penultimate_element = arr[-2]
print(penultimate_element)
8

The syntax for selecting slices also works in a very similar way to regular python. It follows the same pattern:

array[from_this_index_inclusive, to_this_index_exclusive]

For example, by writing arr[2:6] you are saying something like select from arr all the elements from the entry at index 2 (inclusive) until the entry at index 6 (exclusive).

subsect = arr[2:6]
print(subsect)
[2 3 4 5]

If you omit the second element of the slice selection it will grab everything until the end of the array. If you omit the first one, you will get everything from the beginning until the specified index.

from_index_two_until_end = arr[2:] # Selects from index two until the end of the array
print(from_index_two_until_end)
[2 3 4 5 6 7 8 9]
until_index_five = arr[:5] # Selects from the beginning until index 5 (exclusive)
print(until_index_five)
[0 1 2 3 4]

There is an important difference between ndarray slices and regular Python slices that is worth mentioning: They don't copy data, instead, they return a view to the original array. This means that any changes you make to a variable referencing the slice will be reflected in the original array.

slice_ref = arr[2:6]
print(slice_ref)
[2 3 4 5]
# The changes made on slice_ref will be reflected on arr
slice_ref[2] = 7171 # Index 2 of slice_ref points to index 4 of arr
print(arr)
[   0    1    2    3 7171    5    6    7    8    9]

Multi-dimensional arrays

For arrays with dimensions higher than one you just need to provide one index per dimension to access individual entries.

darr = np.arange(16).reshape(4,4)
print(darr)
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]
# Select the element on the third row, second column (remember they are all 0-indexed)
element = darr[2,1]
print(element)
9
# You can also use this syntax to accomplish the same
element = darr[2][1]
print(element)
9

If you don't provide an index for an array with dimensions higher than 1 it will select all the contents of that entry. For example, if you provide only 1 index when selecting from a 2-dimensional array you will get a complete row:

row = darr[-1] # Get the last row
print(row)
[12 13 14 15]

Slicing works in a very similar way to the 1-dimensional case, the only difference is that now you can provide extra selection ranges. This is much easier to understand with examples, so let's take a look at some of the things you can do.

# Select the first two rows
ddslice = darr[:2]
print(ddslice)
[[0 1 2 3]
 [4 5 6 7]]
# Select the third element of the first two rows
ddslice2 = darr[:2, 2]
print(ddslice2)
[2 6]
# Select the first three rows and the first two elements of each
ddslice3 = darr[:3,:2]
print(ddslice3)
[[0 1]
 [4 5]
 [8 9]]
# Select every entry on each column but skip the first one
ddslice4 = darr[:, 1:]
print(ddslice4)
[[ 1  2  3]
 [ 5  6  7]
 [ 9 10 11]
 [13 14 15]]
# Select the elements from the center square of the matrix
# That is, the elements at coordinates [1,1] [1,2] [2,1] and [2,2]
ddslice5 = darr[1:3, 1:3]
print(ddslice5)
[[ 5  6]
 [ 9 10]]

It doesn't matter if you are selecting slices from a 1-dimensional array or higher, they will always return views to the original subsection of the array. This means that any change performed on the view is reflected back in the original array:

ddslice5[:] = 255
print(darr)
[[  0   1   2   3]
 [  4 255 255   7]
 [  8 255 255  11]
 [ 12  13  14  15]]

Fancy indexing

Fancy indexing is just, well, a fancy term for a simple concept: You can pass an array with indexes when selecting elements from a ndarray and it will return the elements in the right order.

tarr = np.arange(100).reshape(10, 10)
print(tarr)
[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]
# Let's get the elements in the 8th, 4th and 2nd row in that order (Being the first row the zeroth)
ordered = tarr[[8,4,2]]
print(ordered)
[[80 81 82 83 84 85 86 87 88 89]
 [40 41 42 43 44 45 46 47 48 49]
 [20 21 22 23 24 25 26 27 28 29]]

This is a very neat feature that comes in handy very often, so make sure to remember it exists and consult the documentation if you forget how to use it.

Practice slicing a bit more on your own

Selecting elements from arrays of many dimensions is very easy once you get the hang of it, but it can take a while to get used to the syntax.

By far the easiest way to learn how to use this feature is to practice. Go and generate a bidimensional array and try to select different subsets of elements. If you can navigate the 2d scenario you will have no problem with more dimensions.

In the next article, we will talk about universal functions and array-oriented programming, remember to check it out.

Thanks for reading!

What to do next

Author image
Budapest, Hungary
Hey there, I'm Juan. A programmer currently living in Budapest. I believe in well-engineered solutions, clean code and sharing knowledge. Thanks for reading, I hope you find my articles useful!