web-dev-qa-db-fra.com

Manière pythonique de rassembler/regrouper une liste pour agréger max/min

Disons que j'ai la liste suivante en python. Il est commandé en premier par Equip, puis par Date:

my_list = [
    {'Equip': 'A-1', 'Job': 'Job 1', 'Date': '2018-01-01'},
    {'Equip': 'A-1', 'Job': 'Job 1', 'Date': '2018-01-02'},
    {'Equip': 'A-1', 'Job': 'Job 1', 'Date': '2018-01-03'},
    {'Equip': 'A-1', 'Job': 'Job 2', 'Date': '2018-01-04'},
    {'Equip': 'A-1', 'Job': 'Job 2', 'Date': '2018-01-05'},
    {'Equip': 'A-2', 'Job': 'Job 1', 'Date': '2018-01-03'},
    {'Equip': 'A-2', 'Job': 'Job 3', 'Date': '2018-01-04'},
    {'Equip': 'A-2', 'Job': 'Job 3', 'Date': '2018-01-05'}
]

Ce que je veux faire, c'est réduire la liste de chaque jeu où un élément donné du travail de l'équipement ne change pas, et saisir la première et la dernière date à laquelle l'équipement était là. Par exemple, cet exemple simple devrait devenir:

list_by_job = [
    {'Equip': 'A-1', 'Job': 'Job 1', 'First': '2018-01-01', 'Last': '2018-01-03'},
    {'Equip': 'A-1', 'Job': 'Job 2', 'First': '2018-01-04', 'Last': '2018-01-05'},
    {'Equip': 'A-2', 'Job': 'Job 1', 'First': '2018-01-03', 'Last': '2018-01-03'},
    {'Equip': 'A-2', 'Job': 'Job 3', 'First': '2018-01-04', 'Last': '2018-01-05'}
]

Quelques points à noter:

  1. A-2 sur Job 1 n'est présent que pour un seul jour. Par conséquent, ses First et Last Date devraient être identiques.
  2. Une pièce d’équipement peut être sur un travail, quitter ce travail et revenir. Dans ce cas, il me faudrait voir une entrée pour chaque moment passé au travail, pas un seul résumé.
  3. Comme indiqué précédemment, la liste est déjà triée d'abord par équipement, puis par date, de sorte que la commande peut être supposée. (S'il y a un meilleur moyen de trier pour accomplir cela, je suis tout ouïe)

Pour le point 3, la liste

my_list = [
    {'Equip': 'A-1', 'Job': 'Job 1', 'Date': '2018-01-01'},
    {'Equip': 'A-1', 'Job': 'Job 2', 'Date': '2018-01-02'},
    {'Equip': 'A-1', 'Job': 'Job 1', 'Date': '2018-01-03'}
]

devrait céder

    list_by_job = [
        {'Equip': 'A-1', 'Job': 'Job 1', 'First': '2018-01-01', 'Last': '2018-01-01'},
        {'Equip': 'A-2', 'Job': 'Job 2', 'First': '2018-01-02', 'Last': '2018-01-02'},
        {'Equip': 'A-1', 'Job': 'Job 1', 'First': '2018-01-03', 'Last': '2018-01-03'}
    ]

Actuellement, je le fais d'une manière simple en boucle/non-Pythonique:

list_by_job = []

last_entry = None
for entry in my_list:
    if last_entry is None or last_entry['Equip'] != entry['Equip'] or last_entry['Job'] != entry['Job']:
      list_by_job.append({'Equip': entry['Equip'], 'Job': entry['Job'], 'First': entry['Date'], 'Last': entry['Date']})
    else:
      list_by_job[-1]['Last'] = entry['Date']
    last_entry = entry

Existe-t-il une manière plus pythonique de faire cela en utilisant la compréhension de liste de Python, etc.? 

9
MarkD

Vous pouvez utiliser itertools.groupby:

import itertools
def _key(d):
  return (d['Equip'], d['Job'])

my_list = [{'Date': '2018-01-01', 'Equip': 'A-1', 'Job': 'Job 1'}, {'Date': '2018-01-02', 'Equip': 'A-1', 'Job': 'Job 1'}, {'Date': '2018-01-03', 'Equip': 'A-1', 'Job': 'Job 1'}, {'Date': '2018-01-04', 'Equip': 'A-1', 'Job': 'Job 2'}, {'Date': '2018-01-05', 'Equip': 'A-1', 'Job': 'Job 2'}, {'Date': '2018-01-03', 'Equip': 'A-2', 'Job': 'Job 1'}, {'Date': '2018-01-04', 'Equip': 'A-2', 'Job': 'Job 3'}, {'Date': '2018-01-05', 'Equip': 'A-2', 'Job': 'Job 3'}]
new_data = [[a, list(b)] for a, b in itertools.groupby(my_list, key=_key)]
final_result = [{"Equip":c, 'Job':d, 'First':b[0]['Date'], 'Last':b[-1]['Date']} for [c, d], b in new_data]

Sortie:

[{'Equip': 'A-1', 'Job': 'Job 1', 'Last': '2018-01-03', 'First': '2018-01-01'}, 
 {'Equip': 'A-1', 'Job': 'Job 2', 'Last': '2018-01-05', 'First': '2018-01-04'}, 
 {'Equip': 'A-2', 'Job': 'Job 1', 'Last': '2018-01-03', 'First': '2018-01-03'}, 
 {'Equip': 'A-2', 'Job': 'Job 3', 'Last': '2018-01-05', 'First': '2018-01-04'}]

Modifier:

Utilisation des données comme suggéré dans votre commentaire:

my_list = [{'Date': '2018-01-01', 'Equip': 'A-1', 'Job': 'Job 1'}, {'Date': '2018-01-02', 'Equip': 'A-1', 'Job': 'Job 2'}, {'Date': '2018-01-03', 'Equip': 'A-1', 'Job': 'Job 1'}, {'Date': '2018-01-04', 'Equip': 'A-1', 'Job': 'Job 2'}, {'Date': '2018-01-05', 'Equip': 'A-1', 'Job': 'Job 2'}, {'Date': '2018-01-03', 'Equip': 'A-2', 'Job': 'Job 1'}, {'Date': '2018-01-04', 'Equip': 'A-2', 'Job': 'Job 3'}, {'Date': '2018-01-05', 'Equip': 'A-2', 'Job': 'Job 3'}]

Sortie:

[{'Equip': 'A-1', 'Job': 'Job 1', 'Last': '2018-01-01', 'First': '2018-01-01'}, 
 {'Equip': 'A-1', 'Job': 'Job 2', 'Last': '2018-01-02', 'First': '2018-01-02'}, 
 {'Equip': 'A-1', 'Job': 'Job 1', 'Last': '2018-01-03', 'First': '2018-01-03'}, 
 {'Equip': 'A-1', 'Job': 'Job 2', 'Last': '2018-01-05', 'First': '2018-01-04'}, 
 {'Equip': 'A-2', 'Job': 'Job 1', 'Last': '2018-01-03', 'First': '2018-01-03'}, 
 {'Equip': 'A-2', 'Job': 'Job 3', 'Last': '2018-01-05', 'First': '2018-01-04'}]
12
Ajax1234

Je suggère d'utiliser pandas pour cela. 

itertools.groupby est cool mais IMO un peu plus difficile à comprendre.

>>> import pandas as pd
>>>
>>> my_list = [
...:    {'Equip': 'A-1', 'Job': 'Job 1', 'Date': '2018-01-01'},
...:    {'Equip': 'A-1', 'Job': 'Job 1', 'Date': '2018-01-02'},
...:    {'Equip': 'A-1', 'Job': 'Job 1', 'Date': '2018-01-03'},
...:    {'Equip': 'A-1', 'Job': 'Job 2', 'Date': '2018-01-04'},
...:    {'Equip': 'A-1', 'Job': 'Job 2', 'Date': '2018-01-05'},
...:    {'Equip': 'A-2', 'Job': 'Job 1', 'Date': '2018-01-03'},
...:    {'Equip': 'A-2', 'Job': 'Job 3', 'Date': '2018-01-04'},
...:    {'Equip': 'A-2', 'Job': 'Job 3', 'Date': '2018-01-05'}
...:]
>>>
>>> df = pd.DataFrame(my_list)
>>> df['Date'] = pd.to_datetime(df['Date'])
>>> groups = df.groupby(['Equip', 'Job']).agg({'Date': [min, max]}).reset_index()    
>>> groups.columns = ['Equip', 'Job', 'First', 'Last']
>>> groups
>>> 
  Equip    Job      First       Last
0   A-1  Job 1 2018-01-01 2018-01-03
1   A-1  Job 2 2018-01-04 2018-01-05
2   A-2  Job 1 2018-01-03 2018-01-03
3   A-2  Job 3 2018-01-04 2018-01-05
>>>
>>> groups.to_dict(orient='records')
>>> 
[{'Equip': 'A-1',
  'First': Timestamp('2018-01-01 00:00:00'),
  'Job': 'Job 1',
  'Last': Timestamp('2018-01-03 00:00:00')},
 {'Equip': 'A-1',
  'First': Timestamp('2018-01-04 00:00:00'),
  'Job': 'Job 2',
  'Last': Timestamp('2018-01-05 00:00:00')},
 {'Equip': 'A-2',
  'First': Timestamp('2018-01-03 00:00:00'),
  'Job': 'Job 1',
  'Last': Timestamp('2018-01-03 00:00:00')},
 {'Equip': 'A-2',
  'First': Timestamp('2018-01-04 00:00:00'),
  'Job': 'Job 3',
  'Last': Timestamp('2018-01-05 00:00:00')}]

Je suggère de garder les dates comme horodatage.

3
timgeb

Vous pouvez utiliser des pandas ici, qui sont une sorte "d'interface de base de données" pour les données:

import pandas as pd

df = pd.DataFrame(my_list)
df2 = df.groupby(['Equip', 'Job']).agg(['min', 'max']).rename(columns={'min': 'First', 'max': 'Last'})
df2.columns = df2.columns.droplevel()
df2 = df2.reset_index()
result = df2.to_dict('records')

pour l'échantillon donné, cela donne:

>>> df2.to_dict('records')
[{'Equip': 'A-1', 'Job': 'Job 1', 'First': '2018-01-01', 'Last': '2018-01-03'},
 {'Equip': 'A-1', 'Job': 'Job 2', 'First': '2018-01-04', 'Last': '2018-01-05'},
 {'Equip': 'A-2', 'Job': 'Job 1', 'First': '2018-01-03', 'Last': '2018-01-03'},
 {'Equip': 'A-2', 'Job': 'Job 3', 'First': '2018-01-04', 'Last': '2018-01-05'}]

Si le format de date est pas '%Y-%m-%d', il faut d’abord le convertir avec pd.to_datetime(..) comme:

import pandas as pd

df = pd.DataFrame(my_list)
df['Date'] = pd.to_datetime(df['Date'])
df2 = df.groupby(['Equip', 'Job']).agg(['min', 'max']).rename(columns={'min': 'First', 'max': 'Last'})
df2.columns = df2.columns.droplevel()
df2 = df2.reset_index()
result = df2.to_dict('records')
2
Willem Van Onsem