ndarrays 上的索引#

参见

索引程序

ndarrays 可以使用标准的 Python x[obj] 语法进行索引,其中 x 是数组,*obj* 是选择.根据 obj 的不同,有不同类型的索引可用:基本索引、高级索引和字段访问.

以下大多数示例展示了在引用数组中的数据时使用索引的情况.这些示例在向数组赋值时也同样适用.有关特定示例和赋值工作原理的解释,请参见 分配值给索引数组.

注意在Python中,``x[(exp1, exp2, …, expN)]`` 等价于 x[exp1, exp2, ..., expN];后者只是前者的语法糖.

基本索引#

单元素索引#

单个元素的索引操作与其他标准 Python 序列完全相同.它是基于 0 的,并且接受负索引从数组的末尾进行索引.:

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

没有必要将每个维度的索引分开到自己的一组方括号中.:

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

请注意,如果用少于维度的索引数来索引多维数组,会得到一个子维数组.例如::

>>> x[0]
array([0, 1, 2, 3, 4])

也就是说,每个指定的索引选择对应于所选其余维度的数组.在上面的例子中,选择0意味着长度为5的剩余维度未被指定,并且返回的是该维度和大小的数组.必须注意的是,返回的数组是一个 视图 ,即它不是原始数组的副本,而是指向与原始数组在内存中相同的值.在这种情况下,返回第一个位置(0)的1-D数组.因此,在返回的数组上使用单个索引,结果是返回单个元素.即::

>>> x[0][2]
2

所以请注意 x[0, 2] == x[0][2] 尽管第二种情况效率更低,因为在第一个索引之后会创建一个新的临时数组,随后由 2 进行索引.

备注

NumPy 使用 C 顺序索引.这意味着最后一个索引通常表示内存中变化最快的部分,不同于 Fortran 或 IDL,在这些语言中,第一个索引表示内存中变化最快的部分.这种差异代表了一个巨大的潜在混淆点.

切片和步幅#

基本切片扩展了 Python 的基本切片概念到 N 维.当 obj 是一个 slice 对象(通过括号内的 start:stop:step 符号构造)、一个整数或一个由切片对象和整数组成的元组时,会发生基本切片.:py:data:Ellipsisnewaxis 对象也可以与这些对象交错.

最简单的用 N 个整数索引的情况会返回一个 数组标量,表示相应的项.与Python一样,所有索引都是从零开始的:对于第 i 个索引 \(n_i\),有效范围是 \(0 \le n_i < d_i\),其中 \(d_i\) 是数组形状的第 i 个元素.负索引被解释为从数组末尾开始计数(即,如果 \(n_i < 0\),它意味着 \(n_i + d_i\)).

通过基本切片生成的所有数组始终是原始数组的 视图.

备注

NumPy 切片创建的是一个 视图 而不是像字符串、元组和列表这样的内置 Python 序列中的副本.在从一个大的数组中提取一小部分时必须小心,因为提取的小部分包含对大原始数组的引用,其内存不会在所有从它派生的数组被垃圾回收之前释放.在这种情况下,建议显式使用 copy().

