Вложенный словарь в мультииндексный фрейм данных, где ключи словаря являются метками столбцов

Скажем, у меня есть словарь, который выглядит так:

dictionary = {'A' : {'a': [1,2,3,4,5],
                     'b': [6,7,8,9,1]},

              'B' : {'a': [2,3,4,5,6],
                     'b': [7,8,9,1,2]}}

и я хочу, чтобы кадр данных выглядел примерно так:

     A   B
     a b a b
  0  1 6 2 7
  1  2 7 3 8
  2  3 8 4 9
  3  4 9 5 1
  4  5 1 6 2

Есть ли удобный способ сделать это? Если я попытаюсь:

In [99]:

DataFrame(dictionary)

Out[99]:
     A               B
a   [1, 2, 3, 4, 5] [2, 3, 4, 5, 6]
b   [6, 7, 8, 9, 1] [7, 8, 9, 1, 2]

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


person pbreach    schedule 28.07.2014    source источник


Ответы (5)


Pandas хочет, чтобы значения MultiIndex были кортежами, а не вложенными словарями. Самое простое — преобразовать ваш словарь в нужный формат, прежде чем пытаться передать его в DataFrame:

>>> reform = {(outerKey, innerKey): values for outerKey, innerDict in dictionary.iteritems() for innerKey, values in innerDict.iteritems()}
>>> reform
{('A', 'a'): [1, 2, 3, 4, 5],
 ('A', 'b'): [6, 7, 8, 9, 1],
 ('B', 'a'): [2, 3, 4, 5, 6],
 ('B', 'b'): [7, 8, 9, 1, 2]}
>>> pandas.DataFrame(reform)
   A     B   
   a  b  a  b
0  1  6  2  7
1  2  7  3  8
2  3  8  4  9
3  4  9  5  1
4  5  1  6  2

[5 rows x 4 columns]
person BrenBarn    schedule 28.07.2014
comment
Немного сложно обернуть голову, но это именно то, на что я надеялся. - person pbreach; 28.07.2014
comment
+1, но это не работает, если длина списков значений словаря, например. 5 в этом конкретном примере не совпадают. Любые идеи, как я могу справиться с этим? Например, если reform= ('A', 'a'): [1, 2, 3, 4, 5], ('A', 'b'): [6, 7, 8, 9,] - person Zhubarb; 22.10.2015
comment
@Zhubarb: Как вы ожидаете, как будет выглядеть полученный DataFrame? DataFrame должен быть прямоугольным; он не может иметь столбцы разной длины. - person BrenBarn; 22.10.2015
comment
Вы правы, я изначально думал взять максимальную длину списка в качестве номера строки и дополнить списки, которые не подошли, но это всего лишь грубый обходной путь. Не имеет большого смысла - person Zhubarb; 22.10.2015
comment
@BrenBarn При выполнении (pandas.DataFrame(reform)).to_csv(path_or_buf='testpath.csv',encoding='utf-8',index=False) я получаю пробел между строками между верхним столбцом (a b a b) и первой строкой данных (1 6 2 7). Как я могу удалить это/почему это? - person Dhruv Ghulati; 17.08.2016
comment
@DhruvGhulati: я не уверен, что вы описываете, но похоже, что вам следует задать отдельный вопрос. - person BrenBarn; 17.08.2016
comment
Столбцы упорядочены в фрейме данных случайным образом, поскольку dict неупорядочен. Как можно обеспечить соблюдение желаемого порядка, если не использовать OrderedDict? - person Dave Kielpinski; 12.11.2016
comment
@DaveKielpinski: Вы не можете обеспечить соблюдение порядка в dict. Просто получите столбцы в нужном вам порядке после создания DataFrame, например, DataFrame(...)[["A", "B"]]. - person BrenBarn; 12.11.2016
comment
@BrenBarn Это становится очень неприятно, когда на каждом уровне есть несколько ярлыков. - person Dave Kielpinski; 12.11.2016
comment
@DaveKielpinski Вы не можете обеспечить соблюдение порядка в dict. Если вы хотите обеспечить соблюдение порядка, не используйте dict. Вместо этого поместите данные в формат списка. Если вы просто хотите сортировать столбцы, вы можете использовать .sort(axis=1), но если вам нужен определенный порядок, вам придется сделать что-то еще. В любом случае, если у вас есть вопрос по этому поводу, вы должны задать его как отдельный вопрос. - person BrenBarn; 12.11.2016
comment
@BrenBarn В исходном вопросе метки столбцов фрейма данных расположены в том же порядке, что и представленные данные, поэтому я думаю, что это актуально. Однако ОП принял ваш ответ, поэтому вы, очевидно, обратились к намерению ОП. - person Dave Kielpinski; 14.11.2016
comment
вышеприведенное решение работает только для python 3.5 и выше, если .iteritems() заменено на .items() - person tsando; 26.07.2017
comment
Это замечательно. К вашему сведению, это также можно сделать с помощью pd.DataFrame.from_dict, если values находится в форме записей: [{'a': 1, 'b': 2}, {'a': 3, 'b': 4}, ...] - person EliadL; 03.02.2019
comment
@BrenBarn к первому комментарию @Zhubarb -› Что делать, если у нас есть список словарей разной длины. Нам нужно set всех ключей в виде столбцов, а остальные заполнить нулями. Он остается прямоугольным, хотя и с некоторым вменением. РЕДАКТИРОВАТЬ: Говорил слишком рано, ответ @Dmitri касается этого. Спасибо. - person technazi; 12.04.2021

