索引

ndarrays 可以使用标准Python x[obj] 语法进行索引 其中 x 是数组,*obj* 是选择。有三种索引可用:字段访问,基本切片,高级索引, 使用哪一个取决于 obj

注解

In Python, x[(exp1, exp2, ..., expN)] is equivalent to x[exp1, exp2, ..., expN]; the latter is just syntactic sugar for the former.

基本切片和索引

基本切片将Python的切片基本概念扩展到N维。 当 obj 是切片 slice 对象 (由 start:stop:step 括号内的步骤符号构造),整数或切片对象和整数的元组时,就会发生基本的切片。 Ellipsisnewaxis 对象也可以与这些对象交织在一起。

为了保持向后兼容Numeric中的常见用法,如果选择对象是包含切片对象, Ellipsis 对象或 newaxis 对象的任何非nararray序列(例如列表), 则也会启动基本切片,但不用于整数数组或其他嵌入式序列。

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

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

序列切片的标准规则适用于基于每维的基本切片(包括使用步骤索引)。 要记住的一些有用的概念包括:

  • 基本切片语法是 i:j:k 其中 i 是起始索引,j 是停止索引,k 是步骤( k\neq0 )。 这选择了具有索引值 i, i + k, …, i + (m - 1) km 个元素(在相应的维度中), 其中 m = q + (r\neq0) 并且 qr 是获得的商和余数通过将 j - i 除以 j - i by 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]]])
    
  • Ellipsis 扩展为 : 对象的数量必须与选择元组的 x.ndim 长度相同, 可能只存在一个省略号。

    例子

    >>> x[...,0]
    array([[1, 2, 3],
           [4, 5, 6]])
    
  • 选择元组中的每个 newaxis 对象用于将结果选择的维度扩展一个单位长度维度。 添加的维度是选择元组中 newaxis 对象的位置。

    例子

    >>> x[:,np.newaxis,:,:].shape
    (2, 1, 3, 1)
    
  • 整数 i 返回与 i:i+1 相同的值,除了返回对象的维数减少1.特别是, 具有第p个元素的整数(和所有其他条目 : )的选择元组返回对应的子数组, 其维数为 N - 1 .如果 N = 1,则返回的对象是数组标量 标量

  • 如果选择元组具有所有条目 : 除了作为切片对象 i:j:k 的第p个条目之外, 返回的数组通过连接元素 i, i+k, …, i + (m - 1) k < j 的整数索引返回的子数组来生成维度N

  • 在切片元组中使用多个非 : 条目进行基本切片就像使用单个非 : 条目重复应用切片一样。 其中连续采用非 : 条目(所有其他非 : 条目替换为 : )。因此, 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] ; x[obj]。这对于构造适用于任意维数组的通用代码非常有用。

numpy.newaxis

可以在所有切片操作中使用 newaxis 对象来创建长度为1的轴。 newaxis 是 ‘None’ 的别名,’None’ 可以代替它使用相同的结果。

高级索引

当选择对象 obj 是非元组序列对象,ndarray (数据类型为integer或bool)或 具有至少一个序列对象或ndarray(数据类型为integer或bool)的元组时,将触发高级索引。 高级索引有两种类型:整数和布尔值

高级索引始终返回数据的副本,与此相反,切片只提供了一个视图

警告

高级索引的定义意味着 x[(1,2,3),]x[(1,2,3)] 根本不同。 后者相当于 x[1,2,3] ,它将触发基本选择,而前者将触发高级索引。 一定要明白为什么会这样。

还要认识到 x[[1,2,3]] 将触发高级索引,而 x[[1,2,slice(None)]] 将触发基本切片。

整数数组索引

整数数组索引允许基于其N维索引选择数组中的任意项。 每个整数数组表示该维度的多个索引

纯整数数组索引

当索引由被索引的数组具有维度的整数数组组成时,索引是直接的,但与切片不同

高级索引始终作为一个 广播 和迭代:

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 相同。

例子

从每一行开始,应选择一个特定元素。 行索引只是 [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 = 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_ 调用,只会选择对角线元素,就像前面的例子中所使用的那样。 对于使用多个高级索引进行索引,这个差异是最重要的

结合高级和基本索引

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

在最简单的情况下,只有一个高级索引。单个高级索引可以例如替换切片,并且结果数组将是相同的, 但是,它是副本并且可以具有不同的存储器布局。 当可能时,切片是优选的。

Example

>>> 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,3,4) 形索引的 intp 数组, 那么 result = x[...,ind,:] 有shape (10,2,3,4,30) 因为 (20,) 子空间已被 (2,3,4) 替代, 如果我们让 i, j, k 在 ··(2,3,4)·· 子空间上循环,那么结果为 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 进行复制。

