从没有中间索引数组

时间:2016-07-20 14:06:45

标签: python arrays performance numpy

鉴于以下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)))

3 个答案:

答案 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倍。本身。您可以计算该二进制数组的非零值以预先分配输出数组;虽然如果!=的输出与你的例子所暗示的那样稀疏,那么这应该不是很重要。