Как перенести мультииндексированный DataFrame во вложенную структуру словаря?

Как вы можете создать фрейм данных с мультииндексом и превратить его в хороший вложенный словарь?

Вот что я пробовал до сих пор, и это близко, однако ключи - это кортежи. Глядя на то, чтобы разбить их на большее количество словарных ключей.

Что я пробовал:

that = {'Food':['Apple','Apple','Apple','Apple','Banana','Banana','Orange','Orange'],
    'Color':['Red','Green','Yellow','Red','Red','Green','Green','Yellow'],
    'Type':['100','4','7','101','100','100','4','7'],
    'time':[np.linspace(0,10,2) for i in range(8)]}

nn = pd.DataFrame(that)
nn = nn.set_index(['Food','Color','Type'])
vv = {}
for idx in nn.index:
   vv[idx] = nn.loc[idx]

vv
Out[1]: 
{('Apple', 'Red', '100'): time    [0.0, 10.0]
 Name: (Apple, Red, 100), dtype: object,
 ('Apple', 'Green', '4'): time    [0.0, 10.0]
 Name: (Apple, Green, 4), dtype: object,
 ('Apple', 'Yellow', '7'): time    [0.0, 10.0]
 Name: (Apple, Yellow, 7), dtype: object,
 ('Apple', 'Red', '101'): time    [0.0, 10.0]
 Name: (Apple, Red, 101), dtype: object,
 ('Banana', 'Red', '100'): time    [0.0, 10.0]
 Name: (Banana, Red, 100), dtype: object,
 ('Banana', 'Green', '100'): time    [0.0, 10.0]
 Name: (Banana, Green, 100), dtype: object,
 ('Orange', 'Green', '4'): time    [0.0, 10.0]
 Name: (Orange, Green, 4), dtype: object,
 ('Orange', 'Yellow', '7'): time    [0.0, 10.0]
 Name: (Orange, Yellow, 7), dtype: object}

Как я хочу, чтобы результат выглядел.

vv = {'Apple':{'Red':{'100':[0,10],'101':[0,10]},
               'Green':{'4':[0,10]},
               'Yellow':{'7':[0,10]}},
      'Banana':{'Red':{'100':[0,10]},
                'Green':{'100':[0,10]}}
      'Orange':{'Green':{'4':[0,10]},
                'Yellow':{'7':[0,10]}}}

Изменить: диапазон снова изменен на 8... это опечатка, и изменено количество точек в linspace на 2 точки для простоты, чтобы отразить пример.

Редактировать 2: Поиск общего способа сделать это. В частности, мой коллега написал модель treeView на pyqt, которая принимает вложенный словарь для дерева. Я просто хочу иметь возможность быстро преобразовать созданные мной кадры данных в нужный формат.

Для тех, кому интересно, как это сделать в целом, вот вам. Хорошая маленькая функция, которую я написал. Работает больше на то, что мне нужно.

that = {'Food':['Apple','Apple','Apple','Apple','Banana','Banana','Orange','Orange'],
        'Color':['Red','Green','Yellow','Red','Red','Green','Green','Yellow'],
        'Type':['100','4','7','101','100','100','4','7'],
        'time':[np.linspace(0,10,2) for i in range(8)]}

x = pd.DataFrame(that)

def NestedDict_fromDF(iDF,keyorder,values):
    if not isinstance(keyorder,list):
        keyorder = [keyorder]
    if not isinstance(values,list):
        values = [values]
    for i in reversed(range(len(keyorder))):
        if keyorder[i] not in iDF:
            keyorder.pop(i)
    for i in reversed(range(len(values))):
        if values[i] not in iDF:
            values.pop(i)
    rdict = {}
    if keyorder:
        ndf = iDF.set_index(keyorder)
        def makeDict(basedict,group):
            for k,g in group:
                basedict[k] = {}
                try:
                    makeDict(basedict[k], g.droplevel(0).groupby(level=0))
                except:
                    if values:
                        basedict[k] = g[values].reset_index(drop=True)
                    else:
                        basedict[k] = []
            return basedict
        rdict = makeDict({}, ndf.groupby(level=0))
    return rdict

