0%

numpy学习——03索引切片

本文是根据 DataWhale『巨硬的 numpy』文档进行学习, 辅之以天池平台, 之前学习过, 此时进行进一步的整理, 着重学习未完全熟悉的知识点.

详细的代码我也放入了天池学习的AI训练营合集中

副本与视图

需要注意的是, 对数组的复制不能简单的直接使用赋值符号, 在数组中存在副本视图一说.

  • 副本可以使用 copy() 来创建, 并且对副本数据进行修改, 并不会影响原本的数据, 它们指向不同的内存.
  • 但是对于视图, 却是指向同一个内存地址的, 修改视图也会修改原始数据.
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
x = np.ones((4,4),dtype='int32')
y = x
y[1:3, 1:3] = 0
print(x)
# [[1 1 1 1]
# [1 0 0 1]
# [1 0 0 1]
# [1 1 1 1]]
print(y)
# [[1 1 1 1]
# [1 0 0 1]
# [1 0 0 1]
# [1 1 1 1]]

x = np.ones((4,4),dtype='int32')
y = x.copy()
y[1:3, 1:3] = 0
print(x)
# [[1 1 1 1]
# [1 1 1 1]
# [1 1 1 1]
# [1 1 1 1]]
print(y)
# [[1 1 1 1]
# [1 0 0 1]
# [1 0 0 1]
# [1 1 1 1]]

这点 copy() 可以与之前在 list 时提到过的深拷贝与浅拷贝进行对比学习, 两者的 copy() 并不一样, 更严格来说是 listndarray 不一样.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
l1 = [[1,2],[3,4]]
l2 = l1.copy()
l2[0][0] = 999
print(l1)
# [[999, 2], [3, 4]]
print(l2)
# [[999, 2], [3, 4]]

a1 = np.arange(4).reshape(2,2)
a2 = a1.copy()
a2[0][0] = 999
print(a1)
# [[0 1]
# [2 3]]
print(a2)
# [[999 1]
# [ 2 3]]

索引和切片

整数索引

想要直接获取数组中的单个元素,直接使用和 list 类似的方法就可以了, 也可以使用类似坐标形式的 [1,1].

1
2
3
4
a = np.arange(9).reshape(3,3)
print(a[0]) # [0 1 2]
print(a[1][1]) # 4
print(a[1, 1]) # 4

切片

切片操作是指抽取数组的一部分元素生成新数组.

需要注意的是:

  • list 进行切片操作得到的数组是原数组的副本;
  • 而对 Numpy 数据进行切片操作得到的数组则是指向相同内存的视图.

由于和 list 切片方法类似, 所以下面就举几个例子

1
2
3
4
5
6
7
8
9
x = np.arange(1, 9)
print(x[0:2]) # [1 2]
#用下标0~5,以2为步长选取数组
print(x[1:5:2]) # [2 4]
print(x[2:]) # [3 4 5 6 7 8]
print(x[:-2]) # [1 2 3 4 5 6]
print(x[:]) # [1 2 3 4 5 6 7 8]
#利用负数下标翻转数组
print(x[::-1]) # [8 7 6 5 4 3 2 1]

对于二维的数组, [x][y] 这种格式的切片与 [x, y] 格式的切片结果是不一样的.

  • [x, y]: 是同时对两个维度『行和列』进行切片
  • [x][y]: 是先对第一维『行』axis=0 进行切片 [x], 然后在此基础上再进行第一维『行』 axis=0 切片 [y]得到最终结果, 所以实际上是对一个维度切片两次, 而不是进行二维的切片.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
x = np.arange(9).reshape(3,3)
print(x[:1])
# [[0 1 2]]

print(x[0:2, 0:2])
# [[0 1]
# [3 4]]
print(x[0:2][0:2])
# [[0 1 2]
# [3 4 5]]

print(x[:, :])
# [[0 1 2]
# [3 4 5]
# [6 7 8]]

dots 索引

numpy 可以使用 ... 来表示足够多的冒号, 从而在高维数组切片中表示完整的索引列表.

比如,如果 x 是 5 维数组:

  • x[1,2,...] 等价于 x[1,2,:,:,:]
  • x[...,3] 等价于 x[:,:,:,:,3]
  • x[4,...,5,:] 等价于 x[4,:,:,5,:]

整数数组索引和切片

索引中使用 list, 来表示同时取多个值.

1
2
3
4
5
6
7
8
9
10
11
12
a = np.arange(1, 13).reshape(3,4)
print(a)
# [[ 1 2 3 4]
# [ 5 6 7 8]
# [ 9 10 11 12]]
print(a[[0,2]])
# [[ 1 2 3 4]
# [ 9 10 11 12]]
print(a[:, [0, 2]])
# [[ 1 3]
# [ 5 7]
# [ 9 11]]

整数数组索引也可以结合 np.take(a, indices, axis=None, out=None, mode='raise')使用, 可以指定索引作用的轴, 若不指定轴, 则是数组按行顺序的第几个, 更多参数可以另外查询.

1
2
3
4
5
6
7
8
9
10
x = np.arange(1, 13).reshape(3,4)
print(np.take(x, [0,9]))
# [ 1 10]
print(np.take(x, [0,2], axis=0))
# [[ 1 2 3 4]
# [ 9 10 11 12]]
print(np.take(x, [0,2], axis=1))
# [[ 1 3]
# [ 5 7]
# [ 9 11]]

需要注意的是: 整数索引数组得到结果是新数组(新副本), 而切片得到的结果是数组的子数组(小视图).

1
2
3
4
5
6
7
8
9
10
11
x = np.arange(1, 6)
y = x[1:3]
y[0] = 77
print(x) # [ 1 77 3 4 5]
print(y) # [2 3]

x = np.arange(1, 6)
z = x[[1,2]]
z[0] = 66
print(x) # [1 2 3 4 5]
print(z) # [66 3]

布尔索引

布尔索引是由布尔值组成的索引, 一般作用就是根据条件进行筛选.

1
2
3
4
5
6
x = np.arange(1, 9)
y = x > 5
print(y)
# [False False False False False True True True]
print(x[x > 5])
# [6 7 8]

如果需要对布尔索引取反, 则可以使用 logical_not 操作, 同理, 还有 and, or, xor.

1
2
3
4
x = np.array([np.nan, 1, 2, np.nan, 3, 4, 5])
y = np.logical_not(np.isnan(x))
print(x[y])
# [1. 2. 3. 4. 5.]

需要注意的是, 对于多维数组, 使用布尔索引筛选得到的结果依旧是一维的.

1
2
3
4
5
6
7
8
x = np.arange(9).reshape(3,3)
y = x > 5
print(y)
# [[False False False]
# [False False False]
# [ True True True]]
print(x[x > 5])
# [6 7 8]

个人收获

学到了 dots 索引, 之前觉得跟 : 完全一模一样, 因为是在二维数组中, 但是到了高维数组中就可以实现简单写法了.

使用 logical_and等 对布尔数组进行逻辑操作的『优雅』写法.

最重要的是理清楚了副本与视图之间的关系, 使得之前进行数组操作的一些迷糊地方更深刻, 总结来说还是深浅拷贝的坑 💫, 这次整理清楚了!

------ 本文结束------