valor resueltos recorrer lista elementos ejercicios diccionarios diccionario dentro convertir buscar agregar python dictionary itertools

resueltos - Agrupe por y agregue los valores de una lista de diccionarios en Python



lista de diccionarios python (3)

Estoy tratando de escribir una función, de una manera elegante, que agrupará una lista de diccionarios y agregará (suma) los valores de las claves similares.

Ejemplo:

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

He intentado hacer esto usando itertools para el groupby y sumando cada par de valor-clave similar, pero me falta algo aquí. Así es como se ve mi función actualmente:

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


Este es un enfoque que utiliza more_itertools donde simplemente se enfoca en cómo construir resultados.

Dado

import datetime import collections as ct import more_itertools as mit 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} ]

Código

# Step 1: Build helper functions kfunc = lambda d: d["date"] vfunc = lambda d: {k:v for k, v in d.items() if k.startswith("val")} rfunc = lambda lst: sum((ct.Counter(d) for d in lst), ct.Counter()) # Step 2: Build a dict reduced = mit.map_reduce(dataset, keyfunc=kfunc, valuefunc=vfunc, reducefunc=rfunc) reduced

Salida

defaultdict(None, {datetime.date(2013, 1, 1): Counter({''value1'': 20, ''value2'': 20}), datetime.date(2013, 1, 2): Counter({''value1'': 10, ''value2'': 10})})

Los elementos se agrupan por fecha y los valores pertinentes se reducen como Counters .

Detalles

Pasos

  1. Construye funciones de ayuda para personalizar la construcción de claves , valores y valores reducidos en el último defaultdict . Aquí queremos:
    • grupo por fecha ( kfunc )
    • Dicts construidos manteniendo los parámetros de "valor *" ( vfunc )
    • agregue los dictados ( rfunc ) convirtiéndolos en collections.Counters . Contadores y rfunc . Ver un funcionamiento equivalente a continuación + .
  2. pasar las funciones de ayuda a more_itertools.map_reduce .

Groupby simple

... diga en ese ejemplo que quería agrupar por id y fecha?

No hay problema.

>>> kfunc2 = lambda d: (d["date"], d["id"]) >>> mit.map_reduce(dataset, keyfunc=kfunc2, valuefunc=vfunc, reducefunc=rfunc) defaultdict(None, {(datetime.date(2013, 1, 1), 99): Counter({''value1'': 10, ''value2'': 10}), (datetime.date(2013, 1, 1), 98): Counter({''value1'': 10, ''value2'': 10}), (datetime.date(2013, 1, 2), 99): Counter({''value1'': 10, ''value2'': 10})})

Salida personalizada

Si bien la estructura de datos resultante presenta el resultado de manera clara y concisa, la salida esperada del OP se puede reconstruir como una simple lista de dictados:

>>> [{**dict(date=k), **v} for k, v in reduced.items()] [{''date'': datetime.date(2013, 1, 1), ''value1'': 20, ''value2'': 20}, {''date'': datetime.date(2013, 1, 2), ''value1'': 10, ''value2'': 10}]

Para más información sobre map_reduce , vea more_itertools.map_reduce . Instalar via > pip install more_itertools .

+ Una función reductora equivalente:

def rfunc(lst: typing.List[dict]) -> ct.Counter: """Return reduced mappings from map-reduce values.""" c = ct.Counter() for d in lst: c += ct.Counter(d) return c


Gracias, me olvidé de Counter. Todavía quería mantener el formato de salida y la clasificación de mi conjunto de datos devuelto, así que aquí está mi función final:

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


Puede utilizar collections.Counter y collections.defaultdict .

Usando un dict, esto se puede hacer en O(N) , mientras que la clasificación requiere tiempo 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}) })

La ventaja de Counter es que sumará automáticamente los valores de claves similares:

Ejemplo:

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