如何遍历两个异构Pandas DataFrame并按条件修改其中一个?
问题:将df2的标记匹配到df1对应时间区间行
需求说明
现有两个DataFrame:
- df1的
start_times列存储升序排列的事件开始时间,flag和flag_times列初始全为NaN - df2包含
flag(左l/右r标记)和对应的flag_times时间点
需要将每个df2的flag_times匹配到df1的对应行,满足条件:df1.loc[row, 'start_times'] ≤ df2.loc[key_count, 'flag_times'] < df1.loc[row+1, 'start_times']
匹配成功后,将df2的flag和flag_times填入df1对应行,其余行保留NaN。
df1初始示例
| start_times | flag | flag_times |
|---|---|---|
| 235 | NaN | NaN |
| 457 | NaN | NaN |
| 950 | NaN | NaN |
| 1902 | NaN | NaN |
df2示例
| flag | flag_times |
|---|---|
| l | 390 |
| r | 1000 |
期望结果df1
| start_times | flag | flag_times |
|---|---|---|
| 235 | l | 390 |
| 457 | NaN | NaN |
| 950 | r | 1000 |
| 1902 | NaN | NaN |
原代码问题
用户编写的循环代码存在以下问题:
- 条件判断错误:原代码用
<= df1.at[row+1, 'start_times'],不符合需求中小于下一行start_times的要求 - 循环逻辑漏洞:若df2的
flag_times不在任何df1区间内,或两者顺序不匹配,会导致循环陷入死循环或匹配失败 - 效率低下:
at逐行操作在大数据量下性能极差,无法处理大规模数据集
解决方案
方法1:用pandascut函数(高效处理大数据)
利用df1start_times的有序性,通过pd.cut快速给df2的时间分配对应区间,再批量合并数据:
import pandas as pd import numpy as np import random # 修正测试数据生成代码的错误 times = random.sample(range(1, 10000), 10) times.sort() flag_times = random.sample(range(times[0], times[-1]), 2) flag_times.sort() flags = ['l', 'r'] # 生成对应长度的标记列表 df1 = pd.DataFrame({ 'start_times': times, 'flag': np.nan, 'flag_times': np.nan }) df2 = pd.DataFrame({ 'flag': flags, 'flag_times': flag_times }) # 生成区间边界:df1的start_times + 无穷大作为最后区间上限 bins = df1['start_times'].tolist() + [np.inf] # 给df2的每个flag_times分配对应的df1行索引 df2['row_idx'] = pd.cut(df2['flag_times'], bins=bins, labels=df1.index[:-1], include_lowest=True) # 批量将df2数据映射到df1 df1.loc[df2['row_idx'], ['flag', 'flag_times']] = df2[['flag', 'flag_times']].values print(df1)
方法2:双指针优化(适合理解逻辑,大数据量也高效)
基于两个序列的有序性,用双指针只遍历一次,避免嵌套循环的冗余操作:
row = 0 key_count = 0 len_df1 = len(df1) len_df2 = len(df2) while row < len_df1 - 1 and key_count < len_df2: current_start = df1.iloc[row]['start_times'] next_start = df1.iloc[row+1]['start_times'] flag_time = df2.iloc[key_count]['flag_times'] if current_start <= flag_time < next_start: # 匹配成功,批量填充数据 df1.loc[row, ['flag', 'flag_times']] = df2.iloc[key_count][['flag', 'flag_times']] key_count += 1 row += 1 elif flag_time < current_start: # 时间早于当前区间,跳过该标记 key_count += 1 else: # 时间晚于当前区间,移动到下一个区间 row += 1 # 处理大于df1最后一个start_time的标记(可选逻辑) while key_count < len_df2: if df2.iloc[key_count]['flag_times'] >= df1.iloc[-1]['start_times']: df1.loc[len_df1-1, ['flag', 'flag_times']] = df2.iloc[key_count][['flag', 'flag_times']] key_count += 1 print(df1)
关键优化点
- 双指针遍历时间复杂度为O(n+m),远优于嵌套循环
- 使用
loc/iloc批量赋值,避免逐行at操作的性能损耗 pd.cut是pandas向量化操作,处理大规模数据时性能最优
内容的提问来源于stack exchange,提问作者Revati Shivnekar
相关产品推荐
相关产品推荐

