使用元组进行多索引

Multi-indexing with tuples

提问人:Simon 提问时间:11/11/2023 最后编辑:Simon 更新时间:11/11/2023 访问量:66

问:

我有一个多维的.我知道第一个 N 维和最后一个 M 维的形状。例如,np.array

>>> n = (3,4,5)
>>> m = (6,)
>>> a = np.ones(n + m)
>>> a.shape
(3, 4, 5, 6)

使用元组作为索引可以快速索引,例如前 N 个维度,例如

>>> i = (1,1,2)
>>> a[i].shape
(6,)

使用 list 不会给我我需要的相同结果

>>> i = [1,1,2]
>>> a[i].shape
(3, 4, 5, 6)

但是我在进行多索引(检索/分配值)时遇到了麻烦。例如

>>> i = (1,1,2)
>>> j = (2,2,2)

我需要传递类似的东西

>>> a[[i, j]]

并得到 的输出形状。(2, 6)

相反,我得到了

>>> a[[i, j]].shape
(2, 3, 4, 5, 6)

>>> a[(i, j)].shape
(3, 5, 6)

我总是可以循环或更改索引事物的方式(例如使用 和 ),但是有没有更 pythonic 的方法来实现我需要的东西?np.reshapenp.unravel_index

编辑对于任意数量的索引,我都需要它,例如,

>>> i = (1,1,2)
>>> j = (2,2,2)
>>> k = (0,0,0)
...
python numpy 多索引

评论

1赞 Chrysophylaxs 11/11/2023
您必须分别提供每个维度的索引!例如:。一个快速的方法是通过 .更多信息在这里: numpy.org/doc/stable/user/...a[ [1, 2], [1, 2], [2, 2] ]a[tuple(zip(i, j))]
1赞 Simon 11/11/2023
好!我尝试过,但忘记再次添加:P如果你写一个答案,我很乐意接受:)ziptuple
0赞 Jason 11/11/2023
我相信 Python 2.7,索引工作没有.ziptuple
0赞 ShadowRanger 11/11/2023
@Jason:在 2.7 中,返回 a of s,其行为与索引的 a of s 不同,因此仍然需要转换为以实现预期的语义。ziplisttupletupletuplenumpytuple
1赞 ShadowRanger 11/11/2023
@Chrysophylaxs:由于用 just 解包,它可以稍微缩写一下(解包在索引的上下文中产生一个)。a[*zip(i, j)]tuple

答:

1赞 ShadowRanger 11/11/2023 #1

提取每个选择,然后将它们拼接成一个新数组?

>>> np.array([a[i], a[j]]).shape
(2, 6)

评论

0赞 Simon 11/11/2023
谢谢!顺便说一句,我已经编辑了我的问题,我需要使用任意数量的索引。您的解决方案仍然有效:、。indices = [i, j, k]np.array([a[i] for i in indices])
0赞 hpaulj 11/11/2023 #2

让我们迂腐一点,以澄清每种情况下发生的事情。

In [19]: >>> n = (3,4,5)
    ...: >>> m = (6,)

加上元组(以及列表和字符串)连接它们:

In [20]: n+m
Out[20]: (3, 4, 5, 6)

In [21]: a=np.ones(n+m)

使用元组进行索引与单独输入每个标量相同。组成元组的是逗号,而不是 ()。实际上,解释器将元组传递给对象;它是对象自己的方法来解释它。(如果你给它们一个元组,列表会抱怨,但像这样的数组:) )。getitem

In [22]: a[1,1,2].shape
Out[22]: (6,)

有一个自动尾随切片。为了清楚起见,在下文中,我将包括这一点:

In [23]: a[1,1,2,:].shape
Out[23]: (6,)

In [24]: >>> i = (1,1,2)
    ...: >>> j = (2,2,2)

[i,j]转换为数组 (2,3) 形状:

In [25]: np.array([i,j])
Out[25]: 
array([[1, 1, 2],
       [2, 2, 2]])

因此,该数组仅用于索引第一个维度。其余的都在特拉林。

In [26]: a[np.array([i,j]),:,:,:].shape
Out[26]: (2, 3, 4, 5, 6)

元组的元组:

In [27]: (i,j)
Out[27]: ((1, 1, 2), (2, 2, 2))

内部元组被转换为列表,或者更确切地说是数组。因此,两个索引一起广播,选择一个 (3,) 形状(将其视为“对角线”)

In [29]: a[[1,1,2],[2,2,2],:,:].shape
Out[29]: (3, 5, 6)

添加 ,将得到 (3,6) 形状。ka[i,j,k]

我不知道你应该如何产生 (2,6) 形状i,j

等等,也许它相当于

In [32]: np.stack([a[i],a[j]]).shape
Out[32]: (2, 6)

或者等效地将 2 个选择与 连接起来。np.array

