Побудуйте pandas DataFrame з елементів у вкладеному словнику


90

Припустимо, у мене є вкладений словник 'user_dict' зі структурою:

  • Рівень 1: UserId (Long Integer)
  • Рівень 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" (ідентифікатори користувача) приймаються як стовпці, що є протилежністю до того, що я хочу досягти (мати ідентифікатори користувача як індекс).

Я знаю, що міг би побудувати серію після перегляду словникових статей, але якщо існує більш прямий спосіб, це було б дуже корисно. Подібним питанням буде запитання, чи можна побудувати pandas DataFrame з об'єктів json, перелічених у файлі.


Дивіться цю відповідь для більш простих альтернатив.
cs95

Відповіді:


138

Панда 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 tools)? pandas.pydata.org/pandas-docs/dev/io.html#normalization
Wouter Overmeire

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

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

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
Панель застаріла в останніх версіях панд (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.) Запитує про інші шляхи, крім ітерації значень у дикті. Але я сподіваюся, що ця відповідь все ще актуальна і корисно (I.): таким людям, як я, які намагались знайти спосіб вкласти вкладений дикт у цю фігуру, а google лише повертає це запитання та (II.): оскільки інші відповіді також включають певну ітерацію, і я вважаю це підхід гнучкий і легкий для читання; однак не впевнений у продуктивності.)


0

Спираючись на перевірену відповідь, для мене це працювало найкраще:

ab = pd.concat({k: pd.DataFrame(v).T for k, v in data.items()}, axis=0)
ab.T
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.