Одновременно суммируйте два ключа в списке диктов по нескольким предметам

1

Я хочу суммировать две разные переменные в одной функции, но я хочу, чтобы они были суммированы на основе нескольких других элементов.

Если у меня есть следующий список dicts x:

x=[{'id':1, 'var1':'a', 'var2':'left', 'var3':0.1, 'var4':1},
   {'id':2, 'var1':'a', 'var2':'right', 'var3':0.1, 'var4':1},
   {'id':2, 'var1':'a', 'var2':'right', 'var3':0.2, 'var4':3},
   {'id':4, 'var1':'b', 'var2':'left', 'var3':0.4, 'var4':4},
   {'id':5, 'var1':'b', 'var2':'right', 'var3':0.1, 'var4':5},
   {'id':5, 'var1':'b', 'var2':'right', 'var3':0.4, 'var4':2}]

Затем я могу использовать следующую функцию для суммирования отдельной переменной ("var3") на основе двух других переменных ("var1" и "var2"):

from operator import itemgetter
from itertools import groupby

def aggregate_var3_by_var1_and_var2(data):
    my_data= []
    grouper = itemgetter("id", "var1", "var2")
    for key, grp in groupby(sorted(data, key = grouper), grouper):
        temp_dict = dict(zip(["id", "var1", "var2"], key))
        temp_dict["var3"] = sum(item["var3"] for item in grp)
        my_data.append(temp_dict)
    return my_data

my_output = aggregate_var3_by_var1_and_var2(x)

Тем не менее, я хочу суммировать несколько переменных ("var3" и "var4") по нескольким категориям ("var1" и "var2"), чтобы результат выглядел следующим образом:

 y=[{'id': 1, 'var1': 'a', 'var2': 'left', 'var3': 0.1, 'var4': 1},
    {'id': 2, 'var1': 'a', 'var2': 'right', 'var3': 0.3, 'var4':4},
    {'id': 4, 'var1': 'b', 'var2': 'left', 'var3': 0.4, 'var4':4},
    {'id': 5, 'var1': 'b', 'var2': 'right', 'var3': 0.5, 'var4':7}]

Как суммировать сразу несколько переменных с помощью этого метода?

Теги:
list
dictionary
sum

4 ответа

0
Лучший ответ

Вы можете использовать collections.defaultdict для решения O (n). В отличие от itertools.groupby, это не требует предварительной сортировки.

Идея состоит в том, чтобы группировать предварительно определенные group_keys. Затем используйте понимание списка для объединения ключей и значений вашего defaultdict. Синтаксис {**d1, **d2} используется для объединения двух словарей.

from collections import defaultdict
from operator import itemgetter

d = defaultdict(lambda: defaultdict(int))

group_keys = ['id', 'var1', 'var2']
sum_keys = ['var3', 'var4']

for item in x:
    for key in sum_keys:
        d[itemgetter(*group_keys)(item)][key] += item[key]

res = [{**dict(zip(group_keys, k)), **v} for k, v in d.items()]

print(res)

[{'id': 1, 'var1': 'a', 'var2': 'left', 'var3': 0.1, 'var4': 1},
 {'id': 2, 'var1': 'a', 'var2': 'right', 'var3': 0.3, 'var4': 4},
 {'id': 4, 'var1': 'b', 'var2': 'left', 'var3': 0.4, 'var4': 4},
 {'id': 5, 'var1': 'b', 'var2': 'right', 'var3': 0.5, 'var4': 7}]
  • 0
    Спасибо, я использовал это, потому что мне было легче определить, какие ключи использовались для группировки и суммирования.
1

Простое расширение вашего подхода, предоставление ключей группового вызова и ключей значений в качестве аргументов:

from operator import itemgetter
from itertools import groupby
from itertools import chain

def reducer(ts):
    return map(sum, zip(*ts))

def agg(data, keys, aggfields):
    my_data = []
    getter = itemgetter(*aggfields)
    grouper = itemgetter(*keys)
    for ks, grp in groupby(sorted(data, key=grouper), grouper):
        vs = map(getter, grp)
        kvs = chain(zip(keys,ks), zip(aggfields, reducer(vs)))
        my_data.append(dict(kvs))
    return my_data

В реплике:

In [9]: x=[{'id':1, 'var1':'a', 'var2':'left', 'var3':0.1, 'var4':1},
   ...:    {'id':2, 'var1':'a', 'var2':'right', 'var3':0.1, 'var4':1},
   ...:    {'id':2, 'var1':'a', 'var2':'right', 'var3':0.2, 'var4':3},
   ...:    {'id':4, 'var1':'b', 'var2':'left', 'var3':0.4, 'var4':4},
   ...:    {'id':5, 'var1':'b', 'var2':'right', 'var3':0.1, 'var4':5},
   ...:    {'id':5, 'var1':'b', 'var2':'right', 'var3':0.4, 'var4':2}]

In [10]: agg(x, ['var1','var2'], ['var3','var4'])
Out[10]:
[{'var1': 'a', 'var2': 'left', 'var3': 0.1, 'var4': 1},
 {'var1': 'a', 'var2': 'right', 'var3': 0.30000000000000004, 'var4': 4},
 {'var1': 'b', 'var2': 'left', 'var3': 0.4, 'var4': 4},
 {'var1': 'b', 'var2': 'right', 'var3': 0.5, 'var4': 7}]

Вот альтернативный подход, в котором группы используют словарь (по умолчанию dict counter dicts...)

from collections import Counter, defaultdict
from itertools import chain
from operator import itemgetter

def agg(data, keys, aggfields):

    grouper = defaultdict(Counter)
    pluck_keys = itemgetter(*keys)
    pluck_vals = itemgetter(*aggfields)

    for d in data:
        ctr = grouper[pluck_keys(d)]
        for k, v in zip(aggfields, pluck_vals(d)):
            ctr[k] += v

    return [
        {k:v for k,v in chain(zip(keys, ks), ctr.items())}
        for ks, ctr in grouper.items()
    ]
0
from itertools import groupby
x=[{'id':1, 'var1':'a', 'var2':'left', 'var3':0.1, 'var4':1},
   {'id':2, 'var1':'a', 'var2':'right', 'var3':0.1, 'var4':1},
   {'id':2, 'var1':'a', 'var2':'right', 'var3':0.2, 'var4':3},
   {'id':4, 'var1':'b', 'var2':'left', 'var3':0.4, 'var4':4},
   {'id':5, 'var1':'b', 'var2':'right', 'var3':0.1, 'var4':5},
   {'id':5, 'var1':'b', 'var2':'right', 'var3':0.4, 'var4':2}]

res = []

for key, value in groupby(x, lambda x: x["id"]):
    d = None
    for i in value:
        if not d:
            d = i
        else:
            d["var3"] += i["var3"]
            d["var4"] += i["var4"]
    res.append(d)
print(res)

Выход:

[{'id': 1, 'var1': 'a', 'var2': 'left', 'var3': 0.1, 'var4': 1},
 {'id': 2,
  'var1': 'a',
  'var2': 'right',
  'var3': 0.30000000000000004,
  'var4': 4},
 {'id': 4, 'var1': 'b', 'var2': 'left', 'var3': 0.4, 'var4': 4},
 {'id': 5, 'var1': 'b', 'var2': 'right', 'var3': 0.5, 'var4': 7}]
0

Вы можете использовать Pandas для эффективного, векторизованного решения.

Недостатки itertools.groupby здесь - это необходимость сортировки [дополнительной сложности] и не реализует векторизованные вычисления [неэффективное суммирование].

Если вы хотите спуститься по замкнутому маршруту, я рекомендую collections.defaultdict чтобы убедиться, что у вас все еще есть сложность O (n).

import pandas as pd

df = pd.DataFrame(x)

res = df.groupby(['id', 'var1', 'var2']).agg({'var3': 'sum', 'var4': 'sum'}).reset_index()

print(res.to_dict('records'))

[{'id': 1, 'var1': 'a', 'var2': 'left', 'var3': 0.1, 'var4': 1},
 {'id': 2, 'var1': 'a', 'var2': 'right', 'var3': 0.3, 'var4': 4},
 {'id': 4, 'var1': 'b', 'var2': 'left', 'var3': 0.4, 'var4': 4},
 {'id': 5, 'var1': 'b', 'var2': 'right', 'var3': 0.5, 'var4': 7}]

Ещё вопросы

Сообщество Overcoder
Наверх
Меню