Python科学计算(第2版)
上QQ阅读APP看书,第一时间看更新

2.4.5 分段函数

本节介绍的函数如表2-7所示。

表2-7 本节要介绍的函数

在前面的小节中介绍过如何使用frompyfunc( )函数计算三角波形。由于三角波形是分段函数,需要根据自变量的取值范围决定计算函数值的公式,因此无法直接通过ufunc函数计算。NumPy提供了一些计算分段函数的方法。

在Python 2.6中新增加了如下判断表达式语法,当condition条件为True时,表达式的值为y,否则为z:

    x = y if condition else z

在NumPy中,where( )函数可以看作判断表达式的数组版本:

    x = where(condition, y, z)

其中condition、y和z都是数组,它的返回值是一个形状与condition相同的数组。当condition中的某个元素为True时,x中对应下标的值从数组y获取,否则从数组z获取:

    x = np.arange(10)
    np.where(x < 5, 9 - x, x)
    array([9, 8, 7, 6, 5, 5, 6, 7, 8, 9])

如果y和z是单个数值或者它们的形状与condition的不同,将先通过广播运算使其形状一致:

    np.where(x > 6, 2 *
 x, 0)
    array([ 0,  0,  0,  0,  0,  0,  0, 14, 16, 18])

使用where( )很容易计算前面介绍过的三角波形:

    def triangle_wave1(x, c, c0, hc):
        x = x - x.astype(np.int) # 三角波的周期为1,因此只取x坐标的小数部分进行计算
        return np.where(x >= c, 
                        0, 
                        np.where(x < c0, 
                                 x / c0 *
 hc, 
                                 (c - x) / (c - c0) *
 hc))

由于三角波形分为三段,因此需要两个嵌套的where( )进行计算。由于所有的运算和循环都在C语言级别完成,因此它的计算效率比frompyfunc( )高。

随着分段函数的分段数量的增加,需要嵌套更多层where( )。这样不便于程序的编写和阅读。可以用select( )解决这个问题,它的调用形式如下:

    select(condlist, choicelist, default=0)

其中condlist是一个长度为N的布尔数组列表,choicelist是一个长度为N的存储候选值的数组列表,所有数组的长度都为M。如果列表元素不是数组而是单个数值,那么它相当于元素值都相同、长度为M的数组。

对于从0到M-1的数组下标i,从布尔数组列表中找出满足条件condlist[j][i]==True的j的最小值,则out[i]=choicelist[j][i],其中out是select( )的返回数组。我们可以使用select( )计算三角波形:

    def triangle_wave2(x, c, c0, hc):
        x = x - x.astype(np.int)
        return np.select([x >= c, x < c0 , True            ], 
                          [0     , x/c0*
hc, (c-x)/(c-c0)*
hc])

由于分段函数分为三段,因此每个列表都有三个元素。choicelist的最后一个元素为True,表示前面的所有条件都不满足时,将使用choicelist的最后一个数组中的值。也可以用default参数指定条件都不满足时的候选值数组:

    return np.select([x>= c, x < c0     ], 
                     [0    , x / c0 *
 hc],
                     default=(c-x)/(c-c0)*
hc)

但是where( )和select( )的所有参数都需要在调用它们之前完成计算,因此NumPy会计算下面4个数组:

    x >= c, x < c0, x / c0 *
 hc, (c - x) / (c -c0 ) *
 hc

在计算时还会产生许多保存中间结果的数组,因此如果输入的数组x很大,将会发生大量内存分配和释放。

为了解决这个问题,NumPy提供了piecewise( )专门用于计算分段函数,它的调用参数如下:

    piecewise(x, condlist, funclist)

参数x是一个保存自变量值的数组,condlist是一个长度为M的布尔数组列表,其中的每个布尔数组的长度都和数组x相同。funclist是一个长度为M或M+1的函数列表,这些函数的输入和输出都是数组。它们计算分段函数中的每个片段。如果不是函数而是数值,则相当于返回此数值的函数。每个函数与condlist中下标相同的布尔数组对应,如果funclist的长度为M+1,则最后一个函数计算所有条件都为False时的值。下面是使用piecewise( )计算三角波形的程序:

    def triangle_wave3(x, c, c0, hc):
        x = x - x.astype(np.int)
        return np.piecewise(x, 
            [x >= c, x < c0],
            [0,  # x>=c 
            lambda x: x / c0 *
 hc, # x<c0
            lambda x: (c - x) / (c - c0) *
 hc])  # else

使用piecewise( )的好处在于它只计算需要计算的值。因此在上面的例子中,表达式x/c0* hc和(c-x)/(c-c0)* hc只对输入数组x中满足条件的部分进行计算。下面运行前面定义的三个分段函数,并使用%timeit命令比较这三个函数的运行时间:

    x = np.linspace(0, 2, 10000) 
    y1 = triangle_wave1(x, 0.6, 0.4, 1.0)
    y2 = triangle_wave2(x, 0.6, 0.4, 1.0)
    y3 = triangle_wave3(x, 0.6, 0.4, 1.0)
    np.all(y1 == y2), np.all(y1 == y3)
    (True, True)
    %timeit triangle_wave1(x, 0.6, 0.4, 1.0)
    %timeit triangle_wave2(x, 0.6, 0.4, 1.0)
    %timeit triangle_wave3(x, 0.6, 0.4, 1.0)
    1000 loops, best of 3: 614 µs per loop
    1000 loops, best of 3: 736 µs per loop
    1000 loops, best of 3: 311 µs per loop