我有一个numpy / pandas值列表:
a = np.random.randint(-100, 100, 10000)
b = a/100
我想应用自定义的cumsum函数,但是我还没有找到一种没有循环的方法。自定义函数将累加值设置为1的上限和-1的下限,如果求和的“加”超出了这些限制,则“加”变为0。
如果总和在-1和1的限制之间,但“加”的值将超出限制,则“加”将成为-1或1的余数。
这是循环版本:
def cumsum_with_limits(values):
cumsum_values = []
sum = 0
for i in values:
if sum+i <= 1 and sum+i >= -1:
sum += i
cumsum_values.append(sum)
elif sum+i >= 1:
d = 1-sum # Remainder to 1
sum += d
cumsum_values.append(sum)
elif sum+i <= -1:
d = -1-sum # Remainder to -1
sum += d
cumsum_values.append(sum)
return cumsum_values
有什么方法可以对此向量化?我需要在大型数据集上运行此功能,而性能是我当前的问题。感谢任何帮助!
更新:修复了一些代码,并对输出进行了一些澄清: 使用np.random.seed(0),前6个值是:
b = [0.72, -0.53, 0.17, 0.92, -0.33, 0.95]
预期输出:
o = [0.72, 0.19, 0.36, 1, 0.67, 1]
答案 0 :(得分:4)
循环不一定是不希望的。如果性能是一个问题,请考虑使用numba
。在不实质改变逻辑的情况下,性能提高了约330倍:
from numba import njit
np.random.seed(0)
a = np.random.randint(-100, 100, 10000)
b = a/100
@njit
def cumsum_with_limits_nb(values):
n = len(values)
res = np.empty(n)
sum_val = 0
for i in range(n):
x = values[i]
if (sum_val+x <= 1) and (sum_val+x >= -1):
res[i] = x
sum_val += x
elif sum_val+x >= 1:
d = 1-sum_val # Remainder to 1
res[i] = d
sum_val += d
elif sum_val+x <= -1:
d = -1-sum_val # Remainder to -1
res[i] = d
sum_val += d
return res
assert np.isclose(cumsum_with_limits(b), cumsum_with_limits_nb(b)).all()
如果您不介意牺牲某些性能,则可以更简洁地重写此循环:
@njit
def cumsum_with_limits_nb2(values):
n = len(values)
res = np.empty(n)
sum_val = 0
for i in range(n):
x = values[i]
next_sum = sum_val + x
if np.abs(next_sum) >= 1:
x = np.sign(next_sum) - sum_val
res[i] = x
sum_val += x
return res
与nb2
具有类似的性能,这是一种替代方法(感谢@jdehesa):
@njit
def cumsum_with_limits_nb3(values):
n = len(values)
res = np.empty(n)
sum_val = 0
for i in range(n):
x = min(max(sum_val + values[i], -1) , 1) - sum_val
res[i] = x
sum_val += x
return res
性能比较:
assert np.isclose(cumsum_with_limits(b), cumsum_with_limits_nb(b)).all()
assert np.isclose(cumsum_with_limits(b), cumsum_with_limits_nb2(b)).all()
assert np.isclose(cumsum_with_limits(b), cumsum_with_limits_nb3(b)).all()
%timeit cumsum_with_limits(b) # 12.5 ms per loop
%timeit cumsum_with_limits_nb(b) # 40.9 µs per loop
%timeit cumsum_with_limits_nb2(b) # 54.7 µs per loop
%timeit cumsum_with_limits_nb3(b) # 54 µs per loop
答案 1 :(得分:2)
以常规的累积金额开头:
b = ...
s = np.cumsum(b)
找到第一个剪辑点:
i = np.argmax((s[0:] > 1) | (s[0:] < -1))
调整以下内容:
s[i:] += (np.sign(s[i]) - s[i])
冲洗并重复。这仍然需要一个循环,但仅在调整点上进行,通常希望该调整点比数组大小的总数小得多。
b = ...
s = np.cumsum(b)
while True:
i = np.argmax((s[0:] > 1) | (s[0:] < -1))
if np.abs(s[i]) <= 1:
break
s[i:] += (np.sign(s[i]) - s[i])
我仍然没有找到一种方法可以完全预先预先计算调整点,因此,即使要使用numba进行编译,我也不得不猜测numba解决方案会比此更快。
从np.seed(0)
开始,您的原始示例具有3090个调整点,大约为1/3。不幸的是,使用所有临时数组和额外的和,这使我的解决方案的算法复杂度趋于O(n 2 )。这是完全不能接受的。