序列切片的常规规则适用于按维度进行的基本切片(包括使用步长索引).一些需要记住的有用概念包括:

  • 基本的切片语法是 i:j:k ,其中 i 是起始索引,*j* 是停止索引,*k* 是步长(\(k\neq0\)).这会选择 m 个元素(在相应的维度中),其索引值为 i, i + k, …, i + (m - 1) k ,其中 \(m = q + (r\neq0)\) ,*q* 和 r 是通过将 j - i 除以 k 得到的商和余数:j - i = q k + r ,因此 i + (m - 1) k < j.例如:

    >>> x = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    >>> x[1:7:2]
    array([1, 3, 5])
    
  • 负的 ij 被解释为 n + in + j,其中 n 是相应维度中的元素数量.负的 k 使步进向较小的索引方向进行.从上面的例子:

    >>> x[-2:10]
    array([8, 9])
    >>> x[-3:3:-1]
    array([7, 6, 5, 4])
    
  • 假设 n 是要切片的维度中的元素数量.那么,如果 i 未给出,对于 k > 0 默认值为 0,对于 k < 0 默认值为 n - 1 .如果 j 未给出,对于 k > 0 默认值为 n ,对于 k < 0 默认值为 -n-1 .如果 k 未给出,默认值为 1.注意 ::: 相同,表示选择此轴上的所有索引.从上述例子:

    >>> x[5:]
    array([5, 6, 7, 8, 9])
    
  • 如果选择元组中的对象数量少于 N,则假设后续维度为 :.例如:

    >>> x = np.array([[[1],[2],[3]], [[4],[5],[6]]])
    >>> x.shape
    (2, 3, 1)
    >>> x[1:2]
    array([[[4],
            [5],
            [6]]])
    
  • 一个整数,*i*,返回与 i:i+1 相同的值,**除了** 返回对象的维度减少1.特别是,选择元组的第 p 个元素为整数(且所有其他条目为 :)时,返回相应子数组,维度为 N - 1.如果 N = 1,则返回的对象是数组标量.这些对象在 标量 中解释.

  • 如果选择元组的所有条目都是 : 除了第 p 个条目是一个切片对象 i:j:k,那么返回的数组具有维度 N,通过沿第 p 轴堆叠由整数索引返回的子数组形成,这些子数组由元素 i, i+k, …, i + (m - 1) k < j 的整数索引返回.

  • 在切片元组中使用多个非``:的条目进行基本切片,其行为类似于使用单个非:条目进行切片的重复应用,其中非:条目被依次取用(所有其他非:条目替换为:).因此,``x[ind1, ..., ind2,:] 的行为类似于 x[ind1][..., ind2, :] 在基本切片下.

    警告

    上述内容对于高级索引 适用.

  • 你可以使用切片来设置数组中的值,但(与列表不同)你永远不能扩展数组.要在 x[obj] = value 中设置的值的大小必须(可广播为)与 x[obj] 的形状相同.

  • 一个切片元组可以总是被构造为 obj 并在 x[obj] 符号中使用.切片对象可以在构造中代替 [start:stop:step] 符号使用.例如, x[1:10:5, ::-1] 也可以实现为 obj = (slice(1, 10, 5), slice(None, None, -1)); x[obj] .这对于构造适用于任意维度数组的通用代码非常有用.更多信息请参见 在程序中处理可变数量的索引.

维度索引工具#

有一些工具可以方便地匹配数组形状与表达式和赋值.

Ellipsis 扩展为选择元组索引所有维度所需的 : 对象的数量.在大多数情况下,这意味着扩展选择元组的长度是 x.ndim.可能只存在一个省略号.从上面的例子:

>>> x[..., 0]
array([[1, 2, 3],
      [4, 5, 6]])

这等同于:

>>> x[:, :, 0]
array([[1, 2, 3],
      [4, 5, 6]])

选择元组中的每个 newaxis 对象通过一个单位长度维度扩展结果选择.添加的维度是 newaxis 对象在选择元组中的位置.:const:newaxisNone 的别名,并且 None 可以与此具有相同结果的地方使用.从上面的例子:

>>> x[:, np.newaxis, :, :].shape
(2, 1, 3, 1)
>>> x[:, None, :, :].shape
(2, 1, 3, 1)

这可以方便地将两个数组以一种否则需要显式重塑操作的方式组合起来.例如:

>>> x = np.arange(5)
>>> x[:, np.newaxis] + x[np.newaxis, :]
array([[0, 1, 2, 3, 4],
      [1, 2, 3, 4, 5],
      [2, 3, 4, 5, 6],
      [3, 4, 5, 6, 7],
      [4, 5, 6, 7, 8]])

高级索引#

当选择对象 obj 是一个非元组序列对象、一个 ndarray (数据类型为整数或布尔),或至少包含一个序列对象或数据类型为整数或布尔的 ndarray 的元组时,会触发高级索引.高级索引有两种类型:整数和布尔.

高级索引总是返回数据的 副本 (与返回 视图 的基本切片形成对比).

警告

高级索引的定义意味着 x[(1, 2, 3),]x[(1, 2, 3)] 本质上不同.后者等同于 x[1, 2, 3] 这将触发基本选择,而前者将触发高级索引.请务必理解为什么会发生这种情况.

整数数组索引#

整数数组索引允许根据数组的 N 维索引选择任意项.每个整数数组表示该维度中的若干索引.

索引数组中允许使用负值,其工作方式与单个索引或切片相同:

>>> 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])
>>> x[np.array([3, 3, -3, 8])]
array([7, 7, 4, 2])

如果索引值超出范围,则会抛出 IndexError:

