一个接受标量或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 &lt; 0 又是一个标量而不是一个0-d 数组。如果您尝试y[asarray(x &lt; 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 我不会说选择是无用的,如果你要做出三重决定。 “标量版本”中的每个ifelif 语句都可以转换为对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) 给出1sign(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&gt;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函数的主要内容,如果未能解决你的问题,请参考以下文章

为啥我不能将一个 numpy 数组除以(或乘以)一个标量?

Python OpenCV - ConvexHull 错误“点不是 numpy 数组,也不是标量”?

Python + alglib + NumPy:如何避免将数组转换为列表?

numpy2

numpy笔记

python数据分析-02numpy库