【问题标题】:A python function that accepts as an argument either a scalar or a numpy array一个接受标量或numpy数组作为参数的python函数
【发布时间】: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)

标签: python arrays function numpy


【解决方案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])
        

        【讨论】:

          猜你喜欢
          • 2012-09-21
          • 1970-01-01
          • 1970-01-01
          • 2022-11-01
          • 1970-01-01
          • 1970-01-01
          • 2023-01-26
          • 2011-07-13
          • 2022-01-15
          相关资源
          最近更新 更多