Quel est le moyen le plus pythonique de regrouper plusieurs clés et de résumer/les valeurs moyennes d'une liste de dictionnaires en Python, s'il vous plaît? Dites que j'ai une liste de dictionnaires comme ci-dessous:
input = [
{'dept': '001', 'sku': 'foo', 'transId': 'uniqueId1', 'qty': 100},
{'dept': '001', 'sku': 'bar', 'transId': 'uniqueId2', 'qty': 200},
{'dept': '001', 'sku': 'foo', 'transId': 'uniqueId3', 'qty': 300},
{'dept': '002', 'sku': 'baz', 'transId': 'uniqueId4', 'qty': 400},
{'dept': '002', 'sku': 'baz', 'transId': 'uniqueId5', 'qty': 500},
{'dept': '002', 'sku': 'qux', 'transId': 'uniqueId6', 'qty': 600},
{'dept': '003', 'sku': 'foo', 'transId': 'uniqueId7', 'qty': 700}
]
Sortie souhaitée pour l'agrégation:
output=[
{'dept': '001', 'sku': 'foo', 'qty': 400},
{'dept': '001', 'sku': 'bar', 'qty': 200},
{'dept': '002', 'sku': 'baz', 'qty': 900},
{'dept': '002', 'sku': 'qux', 'qty': 600},
{'dept': '003', 'sku': 'foo', 'qty': 700}
]
ou moyenne:
output=[
{'dept': '001', 'sku': 'foo', 'avg': 200},
{'dept': '001', 'sku': 'bar', 'avg': 200},
{'dept': '002', 'sku': 'baz', 'avg': 450},
{'dept': '002', 'sku': 'qux', 'avg': 600},
{'dept': '003', 'sku': 'foo', 'avg': 700}
]
J'ai trouvé ceci: Grouper par et agréger les valeurs d'une liste de dictionnaires en Python mais cela ne semble pas me donner ce que je veux.
Pour obtenir les résultats agrégés
from itertools import groupby
from operator import itemgetter
grouper = itemgetter("dept", "sku")
result = []
for key, grp in groupby(sorted(input_data, key = grouper), grouper):
temp_dict = dict(Zip(["dept", "sku"], key))
temp_dict["qty"] = sum(item["qty"] for item in grp)
result.append(temp_dict)
from pprint import pprint
pprint(result)
Sortie
[{'dept': '001', 'qty': 200, 'sku': 'bar'},
{'dept': '001', 'qty': 400, 'sku': 'foo'},
{'dept': '002', 'qty': 900, 'sku': 'baz'},
{'dept': '002', 'qty': 600, 'sku': 'qux'},
{'dept': '003', 'qty': 700, 'sku': 'foo'}]
Et pour obtenir les moyennes, vous pouvez simplement changer le contenu de la boucle for, comme ceci
temp_dict = dict(Zip(["dept", "sku"], key))
temp_list = [item["qty"] for item in grp]
temp_dict["avg"] = sum(temp_list) / len(temp_list)
result.append(temp_dict)
Sortie
[{'avg': 200, 'dept': '001', 'sku': 'bar'},
{'avg': 200, 'dept': '001', 'sku': 'foo'},
{'avg': 450, 'dept': '002', 'sku': 'baz'},
{'avg': 600, 'dept': '002', 'sku': 'qux'},
{'avg': 700, 'dept': '003', 'sku': 'foo'}]
Suggestion: De toute façon, j'aurais ajouté à la fois qty
et avg
dans le même dict
comme ceci
temp_dict = dict(Zip(["dept", "sku"], key))
temp_list = [item["qty"] for item in grp]
temp_dict["qty"] = sum(temp_list)
temp_dict["avg"] = temp_dict["qty"] / len(temp_list)
result.append(temp_dict)
Sortie
[{'avg': 200, 'dept': '001', 'qty': 200, 'sku': 'bar'},
{'avg': 200, 'dept': '001', 'qty': 400, 'sku': 'foo'},
{'avg': 450, 'dept': '002', 'qty': 900, 'sku': 'baz'},
{'avg': 600, 'dept': '002', 'qty': 600, 'sku': 'qux'},
{'avg': 700, 'dept': '003', 'qty': 700, 'sku': 'foo'}]
Inspiré par la réponse d'Eelco Hoogendoorn. Voici un autre moyen de résoudre ce problème en utilisant le paquet Pandas. Le code est plus lisible.
import numpy as np
import pandas as pd
def sum_by_cusip_and_dept(data):
df = pd.DataFrame(data)
grouped = df.groupby(['sku', 'dept'])
sum = grouped.sum()
return [{'sku': r[0], 'dept': r[1], 'qty': kv.to_dict().get('qty')} for r, kv in sum.iterrows()]
En utilisant le numpy EP, vous pouvez trouver ici , vous pouvez écrire:
inputs = dict( (k, [i[k] for i in input ]) for k in input[0].keys())
print group_by((inputs['dept'], inputs['sku'])).mean(inputs['qty'])
Cependant, vous voudrez peut-être envisager d’utiliser le paquet pandas si vous faites beaucoup d’opérations relationnelles de ce type.
@thefourtheye Si nous utilisons groupby
seulement une clé, nous devrions vérifier le type de clé après le groupe, si ce n'est pas un Tuple, renvoyer une liste.
for key, grp in groupby(sorted(input_data, key = grouper), grouper):
if not isinstance(key, Tuple):
key = [key]
Vous pouvez mettre les quantités et le nombre d’occurrences dans un dictée par défaut:
from collections import defaultdict
counts = defaultdict(lambda: [0, 0])
for line in input_data:
entry = counts[(line['dept'], line['sku'])]
entry[0] += line['qty']
entry[1] += 1
Maintenant, il ne reste plus qu’à obtenir les chiffres dans une liste de dict:
sums_dict = [{'dept': k[0], 'sku': k[1], 'qty': v[0]}
for k, v in counts.items()]
avg_dict = [{'dept': k[0], 'sku': k[1], 'avg': float(v[0]) / v[1]} for
k, v in counts.items()]
Les résultats pour les sommes:
sums_dict
[{'dept': '002', 'qty': 600, 'sku': 'qux'},
{'dept': '001', 'qty': 400, 'sku': 'foo'},
{'dept': '003', 'qty': 700, 'sku': 'foo'},
{'dept': '002', 'qty': 900, 'sku': 'baz'},
{'dept': '001', 'qty': 200, 'sku': 'bar'}]
et pour les moyennes:
avg_dict
[{'avg': 600.0, 'dept': '002', 'sku': 'qux'},
{'avg': 200.0, 'dept': '001', 'sku': 'foo'},
{'avg': 700.0, 'dept': '003', 'sku': 'foo'},
{'avg': 450.0, 'dept': '002', 'sku': 'baz'},
{'avg': 200.0, 'dept': '001', 'sku': 'bar'}]
Une version alternative sans le dict par défaut:
counts = {}
for line in input_data:
entry = counts.setdefault((line['dept'], line['sku']), [0, 0])
entry[0] += line['qty']
entry[1] += 1
Le reste est le même:
sums_dict = [{'dept': k[0], 'sku': k[1], 'qty': v[0]}
for k, v in counts.items()]
avg_dict = [{'dept': k[0], 'sku': k[1], 'avg': float(v[0]) / v[1]} for
k, v in counts.items()]
Comme toujours, il y a beaucoup de solutions valables, j'aime bien la solution defaultdict, car je la trouve plus facile à comprendre.
from collections import defaultdict as df
food = df(lambda:df(lambda:df(int)))
for dct in input: food[dct['transId']][dct['sku']][dct['dept']]=dct['qty']
output_tupl=[(d1,d2,sum(food[d1][d2][d3] for d3 in food[d1][d2]) )for d1 in food for d2 in food[d1]]
J'avais des exigences supplémentaires en plus de la question initiale. Je voulais faire circuler le mérou et ne pas avoir à contourner l'ordre original des champs si vous devez reconstituer la clé de regroupement sous forme de dict.
namedtuple () fonctionne assez bien en ce sens qu'il vous permet de trier et d'utiliser ._asdict ()
from collections import namedtuple
def get_grouper(fields):
key = namedtuple('GroupingKey', fields)
def get_key(row):
return key(**{field: row[field] for field in fields})
return get_key
rows = [
{'a': 1, 'b': 1, 'c': 1},
{'a': 1, 'b': 2, 'c': 3},
{'a': 1, 'b': 1, 'c': 2},
{'a': 1, 'b': 0},
{'a': 1, 'b': 2, 'c': 4}
]
grouper = get_grouper(['a','b'])
rows = sorted(rows, key=grouper)
for k, g in groupby(rows, key=grouper):
print(k, list(g))