2 votes

Regroupement imbriqué de defaultdict(list)

J'ai un bloc de lignes de résultats et j'essaie de les regrouper en deux niveaux d'imbrication [{key: value[{key:value[]}]}] . Les valeurs sont non uniques au niveau des clés de niveau supérieur.

J'ai essayé d'utiliser defaultdict mais je n'ai pas réussi à regrouper les deux niveaux en raison de la non-unicité. L'itération sur les données pourrait être plus efficace, mais je n'ai pas non plus réussi à le faire.

Données de départ :

data = 
[{'Name': 'Bob', 'Time': 12, 'Place': 'Home'}, 
{'Name': 'Bob', 'Time': 11, 'Place': 'Home'}, 
{'Name': 'Jerry', 'Time': 5, 'Place': 'Home'}, 
{'Name': 'Jerry', 'Time': 11, 'Place': '-----'}, 
{'Name': 'Jerry', 'Time': 11, 'Place': 'Work'}]

Données finales souhaitées :

[{"Name": "Bob", "Details":[{"Place":"Home", "Time":[12, 11]}]}, 
{"Name": "Jerry", "Details":[{"Place":"Home", "Time":[5]}, 
                             {"Place":"-----", "Time":[11]}, 
                             {"Place":"Work", "Time":[11]}]}]

2voto

han solo Points 4924

Vous pourriez regrouper par le Name y Place en utilisant itertools.groupby ,

>>> import itertools
>>> from collections import defaultdict
>>> data
[{'Name': 'Bob', 'Time': 12, 'Place': 'Home'}, {'Name': 'Bob', 'Time': 11, 'Place': 'Home'}, {'Name': 'Jerry', 'Time': 5, 'Place': 'Home'}, {'Name': 'Jerry', 'Time': 11, 'Place': '-----'}, {'Name': 'Jerry', 'Time': 11, 'Place': 'Work'}]
>>> sorted_data = sorted(data, key=lambda x: (x['Name'], x['Place'])) # sorting before grouping as suggested by @wwii, because The returned group is itself an iterator that shares the underlying iterable with groupby(). Please see (https://docs.python.org/3/library/itertools.html#itertools.groupby)
>>> d = defaultdict(list)
>>> y = itertools.groupby(sorted_data, lambda x: (x['Name'], x['Place']))
>>> for group, grouper in y:
...   time_ = [item['Time'] for item in grouper]
...   name, place = group
...   d[name].append({'Place': place, 'Time': time_})
... 
>>> d
defaultdict(<class 'list'>, {'Bob': [{'Place': 'Home', 'Time': [12, 11]}], 'Jerry': [{'Place': 'Home', 'Time': [5]}, {'Place': '-----', 'Time': [11]}, {'Place': 'Work', 'Time': [11]}]})
>>> pprint.pprint(dict(d))
{'Bob': [{'Place': 'Home', 'Time': [12, 11]}],
 'Jerry': [{'Place': 'Home', 'Time': [5]},
           {'Place': '-----', 'Time': [11]},
           {'Place': 'Work', 'Time': [11]}]}

Si vous avez besoin de la structure exacte que vous avez montrée alors,

>>> f_data = []
>>> for key, value in d.items():
...   f_data.append({'Name': key, 'Details': value})
... 
>>> pprint.pprint(f_data)
[{'Details': [{'Place': 'Home', 'Time': [12, 11]}], 'Name': 'Bob'},
 {'Details': [{'Place': '-----', 'Time': [11]},
              {'Place': 'Home', 'Time': [5]},
              {'Place': 'Work', 'Time': [11]}],
  'Name': 'Jerry'}]

2voto

wwii Points 2255

Trier les données ; regrouper par 'Name' , regrouper ce résultat par 'Place' ; extraire les temps.

import operator
name = operator.itemgetter('Name')
where = operator.itemgetter('Place')
time = operator.itemgetter('Time')

data.sort(key=lambda x: (name(x),where(x)))
result = []
for name, group in itertools.groupby(data,key=name):
    d = {'Name':name, 'Details':[]}
    for place, times in itertools.groupby(group,key=where):
        times = map(time, times)
        d['Details'].append({'Place':place, 'Time':list(times)})
    result.append(d)

J'aime utiliser operator.itemgetter au lieu d'une fonction lambda si elle est utilisée plus d'une fois. C'est juste ma préférence personnelle.

0voto

Ruhshan Points 53

J'ai essayé de le résoudre avec un peu d'aide de Pandas. Jetez-y un coup d'œil :

import pandas as pd

data = [{'Name': 'Bob', 'Time': 12, 'Place': 'Home'}, 
{'Name': 'Bob', 'Time': 11, 'Place': 'Home'}, 
{'Name': 'Jerry', 'Time': 5, 'Place': 'Home'}, 
{'Name': 'Jerry', 'Time': 11, 'Place': '-----'}, 
{'Name': 'Jerry', 'Time': 11, 'Place': 'Work'}]

df = pd.DataFrame.from_dict(data)

#Take the unique names only
names = df["Name"].unique()

#This list will hold the desired values
new_list = []

# Iterate over names
for n in names:
    # Make subset off the data set where name is n
    subset = df[df["Name"]==n]
    # Get Unique Places in the subset
    places = subset["Place"].unique()
    # This will hold the details
    details = []
    # Iterate over unique places
    for p in places:
        # Get times from subset where place is  and convert to list
        times = subset[subset["Place"]==p]["Time"].tolist()
        # Append to details list
        details.append({"Place":p,"Time":times})
    # Add the details in new_list as the format you preferred
    new_list.append({"Name":n, "Details":details})

print(new_list)

0voto

Vous avez la bonne idée avec defaultdict plus l'itération. La seule chose un peu délicate est de faire une imbrication de defaultdict .

from collections import defaultdict

def timegroup(data):
    grouped = defaultdict(lambda:defaultdict(list))
    for d in data:
        grouped[d['Name']][d['Place']].append(d['Time'])
    for name, details in grouped.items():
        yield {'Name': name,
               'Details': [{'Place': p, 'Time': t} for p, t in details.items()]}

(J'aime utiliser des générateurs pour des choses comme ça, parce que parfois vous voulez juste itérer sur les résultats, dans ce cas vous n'avez pas besoin d'une liste, et si vous avez besoin d'une liste, il est facile d'en faire une).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X