>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[np.array([1, -1])]
array([[3, 4],
      [5, 6]])
>>> x[np.array([3, 4])]
Traceback (most recent call last):
  ...
IndexError: index 3 is out of bounds for axis 0 with size 3

当索引由与被索引数组维度数量相同的整数数组组成时,索引是直接的,但不同于切片.

高级索引总是 广播 并且作为 一个 迭代:

result[i_1, ..., i_M] == x[ind_1[i_1, ..., i_M], ind_2[i_1, ..., i_M],
                           ..., ind_N[i_1, ..., i_M]]

请注意,结果的形状与(广播)索引数组形状 ind_1, ..., ind_N 相同.如果索引不能广播到相同的形状,则会引发异常 IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes....

使用多维索引数组进行索引往往是更不常见的用法,但它们是允许的,并且在某些问题上是有用的.我们将从最简单的多维情况开始:

>>> y = np.arange(35).reshape(5, 7)
>>> y
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, 30, 31, 32, 33, 34]])
>>> 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])]
Traceback (most recent call last):
  ...
IndexError: shape mismatch: indexing arrays could not be broadcast
together with shapes (3,) (2,)

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

>>> 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]])

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

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

示例

从每一行中,应选择一个特定元素.行索引只是 [0, 1, 2] ,列索引指定为相应行选择的元素,这里是 [0, 1, 0] .结合使用这两个索引,可以使用高级索引解决任务:

>>> x = np.array([[1, 2], [3, 4], [5, 6]])
>>> x[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])

要实现类似于上述基本切片的行为,可以使用广播.函数 ix_ 可以帮助进行这种广播.这最好通过一个例子来理解.

示例

从一个 4x3 数组中,应使用高级索引选择角元素.因此,需要选择列是 [0, 2] 之一且行是 [0, 3] 之一的所有元素.要使用高级索引,需要显式选择所有元素.使用前面解释的方法,可以写成:

>>> x = np.array([[ 0,  1,  2],
...               [ 3,  4,  5],
...               [ 6,  7,  8],
...               [ 9, 10, 11]])
>>> rows = np.array([[0, 0],
...                  [3, 3]], dtype=np.intp)
>>> columns = np.array([[0, 2],
...                     [0, 2]], dtype=np.intp)
>>> x[rows, columns]
array([[ 0,  2],
       [ 9, 11]])

然而,由于上述索引数组只是重复自身,可以使用广播(比较操作如 rows[:, np.newaxis] + columns)来简化这个:

>>> rows = np.array([0, 3], dtype=np.intp)
>>> columns = np.array([0, 2], dtype=np.intp)
>>> rows[:, np.newaxis]
array([[0],
       [3]])
>>> x[rows[:, np.newaxis], columns]
array([[ 0,  2],
       [ 9, 11]])

这种广播也可以通过使用函数 ix_ 来实现:

>>> x[np.ix_(rows, columns)]
array([[ 0,  2],
       [ 9, 11]])

注意,如果没有 np.ix_ 调用,只会选择对角元素:

>>> x[rows, columns]
array([ 0, 11])

这个区别是使用多个高级索引进行索引时需要记住的最重要的东西.

示例

在实际应用中,高级索引可能有用的一个例子是颜色查找表,我们希望将图像的值映射为用于显示的RGB三元组.查找表可以具有形状 (nlookup, 3).使用形状为 (ny, nx) 且 dtype=np.uint8 的图像(或任何整数类型,只要值在查找表的范围内)对这样的数组进行索引,将产生一个形状为 (ny, nx, 3) 的数组,其中每个像素位置都关联一个RGB值三元组.

布尔数组索引#

obj 是一个布尔类型的数组对象时,会发生这种高级索引,例如可能由比较运算符返回的对象.单个布尔索引数组实际上与 x[obj.nonzero()] 相同,其中如上所述,:meth:obj.nonzero() 返回一个整数索引数组的元组(长度为 obj.ndim),显示 objTrue 元素.然而,当 obj.shape == x.shape 时,它的速度更快.

如果 obj.ndim == x.ndim,``x[obj]`` 返回一个填充了 x 中对应于 objTrue 值的元素的一维数组.搜索顺序将是 行优先,C 风格.如果 obj 的形状与 x 的相应维度不匹配,无论这些值是 True 还是 False,都将引发索引错误.

