
2.2 ufunc函数
与本节内容对应的Notebook为:02-numpy/numpy-200-ufunc.ipynb。
ufunc是universal function的缩写,它是一种能对数组的每个元素进行运算的函数。NumPy内置的许多ufunc函数都是用C语言实现的,因此它们的计算速度非常快。让我们先看一个例子:
x = np.linspace(0, 2*np.pi, 10) y = np.sin(x) y array([ 0.00000000e+00, 6.42787610e-01, 9.84807753e-01, 8.66025404e-01, 3.42020143e-01, -3.42020143e-01, -8.66025404e-01, -9.84807753e-01, -6.42787610e-01, -2.44929360e-16])
先用linspace()产生一个从0到2π的等差数组,然后将其传递给np.sin()函数计算每个元素的正弦值。由于np.sin()是一个ufunc函数,因此在其内部对数组x的每个元素进行循环,分别计算它们的正弦值,并返回一个保存各个计算结果的数组。运算之后数组x中的值并没有改变,而是新创建了一个数组来保存结果。也可以通过out参数指定保存计算结果的数组。因此如果希望直接在数组x中保存结果,可以将它传递给out参数:
t = np.sin(x, out=x) t is x True
ufunc函数的返回值仍然是计算的结果,只不过它就是数组x。下面比较np.sin()和Python标准库的math.sin()的计算速度:
import math x = [i * 0.001 for i in xrange(1000000)] def sin_math(x): for i, t in enumerate(x): x[i] = math.sin(t) def sin_numpy(x): np.sin(x, x) def sin_numpy_loop(x): for i, t in enumerate(x): x[i] = np.sin(t) xl = x[:] %time sin_math(x) xa = np.array(x) %time sin_numpy(xa) xl = x[:] %time sin_numpy_loop(x) Wall time: 302 ms Wall time: 30 ms Wall time: 1.28 s
可以看出,np.sin()比math.sin()快10倍多,这得益于np.sin()在C语言级别的循环计算。
列表推导式比循环更快
事实上,标准Python中有比for循环更快的方案:使用列表推导式x=[math.sin(t) fort in x]。但是列表推导式将产生一个新的列表,而不是直接修改原列表中的元素。
np.sin()同样也支持计算单个数值的正弦值。不过值得注意的是,对单个数值的计算,math.sin()则比np.sin()快很多。在Python级别进行循环时,np.sin()的计算速度只有math.sin()的1/6。这是因为:np.sin()为了同时支持数组和单个数值的计算,其C语言的内部实现要比math.sin()复杂很多。此外,对于单个数值的计算,np.sin()的返回值类型和math.sin()的不同,math.sin()返回的是Python的标准float类型,而np.sin()返回float64类型:
type(math.sin(0.5)) type(np.sin(0.5)) ------------------- ----------------- float numpy.float64
通过下标运算获取的数组元素的类型为NumPy中定义的类型,将其转换为Python的标准类型还需要花费额外的时间。为了解决这个问题,数组提供了item()方法,它用来获取数组中的单个元素,并且直接返回标准的Python数值类型:
a = np.arange(6.0).reshape(2, 3) print a.item(1, 2), type(a.item(1, 2)), type(a[1, 2]) 5.0 <type 'float'><type 'numpy.float64'>
通过上面的例子我们了解了如何最有效率地使用math模块和NumPy中的数学函数。由于它们各有优缺点,因此在导入时不建议使用import *全部载入,而是应该使用import numpy as np载入,这样可以根据需要选择合适的函数。