Pandas执行.gt()比较后索引自动排序异常问题排查
问题描述
现有按person和time_bought字段排序的DataFrame,索引无重复值,且不是从0开始的连续编号,需求是根据上一行的product字段值匹配对应时间阈值,校验相邻记录的时间差。
相关样例数据如下:
person time_bought product 42 abby 2:21 fruit 12 abby 2:55 fruit 10 abby 10:35 other 3 barry 12:00 fruit ...
初始阈值定义与参考阈值列计算代码:
thresh = {'fruit': pd.Timedelta('10min'), 'other': pd.Timedelta('2min')} # 基于上一行的product映射自定义阈值 ref = df.groupby('person')['product'].shift().map(thresh)
执行过程中遇到异常:计算布尔掩码m1时,返回结果没有保留原DataFrame的索引,而是从最小索引值开始重新排序,问题代码与错误返回如下:
# 将时间差和自定义阈值比较 m1 = df.loc[df.product=="fruit", 'time_bought'].groupby(df['person']).diff().gt(ref) # 错误输出,索引被重排 3 False 4 False
如果移除代码末尾的.gt(ref)调用,仅执行到diff()步骤,返回结果可以正常保留筛选行的原始索引,对应执行结果如下:
df.loc[df.product=="fruit", 'time_bought'].groupby(df['person']).diff() # 正常输出,保留原始索引 42 NaT 12 0 Days 00:34:00 ...
该索引自动排序的问题会导致后续执行m1.cumsum()的逻辑出错。
问题根因
索引异常重排的核心原因是pandas的索引自动对齐机制:
- 执行到
diff()步骤时返回的Series,仅包含product=="fruit"的筛选行,索引为这些行对应的原始非连续索引 - 预先计算的
ref是和全量DataFrame等长的Series,包含所有行的索引 - 当两个长度、索引标签都不匹配的Series做
gt()比较时,pandas会自动对齐两者索引,取索引并集后按索引值升序排列,最终返回的结果会丢失原筛选行的索引顺序,还会引入多余的无效索引项。
解决方案
选择以下任意一种方案即可保证索引正常保留:
- 方案1:比较前先对齐两个Series的索引,保证参与比较的两个对象索引完全一致
# 先提取需要的fruit行子集 fruit_sub = df.loc[df["product"] == "fruit"] # 计算子集内的相邻时间差 time_diff = fruit_sub.groupby("person")["time_bought"].diff() # 从全量ref中筛出子集行对应的阈值,保证索引和time_diff完全匹配 sub_ref = ref.loc[fruit_sub.index] # 比较生成掩码,此时不会触发索引重排 m1 = time_diff.gt(sub_ref)
- 方案2:先在全量DataFrame上完成所有计算,最后再筛选目标行,全程不破坏原始索引
# 全量计算每个人相邻购买记录的时间差 df["delta"] = df.groupby("person")["time_bought"].diff() # 直接在全量数据上比较时间差和阈值,再筛出fruit行的掩码 m1 = df.loc[df["product"] == "fruit", "delta"].gt(ref[df["product"] == "fruit"]) # 不需要保留中间计算列的话可以直接删除 df.drop(columns="delta", inplace=True)
内容的提问来源于stack exchange,提问作者asd
相关产品推荐
相关产品推荐

