Условная совокупная сумма с зависимостью в фрейме данных Pandas

#python #pandas #dataframe #finance

#python #панды #фрейм данных #финансы

Вопрос:

Я пытаюсь вычислить две совокупные суммы с учетом серии финансовых транзакций. Существует 4 типа транзакций, каждый из которых содержит сумму для транзакции: D — депозит W — снятие G — прибыль L — убыток

Фрейм данных создается следующим образом

 import pandas as pd
import numpy as np

data = { 'Type': ['D', 'D', 'W', 'D', 'G', 'G', 'G', 'L', 'W', 'G', 'W', 'G', 'L' ],
         'Amount': [10, 10, -5, 10, 5, 5, 5, -5, -10, 10, -25, 25, -30]
       }
df = pd.DataFrame(data, columns = ['Type', 'Amount'])
 

Текущий капитал легко вычислить с помощью cumsum() , который по существу включает все транзакции.

 df['Capital'] = df['Amount'].cumsum()
 

Другой объект, который я хочу вычислить, — это принципал, который представляет сумму денег, вводимую на счет. При этом учитываются только транзакции типа D и W. Я могу сделать простой фильтр здесь, используя:

 df['Principal'] = df.apply(lambda row : row['Amount'] if (row['Type'] == 'W' or row['Type'] == 'D') else 0, axis=1).cumsum()
 

Однако в этом есть проблема. Когда есть выигрыши и есть изъятия, изъятие должно быть снято с прибыли, прежде чем оно повлияет на этого принципала. Результат, приведенный выше, содержит ошибку в приведенном ниже результате (строки 8 и 10):

     Type    Amount  Capital Principal
0   D       10      10      10
1   D       10      20      20
2   W       -5      15      15
3   D       10      25      25
4   G       5       30      25
5   G       5       35      25
6   G       5       40      25
7   L       -5      35      25
8   W       -10     25      15   <- should stays at 25
9   G       10      35      15   <- now wrong because of above
10  W       -25     10      -10  <- error escalades
11  G       25      35      -10
12  L       -30     5       -10
 

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

 df['Principal'] = np.nan
currentPrincipal = 0
for index, row in df.iterrows():
    if (row['Type'] == 'D'):
        #row['Principal'] = currentPrincipal   row['Amount']
        df.loc[index, 'Principal'] = currentPrincipal   row['Amount']
    elif (row['Type'] == 'W' and row['Capital'] <= currentPrincipal):
        #row['Principal'] = row['Capital']
        df.loc[index, 'Principal'] = row['Capital']
    else:
        df.loc[index, 'Principal'] = currentPrincipal
        
    currentPrincipal = df.loc[index, 'Principal']
 

Мне не удалось попытаться использовать apply , поскольку у нас есть зависимость от предыдущего результата принципала, который необходимо перенести.
Правильный результат:

     Type    Amount  Capital Principal
0   D       10      10      10
1   D       10      20      20
2   W       -5      15      15
3   D       10      25      25
4   G       5       30      25
5   G       5       35      25
6   G       5       40      25
7   L       -5      35      25
8   W       -10     25      25
9   G       10      35      25
10  W       -25     10      10
11  G       25      35      10
12  L       -30     5       10
 

Ответ №1:

Вы можете сделать:

 # calculate cumulative withdrawals
w = df['Amount'].where(df['Type'].eq('W')).cumsum()

# calculate cumulative deposits
d = df['Amount'].where(df['Type'].eq('D'), 0).cumsum()

# calculate cumulative gain amp; loss
g = df['Amount'].where(df['Type'].isin(['G', 'L']), 0).cumsum()

# calculate principal = deposit   net_withdrawal(if any)
df['Principal'] =  d   (g   w).where(lambda x: x < 0).ffill().fillna(0)
 

Результат:

    Type  Amount  Capital  Principal
0     D      10       10       10.0
1     D      10       20       20.0
2     W      -5       15       15.0
3     D      10       25       25.0
4     G       5       30       25.0
5     G       5       35       25.0
6     G       5       40       25.0
7     L      -5       35       25.0
8     W     -10       25       25.0
9     G      10       35       25.0
10    W     -25       10       10.0
11    G      25       35       10.0
12    L     -30        5       10.0
 

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

1. Спасибо Shubham. Выглядит чище. Знаете ли вы относительную производительность этого? Похоже, что для вычисления всех совокупных сумм требуется много итераций?

2. @phandinhlan Я чувствую, что это должно быть быстрее, поскольку cumsum операция по своей сути векторизована в numpy. Но я думаю, вы можете проверить это на своих данных, чтобы увидеть результаты самостоятельно..

3. Меня беспокоит то, что cumsum() это вызывалось 3 раза по сравнению с одним циклом for, а не то, что каждый cumsum() из них не быстрый.