Построить панды 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
Другие вопросы по тегам