基于前一行值计算Pandas列值时遇KeyError的解决方法
问题描述
现有包含多日期订单的DataFrame,需计算每个订单的实际完成时间,规则如下:
- 若前一个订单的完成时间晚于当前订单的原始开始时间,则当前订单的实际开始时间更新为前一个订单的完成时间;
- 计算需按日期分段,每日首个订单不依赖前一日最后一个订单的完成时间。
示例数据及DataFrame创建代码:
data = {'Date': ['2022-12-01', '2022-12-01', '2022-12-01', '2022-12-01', '2022-12-02', '2022-12-02', '2022-12-02', '2022-12-02'], 'Order Start Time': ['2022-12-01 10:50:00.000', '2022-12-01 10:53:33.721', '2022-12-01 10:58:14.217', '2022-12-01 10:58:57.162', '2022-12-02 10:50:00.000', '2022-12-02 10:53:33.721', '2022-12-02 10:58:14.217', '2022-12-02 10:58:57.162'], 'Order Rank Within Day': [1, 2, 3, 4, 1, 2, 3, 4], 'Expected Completion Time': [125, 153, 75, 99, 165, 133, 85, 129]} df = pd.DataFrame(data, columns=['Date', 'Order Start Time', 'Order Rank Within Day', 'Expected Completion Time'])
用户尝试的代码(运行出现KeyError):
for row in df.itertuples(): df['completion_time'] = np.where(df['Order Rank Within Day']==1, pd.to_datetime(df['Order Start Time']) + pd.to_timedelta(df['Expected Completion Time'], unit='s'), df['completion_time'].at[row.Index-1])
问题分析
- 初始化错误:第一次循环时,
row.Index为0,访问row.Index-1=-1,且completion_time列尚未创建,直接触发KeyError; - 逻辑错误:
np.where是整列批量操作,循环中每次覆盖整列,无法实现「当前订单依赖前一个订单结果」的递推计算; - 分组缺失:未按
Date分组,会导致跨日期订单互相影响,违反「每日独立计算」的需求。
解决方案
采用按日期分组+组内递推计算的方式,实现符合需求的逻辑:
步骤1:转换时间格式
先将Order Start Time转换为datetime类型,便于时间运算:
import pandas as pd df['Order Start Time'] = pd.to_datetime(df['Order Start Time'])
步骤2:定义组内计算函数
针对每个日期的订单组,逐行递推计算实际开始时间和完成时间:
def calculate_daily_completion(group): # 确保组内按订单排序(示例已排序,此步为通用兼容) group = group.sort_values('Order Rank Within Day').reset_index(drop=True) # 初始化实际开始时间:首个订单用原始开始时间 group['Actual Start Time'] = group['Order Start Time'] # 从第二个订单开始递推 for i in range(1, len(group)): # 获取前一个订单的完成时间 prev_completion = group['Actual Start Time'].iloc[i-1] + pd.to_timedelta(group['Expected Completion Time'].iloc[i-1], unit='s') # 实际开始时间取「原始开始时间」和「前一个完成时间」的较大值 group['Actual Start Time'].iloc[i] = max(group['Order Start Time'].iloc[i], prev_completion) # 计算实际完成时间 group['Actual Completion Time'] = group['Actual Start Time'] + pd.to_timedelta(group['Expected Completion Time'], unit='s') return group
步骤3:分组应用函数
按Date分组后应用上述函数,得到最终结果:
df = df.groupby('Date').apply(calculate_daily_completion).reset_index(drop=True)
优化版(矢量化递推,避免显式循环)
如果订单量较大,可使用expanding结合apply实现矢量化递推,提升效率:
def vectorized_completion(group): group = group.sort_values('Order Rank Within Day').reset_index(drop=True) # 计算每个订单的理论完成时间(原始开始+时长) group['Theoretical Completion'] = group['Order Start Time'] + pd.to_timedelta(group['Expected Completion Time'], unit='s') # 递推计算实际完成时间:取当前理论完成时间和前一个实际完成时间的较大值 group['Actual Completion Time'] = group['Theoretical Completion'].expanding().apply( lambda x: max(x.iloc[-1], x.iloc[-2]) if len(x)>=2 else x.iloc[0] ) # 反推实际开始时间 group['Actual Start Time'] = group['Actual Completion Time'] - pd.to_timedelta(group['Expected Completion Time'], unit='s') return group df = df.groupby('Date').apply(vectorized_completion).reset_index(drop=True)
内容的提问来源于stack exchange,提问作者D Patel
相关产品推荐
相关产品推荐

