Pandas Groupby с FIFO - PullRequest
       1

Pandas Groupby с FIFO

0 голосов
/ 04 июня 2018

У меня есть фрейм данных с идентификатором безопасности, датой транзакции и количеством.Количество может быть положительным (покупка) или отрицательным (продажа).Я хочу перейти от этого фрейма данных транзакций к фрейму данных холдинга, который в качестве первого прохода выглядит как простой groupby security_id и sum.

НО дата транзакции релевантна для транзакций покупки, потому что при покупке ценной бумаги определяется, когда она выплачивает проценты (1 год с даты покупки).Это означает, что фактически каждая защита имеет многоиндексную комбинацию security_id + транзакция_дата.

Итак, в следующем примере:

df1 = pd.DataFrame({'security_id': [2,2,2,3,3,3,5,5,6], 'transaction_date': ['2015-11-20', '2016-04-01', '2016-11-01', '2015-02-01', '2015-05-01', '2016-03-01', '2015-11-20', '2016-06-01', '2015-02-01'], 'quantity': [20, 30, -40, 25, 35, -15, 50, -50, 35]})

In [28]: df1
Out[28]:
   quantity  security_id transaction_date
0        20            2       2015-11-20
1        30            2       2016-04-01
2       -40            2       2016-11-01
3        25            3       2015-02-01
4        35            3       2015-05-01
5       -15            3       2016-03-01
6        50            5       2015-11-20
7       -50            5       2016-06-01
8        35            6       2015-02-01

Как вы можете видеть, здесь есть 4 ценные бумаги, которые охватывают каждый соответствующий случай.

У Ценной бумаги 2 сначала есть покупка 20 ценных бумаг, затем 30, а затем продажа 40. Таким образом, чистые авуары для этого должны (с First In First Out) составить 10 ценных бумаг, купленных в 2016-04-01.

Security 3 имеет покупку 25, затем 35, затем продажу 15, поэтому чистые авуары составляют 10 на 2015-02-01 и 35 на 2015-05-01.

Security5 имеет покупку 50, а затем продажу 50, поэтому чистое владение равно 0 (не имеет значения, удалено ли оно из df или осталось там с количеством = 0).

Безопасность 6 имеетпродаж нет, поэтому не должен быть затронут.

Визуально результат, который я хочу, выглядел бы так:

   quantity  security_id transaction_date
0        10            2       2016-04-01
1        10            3       2015-02-01
2        35            3       2015-05-01
3        35            6       2015-02-01

Я могу добавить столбец "net holdings" с преобразованием:

In [35]: df1['net_holdings'] = df1.groupby('security_id')['quantity'].transform('sum')

In [36]: df1
Out[36]:
   quantity  security_id transaction_date  net_holdings
0        20            2       2015-11-20            10
1        30            2       2016-04-01            10
2       -40            2       2016-11-01            10
3        25            3       2015-02-01            45
4        35            3       2015-05-01            45
5       -15            3       2016-03-01            45
6        50            5       2015-11-20             0
7       -50            5       2016-06-01             0
8        35            6       2015-02-01            35

И удалить продажи, отфильтровав отрицательные строки

In [37]: df1 = df1[df1['quantity']>=0]
Out[37]:
   quantity  security_id transaction_date  net_holdings
0        20            2       2015-11-20            10
1        30            2       2016-04-01            10
3        25            3       2015-02-01            45
4        35            3       2015-05-01            45
6        50            5       2015-11-20             0
8        35            6       2015-02-01            35

И теперь у меня осталось почти то, что я хочу, но все еще нужно сгруппировать ценные бумаги 2 и 3, чтобы 2 убрала однукупленный 2015-11-20, а в случае 3 распределение чистых активов по методу FIFO как 10 длякупленный в 2015-02-01 и 35 для купленного в 2015-05-01.

Я могу как-то увидеть, как куча вложенных циклов if и for справится, но я надеюсь, что большеэлегантное решение существует, прежде чем спуститься в эту кроличью нору.Любая помощь действительно приветствуется!

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

df = pd.DataFrame({'security_id': [2,2,2,3,3,3,5,5,6], 'transaction_date': ['2015-11-20', '2016-04-01', '2016-11-01', '2015-02-01', '2015-05-01', '2016-03-01', '2015-11-20', '2016-06-01', '2015-02-01'], 'quantity': [20, 30, -40, 25, 35, -15, 50, -50, 35]})
df['q_sold'] = df.groupby('security_id')['quantity'].transform(lambda x: x[x<0].sum())
#filter out sales from quantity col
df = df[df['quantity']>=0]
df = df.copy()
#sort by transaction date + security_id
df.sort_values(['security_id', 'transaction_date'], inplace=True)
#separate df that tracks running amount sold
df_sales = df.groupby('security_id')['q_sold'].unique().apply(lambda x: x[0])

for idx, sec_id in enumerate(df['security_id']):
    df.loc[df.index[idx], 'net_holdings'] = (df.loc[df.index[idx], 'quantity']
                                        + df_sales[sec_id])
    df_sales[sec_id] = df_sales[sec_id] + df.loc[df.index[idx], 'quantity']
    if df_sales[sec_id]>=0:
        df_sales[sec_id] = 0
df = df[df['net_holdings']>=0]
df.drop(['quantity', 'q_sold'], axis=1, inplace=True)
df.rename(columns={'net_holdings': 'quantity'})

Out[14]:
   security_id transaction_date  quantity
1            2       2016-04-01      10.0
3            3       2015-02-01      10.0
4            3       2015-05-01      35.0
6            5       2015-11-20       0.0
8            6       2015-02-01      35.0

1 Ответ

0 голосов
/ 11 сентября 2018

Ниже код должен работать для вас:

import pandas as pd
import numpy as np


df = df1 = pd.DataFrame({'security_id': [2,2,2,3,3,3,5,5,6],
                         'transaction_date': ['2015-11-20', '2016-04-01', '2016-11-01', '2015-02-01', '2015-05-01',
                                              '2016-03-01', '2015-11-20', '2016-06-01', '2015-02-01'],
                         'quantity': [20, 30, -40, 25, 35, -15, 50, -50, 35]})

def FiFo(dfg):
    if dfg[dfg['CS'] < 0]['quantity'].count():
        subT = dfg[dfg['CS'] < 0]['CS'].iloc[-1]
        dfg['quantity'] = np.where((dfg['CS'] + subT) <= 0, 0, dfg['quantity'])
        dfg = dfg[dfg['quantity'] > 0]
        if (len(dfg) > 0):
            dfg['quantity'].iloc[0] = dfg['CS'].iloc[0] + subT
    return dfg

df['PN'] = np.where(df['quantity'] > 0, 'P', 'N')
df['CS'] = df.groupby(['security_id', 'PN'])['quantity'].cumsum()
dfR = df.groupby(['security_id'], as_index=False)\
    .apply(FiFo) \
    .drop(['CS', 'PN'], axis=1) \
    .reset_index(drop=True)

print(dfR[dfR['quantity'] > 0])

И это дает результаты, как показано ниже:

   security_id transaction_date  quantity
0            2       2016-04-01        10
1            3       2015-02-01        10
2            3       2015-05-01        35
3            6       2015-02-01        35
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...