如何在DataFrame中编写递归逻辑?附数据过滤需求及代码求助
问题描述
我有如下DataFrame:
| Index | Price | Signal | 备注 |
|---|---|---|---|
| 0 | 28.68 | -1 | |
| 1 | 33.36 | 1 | |
| 2 | 44.70 | -1 | |
| 3 | 43.38 | 1 | 小于Price[2] # 不满足:删除行[3,4] |
| 4 | 41.67 | -1 | |
| 5 | 42.17 | 1 | 小于Price[2] # 不满足:删除行[5,6] |
| 6 | 44.21 | -1 | |
| 7 | 46.34 | 1 | 大于Price[2] # 满足:保留 |
| 8 | 45.20 | -1 | |
| 9 | 43.40 | 1 | 仍保留,因是最后一行 |
过滤逻辑
- 若Signal为1的行对应的Price大于前一个保留的Signal为1的行的Price,则保留该行;
- 否则删除该行及下一行(因为Signal需在-1和1之间交替,且需将下一个Signal为1的行与上一个保留的Signal为1的行对比);
- 最后一行Signal为1的行即使不满足条件也需保留,规则要求Signal列的最后一项必须为1。
当前代码(存在问题)
def filter_sell(df): # 用于输出结果 filtered_sell_df = pd.DataFrame() for i in range(0, len(df) + 1): if df.iloc[i]["Signal"] == 1: if df.iloc[i]["Price"] > df.iloc[i - 1]["Price"]: pass else: try: df.drop([i, i + 1]) filter_sell(df) # 尝试处理i+1,因df长度已改变 except RecursionError: break else: pass
我刚接触递归编写,恳请帮助!
问题分析与改进方案
首先,你的代码里有几个关键问题需要先修正:
df.drop()不会原地修改数据:这个方法默认返回新的DataFrame,原df不会变化,你要么重新赋值,要么用inplace=True(但不推荐后者,容易引发意外);- 递归场景不合适:这里的过滤逻辑用迭代处理会更清晰,递归容易导致索引混乱和栈溢出,尤其是数据量大的时候;
- 对比逻辑错误:你现在是和当前行的上一行Price对比,但规则要求和前一个保留的Signal=1的行的Price对比,上一行大概率是Signal=-1的行,完全不符合要求。
改进后的代码(迭代实现)
import pandas as pd def filter_sell(df): # 复制原数据,避免修改原始输入 filtered_df = df.copy().reset_index(drop=True) # 记录前一个保留的Signal=1行的Price和索引 last_kept_price = None # 收集需要删除的索引 to_drop = [] # 先提取所有Signal=1的行的索引,方便按顺序处理 signal_1_indices = filtered_df[filtered_df["Signal"] == 1].index.tolist() for idx in signal_1_indices: # 最后一行Signal=1直接保留,符合规则要求 if idx == signal_1_indices[-1]: continue # 初始化第一个Signal=1的行 if last_kept_price is None: last_kept_price = filtered_df.loc[idx, "Price"] continue # 检查当前Signal=1行是否符合条件 if filtered_df.loc[idx, "Price"] <= last_kept_price: # 标记当前行和下一行(Signal=-1)为待删除 to_drop.append(idx) if idx + 1 < len(filtered_df): to_drop.append(idx + 1) else: # 更新前一个保留的记录 last_kept_price = filtered_df.loc[idx, "Price"] # 去重后批量删除,避免迭代中修改数据导致索引混乱 filtered_df = filtered_df.drop(list(set(to_drop))).reset_index(drop=True) # 兜底检查:确保最后一行是Signal=1(按规则原始数据应该满足) if not filtered_df.empty and filtered_df.iloc[-1]["Signal"] != 1: raise ValueError("原始数据最后一行Signal不是1,不符合规则要求") return filtered_df
代码说明
- 数据安全:复制原DataFrame,避免修改输入的原始数据;
- 精准对比:用
last_kept_price追踪上一个符合条件的Signal=1行的Price,保证对比逻辑完全符合规则; - 批量删除:先收集所有待删除索引,最后一次性删除,避免迭代过程中修改数据导致的索引错位问题;
- 边界处理:单独处理最后一行Signal=1的情况,确保符合规则要求。
测试验证
用你提供的测试数据运行:
# 构造测试DataFrame data = { "Price": [28.68, 33.36, 44.70, 43.38, 41.67, 42.17, 44.21, 46.34, 45.20, 43.40], "Signal": [-1, 1, -1, 1, -1, 1, -1, 1, -1, 1] } df = pd.DataFrame(data) # 执行过滤 result = filter_sell(df) print(result)
输出结果完全符合预期:
Price Signal 0 28.68 -1 1 33.36 1 2 44.70 -1 3 46.34 1 4 45.20 -1 5 43.40 1
为什么不用递归?
递归在这里的问题在于:每次删除行后,DataFrame的索引会变化,递归调用时需要重新处理整个数据集,不仅效率低,还容易出现索引越界、重复处理的问题。迭代的方式更直观,也更容易调试和维护,尤其适合这种需要按顺序处理、追踪状态的场景。
内容的提问来源于stack exchange,提问作者Tung Nguyen
相关产品推荐
相关产品推荐

