Numpy 索引与广播

索引

数组索引是指任何使用方括号 ([]) 来索引数组值的方法。索引有很多选项,这赋予了 NumPy 索引强大的功能,但伴随着强大的功能而来的是一些复杂性和混乱的可能性。

单元素索引

一维数组的单个元素索引是人们所期望的。它的工作方式与其他标准 Python 序列完全一样。它是基于 0 的,并接受从数组末尾开始索引的负索引。

>>> x = np.arange(10)
>>> x[2]
2
>>> x[-2]
8

与列表和元组不同,NumPy 数组支持多维数组的多维索引。这意味着没有必要将每个维度的索引分隔到它自己的一组方括号中。

>>> x.shape = (2,5) # now x is 2-dimensional
>>> x[1,3]
8
>>> x[1,-1]
9

其他索引选项

可以对数组进行切片和跨步以提取维数相同但大小与原始数组不同的数组。切片和跨步的工作方式与列表和元组的工作方式完全相同,只是它们也可以应用于多个维度。几个例子最能说明问题:

>>> x = np.arange(10)
>>> x[2:5]
array([2, 3, 4])
>>> x[:-7]
array([0, 1, 2])
>>> x[1:7:2]
array([1, 3, 5])
>>> y = np.arange(35).reshape(5,7)
>>> y[1:5:2,::3]
array([[ 7, 10, 13],
       [21, 24, 27]])

请注意,**数组切片不会复制内部数组数据,而只会生成原始数据的新视图。**这与列表或元组切片不同,copy()如果不再需要原始数据,建议使用显式切片。

为了从数组中选择值列表到新数组中,可以用其他数组索引数组。有两种不同的方法可以实现这一点。一种使用一个或多个索引值数组。另一个涉及提供适当形状的布尔数组来指示要选择的值。索引数组是一种非常强大的工具,可以避免遍历数组中的单个元素,从而大大提高性能。

可以使用特殊功能通过索引有效地增加数组中的维数,以便生成的数组获得在表达式或特定函数中使用所需的形状。

索引数组

NumPy 数组可以与其他数组(或任何其他可以转换为数组的类似序列的对象,例如列表,元组除外;有关原因,请参见本文档的末尾)进行索引。索引数组的使用范围从简单、直接的情况到复杂、难以理解的情况。对于索引数组的所有情况,返回的是原始数据的副本,而不是切片的视图。

索引数组必须是整数类型。数组中的每个值指示使用数组中的哪个值来代替索引。为了显示:

>>> x = np.arange(10,1,-1)
>>> x
array([10,  9,  8,  7,  6,  5,  4,  3,  2])
>>> x[np.array([3, 3, 1, 8])]
array([7, 7, 9, 2])

由值 3、3、1 和 8 组成的索引数组相应地创建了一个长度为 4 的数组(与索引数组相同),其中每个索引都被索引数组在被索引的数组中的值替换。

负值是允许的,并且可以像处理单个索引或切片一样工作:

>>> x[np.array([3,3,-3,8])]
array([7, 7, 4, 2])

索引值越界是错误的:

>>> x[np.array([3, 3, 20, 8])]
<type 'exceptions.IndexError'>: index 20 out of bounds 0<=index<9

一般而言,使用索引数组时返回的是一个与索引数组形状相同的数组,但被索引的数组的类型和值。例如,我们可以改用多维索引数组:

>>> x[np.array([[1,1],[2,3]])]
array([[9, 9],
       [8, 7]])

索引多维数组

当多维数组被索引时,事情变得更加复杂,尤其是多维索引数组。这些往往是更不寻常的用途,但它们是允许的,并且它们对某些问题很有用。我们将从最简单的多维情况开始(使用前面示例中的数组 y):

>>> y[np.array([0,2,4]), np.array([0,1,2])]
array([ 0, 15, 30])

在这种情况下,如果索引数组具有匹配的形状,并且被索引的数组的每个维度都有一个索引数组,则结果数组与索引数组具有相同的形状,并且值对应于为每个数组设置的索引在索引数组中的位置。在此示例中,两个索引数组的第一个索引值都是 0,因此结果数组的第一个值是 y[0,0]。下一个值是 y[2,1],最后一个值是 y[4,2]。

如果索引数组的形状不同,则会尝试将它们广播为相同的形状。如果它们不能广播到相同的形状,则会引发异常:

>>> y[np.array([0,2,4]), np.array([0,1])]
<type 'exceptions.ValueError'>: shape mismatch: objects cannot be
broadcast to a single shape

广播机制允许索引数组与其他索引的标量组合。效果是标量值用于索引数组的所有对应值:

>>> y[np.array([0,2,4]), 1]
array([ 1, 15, 29])

跳到下一个复杂级别,可以仅部分索引具有索引数组的数组。理解在这种情况下会发生什么需要一些思考。例如,如果我们只使用一个带有 y 的索引数组:

>>> y[np.array([0,2,4])]
array([[ 0,  1,  2,  3,  4,  5,  6],
       [14, 15, 16, 17, 18, 19, 20],
       [28, 29, 30, 31, 32, 33, 34]])

结果是构建一个新数组,其中索引数组的每个值从被索引的数组中选择一行,结果数组具有结果形状(索引元素的数量,行的大小)。

这可能有用的一个例子是颜色查找表,我们希望将图像的值映射到 RGB 三元组以进行显示。查找表可以有一个形状 (nlookup, 3)。用 dtype=np.uint8(或任何整数类型,只要值在查找表的范围内)形状为 (ny, nx) 的图像索引这样的数组将导致形状为 (ny, nx, 3) 其中三个 RGB 值与每个像素位置相关联。

通常,结果数组的形状将是索引数组的形状(或所有索引数组被广播到的形状)与正在索引的数组中任何未使用的维度(未索引的维度)的形状的串联.

布尔或“掩码”索引数组

用作索引的布尔数组的处理方式与索引数组完全不同。布尔数组的形状必须与被索引的数组的初始维度相同。在最直接的情况下,布尔数组具有相同的形状:

>>> b = y>20
>>> y[b]
array([21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34])

与整数索引数组的情况不同,在布尔情况下,结果是一个一维数组,其中包含索引数组中与布尔数组中所有真实元素相对应的所有元素。索引数组中的元素始终以行优先(C 样式)顺序迭代和返回 。结果也与 相同 y[np.nonzero(b)]。与索引数组一样,返回的是数据的副本,而不是切片时的视图。

如果 y 的维度多于 b,结果将是多维的。例如:

>>> b[:,5] # use a 1-D boolean whose first dim agrees with the first dim of y
array([False, False, False,  True,  True])
>>> y[b[:,5]]
array([[21, 22, 23, 24, 25, 26, 27],
       [28, 29, 30, 31, 32, 33, 34]])

这里从索引数组中选择第 4 行和第 5 行并组合成一个二维数组。

一般来说,当布尔数组的维数比被索引的数组少时,这相当于 y[b, …],这意味着 y 由 b 索引,后跟尽可能多的 : 以填充 y 的秩。因此,结果的形状是一维,包含布尔数组的 True 元素的数量,然后是被索引的数组的其余维度。

例如,使用具有四个 True 元素的形状 (2,3) 的二维布尔数组从形状 (2,3,5) 的 3-D 数组中选择行会导致形状 (4 ,5):

>>> x = np.arange(30).reshape(2,3,5)
>>> x
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]]])
>>> b = np.array([[True, True, False], [False, True, True]])
>>> x[b]
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [20, 21, 22, 23, 24],
       [25, 26, 27, 28, 29]])

广播

广播含义:描述了 numpy 在算术运算期间如何处理具有不同形状的数组。

受限于某些限制,较小的阵列在较大的阵列上“广播”,以便它们具有兼容的形状。广播提供了一种向量化数组操作的方法,以便循环发生在 C 而不是 Python 中。它不会制作不必要的数据副本,并且通常会导致高效的算法实现。然而,在某些情况下,广播是一个坏主意,因为它会导致内存使用效率低下,从而减慢计算速度。

NumPy 操作通常在逐个元素的基础上对成对的数组进行。在最简单的情况下,两个数组必须具有完全相同的形状,如下例所示:

a = np.array([1.0, 2.0, 3.0])
b = np.array([2.0, 2.0, 2.0])
a * b
array([ 2.,  4.,  6.])

当数组的形状满足某些约束时,NumPy 的广播规则会放宽此约束。最简单的广播示例发生在一个数组和一个标量值在一个操作中组合时:

