在ndarrays上索引
来源:Numpy官网:https://numpy.org/doc/stable/user/basics.html
文章目录
- 在ndarrays上索引
- 导包
- 【1】基本索引
- 【2】高级索引
- 【3】结合高级索引和基本索引
- 【3】现场访问
- 【4】展开迭代器索引
- 【5】为索引数组赋值
- 【6】处理程序中可变数量的索引
导包
import numpy as np
【1】基本索引
① 单元素索引
单元素索引的工作方式与其他标准 Python 序列完全相同。它从 0 开始,并接受负索引从数组末尾开始索引。
x = np.arange(10)
print(x)
[0 1 2 3 4 5 6 7 8 9]
x[2]
2
x[-2]
8
没有必要将每个维度的索引分隔到其自己的一组方括号中。
x.shape = (2, 5)
print(x)
[[0 1 2 3 4]
[5 6 7 8 9]]
x[1, 3]
8
x[1, -1]
9
注意,如果索引的多维数组少于维度,则会得到一个子维度数组。例如
print(x[0])
[0 1 2 3 4]
必须注意的是,返回的数组是一个视图,即它不是原始数组的副本,而是指向内存中与原始数组相同的值。
x[0][2]
2
注意 x[0, 2] == x[0][2],尽管第二种情况效率更低,因为在第一个索引之后创建了一个新的临时数组,随后由 2 索引。
② 切片与步长
基本切片将 Python 的基本切片概念扩展到 N 维。
【注意】NumPy 切片创建一个视图,而不是像内置 Python 序列(如字符串、元组和列表)那样创建副本。从大数组中提取一小部分时必须小心,该部分在提取后变得无用,因为提取的一小部分包含对大型原始数组的引用,直到从它派生的所有数组都被垃圾回收后,其内存才会被释放。在这种情况下,建议使用显式 copy()
基本的切片语法是 i:j:k,其中 i 是起始索引,j 是停止索引,k 是步长 (k ≠ 0)
x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print(x)
[0 1 2 3 4 5 6 7 8 9]
print(x[1:7:2])
[1 3 5]
负 i 和 - j 被解释为 n + i 和 n + j,其中 n 是相应维度中的元素数。
print(x)
[0 1 2 3 4 5 6 7 8 9]
print(x[-2:10])
[8 9]
print(x[8:10]) ## 等价
[8 9]
print(x[-3:3:-1])
[7 6 5 4]
print(x[7:3:-1])
[7 6 5 4]
:: 与 : 相同,表示沿着此轴选择所有索引
print(x)
[0 1 2 3 4 5 6 7 8 9]
print(x[5:])
[5 6 7 8 9]
print(x[5::])
[5 6 7 8 9]
如果选择元组中的对象数小于 N,则假定 : 对于任何后续维度。例如:
x = np.array([[[1], [2], [3]], [[4], [5], [6]]])
print(x)
[[[1]
[2]
[3]]
[[4]
[5]
[6]]]
x.shape
(2, 3, 1)
print(x[1:2])
[[[4]
[5]
[6]]]
③ 维度索引工具
x
array([[[1],
[2],
[3]],
[[4],
[5],
[6]]])
print(x[..., 0])
[[1 2 3]
[4 5 6]]
这等价于
x[:, :, 0]
array([[1, 2, 3],
[4, 5, 6]])
newaxis 对象用于将生成的所选内容的尺寸扩展一个单位长度的维度。【newaxis 是 None 的别名】
x.shape
(2, 3, 1)
x[:, np.newaxis, :, :].shape
(2, 1, 3, 1)
x[:, None, :, :].shape
(2, 1, 3, 1)
这可以方便地组合两个数组,否则需要显式重塑操作。例如:
x = np.arange(5)
print(x)
[0 1 2 3 4]
print(x[:, np.newaxis])
[[0]
[1]
[2]
[3]
[4]]
print(x[np.newaxis, :])
[[0 1 2 3 4]]
x[:, np.newaxis] + x[np.newaxis, :]
array([[0, 1, 2, 3, 4],
[1, 2, 3, 4, 5],
[2, 3, 4, 5, 6],
[3, 4, 5, 6, 7],
[4, 5, 6, 7, 8]])
【2】高级索引
高级索引始终返回数据的副本(与返回视图的基本切片相反)).
① 整数数组索引
x = np.arange(10, 1, -1)
print(x)
[10 9 8 7 6 5 4 3 2]
print(x[np.array([3, 3, 1, 8])])
[7 7 9 2]
print(x[np.array([3, 3, -3, 8])])
[7 7 4 2]
使用多维索引数组进行索引往往是更不寻常的用途,但它们是允许的,并且它们对某些问题很有用。我们将从最简单的多维案例开始:
y = np.arange(35).reshape(5, 7)
print(y)
[[ 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]]
print(y[np.array([0, 2, 4]), np.array([0, 1, 2])])
[ 0 15 30]
结果数组的第一个值为 y[0, 0]下一个值是 y[2, 1],最后一个值是 y[4, 2].
广播机制允许索引数组与其他索引的标量组合。效果是标量值用于索引数组的所有相应值:
print(y)
[[ 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]]
print(y[np.array([0, 2, 4]), 1])
[ 1 15 29]
x = np.array([[1, 2], [3, 4], [5, 6]])
print(x)
[[1 2]
[3 4]
[5 6]]
print(x[[0, 1, 2], [0, 1, 0]])
[1 4 5]
x = np.array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[9, 10, 11]])
print(x)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
rows = np.array([[0, 0],
[3, 3]], dtype=np.intp)
print(rows)
[[0 0]
[3 3]]
columns = np.array([[0, 2],
[0, 2]], dtype=np.intp)
print(columns)
[[0 2]
[0 2]]
print(x[rows, columns])
[[ 0 2]
[ 9 11]]
但是,由于上面的索引数组只是重复自己,因此可以使用广播
rows = np.array([0, 3], dtype=np.intp)
print(rows)
[0 3]
columns = np.array([0, 2], dtype=np.intp)
print(columns)
[0 2]
print(rows[:, np.newaxis])
[[0]
[3]]
print(x[rows[:, np.newaxis], columns])
[[ 0 2]
[ 9 11]]
这种广播也可以使用ix_ 函数实现
print(x)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
print(x[np.ix_(rows, columns)])
[[ 0 2]
[ 9 11]]
注意,如果没有np.ix_调用,将仅选择对角线元素:
print(x[rows, columns])
[ 0 11]
② 布尔数组索引
一个常见的用例是筛选所需的元素值。例如,可能希望从数组中选择所有不是 NaN 的条目:
x = np.array([[1., 2.], [np.nan, 3.], [np.nan, np.nan]])
print(x)
[[ 1. 2.]
[nan 3.]
[nan nan]]
print(x[~np.isnan(x)])
[1. 2. 3.]
或者希望为所有负元素添加一个常量:
x = np.array([1., -1., -2., 3])
print(x)
[ 1. -1. -2. 3.]
x[x < 0] += 20
print(x)
[ 1. 19. 18. 3.]
x = np.arange(35).reshape(5, 7)
print(x)
[[ 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]]
b = x > 20
print(b)
[[False False False False False False False]
[False False False False False False False]
[False False False False False False False]
[ True True True True True True True]
[ True True True True True True True]]
print(b[:, 5])
[False False False True True]
print(x[b[:, 5]])
[[21 22 23 24 25 26 27]
[28 29 30 31 32 33 34]]
在这里,从索引数组中选择第 4 行和第 5 行并组合成二维数组。
从数组中,选择总和小于或等于 2 的所有行:
x = np.array([[0, 1], [1, 1], [2, 2]])
print(x)
[[0 1]
[1 1]
[2 2]]
rowsum = x.sum(-1)
print(rowsum)
[1 2 4]
print(x[rowsum <= 2, :])
[[0 1]
[1 1]]
使用布尔索引选择所有行,总和为偶数。
x = np.array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[9, 10, 11]])
print(x)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
rows = (x.sum(-1) % 2) == 0
print(rows)
[False True False True]
columns = [0, 2]
print(columns)
[0, 2]
x[np.ix_(rows, columns)]
array([[ 3, 5],
[ 9, 11]])
如果没有np.ix_调用,则只会选择对角线元素。
使用形状 (2, 3) 的二维布尔数组和四个 True 元素从形状 (2, 3, 5) 的三维数组中选择行,结果为形状 (4, 5):
x = np.arange(30).reshape(2, 3, 5)
x
array([[[ 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]]])
b = np.array([[True, True, False], [False, True, True]])
print(b)
[[ True True False]
[False True True]]
x[b]
array([[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]])
【3】结合高级索引和基本索引
y = np.arange(35).reshape(5, 7)
print(y)
[[ 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]]
在最简单的情况下,只有一个高级索引与一个切片组合在一起。例如:
y[np.array([0, 2, 4]), 1:3]
array([[ 1, 2],
[15, 16],
[29, 30]])
实际上,切片和索引数组操作是独立的。切片操作提取具有索引 1 和 2 的列(即第 2 列和第 3 列),然后是索引数组操作,该操作提取具有索引 0、2 和 4 的行(即第一、第三和第五行)。这相当于:
y[:, 1:3][np.array([0, 2, 4]), :]
array([[ 1, 2],
[15, 16],
[29, 30]])
x = np.array([[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[9, 10, 11]])
print(x)
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
x[1:2, 1:3]
array([[4, 5]])
x[1:2, [1, 2]]
array([[4, 5]])
切片可以与广播布尔索引结合使用:
x = np.arange(35).reshape(5, 7)
print(x)
[[ 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]]
b = x > 20
print(b)
[[False False False False False False False]
[False False False False False False False]
[False False False False False False False]
[ True True True True True True True]
[ True True True True True True True]]
x[b[:, 5], 1:3]
array([[22, 23],
[29, 30]])
【3】现场访问
x = np.zeros((2, 2), dtype=[('a', np.int32), ('b', np.float64, (3, 3))])
x['a'].shape
(2, 2)
x['a'].dtype
dtype('int32')
x['b'].shape
(2, 2, 3, 3)
x['b'].dtype
dtype('float64')
【4】展开迭代器索引
x.flat 返回一个迭代器,该迭代器将遍历整个数组(采用 C 连续样式,最后一个索引变化最快)。
【5】为索引数组赋值
例如,允许为切片分配常量:
x = np.arange(10)
print(x)
[0 1 2 3 4 5 6 7 8 9]
x[2:7] = 1
print(x)
[0 1 1 1 1 1 1 7 8 9]
或大小合适的数组:
x[2:7] = np.arange(5)
print(x)
[0 1 0 1 2 3 4 7 8 9]
注意,某些操作可能无法像我们天真期望的那样工作。
x = np.arange(0, 50, 10)
print(x)
[ 0 10 20 30 40]
x[np.array([1, 1, 3, 1])] += 1
print(x)
[ 0 11 20 31 40]
人们期望第一个位置将增加 3。事实上,它只会增加 1。原因是从包含 1、1、3、1 值的原始数组(作为临时数组)中提取一个新数组,然后将值 1 添加到临时数组中,然后将临时数组分配回原始数组。因此,x[1] + 1 处的数组值分配给 x[1] 三次,而不是递增 3 次。
【6】处理程序中可变数量的索引
如果向索引提供元组,则元组将被解释为索引列表。例如:
z = np.arange(81).reshape(3, 3, 3, 3)
print(z)
[[[[ 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]]]]
indices = (1, 1, 1, 1)
print(z[indices])
40
因此,可以使用代码构造任意数量的索引的元组,然后在索引中使用这些元组。
indices = (1, 1, 1, slice(0, 2))
print(indices) ## 和 same as [1, 1, 1, 0:2]
(1, 1, 1, slice(0, 2, None))
print(z[indices])
[39 40]
同样,可以使用 Ellipsis 对象由代码指定省略号:
indices = (1, Ellipsis, 1) # 等价于 [1, ... ,1]
print(indices)
(1, Ellipsis, 1)
print(z[indices])
[[28 31 34]
[37 40 43]
[46 49 52]]
由于元组的特殊处理,它们不会像列表那样自动转换为数组。举个例子:
print(z)
[[[[ 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]]]]
print(z[[1, 1, 1, 1]])
[[[[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]]]
[[[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]]]
[[[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]]]
[[[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]]]]
print(z[(1, 1, 1, 1)])
40