1.Python3中切片的定义
切片的语法表达式为:[start_index : end_index : step],其中:
- start_index表示起始索引
- end_index表示结束索引
- step表示步长,步长不能为0,且默认值为1
切片操作是指按照步长,截取从起始索引到结束索引,但不包含结束索引(也就是结束索引减1)的所有元素。
- python3支持切片操作的数据类型有list、tuple、string、unicode、range
- 切片返回的结果类型与原对象类型一致
- 切片不会改变原对象,而是重新生成了一个新的对象(注意此处与Numpy中的切片操作的不同之处,Numpy切片得到的是原多维数组的一个 视图(view) ,修改切片中的内容会导致原多维数组的内容也发生变化)
2.一维数组的切片操作
一维数组的切片操作与Python列表的切片操作很相似,几个特殊的情况说明如下(L是一个包含int64整型数字的一维数组):
- 对于从前往后切片的情况:如果第一个索引是0,可以省略,即写出如下形式:L[:3](与L[0:3]等同)。同样的,与上述类似,对于从后往前切片的情况,最后一位的索引也是可以省略的(但是不是-1,-1只是最后一个元素的索引),因此以下两种情况是不相同的:
- L[-2:]表示从倒数第二个数取到最后
- L[-2:-1]表示取区间[-2, -1)范围内的数,也就是倒数第二个数
补充:L[-1]与L[-1:]均可得到最后一个数,但是L[-1]表示索引取数,取出来的就是一个数字
,而L[-1:]取出来的是一个子数组(对应到numpy就是ndarray类型),子数组中仅包含最后一个数字。
- 对于step的用法,有以下特殊情况:
- L[::5]表示从第0个数开始,所有数,每5个取一个(这个切片操作把start_index和end_index均省略了)
- L[::-1]表示翻转整个数组
- L[:]表示复制整个数组
3.多维数组的切片操作
注意:对于多维数组的切片操作,将不同维度上的切片操作用逗号分开就好了
以下是一些特殊情况的说明(由于多维数组中索引和切片常有联系,故以下情况会掺杂索引和切片的内容):
(1).索引操作
单个元素的索引操作
以下两种情况等价,均为取第一维、第二维、第三维均为0的数字。对于第一种索引方法:每个维度一个索引值,用逗号分隔开,而第二种方法为常规方法。
b = np.arange(24).reshape(2, 3, 4)
print(b)
print(b[0, 0, 0])
print(b[0][0][0])
output:
'''
[[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]]
'''
拓展:
- 倘若写成如下形式:
- b[:, 0, 0]为切片操作,考虑第一维,:表示全取,故第一维全取,而第二维为0,表示取下[0, 1, 2, 3]、[12, 13, 14, 15]这两个数组,第三维为0,表示在第二个维度中得到的结果,再取第0个索引的结果,也就得到[0, 12]
- 同理,当写成b[0, :, :]时,表示取第一维为0的全部元素,另外还有一种写法是b[0]
- 多个冒号可以用一个省略号(…)来代替,因此上面的代码等价于b[0, …]
多个元素的索引操作
对于单个元素的索引操作,似乎索引方法和常规方法无异,但是对于多个元素的索引操作,则体现其简洁性。
1.用逗号分隔的数组序列
- 序列的长度和多维数组的维数要一致
- 序列中每个数组的长度要一致
例如:
arr = np.array([
[1, 2, 3, 4],
[2, 4, 6, 8],
[3, 6, 9, 12],
[4, 8, 12, 16]
])
print(arr[[0, 2], [3, 1]])
output:
'''
[4, 6]
'''
上述代码解释:
首先第一个序列表示操作第一个维度,也就是选择第0行和第2行,之后再对第二个维度进行操作,也就是对第0行选择第3列,对第3行选择第2列。shape: (m, n, p)其中m,n,p分别表示第一、第二、第三个维度。
2.boolean/mask index
所谓 boolean index,就是用一个由 boolean 类型值组成的数组来选择元素的方法。具体参考以下代码即可:
import numpy as np
arr = np.array([[1, 2, 3, 4],
[2, 4, 6, 8],
[3, 6, 9, 12],
[4, 8, 12, 16]])
mask = arr > 5
print('boolean mask is:')
print(mask)
print(arr[mask])
output:
'''
boolean mask is:
[[False False False False]
[False False True True]
[False True True True]
[False True True True]]
[ 6 8 6 9 12 8 12 16]
'''
(2).切片操作
具体例子如下:
b = np.arange(24).reshape(2, 3, 4)
print(b)
output:
'''
[[[ 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(b[0, 1, ::2])
output:
'''
[4 6]
'''
# 第一维为0,第二维不指定,第三维为最后一个索引
print(b[0, :, -1])
output:
'''
[3 7 11]
'''
print(b[0, ::-1, -1])
output:
'''
[11 7 3]
'''
print(b[::-1])
output:
'''
[[[12 13 14 15]
[16 17 18 19]
[20 21 22 23]]
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]]
'''
# 如果在多维数组中执行翻转一维数组的命令,将在最前面的维度上翻转元素的顺序
print(b[::-1].shape)
output:
'''
(2, 3, 4)
'''
4.总结
对于Numpy切片操作的问题总结如下:
- 如果是一维数组,那么在numpy上的切片操作与Python3 list的切片操作类似;
- 如果是多维数组,那么不同维度上的切片操作用逗号分开,后续每一维的操作是在前一维操作的基础上再进行相关操作的(第一维的操作是在原数组上)。
- 对于一些特殊情况,例如多个冒号可以省略不写,反向选取等也与第二点类似(也就是说一维一维下去进行的操作是一样的,只不过这里的操作比较特殊),对于一些维度上的操作省略不写时,往往表示这个维度不用考虑(意思是这个维度上“全取”,而不指定取哪个索引的元素,与:功能类似)。因此,在遇到Numpy切片操作的相关问题时,按照以上几个思路以及上述例子考虑即可。
5.补充内容
1.关于Numpy数组如何确定第几维的问题
在这里,以一个例子来说明:
b = np.arange(24).reshape(2, 3, 4)
print(b)
output:
'''
[[[ 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(b[0, 1, ::2])
output:
'''
[4 6]
'''
例如上述例子,嵌套了三层中括号[[[…]]],在这里,最外层中括号为第一维,然后依次向里类推,最内层的括号为最后一维,因此我们执行上述切片操作时,b[0, 1, ::2],第一维取索引为0的元素,因此我们取得了
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
这个数组,再然后考虑第二维的操作,取索引为1的元素,因此我们取得了
[ 4 5 6 7]
这个数组,最后一维的操作是从第0个索引开始,所有元素,每2个取一个,因此我们取得了
[4 6]
这个数组,也就是切片操作最后得到的结果。
从上述分析可知,无论切片操作多复杂,维度有多高,只要我们抽丝剥茧,从最外层分析到最内层,每一维度的操作都按照要求来分析,这样的话也就不难得到最后的结果了。
2.关于问题1中Python3中对列表进行切片操作与Numpy中对数组进行切片操作不同之处的说明
Numpy对数组进行切片操作的代码如下:
arr = np.arange(12)
print('array is:')
print(arr)
slc = arr[::2]
print('slice is:')
print(slc)
slc[0] = 999
print('modified slice is::')
print(slc)
print('array now is::')
print(arr)
程序运行结果:
array is:
[ 0 1 2 3 4 5 6 7 8 9 10 11]
slice is:
[ 0 2 4 6 8 10]
modified slice is::
[999 2 4 6 8 10]
array now is::
[999 1 2 3 4 5 6 7 8 9 10 11]
而对应的Python3对列表进行切片的代码如下:
arr = list(range(12))
print('array is:')
print(arr)
slc = arr[::2]
print('slice is:')
print(slc)
slc[0] = 999
print('modified slice is::')
print(slc)
print('array now is::')
print(arr)
程序运行结果:
array is:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
slice is:
[0, 2, 4, 6, 8, 10]
modified slice is::
[999, 2, 4, 6, 8, 10]
array now is::
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
很明显,从以上两个程序的运行结果可以看出,切片后的变量与原始的数据共享同一数据存储。而这种特点是专门针对大数据的处理而定制的。
3.矢量化
在Numpy中,矢量化(vectorization)使得不用编写循环就可以对数据进行批量运算。大小相等的数组之间的任何算术运算都会将运算应用到元素级。具体例子如下:
arr1 = np.arange(12)
arr2 = np.arange(12)
arr1 = arr1 / 10
print(arr1)
arr2 = arr2 ** 2
print(arr2)
print(arr1 + arr2)
arr1[0:5] = 10
print(arr1)
output:
'''
[0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. 1.1]
[ 0 1 4 9 16 25 36 49 64 81 100 121]
[ 0. 1.1 4.2 9.3 16.4 25.5 36.6 49.7 64.8 81.9 101. 122.1]
[10. 10. 10. 10. 10. 0.5 0.6 0.7 0.8 0.9 1. 1.1]
'''