a = np.array([1.0, 2.0, 3.0])
b = 2.0
a * b
array([ 2.,  4.,  6.])

结果等同于前面的例子,其中b是一个数组。我们可以想象在算术运算期间标量b被拉伸成一个与 形状相同的数组a。中的新元素 b只是原始标量的副本。拉伸类比只是概念上的。NumPy 足够聪明,可以使用原始标量值而无需实际制作副本,以便广播操作尽可能具有内存和计算效率。

第二个示例中的代码比第一个示例中的代码更高效,因为广播在乘法期间移动的内存更少(b是标量而不是数组)。

一般广播规则

在对两个数组进行操作时,NumPy 按元素比较它们的形状。它从尾随(即最右侧)尺寸开始并向左工作。当两个维度兼容时

  1. 他们是平等的,或者
  2. 其中之一是 1

如果不满足这些条件, 则会抛出异常,表明数组具有不兼容的形状。结果数组的大小是沿着输入的每个轴不为 1 的大小。ValueError: operands could not be broadcast together

阵列不需要有相同数量的尺寸。例如,如果您有一个256x256x3RGB 值数组,并且想要将图像中的每种颜色缩放不同的值,则可以将图像乘以具有 3 个值的一维数组。根据广播规则排列这些数组的尾轴的大小,表明它们是兼容的:

Image  (3d array): 256 x 256 x 3
Scale  (1d array):             3
Result (3d array): 256 x 256 x 3

当比较的任一维度为一个时,将使用另一个。换句话说,尺寸为 1 的维度被拉伸或“复制”以匹配另一个。

在以下示例中,A和B数组都具有长度为 1 的轴,这些轴在广播操作期间扩展为更大的尺寸:

A      (4d array):  8 x 1 x 6 x 1
B      (3d array):      7 x 1 x 5
Result (4d array):  8 x 7 x 6 x 5

更多实例:

A      (2d array):  5 x 4
B      (1d array):      1
Result (2d array):  5 x 4

A      (2d array):  5 x 4
B      (1d array):      4
Result (2d array):  5 x 4

A      (3d array):  15 x 3 x 5
B      (3d array):  15 x 1 x 5
Result (3d array):  15 x 3 x 5

A      (3d array):  15 x 3 x 5
B      (2d array):       3 x 5
Result (3d array):  15 x 3 x 5

A      (3d array):  15 x 3 x 5
B      (2d array):       3 x 1
Result (3d array):  15 x 3 x 5

以下是不广播的形状示例:

A      (1d array):  3
B      (1d array):  4 # 尾部维度不匹配

A      (2d array):      2 x 1
B      (3d array):  8 x 4 x 3 # 第二个维度不匹配

实践中的广播示例:

>>> x = np.arange(4)
>>> xx = x.reshape(4,1)
>>> y = np.ones(5)
>>> z = np.ones((3,4))

>>> x.shape
(4,)

>>> y.shape
(5,)

>>> x + y
ValueError: operands could not be broadcast together with shapes (4,) (5,)

>>> xx.shape
(4, 1)

>>> y.shape
(5,)

>>> (xx + y).shape
(4, 5)

>>> xx + y
array([[ 1.,  1.,  1.,  1.,  1.],
       [ 2.,  2.,  2.,  2.,  2.],
       [ 3.,  3.,  3.,  3.,  3.],
       [ 4.,  4.,  4.,  4.,  4.]])

>>> x.shape
(4,)

>>> z.shape
(3, 4)

>>> (x + z).shape
(3, 4)

>>> x + z
array([[ 1.,  2.,  3.,  4.],
       [ 1.,  2.,  3.,  4.],
       [ 1.,  2.,  3.,  4.]])

广播提供了一种获取两个数组的外积(或任何其他外操作)的便捷方法。以下示例显示了两个一维数组的外加运算:

>>> a = np.array([0.0, 10.0, 20.0, 30.0])
>>> b = np.array([1.0, 2.0, 3.0])
>>> a[:, np.newaxis] + b
array([[  1.,   2.,   3.],
       [ 11.,  12.,  13.],
       [ 21.,  22.,  23.],
       [ 31.,  32.,  33.]])

这里newaxis索引运算符将一个新轴插入到 中a,使其成为二维4x1数组。将4x1数组与b具有 shape 的 组合(3,)产生一个4x3数组。

注:参考Numpy原文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ACxz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值