如何避免按两列分组的三层嵌套FOR循环及SettingWithCopyWarning?
解决方案:Pandas向量化替代嵌套循环+消除SettingWithCopyWarning
问题分析
原代码通过三层嵌套循环按Id1+Id2分组处理数据,核心逻辑是每组按时间顺序遍历,根据Col1和Col3的值设置Col4。但嵌套循环效率低下,且直接修改切片触发SettingWithCopyWarning,适合用Pandas分组向量化操作优化。
核心业务规则(从原循环提炼)
对每个Id1+Id2的分组(按TimeStamp升序):
- 遍历过程中一旦触发终止条件则停止处理后续行:
- 当前行
Col1=1且Col3>125→ 终止,后续行Col4保持0 - 当前行
Col1=3且Col3>=125→ 终止,后续行Col4保持0
- 当前行
- 非终止场景的处理:
Col1=1且Col3<125→ 跳过,Col4保持0,继续下一行Col1=3且Col3<125→ 设置Col4=1
优化方案
1. 向量化替代嵌套循环
利用groupby分组后结合自定义逻辑处理,避免显式循环,同时保证逻辑与原代码一致。对于大数据量,还可以通过累积布尔标记实现完全向量化,进一步提升效率。
2. 消除SettingWithCopyWarning
禁止直接修改切片副本,改为直接在原DataFrame上修改,或通过groupby处理后将结果合并回原DataFrame,彻底避免操作副本引发的警告。
完整实现代码
方案一:分组自定义函数(逻辑清晰,易维护)
import pandas as pd # 原始数据 d = { 'TimeStamp' : [1, 2, 3, 4, 5, 6, 7, 8, 9], 'Id1': [80, 80, 80, 90, 90, 90, 100, 100, 100], 'Id2': [10, 10, 10, 10, 10, 12, 14, 12, 12], 'Col1': [3, 3, 3, 3, 1, 3, 1, 3, 1], 'Col2': [1, 2, 3, 1, 2, 1, 1, 1, 1], 'Col3': [110, 120, 130, 110, 120, 130, 110, 120, 130], 'Col4': [0, 0, 0, 0, 0, 0, 0, 0, 0]} df = pd.DataFrame(data=d) df.sort_values(by='TimeStamp', ascending=True, inplace=True) time_diff = 125 # 定义分组处理函数 def process_group(group): active = True for idx in group.index: if not active: break col1 = group.loc[idx, 'Col1'] col3 = group.loc[idx, 'Col3'] if col1 == 1: if col3 > time_diff: active = False continue elif col1 ==3: if col3 < time_diff: group.loc[idx, 'Col4'] = 1 else: active = False else: active = False return group # 分组处理并合并结果 df = df.groupby(['Id1', 'Id2'], group_keys=False).apply(process_group) # 输出结果 print(df.to_markdown(index=False))
方案二:完全向量化实现(大数据量下效率更高)
import pandas as pd d = { 'TimeStamp' : [1, 2, 3, 4, 5, 6, 7, 8, 9], 'Id1': [80, 80, 80, 90, 90, 90, 100, 100, 100], 'Id2': [10, 10, 10, 10, 10, 12, 14, 12, 12], 'Col1': [3, 3, 3, 3, 1, 3, 1, 3, 1], 'Col2': [1, 2, 3, 1, 2, 1, 1, 1, 1], 'Col3': [110, 120, 130, 110, 120, 130, 110, 120, 130], 'Col4': [0, 0, 0, 0, 0, 0, 0, 0, 0]} df = pd.DataFrame(data=d) df.sort_values(by='TimeStamp', ascending=True, inplace=True) time_diff = 125 # 1. 标记每组内的终止触发行 df['terminate'] = ((df['Col1'] ==1) & (df['Col3'] > time_diff)) | ((df['Col1'] ==3) & (df['Col3'] >= time_diff)) # 2. 计算每组内从开始到第一个终止位置的累积有效标记(True表示可处理) df['active'] = ~df.groupby(['Id1', 'Id2'])['terminate'].cummax() # 3. 设置Col4:满足条件则设为1,否则为0 df['Col4'] = df.apply(lambda x: 1 if x['active'] and x['Col1']==3 and x['Col3']<time_diff else 0, axis=1) # 4. 清理临时列 df.drop(['terminate', 'active'], axis=1, inplace=True) print(df.to_markdown(index=False))
输出结果(与原循环完全一致)
| TimeStamp | Id1 | Id2 | Col1 | Col2 | Col3 | Col4 | |------------:|------:|------:|-------:|-------:|-------:|-------:| | 1 | 80 | 10 | 3 | 1 | 110 | 1 | | 2 | 80 | 10 | 3 | 2 | 120 | 1 | | 3 | 80 | 10 | 3 | 3 | 130 | 0 | | 4 | 90 | 10 | 3 | 1 | 110 | 1 | | 5 | 90 | 10 | 1 | 2 | 120 | 0 | | 6 | 90 | 12 | 3 | 1 | 130 | 0 | | 7 | 100 | 14 | 1 | 1 | 110 | 0 | | 8 | 100 | 12 | 3 | 1 | 120 | 1 | | 9 | 100 | 12 | 1 | 1 | 130 | 0 |
内容的提问来源于stack exchange,提问作者Automacao Na Pratica
相关产品推荐
相关产品推荐

