通用函数在提供的快速的元素级运算的同时,还有一些高级用法能使我们丢开循环而编写出更为简洁的代码。

ufunc实例方法
NumPy中有一些用于执行特定矢量化运算的二元ufunc:

reduce接受一个数组参数,并通过一系列的二元运算对其值进行聚合(可指明轴向)。
用np.add.reduce可对数组中各个元素进行聚合求和:
In [115]: arr = np.arange(10)In [116]: np.add.reduce(arr)Out[116]: 45In [117]: arr.sum()Out[117]: 45
起始值取决于ufunc的实现,对于add函数是0。设置轴号使我们可以更简洁的解决一些问题,比如检查数组各行中的值是否是有序,可以用np.logical_and实现。
首先准备测试数据:
In [118]: np.random.seed(12346)In [119]: arr = np.random.randn(5, 5)In [120]: arr[::2].sort(1) # 排序一些行
对于每一行,将每一个元素与后面相邻的元素进行比较:
In [121]: arr[:, :-1] < arr[:, 1:]Out[121]:array([[ True, True, True, True],[False, True, False, False],[ True, True, True, True],[ True, False, True, True],[ True, True, True, True]], dtype=bool)
使用logical_and.reduce函数将结果合并起来:
In [122]: np.logical_and.reduce(arr[:, :-1] < arr[:, 1:], axis=1)Out[122]: array([ True, False, True, False, True], dtype=bool)
其实logical_and.reduce跟all方法效果是等价的:
(arr[:, :-1] < arr[:, 1:]).all(axis=1)
ccumulate跟reduce的关系就像cumsum跟sum的关系那样。
accumulate可用于产生一个跟原数组大小相同的中间“累计”值数组:
In [123]: arr = np.arange(15).reshape((3, 5))In [124]: np.add.accumulate(arr, axis=1)Out[124]:array([[ 0, 1, 3, 6, 10],[ 5, 11, 18, 26, 35],[10, 21, 33, 46, 60]])
outer用于计算两个数组的叉积:
In [125]: arr = np.arange(3).repeat([1, 2, 2])In [126]: arrOut[126]: array([0, 1, 1, 2, 2])In [127]: np.multiply.outer(arr, np.arange(5))Out[127]:array([[0, 0, 0, 0, 0],[0, 1, 2, 3, 4],[0, 1, 2, 3, 4],[0, 2, 4, 6, 8],[0, 2, 4, 6, 8]])
outer输出结果的维度是两个输入数据的维度的高维合并:
In [128]: x, y = np.random.randn(3, 4), np.random.randn(5)In [129]: result = np.subtract.outer(x, y)In [130]: result.shapeOut[130]: (3, 4, 5)
reduceat用于计算局部聚合,可以看成是一个对数据各切片进行聚合的groupby运算。它接受一个列表作为对值进行拆分的边界:
In [131]: arr = np.arange(10)In [132]: np.add.reduceat(arr, [0, 5, 8])Out[132]: array([10, 18, 17])
最终结果是在arr[0:5]、arr[5:8]以及arr[8:]上执行的聚合。
reduceat也可以传入一个axis参数:
In [133]: arr = np.multiply.outer(np.arange(4), np.arange(5))In [134]: arrOut[134]:array([[ 0, 0, 0, 0, 0],[ 0, 1, 2, 3, 4],[ 0, 2, 4, 6, 8],[ 0, 3, 6, 9, 12]])In [135]: np.add.reduceat(arr, [0, 2, 4], axis=1)Out[135]:array([[ 0, 0, 0],[ 1, 5, 4],[ 2, 10, 8],[ 3, 15, 12]])

编写自己的NumPy ufuncs
比较常见的是使用NumPy C API,但本文仅介绍纯粹的Python ufunc。
使用numpy.frompyfunc即可,它需要传入一个函数,以及该函数输入输出参数的数量。
下面创建一个实现元素级加法的简单函数,函数传入两个参数返回一个值,所以frompyfunc后面传入(2, 1):
In [136]: def add_elements(x, y):.....: return x + yIn [137]: add_them = np.frompyfunc(add_elements, 2, 1)In [138]: add_them(np.arange(8), np.arange(8))Out[138]: array([0, 2, 4, 6, 8, 10, 12, 14], dtype=object)
但是用frompyfunc创建的函数总是返回Python对象数组,不方便。
numpy.vectorize功能虽少,但可以指定输出类型:
In [139]: add_them = np.vectorize(add_elements, otypes=[np.float64])In [140]: add_them(np.arange(8), np.arange(8))Out[140]: array([ 0., 2., 4., 6., 8., 10., 12., 14.])
frompyfunc和vectorize这两个函数提供了一种创建ufunc型函数的手段,但它们在计算每个元素时都要执行一次Python函数调用,性能较差低会比NumPy自带的基于C的ufunc慢很多,所以一般都使用NumPy C API编写高性能的NumPy ufuncs。




