2016-07-11 13 views
0

Ich habe 2 Dimensionen:

dimensions = ('product', 'place') 

Und 2 Metriken:

metrics = ('METRIC_1', 'METRIC_2') 

Eingang ist die folgende Liste von dicts mit Dimensionen und Kennzahlen

input = [ 
    {'product': 'eggs', 'place': 'fridge', 'METRIC_1': 1, 'METRIC_2': 2}, 
    {'product': 'eggs', 'place': 'table', 'METRIC_1': 3, 'METRIC_2': 1}, 
    {'product': 'ham', 'place': 'fridge', 'METRIC_1': 1, 'METRIC_2': 2}, 
    {'product': 'ham', 'place': 'table', 'METRIC_1': 3, 'METRIC_2': 5}, 
] 

Für jede Dimension und jede Kombination von Dimensionen möchte ich "_all_" -Wert erhalten, wo Metriken zusammengefasst werden (oder eine andere Aggregationsmethode angewendet wird)).Python. Berechnen Dimension Summen für die Liste der Wörterbücher

Erwartetes Ergebnis:

result = [ 
    {'product': '_all_', 'place': 'fridge', 'METRIC_1': 2, 'METRIC_2': 4}, 
    {'product': '_all_', 'place': 'table', 'METRIC_1': 6, 'METRIC_2': 6}, 
    {'product': 'eggs', 'place': '_all_', 'METRIC_1': 4, 'METRIC_2': 3}, 
    {'product': 'ham', 'place': '_all_', 'METRIC_1': 4, 'METRIC_2': 7}, 
    {'product': '_all_', 'place': '_all_', 'METRIC_1': 8, 'METRIC_2': 8}, 
] 

Betrachten Sie diese Anzahl von Dimensionen und Kennzahlen ist flexibel. Wäre dankbar, wenn die Antwort eine Funktion mit der folgenden Signatur:

calc_totals(input_list, dimensions_list, {'metric_1': 'sum', 'metric_2': 'sum'}): 
    pass 

Mein Versuch folgt, scheint aber zu kompliziert und nicht sicher, ob es richtig ist:

from operator import itemgetter 
from itertools import groupby, combinations, chain 


def powerset(iterable): 
    xs = list(iterable) 
    return chain.from_iterable(combinations(xs, n) for n in range(len(xs)+1)) 


def calc_totals(input, dimensions): 
    totals = [] 
    dim_combs = list(powerset(dimensions))[1:-1] 
    for dim_comb in dim_combs: 
     current_dims = dimensions.difference(set(dim_comb)) 
     grouper = itemgetter(*current_dims) 
     for key, group in groupby(sorted(input, key=grouper), grouper): 
      temp_dict = dict(zip(list(current_dims), [key])) 

      temp_dict['METRIC_1'] = 0 
      temp_dict['METRIC_2'] = 0 
      for item in group: 
       temp_dict['METRIC_1'] += item['METRIC_1'] 
       temp_dict['METRIC_2'] += item['METRIC_2'] 

      for dim in dim_comb: 
       temp_dict[dim] = '_all_' 
      totals.append(temp_dict) 
    return totals 

Antwort

0

Hier ist ein Code, den ich kam hoch mit. Es nimmt Eingabe, Dimensionen und Wörterbuch von Aggregatfunktionen als Parameter. Dann iteriert Iter über jede Zeile in der Eingabe und aggregiert Metriken zu jeder relevanten Zeile in der Ausgabe, die intern ein Diktat ist. Schließlich wird das Ergebnis dict abgeflachte die Liste Ausgabe zu erzeugen:

from itertools import combinations, chain, product 
from collections import defaultdict 
from operator import add 
from pprint import pprint 

dimensions = ('product', 'place') 

src = [ 
    {'product': 'eggs', 'place': 'fridge', 'METRIC_1': 1, 'METRIC_2': 2}, 
    {'product': 'eggs', 'place': 'table', 'METRIC_1': 3, 'METRIC_2': 1}, 
    {'product': 'ham', 'place': 'fridge', 'METRIC_1': 1, 'METRIC_2': 2}, 
    {'product': 'ham', 'place': 'table', 'METRIC_1': 3, 'METRIC_2': 5}, 
] 

