Как векторизовать / оптимизировать вычисления, основанные на предыдущих строках

#python #pandas #numpy #optimization #vectorization

#python #pandas #numpy #оптимизация #векторизация

Вопрос:

Я работаю над чем-то, где время выполнения чрезвычайно важно, а данные, с которыми мы работаем, велики, но в основном проблема сводится к оптимизации решения для серии x, где x1 известно и x = ax b из предыдущей строки. Так, например, начальное состояние:

 a b x
1 2 3
3 1
2 2
4 8
1 9
  

и конечное состояние будет выглядеть следующим образом:

 a b x
1 2 3
3 1 5
2 2 16
4 8 72
1 9 81
  

потому что 3*1 2 = 5, 5*3 1 = 16, и т.д.

Я попытался разобраться в математике, и в итоге получилось:

 b0 = x1
xi = sum(n=0 to i-1)(bn*product(m=n 1 to i-1)(am)
  

Так, например, для 3-й строки, в которой вы получите:

 x3 = a1*a2*b0   b1*a2   b2 = 3*1*3   2*3   1 = 9   6   1 = 16
  

Но в вычислительном плане это, кажется, хуже, чем просто вычислять каждый x путем перебора строк, что-то вроде этого:

 for i in range(2,len(df)):
    df.x[i] = df.x[i-1]*df.a[i-1] df.b[i-1]
  

Есть ли более простой способ решить эту проблему, которого мне не хватает, или я просто имею дело с дорогостоящей вычислительной операцией, которую мне придется выполнять за счет итерации? Если термин a не существует, часть bn может быть обработана с помощью cumsum, что-то вроде:

 df['b_cumsum'] = x1 cumsum(df.b)
  

но в конечном итоге я натыкаюсь на стену при попытке включить термины a, тем более что в конечном итоге нам требуется так много разных наборов продуктов даже в пределах каждого термина sum.

Спасибо.

Комментарии:

1. Я не думаю, что это можно векторизовать, потому что это скользящий расчет. Итак, вы, вероятно, просите нас обойти циклический код

Ответ №1:

Сначала вы можете вычислить масштабированный x: x‘ = x/cumprod(a), используя соответствующий b‘ = b / cumprod(a)

Это может быть сделано с помощью векторизованных операций, а также обратного преобразования из x‘ в x:

 ab = np.array([[1, 2],
               [3, 1],
               [2, 2],
               [4, 8],
               [1, 9]])

scale = ab.T[0].cumprod()
xp = 3 (ab.T[1]/scale).cumsum()
x = xp*scale
x
array([  5.,  16.,  34., 144., 153.])
  

Комментарии:

1. Это явно работает и выглядит как очень элегантное решение, но не могли бы вы немного объяснить математику? Я понимаю, что масштаб — это просто cumprod (a), но как xp правильно вычисляет x / cumprod (a)? Спасибо.

2. @zachvac Подумайте об этом так: если бы все коэффициенты масштабирования (столбец a ) были равны 1, тогда это было бы легко. Изменяя масштаб x так, как мы это делаем, мы достигаем именно этого. Мы должны использовать cumprod a , потому что исходное масштабирование является кумулятивным. Как только это станет ясно, нам останется только заметить, что b должен масштабироваться так же, как x , для сохранения согласованности.

3. извините, я определенно понял общую цель, возможно, я потратил слишком много времени, пытаясь вывести суммы / продукты, чтобы понять, как это работает. В качестве примера для решения для x5 я получил: [a1-a4] * x1 b1 * [a2-a4] b2 * [a3-a4] b3 * a4 b4, где скобки указывают произведение этих терминов. Как вы можете масштабировать только один раз с помощью cumprod от 1 до 4, когда есть продукты 2-4, 3-4, а затем a4?

4. о, подождите, я разобрался с этим, и я вижу, как это работает, это совокупная сумма совокупных произведений в знаменателе, а числитель отменяет весь знаменатель в 1-м члене, большинство во 2-м и т.д. Я собираюсь изучить это немного подробнее, но это было очень полезно, большое вам спасибо.

5. итак, мое объяснение было таким: мы всегда получаем cumprod (a) * x1 b1 * [a2-an] b2 * [a3-an] … bn итак, x / cumprod (a) = x1 b1 / a1 b2 / a1-a2 … bn / a1-an Поэтому я определенно понимаю, почему это работает, я просто не особенно понимаю объяснение масштабирования или как, возможно, добраться до этого решения без чрезвычайно удачного предположения. Я предполагаю, что понимание того, что [ai-an] / [a1-an] = 1 / (cumprod (a) в i) было бы ключевым, просто кажется, что вы попали туда гораздо более интуитивно понятным способом.

Ответ №2:

Когда я сталкиваюсь с функциями, которые я не могу векторизовать, но это должно быть эффективным, я использую numba . Который является модулем JIT-компиляции точно в срок. В большинстве случаев это может быть даже быстрее, чем собственные методы pandas:

 from numba import njit

@njit
def calculation(arr):
    result = np.empty(arr.shape[0])
    for idx, row in enumerate(arr):
        if idx == 0:
            result[idx] = row[2]
        else:
            row = arr[idx-1]
            result[idx] = result[idx-1] * row[0]   row[1]
    
    return result

df['x'] = calculation(df.to_numpy())
  
 print(df)

   a  b      x
0  1  2    3.0
1  3  1    5.0
2  2  2   16.0
3  4  8   34.0
4  1  9  144.0
  

примечание: когда вы хотите рассчитать время. Не рассчитывайте время при первом запуске, поскольку он еще не скомпилирован. Сначала запустите его один раз, затем рассчитайте время для второго запуска.