Pandas groupby для вложенного json

Я часто использую pandas groupby для создания стоп-таблиц. Но тогда я часто хочу вывести полученные вложенные отношения в json. Есть ли способ извлечь вложенный json файл из таблицы, который он создает?

Скажем, у меня есть df:

year office candidate  amount
2010 mayor  joe smith  100.00
2010 mayor  jay gould   12.00
2010 govnr  pati mara  500.00
2010 govnr  jess rapp   50.00
2010 govnr  jess rapp   30.00

Я могу сделать:

grouped = df.groupby('year', 'office', 'candidate').sum()

print grouped
                       amount
year office candidate 
2010 mayor  joe smith   100
            jay gould    12
     govnr  pati mara   500
            jess rapp    80

Красивая! Конечно, мне бы хотелось, чтобы я ввел json через команду по строкам grouped.to_json. Но эта функция недоступна. Любые обходные пути?

Итак, я действительно хочу что-то вроде:

{"2010": {"mayor": [
                    {"joe smith": 100},
                    {"jay gould": 12}
                   ]
         }, 
          {"govnr": [
                     {"pati mara":500}, 
                     {"jess rapp": 80}
                    ]
          }
}

Дон

Ответ 1

Я не думаю, что есть что-то встроенное в pandas для создания вложенного словаря данных. Ниже приведен код, который должен работать вообще для серии с MultiIndex, используя defaultdict

Код вставки повторяется через каждый уровень MultIndex, добавляя слои в словарь до тех пор, пока самый глубокий уровень не будет присвоен значению Series.

In  [99]: from collections import defaultdict

In [100]: results = defaultdict(lambda: defaultdict(dict))

In [101]: for index, value in grouped.itertuples():
     ...:     for i, key in enumerate(index):
     ...:         if i == 0:
     ...:             nested = results[key]
     ...:         elif i == len(index) - 1:
     ...:             nested[key] = value
     ...:         else:
     ...:             nested = nested[key]

In [102]: results
Out[102]: defaultdict(<function <lambda> at 0x7ff17c76d1b8>, {2010: defaultdict(<type 'dict'>, {'govnr': {'pati mara': 500.0, 'jess rapp': 80.0}, 'mayor': {'joe smith': 100.0, 'jay gould': 12.0}})})

In [106]: print json.dumps(results, indent=4)
{
    "2010": {
        "govnr": {
            "pati mara": 500.0, 
            "jess rapp": 80.0
        }, 
        "mayor": {
            "joe smith": 100.0, 
            "jay gould": 12.0
        }
    }
}

Ответ 2

Я посмотрел на решение выше и понял, что оно работает только для 3 уровней вложенности. Это решение подойдет для любого количества уровней.

import json
levels = len(grouped.index.levels)
dicts = [{} for i in range(levels)]
last_index = None

for index,value in grouped.itertuples():

    if not last_index:
        last_index = index

    for (ii,(i,j)) in enumerate(zip(index, last_index)):
        if not i == j:
            ii = levels - ii -1
            dicts[:ii] =  [{} for _ in dicts[:ii]]
            break

    for i, key in enumerate(reversed(index)):
        dicts[i][key] = value
        value = dicts[i]

    last_index = index


result = json.dumps(dicts[-1])

Ответ 3

Я знаю, что это старый вопрос, но недавно я столкнулся с той же проблемой. Вот мое решение. Я заимствовал много вещей из примера chrisb (Спасибо!).

Это имеет то преимущество, что вы можете передать лямбду, чтобы получить окончательное значение из любого нужного вам числа, а также для каждой группы.

from collections import defaultdict

def dict_from_enumerable(enumerable, final_value, *groups):
    d = defaultdict(lambda: defaultdict(dict))
    group_count = len(groups)
    for item in enumerable:
        nested = d
        item_result = final_value(item) if callable(final_value) else item.get(final_value)
        for i, group in enumerate(groups, start=1):
            group_val = str(group(item) if callable(group) else item.get(group))
            if i == group_count:
                nested[group_val] = item_result
            else:
                nested = nested[group_val]
    return d

В этом вопросе вы бы назвали эту функцию следующим:

dict_from_enumerable(grouped.itertuples(), 'amount', 'year', 'office', 'candidate')

Первым аргументом может быть также массив данных, даже не требующий pandas.