如何将DataFrame转为pandas IndexSlice以索引MultiIndex多层DataFrame
问题描述
我有以下2个DataFrame:df1和df2:
import pandas as pd m_idx = pd.MultiIndex.from_product([range(3), range(3, 6), range(6, 8)]) m_idx.names = ['a', 'b', 'c'] df1 = pd.DataFrame(None, index=m_idx, columns=['x1', 'x2', 'x3']) df1.loc[:, 'x1'] = m_idx.get_level_values('a') + m_idx.get_level_values('b') + m_idx.get_level_values('c') df1.loc[:, 'x2'] = df1.loc[:, 'x1'] * 2 df1.loc[:, 'x3'] = df1.loc[:, 'x1'] * 3 df2 = pd.DataFrame({'a': [0, 2], 'c': [6, 6]})
df1输出示例:
x1 x2 x3 a b c 0 3 6 9 18 27 7 10 20 30 4 6 10 20 30 7 11 22 33 5 6 11 22 33 7 12 24 36 1 3 6 10 20 30 7 11 22 33 4 6 11 22 33 7 12 24 36 5 6 12 24 36 7 13 26 39 2 3 6 11 22 33 7 12 24 36 4 6 12 24 36 7 13 26 39 5 6 13 26 39 7 14 28 42
df2输出示例:
a c 0 0 6 1 2 6
df2的列名对应df1的MultiIndex层级名,每一行是我需要从df1中查询的索引键组合。我希望将df2转换为能执行下述逻辑的索引参数:
df1.loc[pd.IndexSlice[[0, 2], :, [6, 6]], :]
上述代码的返回结果如下:
x1 x2 x3 a b c 0 3 6 9 18 27 4 6 10 20 30 5 6 11 22 33 2 3 6 11 22 33 4 6 12 24 36 5 6 13 26 39
我此前参考其他方案采用逐行遍历df2再拼接结果的方式实现,代码如下:
df_list = [df1.loc[(v[0], slice(None), v[1]), :] for r, v in df2.iterrows()] df_sliced = pd.concat(df_list)
但该方法在数据量大时效率过低,无法满足我的需求,因此希望找到使用pd.IndexSlice或其他更高效的替代方案。
解决方案
以下是3种性能远高于逐行遍历的实现方案:
方案1:多层级布尔索引(性能最优推荐)
无需重置索引,直接通过索引层级匹配构造过滤掩码,适合大数据量场景:
# 把df2的行转为元组集合,用于快速匹配 match_keys = set(df2.itertuples(index=False, name=None)) # 去掉不需要筛选的b层级,直接匹配(a,c)组合是否在目标集合内 mask = df1.index.droplevel('b').isin(match_keys) df_sliced = df1[mask]
方案2:动态生成IndexSlice(适配动态层级场景)
如果需要灵活适配不同的筛选层级,可按索引名自动构造IndexSlice参数,无需手动写死层级顺序:
slice_params = [] for level_name in df1.index.names: if level_name in df2.columns: # 该层级需要筛选,取df2对应列的去重值作为筛选条件 slice_params.append(df2[level_name].unique().tolist()) else: # 该层级不需要筛选,保留所有值 slice_params.append(slice(None)) df_sliced = df1.loc[tuple(slice_params), :]
方案3:merge关联法(逻辑直观易理解)
如果更贴近常规表关联逻辑,写法易懂:
df_sliced = df1.reset_index().merge(df2, on=['a', 'c']).set_index(['a','b','c'])
以上三种方案输出结果均和预期完全一致,数据量越大前两种方案的性能优势越明显,比逐行遍历效率高数十到上百倍。
内容的提问来源于stack exchange,提问作者Arthur
相关产品推荐
相关产品推荐

