2013-08-05 13 views
12

Próbuję napisać funkcję w elegancki sposób, który zgrupuje listę słowników i zagreguje (sumuje) wartości podobnych kluczy.Grupuj według i agreguj wartości listy słowników w Pythonie

Przykład:

my_dataset = [ 
    { 
     'date': datetime.date(2013, 1, 1), 
     'id': 99, 
     'value1': 10, 
     'value2': 10 
    }, 
    { 
     'date': datetime.date(2013, 1, 1), 
     'id': 98, 
     'value1': 10, 
     'value2': 10 
    }, 
    { 
     'date': datetime.date(2013, 1, 2), 
     'id' 99, 
     'value1': 10, 
     'value2': 10 
    } 
] 

group_and_sum_dataset(my_dataset, 'date', ['value1', 'value2']) 

""" 
Should return: 
[ 
    { 
     'date': datetime.date(2013, 1, 1), 
     'value1': 20, 
     'value2': 20 
    }, 
    { 
     'date': datetime.date(2013, 1, 2), 
     'value1': 10, 
     'value2': 10 
    } 
] 
""" 

Próbowałem robić to za pomocą itertools dla GroupBy i zsumowanie każdą parę jak klucz wartość, ale jestem brakuje czegoś tutaj. Oto co moja funkcja obecnie wygląda następująco:

def group_and_sum_dataset(dataset, group_by_key, sum_value_keys): 
    keyfunc = operator.itemgetter(group_by_key) 
    dataset.sort(key=keyfunc) 
    new_dataset = [] 
    for key, index in itertools.groupby(dataset, keyfunc): 
     d = {group_by_key: key} 
     d.update({k:sum([item[k] for item in index]) for k in sum_value_keys}) 
     new_dataset.append(d) 
    return new_dataset 

Odpowiedz

19

Można użyć collections.Counter i collections.defaultdict.

Za pomocą dyktatu można to zrobić w O(N), natomiast sortowanie wymaga czasu O(NlogN).

from collections import defaultdict, Counter 
def solve(dataset, group_by_key, sum_value_keys): 
    dic = defaultdict(Counter) 
    for item in dataset: 
     key = item[group_by_key] 
     vals = {k:item[k] for k in sum_value_keys} 
     dic[key].update(vals) 
    return dic 
... 
>>> d = solve(my_dataset, 'date', ['value1', 'value2']) 
>>> d 
defaultdict(<class 'collections.Counter'>, 
{ 
datetime.date(2013, 1, 2): Counter({'value2': 10, 'value1': 10}), 
datetime.date(2013, 1, 1): Counter({'value2': 20, 'value1': 20}) 
}) 

Zaletą Counter jest, że będzie ona automatycznie zsumować wartości podobnych kluczy .:

Przykład:

>>> c = Counter(**{'value1': 10, 'value2': 5}) 
>>> c.update({'value1': 7, 'value2': 3}) 
>>> c 
Counter({'value1': 17, 'value2': 8}) 
+2

To jest świetne! Czy masz jakieś przemyślenia na temat grupowania według 2 pól? Jak na przykład w tym przykładzie chciałbyś pogrupować według id i date? Obecnie moim pomysłem jest połączenie dwóch pól w jeden, ale nie wydaje się to zbyt eleganckie. – aiguofer

3

Dzięki, zapomniałem o Counter. Nadal chciałem zachować format wyjściowy i sortowanie zwróconego zestawu danych, więc oto, jak wygląda moja ostatnia funkcja:

def group_and_sum_dataset(dataset, group_by_key, sum_value_keys): 

    container = defaultdict(Counter) 

    for item in dataset: 
     key = item[group_by_key] 
     values = {k:item[k] for k in sum_value_keys} 
     container[key].update(values) 

    new_dataset = [ 
     dict([(group_by_key, item[0])] + item[1].items()) 
      for item in container.items() 
    ] 
    new_dataset.sort(key=lambda item: item[group_by_key]) 

    return new_dataset 
Powiązane problemy