• 沒有找到結果。

NumPy-快速处理数据

2.2 ufunc运算

ufunc是universal function的缩写,它是一种能对数组的每个元素进行操作的函数。NumPy内置的许 多ufunc函数都是在C语言级别实现的,因此它们的计算速度非常快。让我们来看一个例子:

>>> x = np.linspace(0, 2*np.pi, 10)

# 对数组x中的每个元素进行正弦计算,返回一个同样大小的新数组

>>> 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.44921271e-16])

先用linspace产生一个从0到2*PI的等距离的10个数,然后将其传递给sin函数,由于np.sin是一个 ufunc函数,因此它对x中的每个元素求正弦值,然后将结果返回,并且赋值给y。计算之后x中的值并

2.2. ufunc运算 25

没有改变,而是新创建了一个数组保存结果。如果我们希望将sin函数所计算的结果直接覆盖到数组x上 去的话,可以将要被覆盖的数组作为第二个参数传递给ufunc函数。例如::

>>> t = np.sin(x,x)

>>> x

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.44921271e-16])

>>> id(t) == id(x)

True

import numpy as np

x = [i * 0.001 for i in xrange(1000000)]

start = time.clock() for i, t in enumerate(x):

x[i] = math.sin(t)

print "math.sin:", time.clock() - start

x = [i * 0.001 for i in xrange(1000000)]

x = np.array(x) start = time.clock() np.sin(x,x)

print "numpy.sin:", time.clock() - start

# 输出

# math.sin: 1.15426932753

# numpy.sin: 0.0882399858083

在我的电脑上计算100万次正弦值,numpy.sin比math.sin快10倍多。这得利于numpy.sin在C语言级 别的循环计算。numpy.sin同样也支持对单个数值求正弦,例如:numpy.sin(0.5)。不过值得注意的 是,对单个数的计算math.sin则比numpy.sin快得多了,让我们看下面这个测试程序:

x = [i * 0.001 for i in xrange(1000000)]

start = time.clock()

26 第 2 章 NumPy-快速处理数据

用Python做科学计算

for i, t in enumerate(x):

x[i] = np.sin(t)

print "numpy.sin loop:", time.clock() - start

# 输出

# numpy.sin loop: 5.72166965355

请注意numpy.sin的计算速度只有math.sin的1/5。这是因为numpy.sin为了同时支持数组和单个值的 短,因此在导入时不建议使用*号全部载入,而是应该使用import numpy as np的方式载入,这样我 们可以根据需要选择合适的函数调用。

NumPy中有众多的ufunc函数为我们提供各式各样的计算。除了sin这种单输入函数之外,还有许多多 个输入的函数,add函数就是一个最常用的例子。先来看一个例子:

>>> a = np.arange(0,4)

>>> a

array([0, 1, 2, 3])

>>> b = np.arange(1,5)

>>> b

2.2. ufunc运算 27

y = x1 + x2: add(x1, x2 [, y]) y = x1 - x2: subtract(x1, x2 [, y]) y = x1 * x2: multiply (x1, x2 [, y])

y = x1 / x2: divide (x1, x2 [, y]), 如果两个数组的元素为整数,那么用整数除法 y = x1 / x2: true divide (x1, x2 [, y]), 总是返回精确的商

y = x1 // x2: floor divide (x1, x2 [, y]), 总是对返回值取整 y = -x: negative(x [,y])

y = x1**x2: power(x1, x2 [, y])

y = x1 % x2: remainder(x1, x2 [, y]), mod(x1, x2, [, y])

数组对象支持这些操作符,极大地简化了算式的编写,不过要注意如果你的算式很复杂,并且要运算 的数组很大的话,会因为产生大量的中间结果而降低程序的运算效率。例如:假设a b c三个数组采用 算式x=a*b+c计算,那么它相当于:

t = a * b x = t + c del t

也就是说需要产生一个数组t保存乘法的计算结果,然后再产生最后的结果数组x。我们可以通过手工将 一个算式分解为x = a*b; x += c,以减少一次内存分配。

通过组合标准的ufunc函数的调用,可以实现各种算式的数组计算。不过有些时候这种算式不易编写,

而针对每个元素的计算函数却很容易用Python实现,这时可以用frompyfunc函数将一个计算单个元 素的函数转换成ufunc函数。这样就可以方便地用所产生的ufunc函数对数组进行计算了。让我们来看 一个例子。

我们想用一个分段函数描述三角波,三角波的样子如下:

图 2.4 - 三角波可以用分段函数进行计算

我们很容易根据上图所示写出如下的计算三角波某点y坐标的函数:

28 第 2 章 NumPy-快速处理数据

用Python做科学计算

def triangle_wave(x, c, c0, hc):

x = x - int(x) # 三角波的周期为1,因此只取x坐标的小数部分进行计算

x = np.linspace(0, 2, 1000)

y = np.array([triangle_wave(t, 0.6, 0.4, 1.0) for t in x])

这种做法每次都需要使用列表包容语法调用函数,对于多维数组是很麻烦的。让我们来看看如何用 frompyfunc函数来解决这个问题:

triangle_ufunc = np.frompyfunc( lambda x: triangle_wave(x, 0.6, 0.4, 1.0), 1, 1) y2 = triangle_ufunc(x)

