Насколько я могу судить, здесь нет интуитивного подхода, который бы не включал явную итерацию, что не идеально для numpy
и pandas
.Однако временная сложность этой проблемы - O (n), что делает ее хорошей целью для библиотеки numba
.Это позволяет нам найти очень эффективное решение.
Одна заметка о моем решении, я округляю с помощью (a + threshold // 2) // threshold * threshold
, что выглядит многословно по сравнению с использованием np.round(a, decimals=-2)
.Это связано с природой использования флага numba
nopython=True
, который не совместим с функцией np.round
.
from numba import jit
@jit(nopython=True)
def cumsum_with_threshold(arr, threshold):
"""
Rounds values in an array, propogating the last value seen until
a cumulative sum reaches a threshold
:param arr: the array to round and sum
:param threshold: the point at which to stop propogation
:return: rounded output array
"""
s = a.shape[0]
o = np.empty(s)
d = a[0]
r = (a + threshold // 2) // threshold * threshold
c = 0
o[0] = r[0]
for i in range(1, s):
if np.abs(a[i] - d) > threshold:
o[i] = r[i]
d = a[i]
else:
o[i] = o[i - 1]
return o
Давайте проверим это:
a = df['input'].values
pd.Series(cumsum_with_threshold(a, 100))
0 11700.0
1 11700.0
2 11700.0
3 11700.0
4 11700.0
5 11700.0
6 11600.0
7 11600.0
8 11600.0
9 11600.0
10 11700.0
11 11700.0
12 11700.0
13 11600.0
14 11600.0
dtype: float64
Если вы хотите сравнить округленное значение с входом, вместо фактического , просто сделайтеследующее изменение в функции выше в цикле, которая дает вывод из вашего вопроса.
for i in range(1, s):
if np.abs(a[i] - d) > t:
o[i] = r[i]
# OLD d = a[i]
d = r[i]
else:
o[i] = o[i - 1]
Чтобы проверить эффективность, давайте запустим это для гораздо большего набора данных:
l = np.random.choice(df['input'].values, 10_000_000)
%timeit cumsum_with_threshold(l, 100)
1.54 µs ± 7.93 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)