Python numpy/pandas为循环自定义矢量化

Python numpy/pandas为循环自定义矢量化,python,pandas,numpy,vectorization,Python,Pandas,Numpy,Vectorization,我创建了一些示例代码来模拟我得到的代码: import numpy as np arr = np.random.random(100) arr2 = np.linspace(0, 1, 20) arr3 = np.zeros(20) # this is the array i want to store the result in for index, num in enumerate(list(arr2)): arr3[index] = np.mean(arr[np.abs(num

我创建了一些示例代码来模拟我得到的代码:

import numpy as np 

arr = np.random.random(100)
arr2 = np.linspace(0, 1, 20)
arr3 = np.zeros(20) # this is the array i want to store the result in
for index, num in enumerate(list(arr2)):
    arr3[index] = np.mean(arr[np.abs(num - arr) < 0.2])

>>> arr3
array([0.10970893, 0.1132479 , 0.14687451, 0.17257954, 0.19401919,
       0.23852137, 0.29151448, 0.35715096, 0.43273118, 0.45800796,
       0.52940421, 0.60345354, 0.63969432, 0.67656363, 0.72921913,
       0.78330793, 0.82693675, 0.83717402, 0.86651827, 0.89782569])
将numpy导入为np
arr=np.随机。随机(100)
arr2=np.linspace(0,1,20)
arr3=np.zeros(20)#这是我要存储结果的数组
对于索引,枚举中的num(列表(arr2)):
arr3[指数]=np.平均值(arr[np.绝对值(num-arr)<0.2])
>>>arr3
阵列([0.10970893,0.1132479,0.14687451,0.17257954,0.19401919,
0.23852137, 0.29151448, 0.35715096, 0.43273118, 0.45800796,
0.52940421, 0.60345354, 0.63969432, 0.67656363, 0.72921913,
0.78330793, 0.82693675, 0.83717402, 0.86651827, 0.89782569])

我的问题是这段代码运行在更大的数据上。我想知道,是否有可能在不使用显式循环的情况下,将numpy或pandas组合起来,以矢量化的方式进行操作。我尝试了许多方法,但脑子里没有什么东西。

解决这个问题的一种方法是将所有不符合您条件的数字设置为
nan
,然后取其余的平均值

import numpy as np 

arr = np.random.random((100))
arr2 = np.linspace(0,1,20)
arr3 = np.zeros(20) # this is the array i want to store the result in...

for index,num in enumerate(list(arr2)): 
    arr3[index] = np.mean(arr[np.abs(num - arr) < 0.2])
    
arr_tile = np.tile(arr, (len(arr2), 1))
arr_tile[np.abs(arr - arr2[:, None]) >= 0.2] = np.NaN
res = np.nanmean(arr_tile, axis=1)

np.allclose(res, arr3)
将numpy导入为np
arr=np.random.random((100))
arr2=np.linspace(0,1,20)
arr3=np.zeros(20)#这是我要存储结果的数组。。。
对于索引,枚举中的num(列表(arr2)):
arr3[指数]=np.平均值(arr[np.绝对值(num-arr)<0.2])
arr_tile=np.tile(arr,(len(arr2),1))
arr_tile[np.abs(arr-arr2[:,None])>=0.2]=np.NaN
res=np.nanmean(arr_tile,轴=1)
np.所有关闭(res,arr3)

这使得
正确

如果处理大型阵列,我建议使用完全不同的方法。现在,您正在整个
arr
中搜索
arr2
中的每个元素。这显然是矫枉过正。相反,您可以对排序后的
arr
进行操作,只需在从中获得的插入点之间求和即可

如果您能够:

arr.sort()
您知道间隔的宽度,因此请查找边界值。我正在制作数组形状的
(20,2)
,以便更容易地匹配边界:

bounds = arr2.reshape(-1, 1) + [-0.2, 0.2]
现在查找插入索引:

ind = np.searchsorted(arr, bounds)
ind
边界的形状相同
ind[i,:]
是指向
arr
的开始(包括)和结束(排除)索引,对应于
arr2
i
第th个元素。换句话说,对于任何给定的
i
,原始问题中的
arr3[i]
arr[ind[i,0]:ind[i,1]].mean()。您可以直接将其用于非矢量化解决方案:

result = np.array([arr[slice(*i)].mean() for i in ind])
有几种方法可以将解决方案矢量化。无论哪种情况,您都需要每次运行中的元素数:

n = np.diff(ind, axis=1).ravel()
np.random.seed(42)
arr = np.random.rand(100)
arr2 = np.linspace(0, 1, 1000)
一个容易出现舍入错误的快速而肮脏的解决方案使用了
ind
,并使用了奇特的索引:

cumulative = np.r_[0, np.cumsum(arr)]
sums = np.diff(cumulative[ind], axis=1).ravel()
result = sums / n
更稳健的解决方案将仅提取您实际需要的总和,使用:

您可以将这两种方法的结果与问题中计算的
arr3
进行比较,以验证第二种方法明显更准确,即使使用您的玩具示例也是如此

定时

def original(arr, arr2, d):
    arr3 = np.empty_like(arr2)
    for index, num in enumerate(arr2):
        arr3[index] = np.mean(arr[np.abs(num - arr) < d])
    return arr3

def ananda(arr, arr2, d):
    arr_tile = np.tile(arr, (len(arr2), 1))
    arr_tile[np.abs(arr - arr2[:, None]) >= d] = np.nan
    return np.nanmean(arr_tile, axis=1)

def mad_0(arr, arr2, d):
    arr.sort()
    ind = np.searchsorted(arr, arr2.reshape(-1, 1) + [-d, d])
    return np.array([arr[slice(*i)].mean() for i in ind])

def mad_1(arr, arr2, d):
    arr.sort()
    ind = np.searchsorted(arr, arr2.reshape(-1, 1) + [-d, d])
    n = np.diff(ind, axis=1).ravel()
    sums = np.diff(np.r_[0, np.cumsum(arr)][ind], axis=1).ravel()
    return sums / n

def mad_2(arr, arr2, d):
    arr.sort()
    ind = np.searchsorted(arr, arr2.reshape(-1, 1) + [-d, d])
    n = np.diff(ind, axis=1).ravel()
    arr = np.r_[arr, 0]
    sums = np.add.reduceat(arr, ind.ravel())[::2]
    return sums / n
结果:

original: 25.5 ms ± 278 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
  ananda: 2.66 ms ± 35.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
   mad_0: 14.5 ms ± 48.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
   mad_1:  211 µs ± 1.41 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
   mad_2:  242 µs ± 1.93 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
对于100个具有1k个存储箱的元素,原始方法比使用
np.tile
慢约10倍。使用列表理解仅比原始方法好2倍。虽然
np.cumsum
方法似乎比
np.add.reduce
快一点,但它在数值上可能不太稳定


使用我建议的方法的另一个优点是,您可以任意更改
arr2
,而
arr
只需要排序一次。

除非
arr2
大于
arr
,否则通过对该循环进行矢量化,您将获得很少的性能改进(如果有的话)。所有矢量化方法都需要将arr广播到arr2,提供一个NxM结构,其大小将抵消这些好处

我衡量了各种方法:

import numpy as np

def original(arr,arr2):
    arr3 = np.zeros(arr2.size) # this is the array i want to store the result in
    for index, num in enumerate(list(arr2)):
        arr3[index] = np.mean(arr[np.abs(num - arr) < 0.2])
    return arr3
        
def vectorized1(arr,arr2):
    delta  = (np.abs(arr2[:,None]-arr)<0.2).astype(np.int)
    return np.sum(arr*delta,axis=1)/np.sum(delta,axis=1)

def vectorized2(arr,arr2):
    return np.fromiter((np.mean(arr[np.abs(num-arr)<0.2]) for num in arr2),np.float64)

def vectorized3(arr,arr2):
    return np.apply_along_axis(lambda num:np.mean(arr[np.abs(num-arr)<0.2]),1,arr2[:,None])

def vectorized4(arr,arr2):
    select  = np.array([np.nan,1])[(np.abs(arr2[:,None]-arr)<0.2).astype(np.int)]
    return np.nanmean(select*arr,axis=1)

def vectorized5(arr,arr2):
    arr_tile = np.tile(arr, (len(arr2), 1))
    arr_tile[np.abs(arr - arr2[:, None]) >= 0.2] = np.NaN
    return np.nanmean(arr_tile, axis=1)
当arr2大于arr时的结果:

arr  = np.random.random(100)
arr2 = np.linspace(0, 1, 200000)

print()
print(f"arr {arr.size}, arr2 {arr2.size}")
t = timeit(lambda:original(arr,arr2),number=count)
print("original    time:",t)

t = timeit(lambda:vectorized1(arr,arr2),number=count)
print("vectorized1 time:",t)

t = timeit(lambda:vectorized2(arr,arr2),number=count)
print("vectorized2 time:",t)