这个的一个常见用例是过滤所需元素值.例如,可能希望从数组中选择所有不是 numpy.nan 的条目:

>>> x = np.array([[1., 2.], [np.nan, 3.], [np.nan, np.nan]])
>>> x[~np.isnan(x)]
array([1., 2., 3.])

或者希望将一个常数添加到所有负元素中:

>>> x = np.array([1., -1., -2., 3])
>>> x[x < 0] += 20
>>> x
array([ 1., 19., 18., 3.])

通常,如果一个索引包含一个布尔数组,结果将与在相同位置插入 obj.nonzero() 并使用上述整数数组索引机制相同.``x[ind_1, boolean_array, ind_2]`` 等价于 x[(ind_1,) + boolean_array.nonzero() + (ind_2,)].

如果只有一个布尔数组而没有整数索引数组存在,这是直接的.只需要注意确保布尔索引的*确切*维度与其要工作的维度相同.

一般来说,当布尔数组比被索引的数组维度少时,这等价于 x[b, ...],这意味着 x 通过 b 进行索引,后面跟着尽可能多的 : 以填充 x 的秩.因此,结果的形状是一个维度,包含布尔数组中 True 元素的数量,接着是被索引数组的剩余维度:

>>> x = np.arange(35).reshape(5, 7)
>>> b = x > 20
>>> b[:, 5]
array([False, False, False,  True,  True])
>>> x[b[:, 5]]
array([[21, 22, 23, 24, 25, 26, 27],
      [28, 29, 30, 31, 32, 33, 34]])

这里从索引数组中选择了第4行和第5行,并将它们组合成一个二维数组.

示例

从一个数组中,选择所有总和小于或等于两的行:

>>> x = np.array([[0, 1], [1, 1], [2, 2]])
>>> rowsum = x.sum(-1)
>>> x[rowsum <= 2, :]
array([[0, 1],
       [1, 1]])

结合多个布尔索引数组或布尔数组与整数索引数组可以通过 obj.nonzero() 类比来最好地理解.函数 ix_ 也支持布尔数组,并且不会出现任何意外.

示例

使用布尔索引选择所有行加起来为偶数的行.同时,应使用高级整数索引选择第0列和第2列.使用 ix_ 函数可以这样做:

>>> x = np.array([[ 0,  1,  2],
...               [ 3,  4,  5],
...               [ 6,  7,  8],
...               [ 9, 10, 11]])
>>> rows = (x.sum(-1) % 2) == 0
>>> rows
array([False,  True, False,  True])
>>> columns = [0, 2]
>>> x[np.ix_(rows, columns)]
array([[ 3,  5],
       [ 9, 11]])

如果没有 np.ix_ 调用,只会选择对角元素.

或者不使用 ``np.ix_``(比较整数数组示例):

>>> rows = rows.nonzero()[0]
>>> x[rows[:, np.newaxis], columns]
array([[ 3,  5],
       [ 9, 11]])

示例

使用形状为 (2, 3) 的二维布尔数组,其中包含四个 True 元素,从形状为 (2, 3, 5) 的三维数组中选择行,结果是一个形状为 (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]])

结合高级和基本索引#

当索引中至少有一个切片 (:)、省略号 (...) 或 newaxis 时(或者数组的维度比高级索引多),行为可能会更复杂.这类似于连接每个高级索引元素的索引结果.

在最简单的情况下,只有一个 单一 高级索引与一个切片结合.例如:

>>> y = np.arange(35).reshape(5,7)
>>> y[np.array([0, 2, 4]), 1:3]
array([[ 1,  2],
       [15, 16],
       [29, 30]])

实际上,切片和索引数组操作是独立的.切片操作提取索引为1和2的列(即第二列和第三列),接着是索引数组操作,提取索引为0、2和4的行(即第一行、第三行和第五行).这等效于:

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

例如,一个高级索引可以替换一个切片,结果数组将是相同的.然而,它是一个副本,并且可能具有不同的内存布局.在可能的情况下,切片是更可取的.例如:

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

理解 多个 高级索引组合的最简单方法可能是从结果的形状来思考.索引操作有两个部分,由基本索引(不包括整数)定义的子空间和由高级索引部分定义的子空间.需要区分两种索引组合的情况:

  • 高级索引通过切片、Ellipsisnewaxis 分隔.例如 x[arr1, :, arr2].

  • 高级索引都是相邻的.例如 x[..., arr1, arr2, :]不是 x[arr1, :, 1] 因为 1 在这方面是一个高级索引.

