Pandas DataFrame数据补全与利润计算:正确性及性能优化问询
问题描述
数据结构
df1(主数据)
| S. No. | Quantity | Product | Option | Price |
|---|---|---|---|---|
| 1 | 2 | X | F1 | 100.0 |
| 2 | 1 | Z | G1 | 150.0 |
df2(补全数据)
注:df2存在重复列名Option,以下分析默认取第一列Option用于匹配
| Product | Option | Option | Buy_price |
|---|---|---|---|
| X | F1 | S1 | 80.0 |
| Z | G1 | H1 | 90.0 |
任务要求
利用df2的Buy_price为df1创建profit列,规则如下:
- 若
Price为0,则利润为0; - 存在
Product+Option精确匹配时,使用对应Buy_price计算利润(利润=Price - Buy_price); - 无精确匹配时,使用该
Option对应的Buy_price均值计算利润; - 仍无匹配时,取
Price的70%作为利润。
现有实现代码
df1['profit'] = float('nan') # 步骤1:Price<=0时利润设为0 df1.loc[df1['Price']<=0,'profit'] = 0.0 # 步骤2:按Product+Option分组处理 df2_gropued_P_O = df2.groupby(['Product','Option']).size().reset_index(name="Time") for row in df2_gropued_P_O.iterrows(): mean_sale = df2.loc[(df2['Product'] == row[1]['Product']) & (df2['Option'] == row[1]['Option']) & (df2['Price']>0)]['Price'].mean() df2.loc[(df2['Product'] == row[1]['Product']) & (df2['Option'] == row[1]['Option']) & (df2['Price']>0),['profit']] = mean_sale - row[1]['Buy_price'] # 步骤3:按Option分组处理 df2_gropued_O = fd2.groupby(['Option'])['Buy_price'].mean().reset_index() option_intersect = set.intersection(set(df2_gropued_O['Option']),set(df1[df1['df2_gropued_O'].isna()]['Option'])) for i in options_intersect: df1.loc[(df1['profit'].isna()) & (df1['Option']==i),['profit']] = option_o_cost_gf.loc[option_o_cost_gf['Option']==i,['Buy_price']].values[0][0] # 步骤4:填充剩余NaN值 for index in df2[df2['profit'].isna()].index.values.tolist(): df2.loc[index,'profit'] = 0.70 * df2.loc[index,'Price']
当前疑问
- 上述实现方法是否正确?
- 步骤4处理约650K行数据时速度极慢,如何优化以提升效率?
解答
一、现有代码的错误点
现有代码存在多处逻辑错误和笔误,完全不符合任务要求:
- 对象混淆:任务目标是给df1添加
profit列,但代码中大量操作df2的profit字段,完全偏离需求; - 笔误问题:步骤3中
fd2应为df2,option_o_cost_gf未定义,df1['df2_gropued_O'].isna()是错误的列引用; - 逻辑偏差:
- 步骤2中计算
mean_sale - row[1]['Buy_price']不符合任务规则(任务要求用df1的Price减去对应Buy_price); - 步骤3中直接赋值Buy_price均值,未用df1的Price减去该均值;
- 步骤4错误操作df2而非df1。
- 步骤2中计算
二、正确实现思路(矢量化操作,避免循环)
Pandas的核心优势是矢量化操作,完全不需要遍历行,以下是符合规则的高效实现:
1. 预处理df2(解决重复列名问题)
# 重命名df2的重复Option列 df2.columns = ['Product', 'Option', 'Option_2', 'Buy_price'] # 预先计算两种匹配所需的Buy_price映射 # ① Product+Option精确匹配的Buy_price(取第一个匹配值,若有重复可按需调整) po_buy_price = df2.drop_duplicates(subset=['Product', 'Option']).set_index(['Product', 'Option'])['Buy_price'] # ② Option分组的Buy_price均值 option_buy_mean = df2.groupby('Option')['Buy_price'].mean()
2. 按规则计算df1的profit
import pandas as pd # 初始化profit列为空值 df1['profit'] = pd.NA # 规则1:Price<=0时利润为0 df1.loc[df1['Price'] <= 0, 'profit'] = 0.0 # 规则2:精确匹配Product+Option,计算profit=Price - Buy_price match_mask = df1.set_index(['Product', 'Option']).index.isin(po_buy_price.index) df1.loc[match_mask, 'profit'] = df1.loc[match_mask, 'Price'] - df1.loc[match_mask].apply( lambda x: po_buy_price.loc[(x['Product'], x['Option'])], axis=1 ) # 规则3:无精确匹配但Option存在于df2中,用Option的Buy_price均值计算 remaining_mask = df1['profit'].isna() option_match_mask = remaining_mask & df1['Option'].isin(option_buy_mean.index) df1.loc[option_match_mask, 'profit'] = df1.loc[option_match_mask, 'Price'] - df1.loc[option_match_mask, 'Option'].map(option_buy_mean) # 规则4:剩余空值用Price的70%填充 df1['profit'] = df1['profit'].fillna(df1['Price'] * 0.7)
三、效率优化说明
你步骤4用循环遍历650K行是Pandas的大忌——行级循环的时间复杂度为O(n),且每一次loc操作都会触发数据查找,速度极慢。优化核心是用矢量化操作替代循环:
- 所有规则通过掩码(mask)、
map、fillna等矢量化方法实现,时间复杂度大幅降低; - 预先计算好映射表(如
po_buy_price、option_buy_mean),避免重复计算; - 利用Pandas的C底层优化处理大规模数据,650K行的处理时间会缩短到几秒甚至更短。
内容的提问来源于stack exchange,提问作者Mohit Kapoor
相关产品推荐
相关产品推荐

