将python函数广播到numpy数组

时间:2011-03-04 18:17:57

标签: python numpy scipy

假设我们有一个特别简单的功能,如

import scipy as sp
def func(x, y):
   return x + y

这个函数显然适用于xy的几个内置python数据类型,如string,list,int,float,array等。由于我们对数组特别感兴趣,我们考虑两个数组:

x = sp.array([-2, -1, 0, 1, 2])
y = sp.array([-2, -1, 0, 1, 2])

xx = x[:, sp.newaxis]
yy = y[sp.newaxis, :]

>>> func(xx, yy)

返回

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

正如我们所期望的那样。

现在如果想要将数组作为以下函数的输入引入呢?

def func2(x, y):
  if x > y:
     return x + y
  else:
     return x - y

执行>>>func(xx, yy)会引发错误。

人们想出的第一个明显的方法是scipy / numpy中的sp.vectorize函数。尽管如此,这种方法已被证明效率不高。任何人都可以想到一种更强大的方式来广播任何函数到numpy数组吗?

如果以阵列友好的方式重写代码是唯一的方法,那么如果您也可以在这里提及它会有所帮助。

3 个答案:

答案 0 :(得分:11)

np.vectorize是将操作数字的Python函数转换为在ndarrays上运行的numpy函数的一般方法。

然而,正如你所指出的那样,它并不是很快,因为它正在“引擎盖下”使用Python循环。

为了获得更好的速度,你必须手工制作一个将numpy数组作为输入的函数,并利用这个numpy-ness:

import numpy as np

def func2(x, y):
    return np.where(x>y,x+y,x-y)      

x = np.array([-2, -1, 0, 1, 2])
y = np.array([-2, -1, 0, 1, 2])

xx = x[:, np.newaxis]
yy = y[np.newaxis, :]

print(func2(xx, yy))
# [[ 0 -1 -2 -3 -4]
#  [-3  0 -1 -2 -3]
#  [-2 -1  0 -1 -2]
#  [-1  0  1  0 -1]
#  [ 0  1  2  3  0]]

关于表现:

<强> test.py

import numpy as np

def func2a(x, y):
    return np.where(x>y,x+y,x-y)      

def func2b(x, y):
    ind=x>y
    z=np.empty(ind.shape,dtype=x.dtype)
    z[ind]=(x+y)[ind]
    z[~ind]=(x-y)[~ind]
    return z

def func2c(x, y):
    # x, y= x[:, None], y[None, :]
    A, L= x+ y, x<= y
    A[L]= (x- y)[L]
    return A

N=40
x = np.random.random(N)
y = np.random.random(N)

xx = x[:, np.newaxis]
yy = y[np.newaxis, :]

跑步:

N = 30:

% python -mtimeit -s'import test' 'test.func2a(test.xx,test.yy)'
1000 loops, best of 3: 219 usec per loop

% python -mtimeit -s'import test' 'test.func2b(test.xx,test.yy)'
1000 loops, best of 3: 488 usec per loop

% python -mtimeit -s'import test' 'test.func2c(test.xx,test.yy)'
1000 loops, best of 3: 248 usec per loop

N = 1000:

% python -mtimeit -s'import test' 'test.func2a(test.xx,test.yy)'
10 loops, best of 3: 93.7 msec per loop

% python -mtimeit -s'import test' 'test.func2b(test.xx,test.yy)'
10 loops, best of 3: 367 msec per loop

% python -mtimeit -s'import test' 'test.func2c(test.xx,test.yy)'
10 loops, best of 3: 186 msec per loop

这似乎表明func2a略快于func2cfunc2b速度非常慢。)

答案 1 :(得分:10)

对于这种特殊情况,您还可以编写一个在NumPy数组和普通Python浮点数上运行的函数:

def func2d(x, y):
    z = 2.0 * (x > y) - 1.0
    z *= y
    return x + z

此版本的速度也是unutbu's func2a()的四倍(使用N = 100测试)。

答案 2 :(得分:1)

为了得到基本的想法,你可以修改你的功能,例如这种方式:

def func2(x, y):
    x, y= x[:, None], y[None, :]
    A= x+ y
    A[x<= y]= (x- y)[x<= y]
    return A

因此,根据您的情况,这样的事情应该是一个非常合理的起点:

In []: def func(x, y):
   ..:     x, y= x[:, None], y[None, :]
   ..:     return x+ y
   ..:
In []: def func2(x, y):
   ..:     x, y= x[:, None], y[None, :]
   ..:     A, L= x+ y, x<= y
   ..:     A[L]= (x- y)[L]
   ..:     return A
   ..:
In []: x, y= arange(-2, 3), arange(-2, 3)
In []: func(x, y)
Out[]:
array([[-4, -3, -2, -1,  0],
       [-3, -2, -1,  0,  1],
       [-2, -1,  0,  1,  2],
       [-1,  0,  1,  2,  3],
       [ 0,  1,  2,  3,  4]])
In []: func2(x, y)
Out[]:
array([[ 0, -1, -2, -3, -4],
       [-3,  0, -1, -2, -3],
       [-2, -1,  0, -1, -2],
       [-1,  0,  1,  0, -1],
       [ 0,  1,  2,  3,  0]])

虽然这种处理似乎浪费了资源,但情况并非如此。始终测量您的程序的实际性能,然后(不是更早)进行必要的更改。

恕我直言还有一个额外的好处:这种“矢量化”使你的代码最终真的一致和可读。