NumPy 系列(三):numpy 数组的索引
前面我们规定,将一维数组称为向量,将二维数组称为矩阵。
3.1 访问数组元素
与 Python 列表一致,访问 NumPy 数组元素时使用中括号,索引由 0 开始。
(1)访问向量
import numpy as np
# 创建向量
arr1=np.arange(1,10)
print(arr1)
[1 2 3 4 5 6 7 8 9]
print(arr1[3])
print(arr1[-1])
4
9
arr1[3]=100
print(arr1)
[ 1 2 3 100 5 6 7 8 9]
(2) 访问矩阵
arr2=np.array([[1,2,3],[4,5,6]])
print(arr2)
[[1 2 3][4 5 6]]
print(arr2[0,2])
print(arr2[1,-2])
3
5
arr2[1,1]=100.9
print(arr2)
[[ 1 2 3][ 4 100 6]]
在 In [7]中,浮点数 100.9 插入到整数型数组时被截断了。
3.2 花式索引
花式索引(Fancy indexing)又名“花哨的索引”,UP 认为不应该用“花哨”
来形容,这里的 Fancy 应取“华丽的、巧妙的、奢华的、时髦的”之义。
上一小节访问单个元素时,向量用 arr1[x],矩阵用 arr2[x,y]。逗号在矩阵里
用于区分行与列,这一小节,逗号新增一个功能,且不会与矩阵里的逗号混淆。
普通索引用一层中括号,花式索引用两层中括号。
(1)向量的花式索引
import numpy as np
arr1=np.arange(0,90,10)
print(arr1)
print(arr1[[0,2]])
[ 0 10 20 30 40 50 60 70 80]
[ 0 20]
(2)矩阵的花式索引
import numpy as np
arr2=np.arange(12).reshape(3,4)
print(arr2)
print(arr2[[0,2],[2,1]])
print(arr2[[1,0,2],[1,1,2]])
arr2[[1,0,2],[1,1,2]]=100
print(arr2)
[[ 0 1 2 3][ 4 5 6 7][ 8 9 10 11]]
[2 9]
[ 5 1 10]
[[ 0 100 2 3][ 4 100 6 7][ 8 9 100 11]]
根据以上实例,花式索引输出的仍然是一个向量。
要在 Markdown 中打出这个表格,可按照以下格式编写:
索引方式 | 向量 | 矩阵 |
---|---|---|
普通索引 | arr1[x,] | arr2[x, y] |
花式索引 | arr1[[x₁,x₂,…,xₙ],] | arr2[[x₁,x₂,…,xₙ], [y₁,y₂,…,yₙ]] |
3.3 访问数组切片
(1)向量的切片
向量与列表切片的操作完全一致,因此本页内容在 Python 基础中均有涉及。
import numpy as np
arr1=np.arange(10)
print(arr1)
print(arr1[1:4])
print(arr1[2:])
print(arr1[:4])
[0 1 2 3 4 5 6 7 8 9]
[1 2 3]
[2 3 4 5 6 7 8 9]
[0 1 2 3]
print(arr1)
print(arr1[2:-2]) # 舍去首尾2个
print(arr1[2:]) # 舍去头2个
print(arr1[:-2]) # 舍去尾部2个
[0 1 2 3 4 5 6 7 8 9]
[2 3 4 5 6 7]
[2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7]
print(arr1)
print(arr1[::2])
print(arr1[::3])
print(arr1[1:-1:2])
[0 1 2 3 4 5 6 7 8 9]
[0 2 4 6 8]
[0 3 6 9]
[1 3 5 7]
(2) 矩阵的切片
import numpy as np
arr2=np.arange(24).reshape(4,6)
print(arr2)
print(arr2[1:3,2:4])
print(arr2[::2,::3])
[[ 0 1 2 3 4 5][ 6 7 8 9 10 11][12 13 14 15 16 17][18 19 20 21 22 23]]
[[ 8 9][14 15]]
[[ 0 3][12 15]]
(3)提取矩阵的行
基于矩阵的切片功能,我们可以提取其部分行,如示例所示。
import numpy as np
arr3=np.arange(1,21).reshape(4,5)
print(arr3)
print(arr3[1:3,:])
[[ 1 2 3 4 5][ 6 7 8 9 10][11 12 13 14 15][16 17 18 19 20]]
[[ 6 7 8 9 10][11 12 13 14 15]]
考虑代码的简洁,当提取矩阵的某几行时可简写(但提取列的时候不可简写)。
print(arr3[2,:]) # 规范提取行
print(arr3[2]) # 简便提取行
[11 12 13 14 15]
[11 12 13 14 15]
所以,有时你可能看到诸如 arr[1][2] 这样的语法,不必吃惊,其实这只是先
提取了第 1 行,再提取该行中第 2 个元素。提一句,UP 并不推荐这样的写法。
(4)提取矩阵的列
基于矩阵的切片功能,我们可以提取其部分列,如示例所示。
import numpy as np
arr4=np.arange(1,21).reshape(4,5)
print(arr4)
print(arr4[:,1]) # 提取第2列(注意:输出的是向量!!)
print(arr4[:,2:-1])
[[ 1 2 3 4 5][ 6 7 8 9 10][11 12 13 14 15][16 17 18 19 20]]
[ 2 7 12 17]
[[ 3 4][ 8 9][13 14][18 19]]
值得注意的是,提取某一个单独的列时,出来的结果是一个向量。其实这么
做只是为了省空间,我们知道,列矩阵必须用两层中括号来存储,而形状为 1000
的向量,自然比形状为(1000,1)的列矩阵更省空间(节约了 1000 对括号)。
import numpy as np
arr5=np.arange(1,16).reshape(3,5)
print(arr5)
print(arr5[:,4])
# print(arr5[:,4].T) 向量无法转置,所以要先变成矩阵
arr6=arr5.reshape(-1,1)
print(arr6)
arr7=arr5.reshape(1,-1).T
print(arr7)
[[ 1 2 3 4 5][ 6 7 8 9 10][11 12 13 14 15]]
[ 5 10 15]
[[ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12][13][14][15]]
[[ 1][ 2][ 3][ 4][ 5][ 6][ 7][ 8][ 9][10][11][12][13][14][15]]
3.4 数组切片仅是视图
(1)数组切片仅是视图
与 Python 列表和 Matlab 不同,NumPy 数组的切片仅仅是原数组的一个视
图。换言之,NumPy 切片并不会创建新的变量,示例如下。
import numpy as np
arr=np.arange(10)
print(10)
arr1=arr[1:5]
arr1[2]=100 # 对切片进行修改
print(arr) # 原数组也会被更改
10
[ 0 1 2 100 4 5 6 7 8 9]
习惯 Matlab 的用户可能无法理解,但其实这正是 NumPy 的精妙之处。试想
一下,一个几百万条数据的数组,每次切片时都创建一个新变量,势必造成大量
的内存浪费。因此,NumPy 的切片被设计为原数组的视图是极好的。
深度学习中为节省内存,将多次使用 arr[:] = <表达式> 来替代 arr = <表达式>。
(2)备份切片为新变量
如果真的需要为切片创建新变量(这种情况很稀少),使用 .copy() 方法。
import numpy as np
arr=np.arange(10)
print(arr)
copy=arr[:4].copy() # 创建切片的副本
copy[2]=100
print(arr) # 原数组不变
print(copy)
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[ 0 1 100 3]
3.5 数组赋值仅是绑定
(1)数组赋值仅是绑定
与 NumPy 数组的切片一样,NumPy 数组完整的赋值给另一个数组,也只是
绑定。换言之,NumPy 数组之间的赋值并不会创建新的变量,示例如下。
import numpy as np
arr1=np.arange(10)
print(arr1)
arr2=arr1
arr2[2]=100
print(arr2)
print(arr1) # 原数组也被改变
[0 1 2 3 4 5 6 7 8 9]
[ 0 1 100 3 4 5 6 7 8 9]
[ 0 1 100 3 4 5 6 7 8 9]
此特性的出现仍然是为了节约空间,破局的方法仍然与前面相同。
(2)复制数组为新变量
如果真的需要赋给一个新数组,使用 .copy() 方法。
import numpy as np
arr1=np.arange(10)
print(arr1)arr2=arr1.copy()
print(arr2)arr2[2]=200
print(arr1)
print(arr2)
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[0 1 2 3 4 5 6 7 8 9]
[ 0 1 200 3 4 5 6 7 8 9]