这里的两个最佳答案建议:
df.groupby(cols).agg(lambda x:x.value_counts().index[0])
或者,最好是
df.groupby(cols).agg(pd.Series.mode)
然而,这两种情况在简单的边缘情况下都失败了,如下所示:
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]
})
首先:
df.groupby(['client_id', 'date']).agg(lambda x:x.value_counts().index[0])
产量IndexError
(因为 group 返回的空系列C
)。第二:
df.groupby(['client_id', 'date']).agg(pd.Series.mode)
返回ValueError: Function does not reduce
,因为第一组返回两个列表(因为有两种模式)。(如此处所述,如果第一组返回单一模式,这将起作用!)
这种情况的两种可能的解决方案是:
import scipy
x.groupby(['client_id', 'date']).agg(lambda x: scipy.stats.mode(x)[0])
以及 cs95 在此处的评论中给我的解决方案:
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)
但是,所有这些都很慢,不适合大型数据集。我最终使用的解决方案a)可以处理这些情况并且b)要快得多,是abw33答案的轻微修改版本(应该更高):
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
本质上,该方法一次处理一个列并输出一个 df,因此concat
您将第一个列视为 df,而不是密集的 df,然后迭代地将输出数组 ( values.flatten()
) 添加为 df 中的列。