【问题标题】:Applying a function to an array using Numpy when the function contains a condition当函数包含条件时,使用 Numpy 将函数应用于数组
【发布时间】:2019-05-04 19:22:10
【问题描述】:

当函数包含条件时,我难以将函数应用于数组。我有一个低效的解决方法,正在寻找一种有效(快速)的方法。举个简单的例子:

pts = np.linspace(0,1,11)
def fun(x, y):
    if x > y:
        return 0
    else:
        return 1

现在,如果我运行:

result = fun(pts, pts)

然后我得到错误

ValueError:具有多个元素的数组的真值不明确。使用 a.any() 或 a.all()

if x > y 行提出。我的低效解决方法是:

result = np.full([len(pts)]*2, np.nan)
for i in range(len(pts)):
    for j in range(len(pts)):
        result[i,j] = fun(pts[i], pts[j])

以更好(更重要的是,更快)的方式获得此信息的最佳方法是什么?

当函数包含条件时,我难以将函数应用于数组。我有一个低效的解决方法,正在寻找一种有效(快速)的方法。举个简单的例子:

pts = np.linspace(0,1,11)
def fun(x, y):
    if x > y:
        return 0
    else:
        return 1

现在,如果我运行:

result = fun(pts, pts)

然后我得到错误

ValueError:具有多个元素的数组的真值不明确。使用 a.any() 或 a.all()

if x > y 行提出。我的低效解决方法是:

result = np.full([len(pts)]*2, np.nan)
for i in range(len(pts)):
    for j in range(len(pts)):
        result[i,j] = fun(pts[i], pts[j])

以更好(更重要的是,更快)的方式获得此信息的最佳方法是什么?

编辑:使用

def fun(x, y):
    if x > y:
        return 0
    else:
        return 1
x = np.array(range(10))
y = np.array(range(10))
xv,yv = np.meshgrid(x,y)
result = fun(xv, yv)  

仍然引发相同的ValueError

【问题讨论】:

  • 查看 x>y 的 while 数组。可以直接用吗?
  • @hpaulj 我不确定我理解你的意思:“while 数组”是什么意思?

标签: python numpy lambda conditional vectorization


【解决方案1】:

错误非常明确 - 假设您有

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

这样

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

if np.array([0,1]) 语句的结果应该是什么?是真的还是假的? numpy 告诉你这是模棱两可的。使用

(x>y).all()

(x>y).any()

是明确的,因此numpy 为您提供解决方案 - 任何单元格对都满足条件,或全部满足条件 - 都是明确的真值。您必须自己准确定义 vector x 大于 vector y 的含义。

numpy 解决方案对所有 xy 对进行操作,使得 x[i]>y[j] 使用网格网格生成所有对:

>>> import numpy as np
>>> x=np.array(range(10))
>>> y=np.array(range(10))
>>> xv,yv=np.meshgrid(x,y)
>>> xv[xv>yv]
array([1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9, 3, 4, 5, 6, 7, 8,
       9, 4, 5, 6, 7, 8, 9, 5, 6, 7, 8, 9, 6, 7, 8, 9, 7, 8, 9, 8, 9, 9])
>>> yv[xv>yv]
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
       2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 8])

要么将xvyv 发送到fun,要么在函数中创建网格,这取决于更有意义的方式。这会生成所有对 xi,yj,例如 xi>yj。如果您想要实际索引只需返回xv>yv,其中每个单元格ij 对应x[i]y[j]。在你的情况下:

def fun(x, y):
    xv,yv=np.meshgrid(x,y)
    return xv>yv

将返回一个矩阵,其中fun(x,y)[i][j] 如果x[i]>y[j] 为True,否则为False。或者

return  np.where(xv>yv)

将返回两个索引对数组的元组,这样

for i,j in fun(x,y):

也会保证x[i]>y[j]

【讨论】:

  • 谢谢@kabanus。我理解错误的含义。问题是我想以更好的方式获得循环正在做的事情。此循环创建一个数组。如果我使用.any.all,那么我会得到一个整数,而不是一个数组。
  • @splinter 啊,你编辑的,我一会儿回答。
  • 是的,我编辑了原始帖子中的一些拼写错误以使其更清晰。谢谢
  • 感谢@kabanus,但是,正如您在我刚刚所做的编辑中看到的那样,当我传入网格数组时,我仍然得到同样的错误。我期待得到一个数组。
  • @splinter 抱歉,我不清楚。你要么发送 xv[...] yv[..] 要么在函数中生成它们——所以你根本不需要 if !数组已经是干净的对。我稍后会进行编辑以澄清。
【解决方案2】:
In [253]: x = np.random.randint(0,10,5)
In [254]: y = np.random.randint(0,10,5)
In [255]: x
Out[255]: array([3, 2, 2, 2, 5])
In [256]: y
Out[256]: array([2, 6, 7, 6, 5])
In [257]: x>y
Out[257]: array([ True, False, False, False, False])
In [258]: np.where(x>y,0,1)
Out[258]: array([0, 1, 1, 1, 1])

对于这两个一维数组的笛卡尔比较,重塑一个,以便它可以使用broadcasting

In [259]: x[:,None]>y
Out[259]: 
array([[ True, False, False, False, False],
       [False, False, False, False, False],
       [False, False, False, False, False],
       [False, False, False, False, False],
       [ True, False, False, False, False]])
In [260]: np.where(x[:,None]>y,0,1)
Out[260]: 
array([[0, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [1, 1, 1, 1, 1],
       [0, 1, 1, 1, 1]])

带有if 的函数仅适用于标量输入。如果给定数组,a>b 会生成一个布尔数组,该数组不能在if 语句中使用。您的迭代有效,因为它传递了标量值。对于一些复杂的函数,你可以做到最好(np.vectorize 可以使迭代更简单,但不会更快)。

我的答案是查看数组比较,并从中得出答案。在这种情况下,参数 3 where 很好地将布尔数组映射到所需的 1/0。还有其他方法可以进行这种映射。

您的双循环需要添加一层编码,即广播的None

【讨论】:

  • 忘了广播,这应该被接受 - 比网格网格快一个数量级。
【解决方案3】:

对于更复杂的示例,或者如果您正在处理的数组有点大,或者如果您可以写入已经预先分配的数组,您可以考虑Numba

示例

import numba as nb
import numpy as np

@nb.njit()
def fun(x, y):
  if x > y:
    return 0
  else:
    return 1

@nb.njit(parallel=False)
#@nb.njit(parallel=True)
def loop(x,y):
  result=np.empty((x.shape[0],y.shape[0]),dtype=np.int32)
  for i in nb.prange(x.shape[0]):
    for j in range(y.shape[0]):
      result[i,j] = fun(x[i], y[j])
  return result

@nb.njit(parallel=False)
def loop_preallocated(x,y,result):
  for i in nb.prange(x.shape[0]):
    for j in range(y.shape[0]):
      result[i,j] = fun(x[i], y[j])
  return result

时间安排

x = np.array(range(1000))
y = np.array(range(1000))

#Compilation overhead of the first call is neglected

res=np.where(x[:,None]>y,0,1) -> 2.46ms
loop(single_threaded)         -> 1.23ms
loop(parallel)                -> 1.0ms
loop(single_threaded)*        -> 0.27ms
loop(parallel)*               -> 0.058ms

*可能受缓存影响。测试您自己的示例。

【讨论】:

    猜你喜欢
    • 2019-07-25
    • 2014-04-20
    • 2020-08-16
    • 2016-08-10
    • 2017-08-20
    • 1970-01-01
    • 2020-02-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多