就像经验一样,这里有一个等价物:

In [45]: b=np.arange(3*4*5*6).reshape(a.shape)

In [46]: c=np.stack([b[i],b[j]])
In [47]: d=b[[1,2],[1,2],[2,2]]
In [48]: np.allclose(c,d)
Out[48]: True

因此,我们需要将元组转换为这个对对元组。i,j

In [55]: tuple([[i1,j1] for i1,j1 in zip(i,j)])
Out[55]: ([1, 2], [1, 2], [2, 2])
In [56]: tuple(np.array([i,j]).T.tolist())
Out[56]: ([1, 2], [1, 2], [2, 2])
In [57]: tuple(np.stack([i,j],1).tolist())
Out[57]: ([1, 2], [1, 2], [2, 2])

并带有第三个元组

In [58]: k=(0,0,0)    
In [59]: tuple(np.stack([i,j,k],1).tolist())
Out[59]: ([1, 2, 0], [1, 2, 0], [2, 2, 0])

我们不需要 ,尽管它非常快:tolist

In [61]: b[tuple(np.stack([i,j,k],1))]
Out[61]: 
array([[162, 163, 164, 165, 166, 167],
       [312, 313, 314, 315, 316, 317],
       [  0,   1,   2,   3,   4,   5]])

评论

1赞 Simon 11/11/2023
使用使它对我来说更具可读性,这就是流行的格式(如黑色)使用的。如果我写,很容易(至少对我来说)错过并认为这是一个标量:)()m = 6,,
0赞 Chrysophylaxs 11/11/2023
有趣的是,我不确定逗号是否为内部制作了元组;因为 、 或 中的一个可以是以形式编写的切片......i, j, ka[i, j, k]ijk:
1赞 Simon 11/11/2023 #3

我测试了 ShadowRanger 和 Chrysophylaxs 的解决方案,将它们与我最初的 ravel 解决方案进行了比较。我给它们计时,Chrysophylaxs'是最快的。

import time
import numpy as np

n = (3, 4, 5)
m = (2, 4, 6)
a = np.random.rand(*(n + m))

indices = [tuple(np.random.randint(n)) for _ in range(100)]

test = 100000

t1 = time.time()

for _ in range(test):
  x = a[tuple(zip(*indices))]  

t2 = time.time()

for _ in range(test):
  y = np.array([a[i] for i in indices])

t3 = time.time()

for _ in range(test):
  b = a.reshape(np.prod(n), np.prod(m))
  j = [np.ravel_multi_index(i, n) for i in indices]
  z = b[j].reshape(-1, *m)

t4 = time.time()
>>> print(t2 - t1, t3 - t2, t4 - t3)
>>> print(x.shape, y.shape, z.shape)
>>> assert(np.all(x - y == 0))
>>> assert(np.all(x - z == 0))
3.8938186168670654 4.831823110580444 37.469303131103516
(100, 2, 4, 6) (100, 2, 4, 6) (100, 2, 4, 6)
1赞 Chrysophylaxs 11/11/2023 #4

考虑以下指数列表:

idx = [
    (1, 1, 2),  # Your i
    (2, 2, 2),  # Your j
    (0, 0, 0),  # Your k
    (1, 2, 1),  # ... 
    (2, 0, 1),  # extend as necessary
]

和你的数组 形状 .a(3, 4, 5, 6)

当你写的时候,numpy是这样解释的:out = a[idx]

out = np.array([
    [a[1], a[1], a[2]],
    [a[2], a[2], a[2]],
    [a[0], a[0], a[0]],
    [a[1], a[2], a[1]],
    [a[2], a[0], a[1]],
])

其中,例如,只是 的第一个子数组,因此具有 形状 !a[0]a(4, 5, 6)

因此,您只剩下表示形状数组(索引的形状)的内容,其中包含 !(...最终结果为 ,或 )。(5, 3)(4, 5, 6)a(5, 3, 4, 5, 6)np.shape(idx) + a.shape[1:]


相反,您想要的是以下内容:

out = np.array([
    a[1, 1, 2],
    a[2, 2, 2],
    a[0, 0, 0],
    a[1, 2, 1],
    a[2, 0, 1],
])

在 numpy 中“矢量化”完成它的方法如下:

out = a[
    [1, 2, 0, 1, 2],  # [idx[0][0], idx[1][0], idx[2][0], ...]
    [1, 2, 0, 2, 0],  # [idx[0][1], idx[1][1], idx[2][1], ...]
    [2, 2, 0, 1, 1]   # [idx[0][2], idx[1][2], idx[2][2], ...]
]

该行为记录在索引指南中:

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

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

若要将原始文件转换为此类索引器,可以使用此技巧。idxtuple(zip(*idx))

Numpy的索引系统具有神奇的灵活性,但这种灵活性的代价是这些“简单”的任务变得不直观......至少在我看来;)