沿numpy数组中的范围应用函数

Posted

技术标签:

【中文标题】沿numpy数组中的范围应用函数【英文标题】:Apply function along ranges in numpy array 【发布时间】:2019-02-05 18:15:56 【问题描述】:

假设我有以下 numpy 数组:

a = np.arange(20)

还有一个包含如下索引的数组:

ix = np.array([4,10,15])

我一直在尝试为以下问题提出一个矢量化解决方案:如何使用ix 中的索引将函数沿a 拆分?

所以说我在哪里将anp.split 分开(我在这里只使用np.split 来说明我想在这里应用函数的组):

np.split(a,ix)

[array([0, 1, 2, 3]),
 array([4, 5, 6, 7, 8, 9]),
 array([10, 11, 12, 13, 14]),
 array([15, 16, 17, 18, 19])]

比如说我想对每个块取总和,所以给:

[6, 39, 60, 85]

如何使用 numpy 对其进行矢量化?

【问题讨论】:

我不认为你可以在这种数组上矢量化你的操作。你会用熊猫吗? 嗯,是的,我可以,但寻找一个 numpy 解决方案以获得更好的性能 您是在寻找总和还是一般的解决方案? 看看这个解决方案:***.com/a/32043366/4001592 我想的不是那个案例,但那家伙有一些严重的技巧,所以可以打赌他已经解决了类似的问题 【参考方案1】:

我不知道这是否是最好的解决方案,但是您可以通过添加零将具有不同大小的数组列表转换为固定大小的数组列表。然后实现一个像 sum 这样不受零影响的函数。

请参阅下面的示例。

a = np.arange(20)
ix = np.array([4,10,15])
b = np.split(a,ix)
print(b)

结果

[array([0, 1, 2, 3]),
 array([4, 5, 6, 7, 8, 9]),
 array([10, 11, 12, 13, 14]),
 array([15, 16, 17, 18, 19])]

然后使用itertools将列表转换为数组from here

import itertools
c = np.array(list(itertools.zip_longest(*b, fillvalue=0))).T
print(c)

导致

[[ 0  1  2  3  0  0]
 [ 4  5  6  7  8  9]
 [10 11 12 13 14  0]
 [15 16 17 18 19  0]]

然后用

求和
np.sum(c, axis = 1)

结果

array([ 6, 39, 60, 85])

【讨论】:

相当不错的解决方案,即使它不是基于 numpy 的,感谢分享! 不要仅仅因为zip_longest 没有np. 前缀就忽略它!这是将列表填充到统一长度的更好方法之一。 是的,我认为这个解决方案确实不错!我只是强调这一事实,因为我确实明确提到我正在寻找一个 numpy 解决方案 但是你为什么想要一个 numpy 解决方案? 因为我会将解决方案作为 a 和 ix 应用于 n 个数组。所以理想情况下,我想将它们堆叠在一起并对它们执行矢量化操作。我从一个 3dim 数组开始,执行一些操作并最终得到这些 2dim 数组 a 和 ix。为简单起见,在此处将其设为 1dim 数组。但如果解决方案是基于 numpy 的,它应该是相同的。如果使用 python 循环,它也不会扩展。【参考方案2】:

熊猫解决方案将是:

import numpy as np
import pandas as pd

a = np.arange(20)

ix = np.array([4, 10, 15])

data = pd.DataFrame(np.split(a, ix))

print(np.nansum(data, axis=1))

输出

[ 6. 39. 60. 85.]

【讨论】:

这是因为DataFrameNaN 填充短行。但是创建框架是一个非常缓慢的过程。我的列表理解速度快了 30 倍。【参考方案3】:

split 生成一个数组列表,其长度可能不同。它实际上是迭代地这样做的

In [12]: alist = []
In [13]: alist.append(a[0:idx[0]])
In [14]: alist.append(a[idx[0]:idx[1]])
In [15]: alist.append(a[idx[1]:idx[2]])
....

sum 单独应用于列表的每个元素是有意义的:

In [11]: [np.sum(row) for row in alist]
Out[11]: [6, 39, 60, 85]

当您有一个形状不同的数组列表时,您最好对它进行 Python 级别的迭代。

快速“向量化”意味着在编译后的代码中执行计算。大多数是围绕多维数组构建的,例如二维的。如果您的 split 生成了相同大小的数组,您可以将 np.sum 与适当的轴参数一起使用。

In [23]: a1 = a.reshape(4,5)
In [24]: a1
Out[24]: 
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14],
       [15, 16, 17, 18, 19]])
In [25]: np.sum(a1, axis=1)
Out[25]: array([10, 35, 60, 85])

有时我们可以使用技巧将问题转换为 n-d 问题,例如,如果拆分的第一个数组用 0 填充。但这种转换本身可能需要迭代。

在这里提出(及其链接)Origin of AttributeError: object has no attribute 'cos' 应用于对象 dtype 数组的数学 (ufunc) 函数最终将操作委托给对象的相应方法。但这仍然涉及对对象进行(接近)Python 级别的迭代。


一些时间安排:

In [57]: timeit [np.sum(row) for row in alist]
31.7 µs ± 1.21 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [58]: timeit np.sum(list(itertools.zip_longest(*alist, fillvalue=0)),axis=0)
25.2 µs ± 82 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [59]: timeit np.nansum(pd.DataFrame(alist), axis=1)
908 µs ± 28.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [61]: timeit np.frompyfunc(sum,1,1)(alist)
12.9 µs ± 21.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

在最后一种情况下,Python sumnp.sum 快。但列表理解也是如此:

In [63]: timeit [sum(row) for row in alist]
6.86 µs ± 13.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

还有 Divakar 的 wiz-bang fillna, Numpy: Fix array with rows of different lengths by filling the empty elements with zeros

In [70]: timeit numpy_fillna(np.array(alist)).sum(axis=1)
44.2 µs ± 208 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

一旦你有了一个多维数组,numpy 代码就会很快。但是如果从一个列表开始,甚至是一个数组列表,Python 列表方法通常更快。构造一个数组(或 Dataframe)所花费的时间从来都不是微不足道的。

【讨论】:

是的,我知道每个数组的长度可能不同。不,这里的拆分是为了使示例易于理解。只是想知道是否有 python 级别迭代的替代方法 真正矢量化的核心是使用标准的 numpy 构建块在编译后的代码中执行操作。在大多数情况下需要矩形多维数组。

以上是关于沿numpy数组中的范围应用函数的主要内容,如果未能解决你的问题,请参考以下文章

14numpy——统计函数

numpy中处理含nan数据的统计函数及其效率

NumPy 中的数据统计分析

python编写自定义lambda函数使用numpy中的np.vectorize函数在numpy数组中的每个元素上施加应用自定义lambda函数生成新的numpy数组

numpy.transpose 是不是在内存中重新排序数据?

numpy如何沿维度拆分数组?