关于Numpy数组切片操作的整理与思考

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]
'''
  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值