在第一种情况下,由高级索引操作产生的维度首先出现在结果数组中,然后是子空间维度.在第二种情况下,来自高级索引操作的维度插入到结果数组中与它们在初始数组中的位置相同的地方(后者的逻辑使得简单的高级索引行为就像切片一样).

示例

假设 x.shape 是 (10, 20, 30) 并且 ind 是一个形状为 (2, 5, 2) 的索引 intp 数组,那么 result = x[..., ind, :] 的形状是 (10, 2, 5, 2, 30),因为 (20,) 形状的子空间已经被一个 (2, 5, 2) 形状的广播索引子空间所替换.如果我们让 i, j, k 遍历 (2, 5, 2) 形状的子空间,那么 result[..., i, j, k, :] = x[..., ind[i, j, k], :].这个例子产生的结果与 x.take(ind, axis=-2) 相同.

示例

x.shape 为 (10, 20, 30, 40, 50) 并且假设 ind_1ind_2 可以广播到形状 (2, 3, 4).那么 x[:, ind_1, ind_2] 的形状为 (10, 2, 3, 4, 40, 50),因为来自 X 的 (20, 30) 形状的子空间已被来自索引的 (2, 3, 4) 子空间替换.然而,``x[:, ind_1, :, ind_2]`` 的形状为 (2, 3, 4, 10, 30, 50),因为不存在明确的位置来插入索引子空间,因此它被附加到开头.总是可以使用 .transpose() 将子空间移动到任何期望的位置.请注意,此示例无法使用 take 复制.

示例

切片可以与广播布尔索引结合使用:

>>> x = np.arange(35).reshape(5, 7)
>>> b = x > 20
>>> b
array([[False, False, False, False, False, False, False],
      [False, False, False, False, False, False, False],
      [False, False, False, False, False, False, False],
      [ True,  True,  True,  True,  True,  True,  True],
      [ True,  True,  True,  True,  True,  True,  True]])
>>> x[b[:, 5], 1:3]
array([[22, 23],
      [29, 30]])

字段访问#

参见

结构化数组

如果 ndarray 对象是一个结构化数组,数组的 字段 可以通过用字符串索引数组来访问,类似字典.

索引 x['field-name'] 返回一个新的 视图 到数组,该数组的形状与 x 相同(除非该字段是子数组),但数据类型为 x.dtype['field-name'] 并且仅包含指定字段中的数据部分.此外,:ref:记录数组 标量也可以通过这种方式”索引”.

索引到一个结构化数组也可以通过一个字段名称列表来完成,例如 x[['field-name1', 'field-name2']].从 NumPy 1.16 开始,这将返回一个仅包含这些字段的视图.在旧版本的 NumPy 中,它返回一个副本.有关多字段索引的更多信息,请参阅用户指南部分 结构化数组.

如果访问的字段是一个子数组,子数组的维度会被附加到结果的形状中.例如:

>>> x = np.zeros((2, 2), dtype=[('a', np.int32), ('b', np.float64, (3, 3))])
>>> x['a'].shape
(2, 2)
>>> x['a'].dtype
dtype('int32')
>>> x['b'].shape
(2, 2, 3, 3)
>>> x['b'].dtype
dtype('float64')

扁平迭代器索引#

x.flat 返回一个迭代器,该迭代器将遍历整个数组(按 C 连续风格,最后一个索引变化最快).这个迭代器对象也可以使用基本切片或高级索引进行索引,只要选择对象不是元组.这一点应该很清楚,因为 x.flat 是一个一维视图.它可以用于一维 C 风格平坦索引的整数索引.因此,任何返回数组的形状都是整数索引对象的形状.

分配值给索引数组#

如前所述,可以使用单个索引、切片和索引和掩码数组选择数组的子集进行赋值.被赋值给索引数组的值必须形状一致(与索引产生的形状相同或可广播到该形状).例如,允许将常量赋值给切片:

>>> x = np.arange(10)
>>> x[2:7] = 1

或者一个正确大小的数组:

>>> x[2:7] = np.arange(5)

请注意,如果将较高类型分配给较低类型(如将浮点数分配给整数),或者甚至引发异常(将复数分配给浮点数或整数),则分配可能会导致更改::

