从NumPy数组中获取第n个元素

4
假设我有一个numpy数组,这个数组是rgb-imagetype类型的,看起来像这样:
d = [ [ [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] ] ]

我使用random函数随机选择几个红色(r)、绿色(g)或蓝色(b)的像素。

import random
r = random.sample(range(1, len(d)*len(d[0])*3), 3)
# for example r = [25, 4, 15]

我该如何选择我想要的数据呢?
比如我想要在第一个 r_value = 25 中选择数组d中的第25个值,它对应着 d[2][2][1],因为它是第25个值。
5个回答

5
你需要做的是将它作为一个平坦或一维数组进行索引。有多种方法可以实现这一点。ravelreshape(-1)创建一维视图,flatten()创建一维副本。
最有效的方法是使用flat迭代器(一个属性,而不是方法):
In [347]: d.flat[25]
Out[347]: 25

这也可以在分配中使用,例如:d.flat[25]=0

In [341]: idx = [25, 4, 15]
In [343]: d.flat[idx]
Out[343]: array([25,  4, 15])

为了找出3D索引是什么,有一个实用程序叫做unravel_index(和相应的ravel_multi_index)。
In [344]: fidx=np.unravel_index(idx,d.shape)
In [345]: fidx
Out[345]: 
(array([2, 0, 1], dtype=int32),
 array([2, 1, 2], dtype=int32),
 array([1, 1, 0], dtype=int32))
In [346]: d[fidx]
Out[346]: array([25,  4, 15])

这是一个元组,一个元素的索引读作“下标”,例如 (2,2,1)。


在大型数组上,使用 flat 索引实际上会更快:

In [362]: dl=np.ones((100,100,100))
In [363]: idx=np.arange(0,1000000,40)
In [364]: fidx=np.unravel_index(idx,dl.shape)
In [365]: timeit x=dl[fidx]
1000 loops, best of 3: 447 µs per loop
In [366]: timeit x=dl.flat[idx]
1000 loops, best of 3: 312 µs per loop

非常有用的答案,介绍了我不知道的一些numpy工具。谢谢。 - Ludisposed

3

如果您需要经常线性检查/更改数组,您可以构建线性视图

d_lin = d.reshape(-1)  # construct a 1d view
d_lin[25]         # access the 25-th element

或者将所有内容放在一行中:
d.reshape(-1)[25]  # construct a 1d view

你现在可以像访问一维数组一样访问(和修改)d_view中的元素。因此,要访问第25个值,你可以使用 d_lin[25]。你不必每次想要访问/修改一个元素时都构建一个新的视图:只需重用 d_lin 视图即可。
此外,可以指定平铺的顺序(order='C'(类C),order='F'(类Fortran)或 order='A'(类Fortran,如果在内存中连续,则类C),order='F' 意味着我们首先迭代最大维度。
视图的优点(但这也可能导致意外行为)是,如果通过d_lin分配一个新值,例如d_lin[25] = 3,它会改变原始矩阵。
替代方案包括.flatnp.ravel。因此,以下几种方式略有差异:
d.reshape(-1)[25]
np.ravel(d)[25]
d.flat[25]

然而,在使用reshape(..)ravel(..)方法与使用flat方法之间存在一些区别。最重要的区别是,d.flat并不会创建一个完整的视图。事实上,如果我们想将这个视图传递给另一个期望numpy数组的函数,它就会崩溃,例如:

>>> d.flat.sum()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'numpy.flatiter' object has no attribute 'sum'
>>> d.reshape(-1).sum()
351
>>> np.ravel(d).sum()
351

这并不是一个问题。如果我们想限制工具的数量(例如作为一种保护机制),那么这实际上会给我们更多的安全性(尽管我们仍然可以批量设置元素,并在flatiter对象上调用np.sum(..))。


1
这正是我想要的,因为我想修改原始矩阵。谢谢。 - Ludisposed
1
使用“flat”迭代器时,我认为构建单独的“view”没有任何速度优势。d.flat索引可能比展开的等效索引更快。 - hpaulj
@hpaulj:我认为视图的优点在于它“表现”得像一个数组。例如,您可以在其上计算sum(),或者检查shape。如果您有一些适用于类似数组对象的方法,这可能非常有用。换句话说,您可以将其视为1D数组,就好像它从未是3D数组一样。 - Willem Van Onsem
我认为hpaulj在下面的回答可能比这个更好,numpy.flat将这个三行代码转换成一个属性引用。 - Louis Maddox
@LouisMaddox:此外,评论中和现在回答中指出的一些问题是,d.flat并不能产生数组的完全扁平视图。例如,我们无法计算.flat.sum()。如果我们想将一个扁平视图传递给一个函数,并且该函数期望一个类似数组的对象,则可能会出现问题。使用.flat,它将返回一个flatiter对象,其工具数量更有限。 - Willem Van Onsem

0

假设您不想展平数组: 如果您事先知道子列表的大小,那么可以轻松地计算出它。在您的示例中,主列表的每个元素都是恰好包含3个元素的列表。因此,要访问n,您可以执行以下操作:

i = n//9 j = (n%9)//3 k = (n%3) element = d[i][j][k]

对于n=25,您将获得i = 2, j = 2, k = 1,正如您所希望的。

在Python2中,您可以(并且必须)使用普通的/运算符,而不是//


np.unravel_index 进行这个计算。 - hpaulj

0
你可以使用numpy.flatten方法,像这样:
a = np.array(d)
d[25] # print 26

-1

如果你只关心值,可以将数组展平,并直接访问

val = d.flatten()[r]

如果你真的想要对应于扁平化索引的索引,你需要像这样做:
ix_2 = r % d.shape[2]
helper_2 = (r - ix_2) / d.shape[2]
ix_1 = helper_2 % d.shape[1]
helper_1 = (helper_2 - ix_1) / d.shape[1]
ix_0 = helper_1 % d.shape[0]

val = d[ix_0, ix_1, ix_2]

3
flatten()方法会产生一个副本而不是视图。 - hpaulj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接