#python #pandas #list #dataframe #dictionary
Вопрос:
Я работаю с данными из проекта Veris через VerisPy и испытываю некоторые проблемы с их переформатированием для использования в других приложениях.
VerisPy выводит фрейм данных, содержащий проанализированную информацию о событиях из файлов JSON, отправленных в Veris. Каждая строка в кадре данных соответствует событию с подробной информацией о том, как/когда оно произошло. Проблема в том, что в итоге получаются тысячи столбцов с логическими флагами.
Таким образом, в каждой строке события могут быть такие столбцы, как:
идентификатор события | действие.вредоносное ПО | Экшен.взлом | действие.злоупотребление | актер.внешний.конкурент | актер.внешнее.преступление | актер.внутренний.сотрудник | actor.internal.customer | target |
---|---|---|---|---|---|---|---|---|
1 | Правда | Ложный | Ложный | Ложный | Ложный | Правда | False | Microsoft |
2 | Ложный | Правда | Ложный | Правда | Ложный | Ложный | False | Bank of America |
Я бы хотел свернуть/объединить их и использовать значения столбцов в качестве фактических данных. В приведенном выше примере это может привести к тому, что мы будем:
идентификатор события | Экшен | Актер | цель |
---|---|---|---|
1 | Вредоносная программа | Внутренний — Сотрудник | Майкрософт |
2 | Взлом | Внешний конкурент | Банк Америки |
Я новичок в Pandas, но перепробовал довольно много вещей из документации со смешанными результатами. Melt казался многообещающим, но я не уверен, как включить имена столбцов в фактические данные на уровне строк. У кого-нибудь есть указатели?
Ответ №1:
в одну сторону:
- Установите
['event_id', 'target']
какindex
. split
иexpand columns
для созданияhierarchical columns
(нам это понадобится в продукте dot).extract
level 1
значения изhierarchical columns
и делайdot product
сdataframe
помощью .- Сделайте некоторые
string manipulations
иrename
columns
(я добавил 2 способа переименования столбцов), чтобы получить желаемоеoutput
.
df1 = df.set_index(['event_id', 'target'])
df1.columns = df1.columns.str.split('.', expand=True, n=1)
df = (
df1.dot(df1.columns.get_level_values(1) ',')
.str.strip(',')
.str.split(',', expand=True)
.rename(columns={0: 'Action', 1: 'Actor'})
.reset_index()
)
выход:
event_id target Action Actor
0 1 Microsoft malware internal.employee
1 2 Bank of America hacking external.competitor
записка:
Вместо renaming columns
via rename
вы также можете использовать level=0
значения столбцов, подобные этому —
df1 = df.set_index(['event_id', 'target'])
df1.columns = df1.columns.str.split('.', expand=True, n=1)
df = (
df1.dot(df1.columns.get_level_values(1) ',')
.str.strip(',')
.str.split(',',expand=True)
)
df.columns = dict.fromkeys(df1.columns.get_level_values(0)).keys()
df = df.reset_index()
Еще одна альтернатива-использовать mul
:
df1 = df.set_index(['event_id', 'target'])
df1.columns = df1.columns.str.split('.', expand=True, n=1)
df = (
df1.mul(df1.columns.get_level_values(1))
.replace('', np.NAN)
.droplevel(1, axis =1)
.stack()
.unstack()
)
записка:
В случае, если у вас есть несколько значений для актора и агента для 1 цели, вы можете использовать pivot_table
вместо stack
/ unstack
:
df1 = df.set_index(['event_id', 'target'])
df1.columns = df1.columns.str.split('.', expand=True, n=1)
(
df1.mul(df1.columns.get_level_values(1))
.replace('', np.NAN)
.droplevel(1, axis =1)
.stack()
.reset_index()
.pivot_table(index = ['event_id', 'target'], columns = 'level_2' , values = 0, aggfunc = ', '.join)
)
Комментарии:
1. Спасибо, это чрезвычайно полезно для новичка. Оказывается, у меня действительно были ситуации с несколькими значениями, и я работаю над методом сводной таблицы, но первый вариант помог мне понять, что происходит.
Ответ №2:
Вы можете переключить код с широкого на длинный, отфильтровать только те строки, где True
есть, внести некоторые изменения, чтобы получить результат.
# flip from wide to long
(pd.wide_to_long(df,
stubnames=['action','actor'],
i=['event_id','target'],
j='subs',
sep='.',
suffix='. ')
.loc[lambda df: df.any(1)] # keeps only rows that have `True`
# if any row has `True`,
# replace it with values from `subs` index
.where(lambda df: df.isna(),
lambda df: df.index.get_level_values('subs'))
.fillna('')
.droplevel('subs') # served its purpose, let it go
# use the groupby with the agg to reduce to single rows
.groupby(['event_id', 'target'])
.agg("".join)
.reset_index()
)
:
event_id target action actor
0 1 Microsoft malware internal.employee
1 2 Bank of America hacking external.competitor