Les deux premières réponses suggèrent :
df.groupby(cols).agg(lambda x:x.value_counts().index[0])
ou, de préférence
df.groupby(cols).agg(pd.Series.mode)
Cependant, ces deux méthodes échouent dans des cas limites simples, comme le montre l'exemple suivant :
df = pd.DataFrame({
'client_id':['A', 'A', 'A', 'A', 'B', 'B', 'B', 'C'],
'date':['2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01', '2019-01-01'],
'location':['NY', 'NY', 'LA', 'LA', 'DC', 'DC', 'LA', np.NaN]
})
Le premier :
df.groupby(['client_id', 'date']).agg(lambda x:x.value_counts().index[0])
donne IndexError
(à cause de la série vide renvoyée par le groupe C
). La seconde :
df.groupby(['client_id', 'date']).agg(pd.Series.mode)
renvoie à ValueError: Function does not reduce
puisque le premier groupe renvoie une liste de deux (puisqu'il y a deux modes). (Comme documenté aquí si le premier groupe renvoyait un mode unique, cela fonctionnerait !)
Deux solutions sont possibles dans ce cas :
import scipy
x.groupby(['client_id', 'date']).agg(lambda x: scipy.stats.mode(x)[0])
Et la solution qui m'a été donnée par cs95 dans les commentaires aquí :
def foo(x):
m = pd.Series.mode(x);
return m.values[0] if not m.empty else np.nan
df.groupby(['client_id', 'date']).agg(foo)
Cependant, toutes ces méthodes sont lentes et ne sont pas adaptées aux grands ensembles de données. Une solution que j'ai fini par utiliser qui a) peut traiter ces cas et b) est beaucoup, beaucoup plus rapide, est une version légèrement modifiée de la réponse d'abw33 (qui devrait être plus élevée) :
def get_mode_per_column(dataframe, group_cols, col):
return (dataframe.fillna(-1) # NaN placeholder to keep group
.groupby(group_cols + [col])
.size()
.to_frame('count')
.reset_index()
.sort_values('count', ascending=False)
.drop_duplicates(subset=group_cols)
.drop(columns=['count'])
.sort_values(group_cols)
.replace(-1, np.NaN)) # restore NaNs
group_cols = ['client_id', 'date']
non_grp_cols = list(set(df).difference(group_cols))
output_df = get_mode_per_column(df, group_cols, non_grp_cols[0]).set_index(group_cols)
for col in non_grp_cols[1:]:
output_df[col] = get_mode_per_column(df, group_cols, col)[col].values
Essentiellement, la méthode travaille sur une colonne à la fois et produit un df, donc au lieu de concat
ce qui est intensif, on traite le premier comme un df, puis on ajoute itérativement le tableau de sortie ( values.flatten()
) comme une colonne dans le df.