Как реализовать обработку датафрейма с помощью цепочки методов?
У меня есть до неприличия кривой брокерский отчет. Ссылка на исходный файл
Я его считываю следующим образом:
df = (
pd.read_excel(tinkoff_folder + tinkoff_files[0], skiprows=7)
.dropna(axis=0, how='all')
.dropna(axis=1, how='all')
)
Для обработки всего массива я его разделяю на 2 части:
- часть таблицы без смещения данных (фильтр по столбцу "Номер сделки" not .isna())
- часть таблицы со смещением (фильтр по столбцу "Номер сделки" .isna())
Так выглядит код для первой части таблицы (дата фрейм обрабатывается с помощью цепочки методов):
last_row = int(df[df.isin(['1.2 Информация о неисполненных сделках на конец отчетного периода']).any(axis=1)].index[0])
columns_lst = ['Номер сделки', 'Дата заключения', 'Время', 'Вид сделки', 'Сокращенное наименование', 'Цена за единицу', 'Количество', 'Сумма сделки', 'Валюта расчетов', 'Дата расчетов']
# Первая часть таблицы
df1 = (
df.copy()
[0: last_row] # оставляю только нужные разделы отчета
[~df['Номер сделки'].isna()] # убираю смещенные вправо данные
.drop(df.filter(regex='Unnamed', axis=1), axis=1) # убираю столбцы без значений
.dropna(axis=0, how='all') # удаляем пустые стоки
[
df[df.columns[~df.columns.str.match('Unnamed')][7]] # выбираем сделки с валютой
.str.contains(match) == True
]
.set_axis( # удаляем спец. символы и лишние пробелы в наименованиях стобцов
[col.strip() for col
in list(pd.Series(df.columns[~df.columns.str.match('Unnamed')]).replace('\n', '', regex=True))]
, axis=1
)
[columns_lst] # оставляем нужные столбцы
)
В данном случае я уверен, что все операции с датафреймом будут проведены корректно. Но при обработке второй части возникают проблемы (мне приходится использовать мутации).
df2 = (
df.copy()[0:last_row] # оставляю только нужные разделы отчета
[df['Номер сделки'].isna()][1:] # оставляю смещенные вправо данные
.dropna(axis=1, how='all').dropna(axis=0, how='all') # удаляем пустые стоки/столбцы
)
Результат следующий (прошу учесть, что в первой строке есть символы \n, поэтому первая строка разбивается на несколько).
Unnamed: 1 | Unnamed: 3 | Unnamed: 7 | Unnamed: 9 | Unnamed: 13 | Unnamed: 18 | Unnamed: 23 | Unnamed: 30 | Unnamed: 33 | Unnamed: 39 | Unnamed: 44 | Unnamed: 46 | Unnamed: 51 | Unnamed: 54 | Unnamed: 60 | Unnamed: 65 | Unnamed: 70 | Unnamed: 76 | Unnamed: 82 | Unnamed: 87 | Unnamed: 93 | Unnamed: 97 | Unnamed: 101 | Unnamed: 107 | Unnamed: 114 | Unnamed: 120 | Unnamed: 124 | Unnamed: 131 | Unnamed: 137 | Unnamed: 141 | Unnamed: 149 | Unnamed: 154 | Unnamed: 160 | Unnamed: 165 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
14 | Номер сделки | Номер поруче | Признак исполнения | Дата заклю | Время | Торговая площадка | Режим торгов | Вид сделки | Сокращен | Код актива | Цена за едини | Валю | Количество | Сумма (без НКД) | НКД | Сумма сделки | Валюта расчетов | Комис | Валю | Комиссия биржи | Валюта комиссии биржи | Комиссия клир. центра | Валюта комиссии клир. центра | Гербовый сбор | Валюта гербового сбора | Ставка РЕПО(%) | Контрагент / Брокер | Дата расчетов | Дата поставки | Статус брокера | Тип дог. | Номер дог. | Дата дог. | Тип расчета по сделке |
ния | чения | ное наименова | цу | та цены | сия брокера | та комис | ||||||||||||||||||||||||||||
ние | сии | |||||||||||||||||||||||||||||||||
15 | 210002528 | 19110370310 | nan | 05.11.2019 | 10:01:12 | ММВБ | CNGD | Покупка | USDRUB_TOM | USD000UTSTOM | 63,37 | RUB | 940 | 59567,8 | 0 | 59567,8 | RUB | 178,7 | RUB | 0 | nan | 0 | nan | nan | nan | nan | НКО НКЦ (АО) | 06.11.2019 | nan | К | nan | nan | nan | CLR |
16 | 210481436 | 19121563787 | nan | 08.11.2019 | 16:02:40 | ММВБ | CNGD | Покупка | USDRUB_TOM | USD000UTSTOM | 63,88 | RUB | 250 | 15970 | 0 | 15970 | RUB | 47,91 | RUB | 0 | nan | 0 | nan | nan | nan | nan | НКО НКЦ (АО) | 12.11.2019 | nan | К | nan | nan | nan | CLR |
17 | 211812301 | 19150608966 | nan | 25.11.2019 | 16:38:58 | ММВБ | CNGD | Покупка | USDRUB_TOM | USD000UTSTOM | 63,9875 | RUB | 80 | 5119 | 0 | 5119 | RUB | 15,36 | RUB | 0 | nan | 0 | nan | nan | nan | nan | НКО НКЦ (АО) | 26.11.2019 | nan | К | nan | nan | nan | CLR |
18 | 3053630966 | 18986970279 | nan | 10.12.2019 | 14:33:10 | ММВБ | TQTF | Покупка | FinEx USD Cash Equivalent | FXTB | 646,7 | RUB | 15 | 9700,5 | 0 | 9700,5 | RUB | 29,1 | RUB | 0.52 | RUB | 0.38 | RUB | nan | nan | nan | НКО НКЦ (АО) | 12.12.2019 | 12.12.2019 | К | nan | nan | nan | CLR |
19 | 213577271 | 19188947668 | nan | 13.12.2019 | 14:05:22 | ММВБ | CNGD | Покупка | USDRUB_TOM | USD000UTSTOM | 62,22 | RUB | 1 | 62,22 | 0 | 62,22 | RUB | 0,19 | RUB | 0 | nan | 0 | nan | nan | nan | nan | НКО НКЦ (АО) | 16.12.2019 | nan | К | nan | nan | nan | CLR |
20 | 3058666741 | 19019713827 | nan | 18.12.2019 | 13:17:26 | ММВБ | TQBR | Покупка | ГАЗПРОМ ао | GAZP | 251,79 | RUB | 10 | 2517,9 | 0 | 2517,9 | RUB | 7,55 | RUB | 0.13 | RUB | 0.1 | RUB | nan | nan | nan | НКО НКЦ (АО) | 20.12.2019 | 20.12.2019 | К | nan | nan | nan | CLR |
В этом моменте начинаются проблемы. Мне нужно взять первую строку из полученного df2 и использовать ее в качестве новых заголовков для столбцов (предварительно удалив спец. символы и лишние пробелы).
Я могу это реализовать следующим образом:
col_list2 = list(df2.iloc[0].astype(str).replace('\n', '', regex=True))
col_list2_strip = [col.strip() for col in col_list2]
df2_1 = df2.copy().set_axis(col_list2_strip, axis=1)
Получаю результат:
Номер сделки | Номер поручения | Признак исполнения | Дата заключения | Время | Торговая площадка | Режим торгов | Вид сделки | Сокращенное наименование | Код актива | Цена за единицу | Валюта цены | Количество | Сумма (без НКД) | НКД | Сумма сделки | Валюта расчетов | Комиссия брокера | Валюта комиссии | Комиссия биржи | Валюта комиссии биржи | Комиссия клир. центра | Валюта комиссии клир. центра | Гербовый сбор | Валюта гербового сбора | Ставка РЕПО(%) | Контрагент / Брокер | Дата расчетов | Дата поставки | Статус брокера | Тип дог. | Номер дог. | Дата дог. | Тип расчета по сделке | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
14 | Номер сделки | Номер поруче | Признак исполнения | Дата заклю | Время | Торговая площадка | Режим торгов | Вид сделки | Сокращен | Код актива | Цена за едини | Валю | Количество | Сумма (без НКД) | НКД | Сумма сделки | Валюта расчетов | Комис | Валю | Комиссия биржи | Валюта комиссии биржи | Комиссия клир. центра | Валюта комиссии клир. центра | Гербовый сбор | Валюта гербового сбора | Ставка РЕПО(%) | Контрагент / Брокер | Дата расчетов | Дата поставки | Статус брокера | Тип дог. | Номер дог. | Дата дог. | Тип расчета по сделке |
ния | чения | ное наименова | цу | та цены | сия брокера | та комис | ||||||||||||||||||||||||||||
ние | сии | |||||||||||||||||||||||||||||||||
15 | 210002528 | 19110370310 | nan | 05.11.2019 | 10:01:12 | ММВБ | CNGD | Покупка | USDRUB_TOM | USD000UTSTOM | 63,37 | RUB | 940 | 59567,8 | 0 | 59567,8 | RUB | 178,7 | RUB | 0 | nan | 0 | nan | nan | nan | nan | НКО НКЦ (АО) | 06.11.2019 | nan | К | nan | nan | nan | CLR |
16 | 210481436 | 19121563787 | nan | 08.11.2019 | 16:02:40 | ММВБ | CNGD | Покупка | USDRUB_TOM | USD000UTSTOM | 63,88 | RUB | 250 | 15970 | 0 | 15970 | RUB | 47,91 | RUB | 0 | nan | 0 | nan | nan | nan | nan | НКО НКЦ (АО) | 12.11.2019 | nan | К | nan | nan | nan | CLR |
17 | 211812301 | 19150608966 | nan | 25.11.2019 | 16:38:58 | ММВБ | CNGD | Покупка | USDRUB_TOM | USD000UTSTOM | 63,9875 | RUB | 80 | 5119 | 0 | 5119 | RUB | 15,36 | RUB | 0 | nan | 0 | nan | nan | nan | nan | НКО НКЦ (АО) | 26.11.2019 | nan | К | nan | nan | nan | CLR |
18 | 3053630966 | 18986970279 | nan | 10.12.2019 | 14:33:10 | ММВБ | TQTF | Покупка | FinEx USD Cash Equivalent | FXTB | 646,7 | RUB | 15 | 9700,5 | 0 | 9700,5 | RUB | 29,1 | RUB | 0.52 | RUB | 0.38 | RUB | nan | nan | nan | НКО НКЦ (АО) | 12.12.2019 | 12.12.2019 | К | nan | nan | nan | CLR |
19 | 213577271 | 19188947668 | nan | 13.12.2019 | 14:05:22 | ММВБ | CNGD | Покупка | USDRUB_TOM | USD000UTSTOM | 62,22 | RUB | 1 | 62,22 | 0 | 62,22 | RUB | 0,19 | RUB | 0 | nan | 0 | nan | nan | nan | nan | НКО НКЦ (АО) | 16.12.2019 | nan | К | nan | nan | nan | CLR |
20 | 3058666741 | 19019713827 | nan | 18.12.2019 | 13:17:26 | ММВБ | TQBR | Покупка | ГАЗПРОМ ао | GAZP | 251,79 | RUB | 10 | 2517,9 | 0 | 2517,9 | RUB | 7,55 | RUB | 0.13 | RUB | 0.1 | RUB | nan | nan | nan | НКО НКЦ (АО) | 20.12.2019 | 20.12.2019 | К | nan | nan | nan | CLR |
Далее я вынужен опять прибегать к мутации, так как на этот раз я буду обращаться к новым названиям столбцов:
columns_lst = ['Номер сделки', 'Дата заключения', 'Время', 'Вид сделки', 'Сокращенное наименование', 'Цена за единицу', 'Количество', 'Сумма сделки', 'Валюта расчетов', 'Дата расчетов']
df2_2 = (
df2_1.copy()
[columns_lst] # оставляем нужные столбцы
[df2_1['Сокращенное наименование'].str.contains(match) == True] # выбираем сделки с валютой
)
И только после нескольких мутаций исходной таблицы я получаю корректную вторую часть таблицы:
Номер сделки | Дата заключения | Время | Вид сделки | Сокращенное наименование | Цена за единицу | Количество | Сумма сделки | Валюта расчетов | Дата расчетов | |
---|---|---|---|---|---|---|---|---|---|---|
15 | 210002528 | 05.11.2019 | 10:01:12 | Покупка | USDRUB_TOM | 63,37 | 940 | 59567,8 | RUB | 06.11.2019 |
16 | 210481436 | 08.11.2019 | 16:02:40 | Покупка | USDRUB_TOM | 63,88 | 250 | 15970 | RUB | 12.11.2019 |
17 | 211812301 | 25.11.2019 | 16:38:58 | Покупка | USDRUB_TOM | 63,9875 | 80 | 5119 | RUB | 26.11.2019 |
19 | 213577271 | 13.12.2019 | 14:05:22 | Покупка | USDRUB_TOM | 62,22 | 1 | 62,22 | RUB | 16.12.2019 |
Есть ли подход избежать данных мутаций или хотя бы быть уверенным, что последовательность преобразований массива будет выполнена корректно и избежать создания нескольких датафреймов?
Update: решил немного по другому подойти к решению проблемы. В первую очередь выделяю данные без смещения, схожим образом выделяю данные со смещением, привожу датафреймы в одному размеру и объединяю их.
last_row = int(df[df.isin(['1.2 Информация о неисполненных сделках на конец отчетного периода']).any(axis=1)].index[0])
columns_lst = ['Номер сделки', 'Дата заключения', 'Время', 'Вид сделки', 'Сокращенное наименование', 'Цена за единицу', 'Количество', 'Сумма сделки', 'Валюта расчетов', 'Дата расчетов']
dfs = [] # list для хранения датафреймов
# Первая часть таблицы
df1 = (
df.copy()
[0: last_row] # оставляю только нужные разделы отчета
[~df['Номер сделки'].isna()] # оставляю несмещенные вправо данные
[df.columns[:-1]] # удаляю последний столбец (в нем нет данных)
)
dfs.append(df1)
# Вторая часть таблицы
df2 = (
df.copy()[0:last_row] # оставляю только нужные разделы отчета
[df['Номер сделки'].isna()][1:] # оставляю смещенные вправо данные
[df.columns[1:]] # удаляю первый столбец (в нем нет данных)
.set_axis(df1.columns, axis=1)
)
dfs.append(df2)
col_list = list(pd.Series(df1.columns).replace('\n', '', regex=True)) # удаляем спец символы
col_list_strip = [col.strip() for col in col_list] # удаляем лишние пробелы
df_con = (
pd.concat(dfs, ignore_index=True) # объединяем датафремы
.set_axis(col_list_strip, axis=1) # переименовываем столбцы
.dropna(axis=1, how='all').dropna(axis=0, how='all') # удаляем пустые столбцы/строки
)
# фильтруем полученный датафрейм
df_con_filtered = (
df_con[df_con['Сокращенное наименование'].str.contains(match)][columns_lst]
)
Но по прежнему остается вопрос: как в данном случае применить цепочку методов?
Особенно это касается операции объединения датафреймов и фильтрации полученного датафрейма.