NumPy: Get and set values in an array using various indexing
This article explains how to get and set values, such as individual elements or subarrays (e.g., rows or columns), in a NumPy array (ndarray
) using various indexing.
See the following articles for information on deleting, concatenating, and adding to ndarray
.
- NumPy: Delete rows/columns from an array with np.delete()
- NumPy: Concatenate arrays with np.concatenate, np.stack, etc.
- NumPy: Insert elements, rows, and columns into an array with np.insert()
The NumPy version used in this article is as follows. Note that functionality may vary between versions.
import numpy as np
print(np.__version__)
# 1.26.1
Basics of selecting values in an ndarray
Individual elements or subarrays (such as rows or columns) in an ndarray
can be selected by specifying their positions or ranges in each dimension with commas, as in [○, ○, ○, ...]
. The trailing :
can be omitted, making [○, ○, :, :]
equivalent to [○, ○]
.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[1, 2])
# 6
print(a_2d[1])
# [4 5 6 7]
print(a_2d[:, 1])
# [1 5 9]
For a 2D array, [i]
selects the ith row, and [:, i]
selects the ith column (indexes start from 0
). More details will be provided later.
Positions in each dimension can be specified not only as integers but also in other formats such as lists or slices, allowing for the selection of any subarray.
a_3d = np.arange(24).reshape(2, 3, 4)
print(a_3d)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]]
print(a_3d[1, [True, False, True], ::2])
# [[12 14]
# [20 22]]
Specify with integers
Positions (indexes) are specified as integers (int
).
Indexes start from 0
, and negative values can be used to specify positions from the end (-1
represents the last). Specifying a non-existent position results in an error.
a_1d = np.arange(10)
print(a_1d)
# [0 1 2 3 4 5 6 7 8 9]
print(a_1d[0])
# 0
print(a_1d[4])
# 4
print(a_1d[-1])
# 9
print(a_1d[-4])
# 6
# print(a_1d[100])
# IndexError: index 100 is out of bounds for axis 0 with size 10
# print(a_1d[-100])
# IndexError: index -100 is out of bounds for axis 0 with size 10
The same applies to multi-dimensional arrays. Positions are specified for each dimension.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[1, -1])
# 7
You can omit the specification of later dimensions.
In a 2D array, [i]
, equivalent to [i, :]
, selects the ith row as a 1D array, and [:, i]
selects the ith column.
print(a_2d[1])
# [4 5 6 7]
print(a_2d[1, :])
# [4 5 6 7]
print(a_2d[:, 1])
# [1 5 9]
Specify with slices
Ranges can be selected with slices (start:end:step
).
Example with a 1D array:
a_1d = np.arange(10)
print(a_1d)
# [0 1 2 3 4 5 6 7 8 9]
print(a_1d[2:7])
# [2 3 4 5 6]
print(a_1d[:7])
# [0 1 2 3 4 5 6]
print(a_1d[2:])
# [2 3 4 5 6 7 8 9]
print(a_1d[2:7:2])
# [2 4 6]
Example with a 2D array:
The trailing :
can be omitted.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[:2, 1:3])
# [[1 2]
# [5 6]]
print(a_2d[:2, :])
# [[0 1 2 3]
# [4 5 6 7]]
print(a_2d[:2])
# [[0 1 2 3]
# [4 5 6 7]]
Using a slice i:i+1
selects a single row or column, preserving the array's dimensions, unlike selection with an integer (int
), which reduces the dimensions.
print(a_2d[1:2])
# [[4 5 6 7]]
print(a_2d[1:2].shape)
# (1, 4)
print(a_2d[1])
# [4 5 6 7]
print(a_2d[1].shape)
# (4,)
print(a_2d[:, 2:3])
# [[ 2]
# [ 6]
# [10]]
print(a_2d[:, 2:3].shape)
# (3, 1)
print(a_2d[:, 2])
# [ 2 6 10]
print(a_2d[:, 2].shape)
# (3,)
print(a_2d[1:2, 2:3])
# [[6]]
print(a_2d[1:2, 2:3].shape)
# (1, 1)
print(a_2d[1, 2])
# 6
print(a_2d[1, 2].shape)
# ()
print(type(a_2d[1, 2]))
# <class 'numpy.int64'>
Slices preserve the original array's dimensions, while integers reduce them. This difference can affect outcomes or cause errors in operations like concatenation, even with the same range selected.
Specify with a list of Boolean values: Boolean indexing
Specifying a list or ndarray
of Boolean values (True
or False
) matching the dimensions' sizes selects True
positions, similar to masking.
Example with a 1D array:
An error occurs if the sizes do not match.
a_1d = np.arange(4)
print(a_1d)
# [0 1 2 3]
print(a_1d[[True, False, True, False]])
# [0 2]
print(a_1d[np.array([True, False, True, False])])
# [0 2]
# print(a_1d[[True, False]])
# IndexError: boolean index did not match indexed array along dimension 0; dimension is 4 but corresponding boolean dimension is 2
Example with a 2D array:
Rows or columns can be extracted using a slice :
.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[[True, False, True]])
# [[ 0 1 2 3]
# [ 8 9 10 11]]
print(a_2d[:, [True, False, True, False]])
# [[ 0 2]
# [ 4 6]
# [ 8 10]]
Note that specifying a list of Boolean values for multiple dimensions simultaneously does not yield the expected result. Using np.ix_()
is necessary.
print(a_2d[[True, False, True], [True, False, True, False]])
# [ 0 10]
print(a_2d[np.ix_([True, False, True], [True, False, True, False])])
# [[ 0 2]
# [ 8 10]]
As with slices, selecting a range of width 1
(a single row or column) preserves the original array's number of dimensions.
print(a_2d[:, [True, False, False, False]])
# [[0]
# [4]
# [8]]
A comparison on an ndarray
yields a Boolean ndarray
. Using this for indexing with []
selects True
values, producing a flattened 1D array.
print(a_2d > 5)
# [[False False False False]
# [False False True True]
# [ True True True True]]
print(a_2d[a_2d > 5])
# [ 6 7 8 9 10 11]
Specify multiple conditions using &
(AND), |
(OR), and ~
(NOT) with parentheses ()
. Using and
, or
, not
, or omitting parentheses results in an error.
print((a_2d > 5) & (a_2d < 10))
# [[False False False False]
# [False False True True]
# [ True True False False]]
print(a_2d[(a_2d > 5) & (a_2d < 10)])
# [6 7 8 9]
For methods of extracting rows or columns that meet certain conditions using Boolean indexing, refer to the following article.
Specify with a list of integers: fancy indexing
It is also possible to select ranges with a list or ndarray
of integers.
Example with a 1D array:
Order can be inverted or repeated, and using negative values is allowed. Essentially, it involves creating a new array by selecting specific positions from the original array.
a_1d = np.arange(4)
print(a_1d)
# [0 1 2 3]
print(a_1d[[0, 2]])
# [0 2]
print(a_1d[[3, 3, 3, -3, 0]])
# [3 3 3 1 0]
print(a_1d[np.array([3, 3, 3, -3, 0])])
# [3 3 3 1 0]
Example with a 2D array:
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[[0, 2]])
# [[ 0 1 2 3]
# [ 8 9 10 11]]
print(a_2d[:, [0, 3]])
# [[ 0 3]
# [ 4 7]
# [ 8 11]]
As with Boolean indexing, specifying lists for multiple dimensions simultaneously does not yield the expected result. Using np.ix_()
is necessary.
print(a_2d[[0, 2], [0, 3]])
# [ 0 11]
print(a_2d[np.ix_([0, 2], [0, 3])])
# [[ 0 3]
# [ 8 11]]
As in the 1D example, the order can be inverted or repeated, and negative values are also permissible.
print(a_2d[np.ix_([2, 1, 1, -1, -1], [0, 2, 1, 3])])
# [[ 8 10 9 11]
# [ 4 6 5 7]
# [ 4 6 5 7]
# [ 8 10 9 11]
# [ 8 10 9 11]]
When selecting with a list of one element, the original array's number of dimensions is preserved, in contrast to specifying with an integer.
print(a_2d[:, [1]])
# [[1]
# [5]
# [9]]
print(a_2d[:, [1]].shape)
# (3, 1)
print(a_2d[:, 1])
# [1 5 9]
print(a_2d[:, 1].shape)
# (3,)
Combine different specification formats
Different formats can be used to specify each dimension.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[1, 1:3])
# [5 6]
print(a_2d[1, [False, True, True, False]])
# [5 6]
print(a_2d[1, [1, 2]])
# [5 6]
print(a_2d[1:, 1:3])
# [[ 5 6]
# [ 9 10]]
print(a_2d[1:, [False, True, True, False]])
# [[ 5 6]
# [ 9 10]]
print(a_2d[1:, [1, 2]])
# [[ 5 6]
# [ 9 10]]
A combination of a list of Boolean values and a list of integers requires the use of np.ix_()
.
print(a_2d[[False, True, True], [1, 2]])
# [ 5 10]
print(a_2d[np.ix_([False, True, True], [1, 2])])
# [[ 5 6]
# [ 9 10]]
Note that np.ix_()
can only accept 1D lists or arrays.
For example, when specifying multiple lists for arrays of three dimensions or more, np.ix_()
is required. However, it cannot be combined with integers or slices in the same selection operation.
a_3d = np.arange(24).reshape(2, 3, 4)
print(a_3d)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]]
# print(a_3d[np.ix_(0, [True, True, False], [0, 2])])
# ValueError: Cross index must be 1 dimensional
# print(a_3d[np.ix_(:1, [True, True, False], [0, 2])])
# SyntaxError: invalid syntax
Integers can be specified as lists containing a single element. In this case, the resulting array retains the same number of dimensions as the original ndarray
.
print(a_3d[np.ix_([0], [True, True, False], [0, 2])])
# [[[0 2]
# [4 6]]]
You can use range()
to achieve similar functionality as slices. For example, to simulate slicing, retrieve the size of the target dimension using the shape
attribute and pass it to range()
as in range(a.shape[n])[::]
.
print(a_3d[np.ix_(range(a_3d.shape[0])[:1], [True, True, False], [0, 2])])
# [[[0 2]
# [4 6]]]
print(list(range(5)[::2]))
# [0, 2, 4]
print(list(range(5)[1:3]))
# [1, 2]
Assign new values to selected ranges
You can assign new values to selected ranges in an ndarray
.
Specifying a scalar value on the right side assigns that value to all elements in the selected range on the left side.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_2d[0, 0] = 10
print(a_2d)
# [[10 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
a_2d[1] = 100
print(a_2d)
# [[ 10 1 2 3]
# [100 100 100 100]
# [ 8 9 10 11]]
a_2d[np.ix_([False, True, True], [1, 3])] = 1000
print(a_2d)
# [[ 10 1 2 3]
# [ 100 1000 100 1000]
# [ 8 1000 10 1000]]
Arrays can also be specified on the right side.
If the shape of the selected range on the left side matches that of the array on the right side, it is directly assigned. Non-contiguous locations pose no problem.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[::2, 1:])
# [[ 1 2 3]
# [ 9 10 11]]
print(np.arange(6).reshape(2, 3) * 100)
# [[ 0 100 200]
# [300 400 500]]
a_2d[::2, 1:] = np.arange(6).reshape(2, 3) * 100
print(a_2d)
# [[ 0 0 100 200]
# [ 4 5 6 7]
# [ 8 300 400 500]]
If the shape of the selected range on the left side does not match that of the array on the right side, it is assigned through broadcasting.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[::2, 1:])
# [[ 1 2 3]
# [ 9 10 11]]
print(np.arange(3) * 100)
# [ 0 100 200]
a_2d[::2, 1:] = np.arange(3) * 100
print(a_2d)
# [[ 0 0 100 200]
# [ 4 5 6 7]
# [ 8 0 100 200]]
An error occurs if the shapes cannot be broadcast.
a_2d = np.arange(12).reshape(3, 4)
print(a_2d)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(a_2d[::2, 1:])
# [[ 1 2 3]
# [ 9 10 11]]
print(np.arange(2) * 100)
# [ 0 100]
# a_2d[::2, 1:] = np.arange(2) * 100
# ValueError: could not broadcast input array from shape (2,) into shape (2,3)
For more information on broadcasting, refer to the following article.
Views and copies
The specification format used for each dimension when selecting subarrays determines whether a view or a copy of the original array is returned.
For example, using slices returns a view.
Whether two arrays refer to the same memory can be checked using np.shares_memory()
.
a = np.arange(6).reshape(2, 3)
print(a)
# [[0 1 2]
# [3 4 5]]
a_slice = a[:, :2]
print(a_slice)
# [[0 1]
# [3 4]]
print(np.shares_memory(a, a_slice))
# True
In the case of a view, changing the value in the selected subarray also changes the value in the original array, and vice versa.
a_slice[0, 0] = 100
print(a_slice)
# [[100 1]
# [ 3 4]]
print(a)
# [[100 1 2]
# [ 3 4 5]]
a[0, 0] = 0
print(a)
# [[0 1 2]
# [3 4 5]]
print(a_slice)
# [[0 1]
# [3 4]]
Boolean indexing or fancy indexing returns a copy.
a_boolean_index = a[:, [True, False, True]]
print(a_boolean_index)
# [[0 2]
# [3 5]]
print(np.shares_memory(a, a_boolean_index))
# False
In this case, changing the value in the selected subarray does not affect the original array, and vice versa.
a_boolean_index[0, 0] = 100
print(a_boolean_index)
# [[100 2]
# [ 3 5]]
print(a)
# [[0 1 2]
# [3 4 5]]
To create a copy of a subarray selected with a slice and process it separately from the original ndarray
, use the copy()
method.
a_slice_copy = a[:, :2].copy()
print(a_slice_copy)
# [[0 1]
# [3 4]]
print(np.shares_memory(a, a_slice_copy))
# False
a_slice_copy[0, 0] = 100
print(a_slice_copy)
# [[100 1]
# [ 3 4]]
print(a)
# [[0 1 2]
# [3 4 5]]
When combining different specification formats, using Boolean or fancy indexing returns a copy.
a_fancy_index_slice = a[:, [0, 2]]
print(a_fancy_index_slice)
# [[0 2]
# [3 5]]
print(np.shares_memory(a, a_fancy_index_slice))
# False
Using integers and slices returns a view.
a_scalar_slice = a[1, :2]
print(a_scalar_slice)
# [3 4]
print(np.shares_memory(a, a_scalar_slice))
# True