dict_of_df = {k: pd.DataFrame(v) for k,v in dictionary.items()}
df = pd.concat(dict_of_df, axis=1)

Обратите внимание, что порядок столбцов теряется для python ‹ 3.6

person user8227892    schedule 28.06.2017
comment
У меня другая проблема с использованием этого метода. из yahoofinancials импортировать тикеры YahooFinancials = ['AAPL', 'WFC', 'F', 'FB', 'DELL', 'SNE'] yahoo_financials = YahooFinancials(тикеры) BB=yahoo_financials.get_key_statistics_data() dict_of_df = {k: pd .DataFrame(v) для k,v в BB.items()} df = pd.concat(dict_of_df, axis=1) ValueError: при использовании всех скалярных значений необходимо передать индекс - person rsc05; 09.11.2019

Этот ответ немного запоздал с игрой, но...

Вы ищете функциональность в .stack:

df = pandas.DataFrame.from_dict(dictionary, orient="index").stack().to_frame()
# to break out the lists into columns
df = pd.DataFrame(df[0].values.tolist(), index=df.index)
person Vira    schedule 07.04.2020
comment
Спасибо! Этот ответ не требует переформатирования вложенных диктов как {(key0, key1): [data0, data_n...]} и не приводит к сбою в pandas v1.x. - person ralex; 22.07.2020
comment
Это замечательно. Работает и с данными, в которых отсутствуют ключи. - person Connor Ferster; 20.10.2020

Эта рекурсивная функция должна работать:

def reform_dict(dictionary, t=tuple(), reform={}):
    for key, val in dictionary.items():
        t = t + (key,)
        if isinstance(val, dict):
            reform_dict(val, t, reform)
        else:
            reform.update({t: val})
        t = t[:-1]
    return reform
person madsentail    schedule 21.08.2020

Если списки в словаре разной длины, можно адаптировать метод БренБарн.

>>> dictionary = {'A' : {'a': [1,2,3,4,5],
                         'b': [6,7,8,9,1]},
                 'B' : {'a': [2,3,4,5,6],
                        'b': [7,8,9,1]}}

>>> reform = {(outerKey, innerKey): values for outerKey, innerDict in dictionary.items() for innerKey, values in innerDict.items()}
>>> reform
 {('A', 'a'): [1, 2, 3, 4, 5],
  ('A', 'b'): [6, 7, 8, 9, 1],
  ('B', 'a'): [2, 3, 4, 5, 6],
  ('B', 'b'): [7, 8, 9, 1]}

>>> pandas.DataFrame.from_dict(reform, orient='index').transpose()
>>> df.columns = pd.MultiIndex.from_tuples(df.columns)
   A     B   
   a  b  a  b
0  1  6  2  7
1  2  7  3  8
2  3  8  4  9
3  4  9  5  1
4  5  1  6  NaN
[5 rows x 4 columns]
person Dimitri    schedule 26.02.2021