Python 使矢量化numpy函数的行为类似于ufunc
假设我们有一个Python函数,它接受Numpy数组并返回另一个数组:Python 使矢量化numpy函数的行为类似于ufunc,python,numpy,cython,Python,Numpy,Cython,假设我们有一个Python函数,它接受Numpy数组并返回另一个数组: import numpy as np def f(x, y, method='p'): """Parameters: x (np.ndarray) , y (np.ndarray), method (str) Returns: np.ndarray""" z = x.copy() if method == 'p': mask = x < 0 else:
import numpy as np
def f(x, y, method='p'):
"""Parameters: x (np.ndarray) , y (np.ndarray), method (str)
Returns: np.ndarray"""
z = x.copy()
if method == 'p':
mask = x < 0
else:
mask = x > 0
z[mask] = 0
return z*y
- 无法更改函数
,如果为f
或x
指定标量参数,则函数将失败y
- 当
和x
是标量时,我们将它们转换为一维数组,进行计算,然后在最后将它们转换回标量y
针对数组进行了优化,标量输入主要是一种方便。因此,编写一个使用标量的函数,然后使用f
或np.vectorize
是不可接受的李>np.frompyfunc
def atleast_1d_inverse(res, x):
# this function fails in some cases (see point 1 below).
if res.shape[0] == 1:
return res[0]
else:
return res
def ufunc_wrapper(func, args=[]):
""" func: the wrapped function
args: arguments of func to which we apply np.atleast_1d """
# this needs to be generated dynamically depending on the definition of func
def wrapper(x, y, method='p'):
# we apply np.atleast_1d to the variables given in args
x = np.atleast_1d(x)
y = np.atleast_1d(x)
res = func(x, y, method='p')
return atleast_1d_inverse(res, x)
return wrapper
f_ufunc = ufunc_wrapper(f, args=['x', 'y'])
它基本上是有效的,但是会通过上面的测试2,产生一个标量输出而不是向量输出。如果我们想解决这个问题,我们需要在输入类型上添加更多的测试(例如,isinstance(x,np.ndarray)
,x.ndim>0
,等等),但我恐怕会忘记一些常见的情况。此外,上述实现不够通用,无法用不同数量的参数包装函数(请参见下面的第2点)
在使用Cython/f2py函数时,这似乎是一个相当常见的问题,我想知道是否有一个通用的解决方案
编辑:在@hpaulj的评论之后再精确一点。基本上,我在寻找
至少\u 1d\u逆(np.至少\u 1d(x),x)=x
,其中第二个参数仅用于确定原始对象的类型或维数x
。返回numpy标量(即ndim=0的数组)而不是python标量是可以的f_ufunc=ufunc_包装(f,args=['x','y'])
然后,如果我们有一个不同的函数def2(x,option=2):返回x**2
,我们也可以使用
f2\u ufunc=ufunc\u包装(f2,args=['x'])
注意:与UFUNC的类比可能有点有限,因为这对应于相反的问题。我没有一个标量函数,我们将其转换为接受向量和标量输入,而是设计了一个用于处理向量的函数(可以看作是以前被向量化的东西),我想再次接受标量,不更改原始函数。这并不能完全回答使矢量化函数真正像
ufunc
那样工作的问题,但我最近确实遇到了与您的问题类似的numpy.vectorize
的问题。即使传递了标量输入,该包装器仍坚持返回一个数组(ndim=0
和shape=()
)
但似乎以下做法是正确的。在本例中,我对一个简单函数进行矢量化,以将浮点值返回到一定数量的有效数字
def signif(x, digits):
return round(x, digits - int(np.floor(np.log10(abs(x)))) - 1)
def vectorize(f):
vf = np.vectorize(f)
def newfunc(*args, **kwargs):
return vf(*args, **kwargs)[()]
return newfunc
vsignif = vectorize(signif)
这给
>>> vsignif(0.123123, 2)
0.12
>>> vsignif([[0.123123, 123.2]], 2)
array([[ 0.12, 120. ]])
>>> vsignif([[0.123123, 123.2]], [2, 1])
array([[ 0.12, 100. ]])
请记住
ufunc
不会返回真正的标量np.add(1,1)
返回一个numpy.int32
,形状()
。它可能使用了np.asarray
或等效物。您能想到任何现有的ufunc
需要转换,如至少\u 1d
。如果asarray
足够强大,那么您可能正在将ufunc
类比推向未知领域。@hpaulj感谢您的评论。是的,当我说标量时,numpy标量(ndim==0的数组)也可以。我对上述问题进行了更详细的编辑,以回应您的评论。类似:
>>> vsignif(0.123123, 2)
0.12
>>> vsignif([[0.123123, 123.2]], 2)
array([[ 0.12, 120. ]])
>>> vsignif([[0.123123, 123.2]], [2, 1])
array([[ 0.12, 100. ]])