鉴于以下2列数组,我想从第二列中选择与第一列中的“edge”对应的项目。这只是一个示例,因为实际上我的a
可能有数百万行。所以,理想情况下,我希望尽快做到这一点,而不是创造中间结果。
import numpy as np
a = np.array([[1,4],[1,2],[1,3],[2,6],[2,1],[2,8],[2,3],[2,1],
[3,6],[3,7],[5,4],[5,9],[5,1],[5,3],[5,2],[8,2],
[8,6],[8,8]])
即。我想找到结果,
desired = np.array([4,6,6,4,2])
是a[:,1]
中与a[:,0]
更改位置相对应的条目。
一个解决方案是,
b = a[(a[1:,0]-a[:-1,0]).nonzero()[0]+1, 1]
给出np.array([6,6,4,2])
,我可以简单地添加第一项,没问题。但是,这会创建第一个项的索引的中间数组。我可以通过使用列表理解来避免中间体:
c = [a[i+1,1] for i,(x,y) in enumerate(zip(a[1:,0],a[:-1,0])) if x!=y]
这也给出了[6,6,4,2]
。假设基于生成器的zip
(在Python 3中为true),这不需要创建中间表示,并且应该非常高效。但是,内部循环不是numpy,它需要生成一个列表,该列表必须随后转回一个numpy数组。
你能想出一个只有numpy的版本,其内存效率为c
但速度效率为b
吗?理想情况下,只需要通过a
一次。
(请注意,测量速度在这里没有多大帮助,除非a
非常大,所以我不打算对此进行基准测试,我只想要一些理论上速度快且内存效率高的东西。例如,您可以假设a
中的行是从文件中流式传输的,并且访问速度很慢 - 这是避免b
解决方案的另一个原因,因为它需要通过a
进行第二次随机访问传递。)
编辑:一种生成大a
矩阵进行测试的方法:
from itertools import repeat
N, M = 100000, 100
a = np.array(zip([x for y in zip(*repeat(np.arange(N),M)) for x in y ], np.random.random(N*M)))
答案 0 :(得分:0)
我担心如果您希望以矢量化方式执行此操作,则无法避免使用中间数组,因为它没有内置数据。
现在,让我们寻找除nonzero()
以外的矢量化方法,这可能更高效。与(a[1:,0]-a[:-1,0])
的原始代码一样,执行差异化的概念,我们可以在寻找对应于" edge"的非零微分之后使用布尔索引。或转移。
因此,我们将采用像这样的矢量化方法 -
a[np.append(True,np.diff(a[:,0])!=0),1]
运行时测试
原始解决方案a[(a[1:,0]-a[:-1,0]).nonzero()[0]+1,1]
会跳过第一行。但是,我们只是为了计时目的而说,这是一个有效的结果。这里是针对此帖中提出的解决方案的运行时间 -
In [118]: from itertools import repeat
...: N, M = 100000, 2
...: a = np.array(zip([x for y in zip(*repeat(np.arange(N),M))\
for x in y ], np.random.random(N*M)))
...:
In [119]: %timeit a[(a[1:,0]-a[:-1,0]).nonzero()[0]+1,1]
100 loops, best of 3: 6.31 ms per loop
In [120]: %timeit a[1:][np.diff(a[:,0])!=0,1]
100 loops, best of 3: 4.51 ms per loop
现在,让我们说你想要包括第一行。更新的运行时看起来像这样 -
In [123]: from itertools import repeat
...: N, M = 100000, 2
...: a = np.array(zip([x for y in zip(*repeat(np.arange(N),M))\
for x in y ], np.random.random(N*M)))
...:
In [124]: %timeit a[np.append(0,(a[1:,0]-a[:-1,0]).nonzero()[0]+1),1]
100 loops, best of 3: 6.8 ms per loop
In [125]: %timeit a[np.append(True,np.diff(a[:,0])!=0),1]
100 loops, best of 3: 5 ms per loop
答案 1 :(得分:0)
好的,实际上我找到了一个解决方案,只是了解了np.fromiter
,它可以构建一个基于生成器的numpy数组:
d = np.fromiter((a[i+1,1] for i,(x,y) in enumerate(zip(a[1:,0],a[:-1,0])) if x!=y), int)
我认为这样做,生成一个没有任何中间数组的numpy数组。但是,需要注意的是,它看起来效率并不高!忘记我在测试问题中所说的内容:
t = [lambda a: a[(a[1:,0]-a[:-1,0]).nonzero()[0]+1, 1],
lambda a: np.array([a[i+1,1] for i,(x,y) in enumerate(zip(a[1:,0],a[:-1,0])) if x!=y]),
lambda a: np.fromiter((a[i+1,1] for i,(x,y) in enumerate(zip(a[1:,0],a[:-1,0])) if x!=y), int)]
from timeit import Timer
[Timer(x(a)).timeit(number=10) for x in t]
[0.16596235800034265, 1.811289312000099, 2.1662971739997374]
似乎第一个解决方案速度更快!我假设这是因为即使它生成中间数据,它也能够在numpy中完全执行内部循环,而在另一个中它为数组中的每个项目运行Python代码。
就像我说的那样,这就是为什么我不确定这种基准测试在这里有意义 - 如果对a
的访问速度慢得多,那么基准测试就不会加载CPU。想法?
不接受"接受"这个答案,因为我希望有人可以更快地拿出一些东西。
答案 2 :(得分:0)
如果您关注内存效率,可以这样解决:输入数据的相同大小顺序的唯一中间可以由bool类型组成(a [1:,0]!= a [: - 1,0]);如果您的输入数据是int32,则比' a'小8倍。本身。您可以计算该二进制数组的非零值以预先分配输出数组;虽然如果!=的输出与你的例子所暗示的那样稀疏,那么这应该不是很重要。