frompyfunc的调用格式为frompyfunc(func, nin, nout),其中func是计算单个元素的函数,nin是此 函数的输入参数的个数,nout是此函数的返回值的个数。虽然triangle_wave函数有4个参数,但是由 于后三个c, c0, hc在整个计算中值都是固定的,因此所产生的ufunc函数其实只有一个参数。为了满足 这个条件,我们用一个lambda函数对triangle_wave的参数进行一次包装。这样传入frompyfunc的函 数就只有一个参数了。这样子做,效率并不是太高,另外还有一种方法:

def triangle_func(c, c0, hc):

def trifunc(x):

# 用trifunc函数创建一个ufunc函数,可以直接对数组进行计算, 不过通过此函数

# 计算得到的是一个Object数组,需要进行类型转换 return np.frompyfunc(trifunc, 1, 1)

y2 = triangle_func(0.6, 0.4, 1.0)(x)

我们通过函数triangle_func包装三角波的三个参数,在其内部定义一个计算三角波的函数trifunc,

trifunc函数在调用时会采用triangle_func的参数进行计算。最后triangle_func返回用frompyfunc转 换结果。

2.2. ufunc运算 29

值得注意的是用frompyfunc得到的函数计算出的数组元素的类型为object,因为frompyfunc函数无

>>> a = np.arange(0, 60, 10).reshape(-1, 1)

>>> a

array([[ 0], [10], [20], [30], [40], [50]])

>>> a.shape

(6, 1)

再创建一维数组b,其shape为(5,):

>>> b = np.arange(0, 5)

>>> b

用Python做科学计算

>>> b = b.repeat(6,axis=0)

>>> b

>>> a = a.repeat(5, axis=1)

>>> a

2.2. ufunc运算 31

>>> x,y = np.ogrid[0:5,0:5]

>>> x, y = np.ogrid[0:1:4j, 0:1:3j]

>>> x

import numpy as np

from enthought.mayavi import mlab

x, y = np.ogrid[-2:2:20j, -2:2:20j]

z = x * np.exp( - x**2 - y**2)

pl = mlab.surf(x, y, z, warp_scale="auto")

32 第 2 章 NumPy-快速处理数据

用Python做科学计算

mlab.axes(xlabel='x', ylabel='y', zlabel='z') mlab.outline(pl)

此程序使用mayavi的mlab库快速绘制3D曲面,关于mlab的相关内容将在今后的章节进行介绍。

图 2.5 -使用Mayavi将二维数组绘制成3D曲面

2.2.2 ufunc的方法

ufunc函数本身还有些方法,这些方法只对两个输入一个输出的ufunc函数有效,其它的ufunc对象调 用这些方法时会抛出ValueError异常。

reduce 方法和Python的reduce函数类似,它沿着axis轴对array进行操作,相当于将<op>运算符插 入到沿axis轴的所有子数组或者元素当中。

<op>.reduce (array=, axis=0, dtype=None)

例如:

>>> np.add.reduce([1,2,3]) # 1 + 2 + 3

6

>>> np.add.reduce([[1,2,3],[4,5,6]], axis=1) # 1,4 + 2,5 + 3,6

array([ 6, 15])

accumulate 方法和reduce方法类似,只是它返回的数组和输入的数组的shape相同,保存所有的中间 计算结果:

>>> np.add.accumulate([1,2,3])

array([1, 3, 6])

2.2. ufunc运算 33

>>> np.add.accumulate([[1,2,3],[4,5,6]], axis=1) array([[ 1, 3, 6],

[ 4, 9, 15]])

reduceat 方法计算多组reduce的结果,通过indices参数指定一系列reduce的起始和终了位置。

reduceat的计算有些特别,让我们通过一个例子来解释一下:

>>> a = np.array([1,2,3,4])

>>> result = np.add.reduceat(a,indices=[0,1,0,2,0,3,0])

>>> result

array([ 1, 2, 3, 3, 6, 4, 10])

对于indices中的每个元素都会调用reduce函数计算出一个值来,因此最终计算结果的长度和indices 的长度相同。结果result数组中除最后一个元素之外,都按照如下计算得出:

if indices[i] < indices[i+1]:

result[i] = np.reduce(a[indices[i]:indices[i+1]]) else:

result[i] = a[indices[i]

而最后一个元素如下计算:

>>> a.shape += (1,)*b.ndim

>>> <op>(a,b)

>>> a = a.squeeze()

其中squeeze的功能是剔除数组a中长度为1的轴。如果你看不太明白这个等同程序的话,让我们来看 一个例子:

34 第 2 章 NumPy-快速处理数据

用Python做科学计算

>>> a = np.matrix([[1,2,3],[5,5,6],[7,9,9]])

>>> a*a**-1

matrix([[ 1.00000000e+00, 1.66533454e-16, -8.32667268e-17], [ -2.77555756e-16, 1.00000000e+00, -2.77555756e-17], [ 1.66533454e-16, 5.55111512e-17, 1.00000000e+00]])

因为a是用matrix创建的矩阵对象,因此乘法和幂运算符都变成了矩阵运算,于是上面计算的是矩 阵a和其逆矩阵的乘积,结果是一个单位矩阵。

矩阵的乘积可以使用dot函数进行计算。对于二维数组,它计算的是矩阵乘积,对于一维数组,它计算 的是其点积。当需要将一维数组当作列矢量或者行矢量进行矩阵运算时,推荐先使用reshape函数将一