Pandas多索引:按组特定条件删行及模式行添加优化
问题描述
我创建了带MultiIndex的DataFrame:
np.random.seed(0) lvl0 = ['A','B'] lvl1 = ['x', 'y', 'z'] idx = pd.MultiIndex.from_product([lvl0, lvl1]) cols = ['c1', 'c2'] df = pd.DataFrame(index=idx, columns=cols) df.loc[:] = np.random.randint(0,2, size=df.shape) for v in lvl0: df.loc[(v, 'mode'), :] = np.nan df.sort_index(inplace=True)
生成的DataFrame:
c1 c2 A mode NaN NaN x 0 1 y 1 0 z 1 1 B mode NaN NaN x 1 1 y 1 1 z 1 0
随后计算A、B组各列的众数并填充到对应行:
for c in cols: modes = df.groupby(level=0)[c].agg(pd.Series.mode) df.loc[(lvl0, 'mode'), c] = modes.values
得到结果:
c1 c2 A mode 1 1 x 0 1 y 1 0 z 1 1 B mode 1 1 x 1 1 y 1 1 z 1 0
现在有两个需求:
- 删除所有与对应一级分组(A、B)众数完全相同的行,期望结果如下:
c1 c2 A mode 1 1 x 0 1 y 1 0 B mode 1 1 z 1 0
我可以通过循环lvl0实现,但想找更优雅的方案(比如用groupby)。
2. 能否在计算众数时直接添加mode行,而非预先添加空(NaN)行?因为不预先添加会触发KeyError。
解决方案
1. 优雅删除与分组众数完全一致的行
不用循环分组,直接用groupby+transform就能搞定:
# 先计算每组的众数,取第一个结果(避免多众数情况) group_modes = df.groupby(level=0).agg(lambda x: pd.Series.mode(x).iloc[0]) # 把每组的众数广播到组内每一行 mode_broadcast = df.groupby(level=0)[cols].transform(lambda x: group_modes.loc[x.name]) # 生成筛选掩码:保留mode行,或者行数据不全等于组众数的行 mask = (df.index.get_level_values(1) == 'mode') | (~df[cols].eq(mode_broadcast).all(axis=1)) df_filtered = df[mask]
运行后df_filtered就是你要的结果。
2. 直接添加mode行(无需预先创建空行)
完全可以不用提前加空行,先算出每组众数,构造新的mode行DataFrame,再和原数据合并排序:
# 生成原始DataFrame(不提前加mode行) np.random.seed(0) lvl0 = ['A','B'] lvl1 = ['x', 'y', 'z'] idx = pd.MultiIndex.from_product([lvl0, lvl1]) cols = ['c1', 'c2'] df = pd.DataFrame(index=idx, columns=cols) df.loc[:] = np.random.randint(0,2, size=df.shape) # 计算每组众数,构造mode行数据 mode_rows = df.groupby(level=0).agg(lambda x: pd.Series.mode(x).iloc[0]) # 调整索引,把level1设为'mode' mode_rows = mode_rows.reset_index().assign(level1='mode').set_index(['level0', 'level1']) mode_rows.index.names = df.index.names # 保持索引名和原数据一致 # 合并原数据和mode行,再排序索引 df_with_mode = pd.concat([df, mode_rows]).sort_index()
之后要删除重复行的话,直接用第一部分的筛选逻辑就行。
内容的提问来源于stack exchange,提问作者MDescamps
相关产品推荐
相关产品推荐