布尔数组索引

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

如果 obj.ndim == x.ndim ,则 x[obj] 返回一个1维数组,该数组填充了与obj的 True 值对应的x元素。 搜索顺序为 row-major ,C风格。如果obj在x的边界之外的条目处具有``True``值,则将引发索引错误。 如果obj小于x,则与填充 False 相同。

例子

一个常见的用例是过滤所需的元素值。 例如,可能希望从阵列中选择非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,)]

如果只有一个布尔数组而不存在整数索引数组,则这是直接的。 必须注意的是,必须确保布尔索引的维数与它应该使用的维数完全相同。

例子

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

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

但是如果 rowsum 也有两个维度:

>>> rowsum = x.sum(-1, keepdims=True)
>>> rowsum.shape
(3, 1)
>>> x[rowsum <= 2, :]    # fails
IndexError: too many indices
>>> x[rowsum <= 2]
array([0, 1])

由于额外的维度,最后一个只给出了第一个元素。 比较 rowsum.nonzero() 来理解这个例子。

将多个布尔索引数组或布尔值与整数索引数组合在一起,可以用:meth:obj.nonzero() 类比来理解。 函数 ix_ 也支持布尔数组,并且可以毫无意外地工作。

例子

使用布尔索引选择加起来为偶数的所有行。 同时,应使用高级整数索引选择列0和2。 使用 ix_ 函数可以通过以下方式完成:

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

详细说明

这些是一些详细的注释,对于日常索引(无特定顺序)并不重要:

  • 原生的NumPy索引类型为 intp ,可能与默认整数数组类型不同。 intp 是足以安全索引任何数组的最小数据类型;相对于高级索引,它可能比其他类型更快。
  • 对于高级分配,通常不能保证迭代顺序。这意味着,如果一个元素被设置了不止一次,就不可能预测最终结果。
  • 空(元组)索引是零维数组的完整标量索引。 如果 x 是零维,则 x[()] 返回标量,否则返回视图。 另一方面,x[...] 总是返回一个视图
  • 如果索引中存在零维数组并且它是完整的整数索引,则结果将是标量而不是零维数组,不会触发高级索引。
  • 当省略号(...) 存在但没有大小(即替换为零 : )时,结果仍将始终为数组。 如果没有高级索引,则为视图,否则为副本。
  • 布尔数组的 nonzero 等价不适用于零维布尔数组
  • 当高级索引操作的结果没有元素但单个索引超出范围时,是否引发 IndexError 是未定义的 (例如 x[[], [123]],其中 123 超出界限)。
  • 当在赋值期间发生转换错误时(例如,使用字符串序列更新数值数组),被分配的数组可能最终处于不可预测的部分更新状态。 但是,如果发生任何其他错误(例如超出范围索引),则数组将保持不变。
  • 高级索引结果的内存布局针对每个索引操作进行了优化,并且不能假设特定的内存顺序。
  • 当使用子类(尤其是操纵其形状的子类)时,默认的 ndarray.__setitem__ 行为将调用 __getitem__ 进行基本索引, 但不调用高级索引。 对于这样的子类,最好使用数据的基类ndarray视图调用 ndarray.__setitem__ 。 如果子类 __getitem__ 没有返回视图,则必须执行此操作。

字段访问

如果 ndarray 对象是一个结构化数组,则可以通过对数组中的字符串、 类似字典的数组进行索引来访问数组的字段。

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

索引到结构化数组也可以使用字段名称列表来完成,例如, x[['field-name1','field-name2']] 。 当前版本,这将返回一个新数组,其中包含列表中指定的字段中的值的副本。从NumPy 1.7开始,不推荐返回副本以支持返回视图。 现在将继续返回副本,但在写入副本时将发出FutureWarning。 如果依赖于当前行为,那么我们建议显式复制返回的数组, 即使用 x[['field-name1','field-name2']].copy() 。这将适用于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')

Flat Iterator indexing

x.flat 返回一个迭代器,它将迭代整个数组(以C-contiguous样式,最后一个索引变化最快)。 只要选择对象不是元组,也可以使用基本切片或高级索引对此迭代器对象建立索引。 从 x.flat 是一维视图的事实可以清楚地看出这一点。它可用于具有1维C风格平面索引的整数索引。 因此,任何返回数组的形状都是整数索引对象的形状。