2013-08-05 8 views
12

Ich versuche, auf elegante Weise eine Funktion zu schreiben, die eine Liste von Wörterbüchern gruppiert und die Werte von Like-Schlüsseln aggregiert (summiert).Gruppieren und aggregiere die Werte einer Liste von Wörterbüchern in Python

Beispiel:

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 
    } 
] 
""" 

Ich habe dies mit itertools für die groupby versuchte dabei und jeder gleichSchlüsselWertPaar Summieren, aber hier bin etwas fehlt. Hier ist, was zur Zeit meine Funktion wie folgt aussieht:

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 

Antwort

19

Sie collections.Counter und collections.defaultdict verwenden können.

Mit einem Diktat kann dies in O(N) durchgeführt werden, während das Sortieren O(NlogN) Zeit erfordert.

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}) 
}) 

Der Vorteil Counter ist, dass es automatisch die Werte ähnlicher Tasten summieren werden .:

Beispiel:

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

Das ist genial! Haben Sie Gedanken zum Gruppieren nach 2 Feldern? Wie in diesem Beispiel wollten Sie nach ID und Datum gruppieren? Im Moment besteht meine Idee darin, die beiden Felder zu einem zusammenzufassen, aber es scheint nicht sehr elegant zu sein. – aiguofer

3

Danke, ich über Counter vergessen. Ich wollte immer noch das Ausgabeformat und die Sortierung meines zurückgegebenen Datasets beibehalten, also hier ist meine endgültige Funktion:

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 
Verwandte Themen