更高效的方式将Numpy数组调整为不同大小的块

4

抱歉,我不确定如何更准确地命名标题。

我有一个数组,想要将其均匀分成3个数组,然后每个数组的大小都是原始数组通过平均值下采样得到的不同版本。

这是我的代码:

import numpy as np
a = np.arange(100)
bins = [5, 4, 3]
split_index = [[20, 39], [40, 59], [60, 80]]
b = []
for count, item in enumerate(bins):
    start = split_index[count][0]
    end = split_index[count][1]
    increment = (end - start) // item
    b_per_band = []
    for i in range(item):
        each_slice = a[start + i * increment : start + (i + 1) * increment]
        b_per_band.append(each_slice.mean())
    b.append(b_per_band)
print(b)

结果:

[[21.0,24.0,27.0,30.0,33.0],[41.5,45.5,49.5,53.5],[62.5,   68.5,74.5]]

所以我通过循环遍历容器,找到每个步骤的增量。然后根据情况进行切片并将平均值添加到结果中。

但这真的很丑陋,最重要的是性能不佳。由于我在处理音频谱的情况下,我真的很想了解实现相同结果的更有效方法。

有什么建议吗?


请澄清一下:1)您从数组a中获取由split_index给出的切片;2)对于每个切片,您计算长度为bins的“子切片”;3)对于每个子切片,您取平均值。这样正确吗? - FObersteiner
(1)(2) 正确的。(3) 对于这种情况,每个切片的大小为20,我想将其下采样到一个箱子中,例如5,这意味着每4个样本我将取这些4个样本的平均值并附加。 - J_yang
2个回答

2
这里有一个使用np.add.reduceat的选项:

最初的回答:

a = np.arange(100)
n_in_bin = [5, 4, 3]
split_index = [[20, 39], [40, 59], [60, 80]]
b = []
for i, sl in enumerate(split_index):
    n_bins = (sl[1]-sl[0])//n_in_bin[i]
    v = a[sl[0]:sl[0]+n_in_bin[i]*(n_bins)]
    sel_bins = np.linspace(0, len(v), n_in_bin[i]+1, True).astype(np.int)
    b.append(np.add.reduceat(v, sel_bins[:-1])/np.diff(sel_bins)))
print(b)
# [array([21., 24., 27., 30., 33.]) array([41.5, 45.5, 49.5, 53.5]) array([62.5, 68.5, 74.5])]

一些注释:

  • 我将名称bins更改为n_in_bin以更清晰地表达。
  • 使用floor division,你会丢失一些数据。不知道这是否真的很重要,只是一个提示。
  • 应该使此代码更快的事情,至少对于大型数组大小和“块”,是使用np.add.reduceat。根据我的经验,这可能比循环更有效率。
  • 如果输入数据中有NaN,请查看此问答

编辑/修订

由于我目前也在处理binning stuff,我尝试了几件事,并针对迄今为止显示的三种方法运行了timeit,“looped”用于问题中的方法,“npredat”使用np.add.reduceat,npsplit使用np.split,并获得100000次迭代的每次迭代的平均时间[µs]:

a = np.arange(10000)
bins = [5, 4, 3]
split_index = [[20, 3900], [40, 5900], [60, 8000]]
-->
looped: 127.3, npredat: 116.9, npsplit: 135.5

对比。

a = np.arange(100)
bins = [5, 4, 3]
split_index = [[20, 39], [40, 59], [60, 80]]
-->
looped: 95.2, npredat: 103.5, npsplit: 100.5

然而,对于100k次迭代的多次运行结果略有不一致,并且可能与我尝试过的机器不同。因此,我的结论是,目前差异微小。所有三个选项都在1µs < domain > 1ms范围内。最初的回答。

是的,这也是我在比较不同方法时发现的。似乎在样本量小于100000时,每种方法都没有明显的优势。但当样本量大于此时,这种方法更有效率。我不知道为什么,因为我看到的操作非常线性。 - J_yang
可能有一些内存优化正在进行,这也取决于内存的当前状态 - 但这只是猜测。也许这个问题也适合在代码审查论坛上提出。 - FObersteiner

0

你的做法看起来对我来说非常奇怪,包括设置,可能需要采用不同的方法,使问题变得更简单。

然而,使用相同的方法,你可以尝试这个:

b = []

for count, item in enumerate(bins):
    start = split_index[count][0]
    end = split_index[count][1]
    increment = (end - start) // item

    b_per_band = np.mean(np.split(a[start:start + item * increment], item),axis=1)

    b.append(b_per_band)

嗯,这是正确的。但我计时了一个包含8000个样本的数组分成3个2000个元素的块。使用np.split()实际上更慢,前一种方法只需要107微秒,而后者则需要125微秒。 - J_yang

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