Создание фрейма данных pandas из элементов вложенного словаря

90

Предположим, у меня есть вложенный словарь 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 понять, что ряды должны быть построены из значений «уровня 3» в словаре?

Если я попробую что-то вроде:

df = pandas.DataFrame(users_summary)

Элементы на «уровне 1» (UserId) принимаются как столбцы, что противоположно тому, чего я хочу достичь (иметь UserId в качестве индекса).

Я знаю, что могу построить серию после перебора словарных статей, но если есть более прямой способ, это было бы очень полезно. Аналогичный вопрос будет спрашивать, можно ли построить pandas DataFrame из объектов json, перечисленных в файле.

владимир монтеалегре
источник
См. Этот ответ для более простых альтернатив.
cs95

Ответы:

138

Pandas 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
Воутер Овермайр
источник
11
Есть ли разумный способ обобщить это для работы с рваными списками произвольной глубины? например, списки на произвольную глубину, где некоторые ветви могут быть короче других, а None или nan используются, когда более короткие ветви не достигают конца?
naught101
5
Вы смотрели на поддержку pandas json (инструменты io) и нормализацию? pandas.pydata.org/pandas-docs/dev/io.html#normalization
Воутер Овермайр
1
для меня первый метод создал фрейм данных с одним индексом с кортежами. второй способ сработал как хотелось / ожидалось!
arturomp
Есть какие-нибудь советы о том, как назвать эти новые столбцы? Например, если я хочу, чтобы эти числа 12 и 15 были в столбце «id».
Черёмушкин
1
@cheremushkin 12 и 15 теперь находятся в строке 'id', если вы переместите ( pandas.pydata.org/pandas-docs/stable/reference/api/… ), они будут в столбце 'id'. Вы также можете распаковать ( pandas.pydata.org/pandas-docs/stable/reference/api/… ). Все зависит от того, что вам действительно нужно.
Wouter Overmeire 03
31

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
cs95
источник
4
Гениально! Намного лучше :)
pg2455
3
Как бы вы это сделали, если бы у вас была еще одна внутренняя категория? Такие как 12:{cat1:{cat11:{att1:val1,att2:val2}}}. Другими словами: как можно обобщить решение на несущественное количество категорий?
Лукас Аймаретто
1
@LucasAimaretto Обычно произвольно вложенные структуры можно сгладить с помощью json_normalize. У меня есть другой ответ, который показывает, как это работает.
cs95
1
Не работает, например, если vэто одно целое число. Вы знаете альтернативу в таком случае?
sk
11

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

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

Надеюсь это поможет.

Мишико
источник
8
Panel устарела в более поздних версиях pandas (v0.23 на момент написания).
cs95
6

Если кто-то хочет получить фрейм данных в «длинном формате» (конечные значения имеют один и тот же тип) без мультииндекса, вы можете сделать это:

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.): потому что другие ответы также включают некоторую итерацию, и я нахожу это подход гибкий и легкий для чтения; однако не уверен в производительности.)

Melkor.cz
источник
0

Основываясь на проверенном ответе, для меня это сработало лучше всего:

ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0)
ab.T
El_1988
источник