yy = NestedDict_fromDF(x,['Food','Color','Type','Integer'],['time'])


{'Apple': {'Green': {'4':DataFrame},
           'Red': {'100':DataFrame,
                   '101':DataFrame},
           'Yellow': {'7':DataFrame}},
 'Banana': {'Green': {'100':DataFrame},
            'Red': {'100':DataFrame}},
 'Orange': {'Green': {'4':DataFrame},
            'Yellow': {'7':DataFrame}}}

person Carl    schedule 08.02.2020    source источник
comment
кадр данных nn не воспроизводится, он выдает ошибку ValueError: arrays must all be same length   -  person anky    schedule 08.02.2020
comment
Измените range(2) на range(8), а для примера лучше заменить linspace на [0.0, 0.10].   -  person Cyttorak    schedule 08.02.2020
comment
Исправлено. Извините, это была опечатка   -  person Carl    schedule 08.02.2020


Ответы (1)


Он стал слишком сложным, слишком быстрым:

from pprint import pprint
import pandas as pd

that = {'Food':['Apple','Apple','Apple','Apple','Banana','Banana','Orange','Orange'],
    'Color':['Red','Green','Yellow','Red','Red','Green','Green','Yellow'],
    'Type':['100','4','7','101','100','100','4','7'],
    'time':[np.linspace(0,10,2) for i in range(8)]}

nn = pd.DataFrame(that)
df = nn.groupby(['Food', 'Color', 'Type']).agg(list)
d = {}
new_df = df.groupby(level=[0,1]).apply(lambda df:df.xs(df.name).to_dict()).to_dict() #[1]
for (food, color), v in new_df.items():
    if not food in d:
        d[food] = {color: {Type: time[0].tolist() for Type, time in v['time'].items()}}
    else:
        d[food][color] = {Type: time[0].tolist() for Type, time in v['time'].items()}
pprint(d)

Выход:

{'Apple': {'Green': {'4': [0.0, 10.0]},
           'Red': {'100': [0.0, 10.0], '101': [0.0, 10.0]},
           'Yellow': {'7': [0.0, 10.0]}},
 'Banana': {'Green': {'100': [0.0, 10.0]}, 'Red': {'100': [0.0, 10.0]}},
 'Orange': {'Green': {'4': [0.0, 10.0]}, 'Yellow': {'7': [0.0, 10.0]}}}

[1] взято из: DataFrame с MultiIndex для ввода

Хм! понял наконец!

that = {'Food':['Apple','Apple','Apple','Apple','Banana','Banana','Orange','Orange'],
    'Color':['Red','Green','Yellow','Red','Red','Green','Green','Yellow'],
    'Type':['100','4','7','101','100','100','4','7'],
    'time':[np.linspace(0,10,2) for i in range(8)]}

nn = pd.DataFrame(that)
nn = nn.set_index(['Food','Color','Type'])
group = nn.groupby(level=0)
d = {k: g.droplevel(0).groupby(level=0)
       .apply(lambda df:df.xs(df.name)['time']
       .apply(lambda x:x.tolist()).to_dict())
       .to_dict() for k,g in group}
pprint(d)

{'Apple': {'Green': {'4': [0.0, 10.0]},
           'Red': {'100': [0.0, 10.0], '101': [0.0, 10.0]},
           'Yellow': {'7': [0.0, 10.0]}},
 'Banana': {'Green': {'100': [0.0, 10.0]}, 'Red': {'100': [0.0, 10.0]}},
 'Orange': {'Green': {'4': [0.0, 10.0]}, 'Yellow': {'7': [0.0, 10.0]}}}
person Cyttorak    schedule 08.02.2020
comment
Я видел это решение в вашем [1]. Мне нравится ваш ответ, но на самом деле я ищу общий способ сделать это. Этот пример был просто упрощенной версией того, что мне нужно. - person Carl; 08.02.2020
comment
@Карл, кажется, я понял, пожалуйста, посмотри, сработает ли это для тебя. - person Cyttorak; 08.02.2020
comment
@Syandip Dutta Сделал функцию, которая будет делать это вообще, в последнем редактировании, на всякий случай, если вам интересно. - person Carl; 08.02.2020