t = timeit(lambda:vectorized3(arr,arr2),number=count)
print("vectorized3 time:",t)

t = timeit(lambda:vectorized4(arr,arr2),number=count)
print("vectorized4 time:",t)

t = timeit(lambda:vectorized5(arr,arr2),number=count)
print("vectorized5 time:",t)

original    time: 1.7699030359999997
vectorized1 time: 0.38871579499999953
vectorized2 time: 1.782099327
vectorized3 time: 2.443001885
vectorized4 time: 0.5951444290000012
vectorized5 time: 0.4536258110000002

请注意,这种明显的改进在一定程度上是正确的,因为当超过内存时,即使arr2大于arr,矢量化时间也会再次爆炸。

查看
arr2[:,None]-arr
。这应该是一个(20100)数组<代码>np。平均值
采用一个
参数,让您将其减少为(20,)结果。我不太确定我是否理解。。。你能演示一下吗?对于
arr3
,我建议
np.empty\u like
而不是
np.zero
,无需将
arr2
变成一个列表来枚举它。它将作为一个函数进行迭代array@hpaulj. 您显示的差异构建了一个遮罩。你建议如何应用它来计算平均值?屏蔽阵列?哦,我明白了,我很困惑,因为你离开了for循环。。当我到办公室的时候,让我试试这个。是的,对不起,我只是把它留下来,以表明它确实提供了与您相同的解决方案。您的解决方案我看起来非常聪明,工作正常,但问题是内存大小,对于更大的数据集,它很容易溢出。。。我还在想办法谢谢你,你的解决方案终于派上了用场。。!这似乎是最慢的方法:对于大型阵列,它比问题中的方法慢得多。有一个错误,这似乎是正确的,但我无法找出错误,因为我仍然没有完全理解整个过程。。。错误:行中
sums=np.diff(累计[ind],axis=1)
错误:
索引器:索引100超出轴0的范围,大小为100
我正在测试它,并试图找出你是否有想法,我将在这里等待:)非常感谢@阿迪拉巴吉尔。当我到桌面时,我会找出错误。同时,为了帮助您可视化,请尝试以下操作:将我的版本中的
arr[ind[i,0]:ind[i,1]].mean()与实现中的
arr3[i]
进行比较。发生此错误的原因是
reduceat
的奇怪索引规则以及我在
cumsum
@adirabargil中忘记的偏移量。我已经修正了答案并添加了第三个选项。非常感谢,
from timeit import timeit
count = 1

arr  = np.random.random(10000)
arr2 = np.linspace(0, 1, 2000)

t = timeit(lambda:original(arr,arr2),number=count)
print("original    time:",t)

t = timeit(lambda:vectorized1(arr,arr2),number=count)
print("vectorized1 time:",t)

t = timeit(lambda:vectorized2(arr,arr2),number=count)
print("vectorized2 time:",t)

t = timeit(lambda:vectorized3(arr,arr2),number=count)
print("vectorized3 time:",t)

t = timeit(lambda:vectorized4(arr,arr2),number=count)
print("vectorized4 time:",t)

t = timeit(lambda:vectorized5(arr,arr2),number=count)
print("vectorized5 time:",t)

original    time: 0.14478049999999998
vectorized1 time: 0.3868172580000001
vectorized2 time: 0.14587923599999986
vectorized3 time: 0.15062318699999988
vectorized4 time: 0.6438709420000002
vectorized5 time: 0.543624409
arr  = np.random.random(100)
arr2 = np.linspace(0, 1, 200000)

print()
print(f"arr {arr.size}, arr2 {arr2.size}")
t = timeit(lambda:original(arr,arr2),number=count)
print("original    time:",t)

t = timeit(lambda:vectorized1(arr,arr2),number=count)
print("vectorized1 time:",t)

t = timeit(lambda:vectorized2(arr,arr2),number=count)
print("vectorized2 time:",t)

t = timeit(lambda:vectorized3(arr,arr2),number=count)
print("vectorized3 time:",t)

t = timeit(lambda:vectorized4(arr,arr2),number=count)
print("vectorized4 time:",t)

t = timeit(lambda:vectorized5(arr,arr2),number=count)
print("vectorized5 time:",t)

original    time: 1.7699030359999997
vectorized1 time: 0.38871579499999953
vectorized2 time: 1.782099327
vectorized3 time: 2.443001885
vectorized4 time: 0.5951444290000012
vectorized5 time: 0.4536258110000002