Die beiden besten Antworten hier legen nahe:
df.groupby(cols).agg(lambda x:x.value_counts().index[0])
oder vorzugsweise
df.groupby(cols).agg(pd.Series.mode)
Beide scheitern jedoch in einfachen Randfällen, wie hier gezeigt:
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]
})
Der Erste:
df.groupby(['client_id', 'date']).agg(lambda x:x.value_counts().index[0])
Ausbeuten IndexError
(wegen der leeren Reihe, die von der Gruppe zurückgegeben wird C
). Der Zweite:
df.groupby(['client_id', 'date']).agg(pd.Series.mode)
gibt zurück ValueError: Function does not reduce
, da die erste Gruppe eine Liste von zwei zurückgibt (da es zwei Modi gibt). (Wie hier dokumentiert , würde dies funktionieren, wenn die erste Gruppe einen einzelnen Modus zurückgeben würde!)
Zwei mögliche Lösungen für diesen Fall sind:
import scipy
x.groupby(['client_id', 'date']).agg(lambda x: scipy.stats.mode(x)[0])
Und die Lösung, die mir cs95 in den Kommentaren hier gegeben hat :
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)
All dies ist jedoch langsam und nicht für große Datenmengen geeignet. Eine Lösung, mit der ich am Ende a) diese Fälle behandeln kann und b) viel, viel schneller ist, ist eine leicht modifizierte Version der Antwort von abw33 (die höher sein sollte):
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
Im Wesentlichen arbeitet die Methode jeweils mit einer Spalte und gibt einen df aus. Statt concat
intensiv zu behandeln, behandeln Sie den ersten als df und fügen dann iterativ das Ausgabearray ( values.flatten()
) als Spalte im df hinzu.