def flatten(keys, d, level=0, cur={}): 
    if level == len(keys): 
     cur.update(d) 
     yield cur.copy() 
    else: 
     for k, v in d.items(): 
      cur[keys[level]] = k 
      for x in flatten(keys, v, level + 1, cur): 
       yield x 
     del cur[keys[level]] 

def calc_totals(input_list, dimension_list, aggregate): 
    if not input_list: 
     return [] 

    # Autovivification dict to store results 
    dd = lambda: defaultdict(dd) 
    result = dd() 

    # Tuple of combos where each combo is a tuple of dimensions that are aggregated 
    combos = tuple(chain.from_iterable(combinations(dimension_list, n) for n in range(1, len(dimension_list) + 1))) 

    # For every row in source 
    for row in src: 
     # For every possible combo 
     for combo in combos: 
      target = result 
      # Navigate to dict where metric should be added automatically generating empty dict 
      # if one doesn't exist 
      for dim in dimensions: 
       key = '_all_' if dim in combo else row[dim] 
       target = target[key] 
      # Add metrics, call aggregate function combine with existing value using 0 as default 
      for metric, func in aggregate.items(): 
       target[metric] = func(target.get(metric, 0), row[metric]) 

    # Finally flatten the results to a list 
    return list(flatten(dimension_list, result)) 

pprint(calc_totals(src, dimensions, {'METRIC_1': add, 'METRIC_2': add})) 

Output:

[{'METRIC_1': 4, 'METRIC_2': 7, 'place': '_all_', 'product': 'ham'}, 
{'METRIC_1': 8, 'METRIC_2': 10, 'place': '_all_', 'product': '_all_'}, 
{'METRIC_1': 2, 'METRIC_2': 4, 'place': 'fridge', 'product': '_all_'}, 
{'METRIC_1': 6, 'METRIC_2': 6, 'place': 'table', 'product': '_all_'}, 
{'METRIC_1': 4, 'METRIC_2': 3, 'place': '_all_', 'product': 'eggs'}] 

Es sollte solange Abmessungen und Aggregatfunktionen werden zur Verfügung gestellt als Parameter beliebige Anzahl von Dimensionen und Metriken unterstützen.

0

@niemmi, danke. Ihr Fehler ist bei der Aggregation von np.mean aufgetreten, also lassen Sie mich eine Lösung hinzufügen, die für mich funktioniert hat.

def powerset(iterable): 
    xs = list(iterable) 
    return chain.from_iterable(combinations(xs, n) for n in range(len(xs)+1)) 

def calc_totals(input_list, dimensions, metric_func_dict): 
    # metric_func_dict = {'METRIC_1': 'sum', 'METRIC_2': 'mean'} 
    dimensions = set(dimensions) 
    totals = [] 
    dim_combs = list(powerset(dimensions))[1:-1] 

    for dim_comb in dim_combs: 
     current_dims = dimensions.difference(set(dim_comb)) 
     grouper = itemgetter(*current_dims) 
     for key, group in groupby(sorted(input_list, key=grouper), grouper): 
      if type(key) == str: 
       temp_dict = dict(zip(list(current_dims), [key])) 
      else: 
       temp_dict = dict(zip(list(current_dims), key)) 

      for metric in metric_func_dict: 
       temp_dict[metric] = [] 
      for item in group: 
       for metric in metric_func_dict: 
        temp_dict[metric].append(item[metric]) 
      for metric in metric_func_dict: 
       method_to_call = getattr(np, metric_func_dict[metric]) 
       temp_dict[metric] = method_to_call(temp_dict[metric]) 

      for dim in dim_comb: 
       temp_dict[dim] = '_all_' 
      totals.append(temp_dict) 
    return totals 

auch, glaube ich, könnte es bessere Lösung Pandas mit

Verwandte Themen