一个接受标量或numpy数组作为参数的python函数
Posted
技术标签:
【中文标题】一个接受标量或numpy数组作为参数的python函数【英文标题】:A python function that accepts as an argument either a scalar or a numpy array 【发布时间】:2014-12-19 23:29:04 【问题描述】:正如标题所说,假设我想写一个符号函数(让我们暂时忘记 sign(0)),显然我们期望 sign(2) = 1 和 sign(array([-2,-2,2] )) = 数组([-1,-1,1])。但是,以下函数不起作用,因为它无法处理 numpy 数组。
def sign(x):
if x>0: return 1
else: return -1
下一个函数也不起作用,因为如果 x 只是一个数字,它就没有形状成员。即使使用了像 y = x*0 + 1 这样的技巧,y 也不会有 [] 方法。
def sign(x):
y = ones(x.shape)
y[x<0] = -1
return y
即使有另一个问题的想法(how can I make a numpy function that accepts a numpy array, an iterable, or a scalar?),当 x 是单个数字时,下一个函数也不起作用,因为在这种情况下 x.shape 和 y.shape 只是 () 并且索引 y 是非法的。
def sign(x):
x = asarray(x)
y = ones(x.shape)
y[x<0] = -1
return y
唯一的解决方案似乎是首先确定 x 是数组还是数字,但我想知道是否有更好的方法。如果你有很多这样的小函数,编写分支代码会很麻烦。
【问题讨论】:
使用掩码索引y
是 合法的:这里的问题是x < 0
又是一个标量而不是一个0-d 数组。如果您尝试y[asarray(x < 0)]
,它应该可以工作。
你考虑过使用内置np.sign
的可能性吗?
@MarkDickinson 这是一个很好的方法,但是当 x 是单个数字时它会出错,因为现在 y 也必须是单个数字 - 然后 y 无法被索引...跨度>
@Jaime 符号函数只是举例,我应该在数学中使用类似分段函数的东西。 BTW 你知道 numpy 是如何实现 sign 函数的吗?
@桃子:对;如果您想采用这种方法,您仍然需要在开始时使用x = asarray(x)
。
【参考方案1】:
np.vectorize
可以用来实现这一点,但会很慢,因为当你的修饰函数用数组调用时,它所做的只是循环遍历数组元素并将标量函数应用于每个元素,即不利用 numpy 的速度。
我发现对涉及 if-else 的函数进行矢量化很有用的方法是使用 np.choose
:
def sign_non_zero(x):
return np.choose(
x > 0, # bool values, used as indices to the array
[
-1, # index=0=False, i.e. x<=0
1, # index=1=True, i.e. x>0
])
这在x
是标量或数组时有效,并且比在 python 空间中循环更快。
使用np.choose
唯一的缺点是用这种方式写if-else逻辑不直观,代码可读性较差。每当我使用它时,我都会将上面的 cmets 包括在内,以使读者更容易理解发生了什么。
【讨论】:
>>> sign_non_zero([1,2,3]) 给出 1 # 应该是 1,1,1 >>> sign_non_zero([1,2,-3]) 给出 1 # 应该一直是 1,1,-1 @BHATIRSHAD,对,就目前而言,sign_non_zero
支持标量和 numpy 数组。要同时支持列表,您只需将 x
替换为 np.asarray(x)
。
@bhat-irshad 这个非常适合在将 x 替换为 np.asarray(x) 后实现符号函数。但是,只有当结果是“是”或“否”时,才使用选择似乎很方便。如果你现在要做出三重决定(假设你考虑 sign(0)),那么选择函数是没用的,我们不得不再次面对老问题——如果 x 可以被索引,也就是 x 是数字还是数组。
@Taozi 我不会说选择是无用的,如果你要做出三重决定。 “标量版本”中的每个if
或elif
语句都可以转换为对choose
的调用,因此您需要使用choose
两次。就 if-statemetns 的数量而言,标量版本的复杂性在您的选择版本中保留,就选择调用的数量而言。这仍然具有 numpy-fast 的优势。
很好地使用了np.choose
。您还可以查看np.select
了解更复杂的返回类型【参考方案2】:
不知道是不是你想要的矢量化函数:
>>> import numpy as NP
>>> def fnx(a):
if a > 0:
return 1
else:
return -1
>>> vfnx = NP.vectorize(fnx)
>>> a = NP.random.randint(1, 10, 5)
array([4, 9, 7, 9, 2])
>>> a0 = 7
>>> vfnx(a)
array([1, 1, 1, 1])
>>> vfnx(a0)
array(1)
【讨论】:
这很好,但是正如 shx2 提到的那样,向量化函数很慢并且没有利用 numpy 的速度,这是真的吗?另外,如果使用这种方法,每个函数都需要定义两次——一个是专注于单个数字的简陋版本,一个是名称应该接近但不同的矢量化版本,这样对吗? 根据文档,矢量化 fn 被实现为 python for 循环,而 NumPy 性能的一个主要原因是面向数组的计算(C 源代码中只有一个 for 循环),它避免了第二个 python for 循环。但是您不需要第二个 fn;矢量化 fn 的目的是在单个 fn 调用中使用相同的 fn 处理 NumPy 数组和标量。【参考方案3】:这里有一个解决方案:
import numpy as np
def sign(x):
y = np.ones_like(x)
y[np.asarray(x) < 0] = -1
if isinstance(x, np.ndarray):
return y
else:
return type(x)(y)
这应该返回与输入相同类型的值。例如sign(42)
给出1
,sign(42.0)
给出1.0
。如果你给它一个ndarray,它会像np.sign
一样工作。
通常,您可能会继续假设您的输入是一个 ndarray。如果您尝试访问 ndarray 具有的属性或方法,但您的输入没有,那么您将退回到对标量类型进行操作。使用异常来实现这一点。例如:
def foo_on_scalars(x):
# do scalar things
def foo(x):
try:
# assume x is an ndarray
except AttributeError:
foo_on_scalars(x)
【讨论】:
【参考方案4】:numpy
函数自然地处理标量或数组输入,并在输出中保留形状。因此,最好找到 numpy
函数来完成这项工作。在这种情况下,函数应该是np.sign
,如上所述。对于不同的逻辑,你可以使用np.where(x>0, 1, -1)
,它适用于x
的标量和数组值。
【讨论】:
【参考方案5】:你可以先把数字转换成单元素数组,
然后专注于对数组进行操作。
你仍然需要检查 x 的类型
【讨论】:
但是该函数返回一个需要从客户端解包的单个元素数组。【参考方案6】:这是一种解决方案:
>>> def sign(x):
... if type(x)==int:
... if x>0: return 1
... else: return -1
... else:
... x=np.array(x)
... pos=np.where(x>=0)
... neg=np.where(x<0)
... res=np.zeros(x.shape[0])
... res[pos]=1
... res[neg]=-1
... return res.tolist()
...
>>> sign(56)
1
>>> sign(-556)
-1
>>> sign([23,4,-3,0,45,-3])
[1.0, 1.0, -1.0, 1.0, 1.0, -1.0]
>>> sign(np.array([23,4,-3,0,45,-3]))
[1.0, 1.0, -1.0, 1.0, 1.0, -1.0]
【讨论】:
sign(56L)
的输出是什么?或sign(np.int32(56))
? sign(56.)
?此外,重点是避免逻辑重复。
@shx2 正是我想问的,类型判断的问题就是类型太多了。可以使用 if type(numpyarray) == 'ndarray',但这些分支都是我想要避免的。【参考方案7】:
我之前采用的方法很像您的上一个示例,但在开头添加了一个额外的标量检查:
def sign(x):
if isscalar(x):
x = (x,)
x = asarray(x)
y = ones(x.shape)
y[x<0] = -1
return y
【讨论】:
【参考方案8】:处理标量和 numpy 数组的简单解决方案:
>>> import numpy as np
>>> def sign_non_zero(x):
return (x > 0) * 1 + (x < 0) * -1
>>> sign_non_zero(2)
1
>>> sign_non_zero(np.array([-2, -2, 2]))
array([-1, -1, 1])
【讨论】:
以上是关于一个接受标量或numpy数组作为参数的python函数的主要内容,如果未能解决你的问题,请参考以下文章
Python OpenCV - ConvexHull 错误“点不是 numpy 数组,也不是标量”?