>>> x[1] = 1.2
>>> x[1]
1
>>> x[1] = 1.2j  
Traceback (most recent call last):
  ...
TypeError: can't convert complex to int

与某些引用(如数组和掩码索引)不同,赋值总是对数组中的原始数据进行(确实,其他任何方式都没有意义!).不过请注意,某些操作可能不会像人们天真地期望的那样工作.这个特定的例子常常让人们感到惊讶::

>>> x = np.arange(0, 50, 10)
>>> x
array([ 0, 10, 20, 30, 40])
>>> x[np.array([1, 1, 3, 1])] += 1
>>> x
array([ 0, 11, 20, 31, 40])

人们期望第1个位置的值会增加3.实际上,它只会增加1.原因是,从原始数组中提取了一个新的数组(作为临时数组),包含1, 1, 3, 1位置的值,然后将值1加到临时数组中,最后将临时数组赋值回原始数组.因此,数组在 x[1] + 1 的值被赋值给 x[1] 三次,而不是增加3次.

在程序中处理可变数量的索引#

索引语法非常强大,但在处理可变数量的索引时会受到限制.例如,如果你想编写一个函数来处理具有各种数量维度的参数,而不必为每种可能的维度数量编写特殊情况代码,这该如何实现呢?如果向索引提供一个元组,该元组将被解释为索引列表.例如:

>>> z = np.arange(81).reshape(3, 3, 3, 3)
>>> indices = (1, 1, 1, 1)
>>> z[indices]
40

因此,可以使用代码构造任意数量的索引元组,然后在索引中使用这些元组.

切片可以通过在Python中使用slice()函数在程序中指定.例如::

>>> indices = (1, 1, 1, slice(0, 2))  # same as [1, 1, 1, 0:2]
>>> z[indices]
array([39, 40])

同样地,可以通过代码使用 Ellipsis 对象来指定省略号::

>>> indices = (1, Ellipsis, 1)  # same as [1, ..., 1]
>>> z[indices]
array([[28, 31, 34],
       [37, 40, 43],
       [46, 49, 52]])

因此,可以直接使用 np.nonzero() 函数的输出来作为索引,因为它总是返回一个索引数组的元组.

由于元组的特殊处理,它们不会像列表那样自动转换为数组.例如::

>>> z[[1, 1, 1, 1]]  # produces a large array
array([[[[27, 28, 29],
         [30, 31, 32], ...
>>> z[(1, 1, 1, 1)]  # returns a single value
40

详细笔记#

这些是一些详细的笔记,它们对于日常索引来说并不重要(没有特定的顺序):

  • NumPy 的原生索引类型是 intp ,可能与默认的整数数组类型不同.``intp`` 是能够安全索引任何数组的最小数据类型;对于高级索引,它可能比其他类型更快.

  • 对于高级赋值,通常不能保证迭代顺序.这意味着如果一个元素被设置多次,则无法预测最终结果.

  • 一个空的(元组)索引是零维数组的完整标量索引.``x[()]`` 如果 x 是零维的则返回一个 标量,否则返回一个视图.另一方面,``x[…]`` 总是返回一个视图.

  • 如果在索引中存在一个零维数组 并且 它是一个完整的整数索引,结果将是一个 标量 而不是一个零维数组.(不会触发高级索引.)

  • 当存在省略号 (...) 但没有大小(即替换零个 :)时,结果仍然总是一个数组.如果没有高级索引,则是一个视图,否则是一个副本.

  • 布尔数组的 nonzero 等价性不适用于零维布尔数组.

  • 当一个高级索引操作的结果没有元素但单个索引超出范围时,是否引发 IndexError 是未定义的(例如 x[[], [123]] 其中 123 超出范围).

  • 当在赋值期间发生 casting 错误时(例如使用字符串序列更新数值数组),被赋值的数组可能会处于不可预测的部分更新状态.然而,如果发生任何其他错误(例如越界索引),数组将保持不变.

  • 高级索引结果的内存布局针对每个索引操作进行了优化,不能假定任何特定的内存顺序.

  • 当使用一个子类(特别是那些操作其形状的子类)时,默认的 ndarray.__setitem__ 行为会为 基本 索引调用 __getitem__ ,但不会为 高级 索引调用.对于这样的子类,可能更倾向于使用 基类 ndarray 视图的数据调用 ndarray.__setitem__ .如果子类的 __getitem__ 不返回视图,则 必须 这样做.