本文首发于“生信补给站”公众号 https://mp.weixin.qq.com/s/HI92_O9uUiFgo_Gl_H6EXw
3数组的获取
获取数组是通过索引 (indexing) 和切片 (slicing) 来完成的,
- 切片是获取一段特定位置的元素
- 索引是获取一个特定位置的元素
索引和切片的方式和列表一模一样,参考 Python 入门篇 (上)的 2.3 节。对于一维数组 arr,
- 切片写法是 arr[start : stop : step]
- 索引写法是 arr[index]
因此,切片的操作是可以用索引操作来实现的 (一个一个总能凑成一段),只是没必要罢了。为了简化,我们在本章三节标题里把切片和索引都叫做索引。
索引数组有三种形式,正规索引 (normal indexing)、布尔索引 (boolean indexing) 和花式索引 (fancy indexing)。
3.1 正规索引
虽然切片操作可以由多次索引操作替代,但两者最大的区别在于
- 切片得到的是原数组的一个视图 (view) ,修改切片中的内容会改变原数组
- 索引得到的是原数组的一个复制 (copy),修改索引中的内容不会改变原数组
请看下面一维数组的例子来说明上述两者的不同。
一维数组
arr = np.arange(10)arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
用 arr[6]索引第 7 个元素 (记住 Python 是从 0 开始记录位置的)
arr[6]
6
把它赋给变量 a,并重新给 a 赋值 1000,但是元数组 arr 第 7 个元素的值还是 6,并没有改成 1000。
a = arr[6]a = 1000arr
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
用 arr[5:8]切片第 6 到 8 元素 (记住 Python 切片包头不包尾)
arr[5:8]
array([5, 6, 7])
把它赋给变量 b,并重新给 b 的第二个元素赋值 12,再看发现元数组 arr 第 7 个元素的值已经变成 12 了。
b = arr[5:8]b[1] = 12arr
array([ 0, 1, 2, 3, 4, 5, 12, 7, 8, 9])
这就证实了切片得到原数组的视图 (view),更改切片数据会更改原数组,而索引得到原数组的复制 (copy), 更改索引数据不会更改原数组。希望用下面一张图可以明晰 view 和 copy 的关系。
了解完一维数组的切片和索引,类比到二维和多维数组上非常简单。
二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])arr2d
array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
索引
情况一:用 arr2d[2] 来索引第三行,更严格的说法是索引「轴 0」上的第三个元素。
arr2d[2]
array([7, 8, 9])
情况二:用 arr2d[0][2] 来索引第一行第三列
arr2d[0][2]
3
索引二维数组打了两个中括号好麻烦,索引五维数组不是要打了五个中括号?还有一个简易方法,用 arr2d[0, 2] 也可以索引第一行第三列
arr2d[0,2]
3
切片
情况一:用 arr2d[:2] 切片前两行,更严格的说法是索引「轴 0」上的前两个元素。
arr2d[:2]
array([[1, 2, 3], [4, 5, 6]])
情况二:用 arr2d[:, [0,2]] 切片第一列和第三列
arr2d[:,[0,2]]
array([[1, 3], [4, 6], [7, 9]])
情况三:用 arr2d[1, :2] 切片第二行的前两个元素
arr2d[1, :2]
array([4, 5])
情况四:用 arr2d[:2, 2] 切片第三列的前两个元素
arr2d[:2, 2]
array([3, 6])
3.2 布尔索引
布尔索引,就是用一个由布尔 (boolean) 类型值组成的数组来选择元素的方法。
假设我们有阿里巴巴 (BABA),脸书 (FB) 和京东 (JD) 的
- 股票代码 code 数组
- 股票价格 price 数组:每行记录一天开盘,最高和收盘价格。
code = np.array(['BABA', 'FB', 'JD', 'BABA', 'JD', 'FB'])price = np.array([[170,177,169],[150,159,153], [24,27,26],[165,170,167], [22,23,20],[155,116,157]])price
array([[170, 177, 169], [150, 159, 153], [ 24, 27, 26], [165, 170, 167], [ 22, 23, 20], [155, 161, 157]])
假设我们想找出 BABA 对应的股价,首先找到 code 里面是 'BABA' 对应的索引 (布尔索引),即一个值为 True 和 False 的布尔数组。
code == 'BABA'
array([ True, False, False, True, False, False])
用该索引可以获取 BABA 的股价:
price[ code == 'BABA' ]
array([[170, 177, 169], [165, 170, 167]])
用该索引还可以获取 BABA 的最高和收盘价格:
price[ code == 'BABA', 1: ]
array([[177, 169], [170, 167]])
再试试获取 JD 和 FB 的股价:
price[ (code == 'FB')|(code == 'JD') ]
array([[150, 159, 153], [ 24, 27, 26], [ 22, 23, 20], [155, 161, 157]])
虽然下面操作没有实际意义,试试把股价小于 25 的清零。
price[ price < 25 ] = 0price
array([[170, 177, 169], [150, 159, 153], [ 0, 27, 26], [165, 170, 167], [ 0, 0, 0], [155, 161, 157]])
注:这种布尔索引的操作在 Pandas 更常用也更方便,看完 pandas 那帖后就可以忽略这一节了。
3.3 花式索引
花式索引是获取数组中想要的特定元素的有效方法。考虑下面数组:
arr = np.arange(32).reshape(8,4)arr
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, 30, 31]])
假设你想按特定顺序来获取第 5, 4 和 7 行时,用 arr[ [4,3,6] ]
arr[ [4,3,6] ]
array([[16, 17, 18, 19], [12, 13, 14, 15], [24, 25, 26, 27]])
假设你想按特定顺序来获取倒数第 4, 3 和 6 行时 (即正数第 4, 5 和 2 行),用 arr[ [-4,-3,-6] ]
arr[ [-4,-3,-6] ]
array([[16, 17, 18, 19], [20, 21, 22, 23], [ 8, 9, 10, 11]])
此外,你还能更灵活的设定「行」和「列」中不同的索引,如下
arr[ [1,5,7,2], [0,3,1,2] ]
array([ 4, 23, 29, 10])
检查一下,上行代码获取的分别是第二行第一列、第六行第四列、第八行第二列、第三行第三列的元素,它们确实是 4, 23, 29 和 10。如果不用花式索引,就要写下面繁琐但等价的代码:
np.array( [ arr[1,0], arr[5,3], arr[7,1], arr[2,2] ] )
array([ 4, 23, 29, 10])
最后,我们可以把交换列,把原先的 [0,1,2,3] 的列换成 [0,3,1,2]。
arr[:,[0,3,1,2]]
array([[ 0, 3, 1, 2], [ 4, 7, 5, 6], [ 8, 11, 9, 10], [12, 15, 13, 14], [16, 19, 17, 18], [20, 23, 21, 22], [24, 27, 25, 26], [28, 31, 29, 30]])
4总结
本帖讨论了 NumPy 的前三节,数组创建、数组存载和数组获取。同样把 numpy 数组当成一个对象,要学习它,无非就是学习怎么
- 创建它:按步就班法、定隔定点法、一步登天法
- 存载它:保存成 .npy, .txt 和 .csv 格式,下次加载即用
- 获取它:一段用切片,一个用索引;有正规法、布尔法、花式法
等等,你好像还没教什么 numpy 数组硬核的东西呢,下帖讨论 NumPy 的后两节就教怎么
- 变形它:重塑和打平,合并和分裂,元素重复和数组重复
- 计算它:元素层面计算,线性代数计算,广播机制计算
回到引言的「数组转置」问题:
arr = np.arange(16).reshape((2, 2, 4))arr
array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7]], [[ 8, 9, 10, 11], [12, 13, 14, 15]]])
将第 1, 2, 3 维度转置到第 2, 1, 3 维度,即将轴 0, 1, 2 转置到轴 1, 0, 2。
解答:
数组转置的本质:交换每个轴 (axis) 的形状 (shape) 和跨度 (stride)。
四幅图解决问题:
原数组
内存块的样子
轴 0 和轴 1 互换
转置结果
用代码验证一下:
arr.transpose(1,0,2)
array([[[ 0, 1, 2, 3], [ 8, 9, 10, 11]], [[ 4, 5, 6, 7], [12, 13, 14, 15]]])
欧了!下篇讨论 NumPy 系列的「数组的变性」和「数组的计算」。Stay Tuned!