Las dos respuestas principales aquí sugieren:
df.groupby(cols).agg(lambda x:x.value_counts().index[0])
o, preferiblemente
df.groupby(cols).agg(pd.Series.mode)
Sin embargo, ambos fallan en casos extremos simples, como se demuestra aquí:
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]
})
El primero:
df.groupby(['client_id', 'date']).agg(lambda x:x.value_counts().index[0])
rendimientos IndexError
(debido a la Serie vacía devuelta por grupo C
). El segundo:
df.groupby(['client_id', 'date']).agg(pd.Series.mode)
devuelve ValueError: Function does not reduce
, ya que el primer grupo devuelve una lista de dos (ya que hay dos modos). (Como se documenta aquí , si el primer grupo devolviera un modo único, ¡esto funcionaría!)
Dos posibles soluciones para este caso son:
import scipy
x.groupby(['client_id', 'date']).agg(lambda x: scipy.stats.mode(x)[0])
Y la solución que me dio cs95 en los comentarios 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)
Sin embargo, todos estos son lentos y no adecuados para grandes conjuntos de datos. Una solución que terminé usando que a) puede lidiar con estos casos yb) es mucho, mucho más rápida, es una versión ligeramente modificada de la respuesta de abw33 (que debería ser más alta):
def get_mode_per_column(dataframe, group_cols, col):
return (dataframe.fillna(-1)
.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))
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
Esencialmente, el método funciona en una columna a la vez y genera un df, por lo que en lugar de concat
, que es intensivo, trata el primero como un df y luego agrega iterativamente la matriz de salida ( values.flatten()
) como una columna en el df.