如何在groupby后向多级索引DataFrame正确添加平均价格列?
修复三级列索引DataFrame添加均值列的结构问题
1. 修复现有代码的问题
你当前代码的核心问题是直接修改了多级列索引的_levels属性——多级索引的levels是共享结构,修改后会篡改原有列的层级信息,导致结构混乱。正确的做法是重新构造均值列的三级索引,而非修改原索引的内部属性:
import pandas as pd import numpy as np df_f_avg = pd.DataFrame({ ('a1c9', 'open', 'call'): [3, 0.50, 3.0], ('a1c9', 'close', 'call'): [4, 0.35, 3.5], ('a1c9', 'high', 'call'): [1, 0.20, 1.5], ('a1c9', 'low', 'call'): [2, 0.09, 2.5], ('a1p9', 'open', 'put'): [5, 0.05, 4.0], ('a1p9', 'close', 'put'): [6, 0.10, 5.5], ('a1p9', 'high', 'put'): [7, 0.20, 6.0], ('a1p9', 'low', 'put'): [8, 0.31, 7.0]}) df_f_avg.columns.names = ['Ticker', 'Price', 'Type'] # 按Ticker分组计算均值 avg_data = df_f_avg.groupby(level='Ticker', axis=1).mean() # 为均值列构造正确的三级列索引 new_columns = [] for ticker in avg_data.columns: # 获取当前Ticker对应的期权类型(符合期权数据中一个Ticker对应单一Type的逻辑) ticker_type = df_f_avg.columns[df_f_avg.columns.get_level_values('Ticker') == ticker][0][2] new_columns.append( (ticker, 'avg', ticker_type) ) avg_data.columns = pd.MultiIndex.from_tuples(new_columns, names=['Ticker', 'Price', 'Type']) # 合并并按索引排序,保证结构规整 df_f_avg = pd.concat([df_f_avg, avg_data], axis=1).sort_index(axis=1) print(df_f_avg)
这段代码会生成符合预期的列结构,原有列的层级不会被破坏,同时新增的均值列会正确匹配('Ticker', 'avg', 'Type')的结构。
2. 更简洁高效的实现方式
利用pandas内置的多级索引操作和分组方法,可以大幅简化代码,同时保证性能和通用性:
方法一:结合stack/unstack的通用写法
import pandas as pd import numpy as np df_f_avg = pd.DataFrame({ ('a1c9', 'open', 'call'): [3, 0.50, 3.0], ('a1c9', 'close', 'call'): [4, 0.35, 3.5], ('a1c9', 'high', 'call'): [1, 0.20, 1.5], ('a1c9', 'low', 'call'): [2, 0.09, 2.5], ('a1p9', 'open', 'put'): [5, 0.05, 4.0], ('a1p9', 'close', 'put'): [6, 0.10, 5.5], ('a1p9', 'high', 'put'): [7, 0.20, 6.0], ('a1p9', 'low', 'put'): [8, 0.31, 7.0]}) df_f_avg.columns.names = ['Ticker', 'Price', 'Type'] # 堆叠Price层级后分组计算均值,再重新展开为三级索引 avg_df = (df_f_avg.stack(['Price']) .groupby(['Ticker', 'Type']) .mean() .unstack(['Ticker', 'Type']) .rename(columns={'0': 'avg'}) .swaplevel(0,1,axis=1) .swaplevel(1,2,axis=1)) avg_df.columns.names = ['Ticker', 'Price', 'Type'] # 合并并排序 df_f_avg = pd.concat([df_f_avg, avg_df], axis=1).sort_index(axis=1) print(df_f_avg)
方法二:transform一行式实现
# 直接为每个Ticker计算均值并添加为新列 df_f_avg = df_f_avg.join( df_f_avg.groupby(level='Ticker', axis=1) .transform('mean') .rename(columns=lambda x: (x, 'avg', df_f_avg.columns[df_f_avg.columns.get_level_values('Ticker')==x][0][2])) ).sort_index(axis=1)
优势说明:
- 可靠性:完全使用pandas官方API操作,避免修改索引内部属性导致的未知问题
- 通用性:支持任意数量的Ticker,无需手动遍历或硬编码
- 高效性:利用pandas内置的向量化操作,比手动构造DataFrame性能更高,适合大数据量场景
内容的提问来源于stack exchange,提问作者Vasiliy Deryuga
相关产品推荐
相关产品推荐

