Numpy学习笔记——概念、索引、切片与广播机制

说明

本文主要参考了 Numpy快速上手指南 — 基础篇Numpy快速上手指南 — 进阶篇,并且带有一些主观的思考与解释,仅用于帮助理解,因此不保证结论的准确性,也欢迎您指出错误。

Numpy 的轴与秩

Numpy 的轴可以认为是维度,结合 C 语言中的多维数组的概念,或者结合数学中坐标轴的概念,都可以帮助理解“轴”的含义。轴的个数叫做秩,或者说是维数,秩的大小可以理解为描述元素位置所需要的参数个数。例如:

array([[1, 2, 3],
       [4, 5, 6]])

元素5位于第二个一维数组的第二个位置,所以我们可以使用(1, 1)来描述它,因此该数组有两个轴,秩为 2。同时我们可以依据寻找元素5的过程来确定0 轴1 轴——我们首先确定元素位于哪个一维数组中,因此0 轴就是“垂直向下”的方向;进而确定元素在一维数组中的具体位置,因此1 轴就是“水平向右”的方向。
之所以详细介绍0 轴1 轴,主要是为了便于理解 获取最大最小值、求和 等操作。举例来讲,我们有一个2*3的矩阵,想要得到每一列的最大值,那么就是沿着纵轴来不断比较得到最大值,存放在长度为 3 的一维数组中,所以我们可以通过设置axis=0来实现:

import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6]])
np.max(arr, axis=0)  # array([4, 5, 6])

同理,我们要得到各行的最大值,可以设置axis=1
如果不清楚数组的秩/形状,可以调用数组的ndimshape属性来得到答案:

arr.ndim  # 2
arr.shape  # (2, 3)

以上仅是介绍了秩为 2 的数组,更高维的情况是类似的。譬如秩为 3 的数组arr2,获取其每一行的最大值,这里每一行其实是2 轴的方向,所以设置axis=2

arr2 = np.arange(24).reshape(2, 3, 4)
'''
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]]])
'''
np.max(arr2, axis=2)
'''
array([[ 3,  7, 11],
       [15, 19, 23]])
'''

了解各轴的含义对于后续的学习很有帮助,如广播机制。前文对于各轴的描述其实也是从打印的效果来给出的,观察上述例子,也可以得到以下打印规则(引自 Numpy快速上手指南 — 基础篇 ):

打印一个数组,NumPy以类似嵌套列表的形式显示它,但是呈以下布局:

  • 最后的轴从左到右打印;
  • 次后的轴从顶向下打印;
  • 剩下的轴从顶向下打印,每个切片通过一个空行与下一个隔开;
  • 一维数组被打印成行,二维数组成矩阵,三维数组成矩阵列表。

索引

Numpy 的索引机制与 Matlab 颇为相似,可以使用整数数组来作为索引得到多个想要的元素,非常灵活。
注意: 下面的介绍暂不考虑切片的情况,仅仅考虑索引。

单数值索引

我们从简单的开始介绍,沿用arr2,我们获取元素20,其位置可以用(1, 2, 0)来唯一对应,具体实现也是如此:

arr2[1, 2, 0]  # 20
arr2[(1, 2, 0)]  # 20

可以看到,我们传入元组索引或者多个整数索引都可以得到元素值,但如果我们传入多个列表,情况就有所不同:

arr2[[1], [2], [0]]  # array([20])

由于没有查询源码,这里推测是使用列表(或数组)时,是可以检索多个元素的,那么返回的查询结果也是多个元素组成的数组。仅检索一个元素是特殊情况,检索两个元素时就比较明晰了:

# 检索(0, 1, 2), (1, 2, 3)两个位置的元素
arr2[[0, 1], [1, 2], [2, 3]]  # array([ 6, 23])

这里传入的各列表需要同等维度或者符合传播机制的规则,数组索引后续会详细介绍,这里只是为了区分几种不同的传参方式。

数组索引

数组索引可以同时检索多个元素,假设需要在秩为 d 的数组中检索 n 个元素 {e1, e2, ···, en},其中 ei 的索引为 (ei1, ei2, ···, eid),那么调用方式即为 arr_name[arg1, arg2, ···, argd],其中 argj = [e1j, e2j, ···, enj]。简单来说,就是要提供每一维度上的索引数组,里面存放着各元素在该维度上的索引值。前文中有对该方式有举例,这里也给出一个较为简单的例子:

arr3 = np.arange(10) * 2  # array([ 0,  2,  4,  6,  8, 10, 12, 14, 16, 18])
arr3[[0, 1, 1, 6]]  # array([ 0,  2,  2, 12])

值得注意的是,这里并不一定传入一维数组,也可以传入高维数组索引,返回结果与传入数组的shape相同:

idx = np.array([[1, 6], [5, 8]])
arr3[idx]
'''
array([[ 2, 12],
       [10, 16]])
'''
布尔索引

通过布尔数组可以实现对元素的筛选,True表示选中,False表示未选中。举个简单的例子,假如我们要筛选arr3中大于 5 的元素,我们可以直接把筛选条件写进去:

arr3[arr3 > 5]  # array([ 6,  8, 10, 12, 14, 16, 18])

这个过程其实可以看成两个步骤,先由 arr3 > 5得到由各元素对应的 bool 值组成的 bool 数组,然后再根据数组索引得到 bool 值为True的元素:

arr3 > 5  # array([False, False, False, True, True, True, True, True, True, True])
arr3[np.array([False, False, False, True, True, True, True, True, True, True])]
# array([ 6,  8, 10, 12, 14, 16, 18]), 与 arr3[arr3 > 5] 结果相同

从这个例子可以看出 bool 数组较好的筛选作用,只需要给出筛选条件就可以得到我们想要的元素,而不是给出具体的索引值。
布尔数组的形状是需要和待检索数组相匹配的,上面的例子是一维数组,传入的 bool 数组对轴上的每一个元素都进行了筛选;对于高维数组,我们自然可以传入和其shape相同的 bool 数组来筛选每一个元素。但是,也可以针对每个维度给出一个 bool 数组,比较类似前面介绍的数组索引。简单来说,检索思路就是通过查看各个维度上第一个True的位置,来确定第一个元素的位置;进而得到所有的待检索元素。所以每一个维度中True的个数要相同。举个例子会更加清晰一点:

arr4 = np.arange(12).reshape(3, 4)
'''
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
'''
arr4[[True, False, True], [False, True, True, False]]  # array([ 1, 10])
小结

前面的索引方式,对于每一维都有限制;但实际上,我们可能对某一维并没有限制条件,只要在其它维上的条件满足即可,那么一个最普适的思路就是,我们对某一维不做任何代码上的限制:

arr4[[True, False, True]]
'''
array([[ 0,  1,  2,  3],
       [ 8,  9, 10, 11]])
'''

可以看到,我们只限制了第一维(第一维的下标需要是02),最后得到所有第一维下标是02的元素。换个角度来看这一过程,考虑到第二维上没有任何限制,我们可以沿着arr4的第一维切成 3 份,那么每一份上的元素就是“等价”的,因为第一维是相同的,而第二维又没有做进一步的限制。这样一来,由于限制条件是[True, False, True],所以把第 1 份和第 3 份拿来就好了,而这也正是最终的结果。虽然这里的描述很是拖沓,但也是我对“切片”的最为直观的理解了。

切片

未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值