2015-10-23 3 views
4

Как и в этом случае Exponential Decay on Python Pandas DataFrame, я бы хотел быстро вычислить экспоненциально убывающие суммы для некоторых столбцов в кадре данных. Однако строки в кадре данных не равномерно распределены по времени. Следовательно, в то время как exponential_sum[i] = column_to_sum[i] + np.exp(-const*(time[i]-time[i-1])) * exponential_sum[i-1], вес np.exp(...) не учитывается, и для меня не очевидно, как изменить этот вопрос и по-прежнему использовать векторию pandas/numpy. Существует ли панда-векторизованное решение этой проблемы?Pandas: Экспоненциально убывающая сумма с переменными весами

Чтобы проиллюстрировать нужный расчет, вот пример кадр с экспоненциальной скользящей суммой A хранящейся в Sum используя константу распада 1:

time A  Sum 
0 1.00 1 1.000000 
1 2.10 3 3.332871 
2 2.13 -1 2.234370 
3 3.70 7 7.464850 
4 10.00 2 2.013708 
5 10.20 1 2.648684 
+0

вы можете RESAMPLE ваш dataframe так, чтобы она равномерно? – maxymoo

+0

@Alexander Я спрашиваю о суммах, а не в среднем, хотя, возможно, есть очевидное преобразование –

+0

@Alexander Я только что прочитал этот вопрос более тщательно, и я не думаю, что он обращается к моему вопросу, а именно к векторизованному вычислению numpy/pandas , У меня нет проблем с вычислением экспоненциальных сумм в цикле python, я просто делаю это на достаточно больших кадрах, которые могут векторизовать вычисления. –

ответ

3

Этот вопрос является более сложным, чем он впервые появился. В итоге я использовал numba jit для компиляции функции генератора для вычисления экспоненциальных сумм. Мой конечный результат вычисляет экспоненциальную сумму в 5 миллионов строк за секунду на моем компьютере, который, надеюсь, будет достаточно быстрым для ваших нужд.

# Initial dataframe. 
df = pd.DataFrame({'time': [1, 2.1, 2.13, 3.7, 10, 10.2], 
        'A': [1, 3, -1, 7, 2, 1]}) 

# Initial decay parameter. 
decay_constant = 1 

Мы можем определить весовые коэффициенты затухания, как ехр (-time_delta * decay_constant), и установить его начальное значение, равное одному:

df['weight'] = np.exp(-df.time.diff() * decay_constant) 
df.weight.iat[0] = 1 

>>> df 
    A time weight 
0 1 1.00 1.000000 
1 3 2.10 0.332871 
2 -1 2.13 0.970446 
3 7 3.70 0.208045 
4 2 10.00 0.001836 
5 1 10.20 0.818731 

Теперь мы будем использовать JIT от numba оптимизировать генератор функция, которая вычисляет тригонометрические суммы:

from numba import jit 

@jit(nopython=True) 
def exponential_sum(A, k): 
    total = A[0] 
    yield total 
    for i in xrange(1, len(A)): # Use range in Python 3. 
     total = total * k[i] + A[i] 
     yield total 

Мы будем использовать генератор для добавления значения в dataframe:

df['expSum'] = list(exponential_sum(df.A.values, df.weight.values)) 

Которая производит желаемый результат:

>>> df 
    A time weight expSum 
0 1 1.00 1.000000 1.000000 
1 3 2.10 0.332871 3.332871 
2 -1 2.13 0.970446 2.234370 
3 7 3.70 0.208045 7.464850 
4 2 10.00 0.001836 2.013708 
5 1 10.20 0.818731 2.648684 

Так давайте масштаб до 5 миллионов строк и проверить производительность:

df = pd.DataFrame({'time': np.random.rand(5e6).cumsum(), 'A': np.random.randint(1, 10, 5e6)}) 
df['weight'] = np.exp(-df.time.diff() * decay_constant) 
df.weight.iat[0] = 1 

%%timeit -n 10 
df['expSum'] = list(exponential_sum(df.A.values, df.weight.values)) 
10 loops, best of 3: 726 ms per loop 
+0

Я использовал Cython для аналогичного решения, но надеялся, что было умное использование numpy/scipy, которого я отсутствовал. Похоже, что консенсус - нет. Вариант этого ответа кажется лучшим, что вы можете сделать. –

0

Расширяющейся на answer вы связаны, я придумал со следующим методом.

Во-первых, обратите внимание, что:

exponential_sum[i] = column_to_sum[i] + 
    np.exp(-const*(time[i]-time[i-1])) * column_to_sum[i-1] + 
    np.exp(-const*(time[i]-time[i-2])) * column_to_sum[i-2] + ... 

Так главное изменение, чтобы сделать это в генерировании weightspace в соответствии с приведенной выше формулой. Я продолжил так:

time = pd.Series(np.random.rand(10)).cumsum() 
weightspace = np.empty((10,10)) 
for i in range(len(time)): 
    weightspace[i] = time - time[i] 
weightspace = np.exp(weightspace) 

Не беспокойтесь о нижнем левом треугольнике матрицы, она не будет использоваться. Кстати, должен быть способ генерации пространства весов без цикла.

Тогда небольшое изменение в том, как вы выбираете веса от weightspace в функции прокатки:

def rollingsum(array): 
    weights = weightspace[len(array)-1][:len(array)] 
    # Convolve the array and the weights to obtain the result 
    a = np.dot(array, weights).sum() 
    return a 

работает, как ожидалось:

dataset = pd.DataFrame(np.random.rand(10,3), columns=["A", "B","C"]) 
a = pd.expanding_apply(dataset, rollingsum) 
+0

Одна проблема в этом решении заключается в том, что весовое пространство теперь очень велико. В решении для обычного случая он был линейным по размеру кадра данных и теперь он квадратичен. Это делает его проблематичным для больших кадров. Большие рамки - это то, почему требуется векторное решение. Неужели это неизбежно? –

+0

Недостаточно оптимизированного для цикла, такого как @Alexander, предположил, что, боюсь, я не вижу другого пути. – IanS

Смежные вопросы