Построить панды DataFrame из элементов во вложенном словаре
Предположим, у меня есть вложенный словарь 'user_dict' со структурой:
Уровень 1: UserId (длинное целое)
Уровень 2: Категория (Строка)
Уровень 3: различные атрибуты (плавающие, целые и т. Д.)
Например, запись этого словаря будет:
user_dict[12] = {
"Category 1": {"att_1": 1,
"att_2": "whatever"},
"Category 2": {"att_1": 23,
"att_2": "another"}}
каждый элемент в "user_dict" имеет одинаковую структуру, а "user_dict" содержит большое количество элементов, которые я хочу передать в pandas DataFrame, создавая серию из атрибутов. В этом случае иерархический индекс будет полезен для этой цели.
В частности, мой вопрос заключается в том, существует ли способ помочь конструктору DataFrame понять, что ряд должен быть построен из значений "уровня 3" в словаре?
Если я попробую что-то вроде:
df = pandas.DataFrame(users_summary)
Элементы в "уровне 1" (идентификаторы пользователя) воспринимаются как столбцы, что противоположно тому, чего я хочу достичь (иметь идентификаторы пользователя в качестве индекса).
Я знаю, что мог бы построить серию после итерации по словарным записям, но если есть более прямой путь, это было бы очень полезно. Аналогичным вопросом будет вопрос о том, возможно ли построить DataFrame pandas из объектов json, перечисленных в файле.
7 ответов
MultiIndex панды состоит из списка кортежей. Таким образом, наиболее естественным подходом было бы изменить форму вашего ввода, чтобы его ключи были кортежами, соответствующими нужным вам многоиндексным значениям. Тогда вы можете просто построить свой фрейм данных, используя pd.DataFrame.from_dict
, используя опцию orient='index'
:
user_dict = {12: {'Category 1': {'att_1': 1, 'att_2': 'whatever'},
'Category 2': {'att_1': 23, 'att_2': 'another'}},
15: {'Category 1': {'att_1': 10, 'att_2': 'foo'},
'Category 2': {'att_1': 30, 'att_2': 'bar'}}}
pd.DataFrame.from_dict({(i,j): user_dict[i][j]
for i in user_dict.keys()
for j in user_dict[i].keys()},
orient='index')
att_1 att_2
12 Category 1 1 whatever
Category 2 23 another
15 Category 1 10 foo
Category 2 30 bar
Альтернативный подход состоит в том, чтобы создать ваш фрейм данных путем объединения компонентных фреймов данных:
user_ids = []
frames = []
for user_id, d in user_dict.iteritems():
user_ids.append(user_id)
frames.append(pd.DataFrame.from_dict(d, orient='index'))
pd.concat(frames, keys=user_ids)
att_1 att_2
12 Category 1 1 whatever
Category 2 23 another
15 Category 1 10 foo
Category 2 30 bar
pd.concat
принимает словарь. Имея это в виду, можно улучшить принятый в настоящее время ответ с точки зрения простоты и производительности, используя понимание словаря для построения словаря, отображающего ключи в подкадры.
pd.concat({k: pd.DataFrame(v).T for k, v in user_dict.items()}, axis=0)
Или же,
pd.concat({
k: pd.DataFrame.from_dict(v, 'index') for k, v in user_dict.items()
},
axis=0)
att_1 att_2
12 Category 1 1 whatever
Category 2 23 another
15 Category 1 10 foo
Category 2 30 bar
Это решение должно работать для произвольной глубины за счет сглаживания ключей словаря в цепочку кортежей.
def flatten_dict(nested_dict):
res = {}
if isinstance(nested_dict, dict):
for k in nested_dict:
flattened_dict = flatten_dict(nested_dict[k])
for key, val in flattened_dict.items():
key = list(key)
key.insert(0, k)
res[tuple(key)] = val
else:
res[()] = nested_dict
return res
def nested_dict_to_df(values_dict):
flat_dict = flatten_dict(values_dict)
df = pd.DataFrame.from_dict(flat_dict, orient="index")
df.index = pd.MultiIndex.from_tuples(df.index)
df = df.unstack(level=-1)
df.columns = df.columns.map("{0[1]}".format)
return df
Если кто-то хочет получить фрейм данных в "длинном формате" (конечные значения имеют один и тот же тип) без мультииндекса, вы можете сделать это:
pd.DataFrame.from_records(
[
(level1, level2, level3, leaf)
for level1, level2_dict in user_dict.items()
for level2, level3_dict in level2_dict.items()
for level3, leaf in level3_dict.items()
],
columns=['UserId', 'Category', 'Attribute', 'value']
)
UserId Category Attribute value
0 12 Category 1 att_1 1
1 12 Category 1 att_2 whatever
2 12 Category 2 att_1 23
3 12 Category 2 att_2 another
4 15 Category 1 att_1 10
5 15 Category 1 att_2 foo
6 15 Category 2 att_1 30
7 15 Category 2 att_2 bar
(Я знаю, что исходный вопрос, вероятно, хочет, чтобы (I.) уровни 1 и 2 были мультииндексными, а уровень 3 - столбцами, а (II.) Спрашивает о других способах, кроме итерации значений в dict. Но я надеюсь, что этот ответ по-прежнему актуален и полезно (I.): людям вроде меня, которые пытались найти способ получить вложенный dict в эту форму, и Google возвращает только этот вопрос и (II.): потому что другие ответы также включают некоторую итерацию, и я нахожу это подход гибкий и легкий для чтения; однако не уверен в производительности.)
Поэтому я использовал цикл for для итераций по словарю, но одна вещь, которую я обнаружил, которая работает намного быстрее, - это преобразование в панель, а затем в фрейм данных. Скажем, у вас есть словарь д
import pandas as pd
d
{'RAY Index': {datetime.date(2014, 11, 3): {'PX_LAST': 1199.46,
'PX_OPEN': 1200.14},
datetime.date(2014, 11, 4): {'PX_LAST': 1195.323, 'PX_OPEN': 1197.69},
datetime.date(2014, 11, 5): {'PX_LAST': 1200.936, 'PX_OPEN': 1195.32},
datetime.date(2014, 11, 6): {'PX_LAST': 1206.061, 'PX_OPEN': 1200.62}},
'SPX Index': {datetime.date(2014, 11, 3): {'PX_LAST': 2017.81,
'PX_OPEN': 2018.21},
datetime.date(2014, 11, 4): {'PX_LAST': 2012.1, 'PX_OPEN': 2015.81},
datetime.date(2014, 11, 5): {'PX_LAST': 2023.57, 'PX_OPEN': 2015.29},
datetime.date(2014, 11, 6): {'PX_LAST': 2031.21, 'PX_OPEN': 2023.33}}}
Команда
pd.Panel(d)
<class 'pandas.core.panel.Panel'>
Dimensions: 2 (items) x 2 (major_axis) x 4 (minor_axis)
Items axis: RAY Index to SPX Index
Major_axis axis: PX_LAST to PX_OPEN
Minor_axis axis: 2014-11-03 to 2014-11-06
где pd.Panel(d)[item] возвращает фрейм данных
pd.Panel(d)['SPX Index']
2014-11-03 2014-11-04 2014-11-05 2014-11-06
PX_LAST 2017.81 2012.10 2023.57 2031.21
PX_OPEN 2018.21 2015.81 2015.29 2023.33
Затем вы можете нажать команду to_frame(), чтобы превратить ее в кадр данных. Я также использую reset_index, чтобы превратить основную и вспомогательную оси в столбцы, а не использовать их в качестве индексов.
pd.Panel(d).to_frame().reset_index()
major minor RAY Index SPX Index
PX_LAST 2014-11-03 1199.460 2017.81
PX_LAST 2014-11-04 1195.323 2012.10
PX_LAST 2014-11-05 1200.936 2023.57
PX_LAST 2014-11-06 1206.061 2031.21
PX_OPEN 2014-11-03 1200.140 2018.21
PX_OPEN 2014-11-04 1197.690 2015.81
PX_OPEN 2014-11-05 1195.320 2015.29
PX_OPEN 2014-11-06 1200.620 2023.33
Наконец, если вам не нравится, как выглядит фрейм, вы можете использовать функцию транспонирования панели, чтобы изменить внешний вид перед вызовом to_frame(), см. Документацию здесь http://pandas.pydata.org/pandas-docs/dev/generated/pandas.Panel.transpose.html
Просто в качестве примера
pd.Panel(d).transpose(2,0,1).to_frame().reset_index()
major minor 2014-11-03 2014-11-04 2014-11-05 2014-11-06
RAY Index PX_LAST 1199.46 1195.323 1200.936 1206.061
RAY Index PX_OPEN 1200.14 1197.690 1195.320 1200.620
SPX Index PX_LAST 2017.81 2012.100 2023.570 2031.210
SPX Index PX_OPEN 2018.21 2015.810 2015.290 2023.330
Надеюсь это поможет.
Для других способов представления данных вам не нужно много делать. Например, если вы просто хотите, чтобы «внешний» ключ был индексом, «внутренний» ключ был столбцами, а значения - значениями ячеек, это поможет:
df = pd.DataFrame.from_dict(user_dict, orient='index')
Основываясь на проверенном ответе, для меня это сработало лучше всего:
ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0)
ab.T