如何合并带时间切片的DataFrame:填充无Grade时段并拆分重叠行
解决思路与实现代码
核心思路
不用循环分组,而是通过交叉连接+时间区间计算高效处理,步骤如下:
- 将DF1和DF2按
Country+City做交叉连接,覆盖所有可能的组合; - 计算每个组合的时间重叠区间,筛选出有重叠的记录;
- 拆分DF1中与Grade区间重叠的部分,同时保留DF1中无重叠的原始区间(对应DF2字段为空);
- 合并所有结果,得到符合要求的最终DataFrame。
代码实现
首先导入必要库:
import pandas as pd
定义原始DataFrame(修正DF2字段拼写错误:Garde_Validity_End→Grade_Validity_End):
df1 = pd.DataFrame({ "Country": ['USA', 'USA','USA','UK','UK','UK'], "City": ['NYC','NYC','NYC','London','London','London'], "Start_Range": pd.to_datetime(['2016-01-01', '2020-07-01','2022-01-01','2019-01-01','2021-01-01','2023-01-01']), "End_Range": pd.to_datetime(['2020-06-30', '2021-12-31','2023-12-31','2020-12-31','2022-12-31','2023-12-31']) }) df2 = pd.DataFrame({ "Country": ['USA', 'USA','UK'], "City": ['NYC','NYC','London'], "Grade_Validity_Begin": pd.to_datetime(['2021-10-01','2023-01-01','2021-10-01']), "Grade_Validity_End": pd.to_datetime(['2022-12-31', '2099-12-31','2023-12-31']) })
步骤1:交叉连接并计算重叠区间
# 按Country和City做交叉连接,保留DF1的所有行 cross_join = df1.merge(df2, on=['Country', 'City'], how='left') # 计算每个组合的重叠区间起止时间 cross_join['overlap_start'] = cross_join[['Start_Range', 'Grade_Validity_Begin']].max(axis=1) cross_join['overlap_end'] = cross_join[['End_Range', 'Grade_Validity_End']].min(axis=1) # 筛选出存在时间重叠的记录 has_overlap = cross_join[cross_join['overlap_start'] <= cross_join['overlap_end']].copy() # 提取重叠部分的有效行,替换时间区间为重叠区间 overlap_rows = has_overlap.rename(columns={ 'overlap_start': 'Start_Range', 'overlap_end': 'End_Range' })[['Country', 'City', 'Start_Range', 'End_Range', 'Grade_Validity_Begin', 'Grade_Validity_End']]
步骤2:提取DF1中无重叠的区间部分
# 标记DF1中哪些行存在至少一个重叠 df1_with_flag = df1.merge( has_overlap[['Country', 'City', 'Start_Range', 'End_Range']].drop_duplicates(), on=['Country', 'City', 'Start_Range', 'End_Range'], how='left', indicator=True ) # 情况1:DF1中完全没有对应Grade的行 no_grade_rows = df1_with_flag[df1_with_flag['_merge'] == 'left_only'].copy() no_grade_rows[['Grade_Validity_Begin', 'Grade_Validity_End']] = pd.NA no_grade_rows = no_grade_rows.drop(columns='_merge') # 情况2:DF1中有部分区间与Grade重叠,拆分出不重叠的前后段 split_rows = [] for _, row in has_overlap.iterrows(): # 重叠前的区间:若DF1起始早于重叠起始,保留该段 if row['Start_Range'] < row['overlap_start']: split_rows.append({ 'Country': row['Country'], 'City': row['City'], 'Start_Range': row['Start_Range'], 'End_Range': row['overlap_start'] - pd.Timedelta(days=1), 'Grade_Validity_Begin': pd.NA, 'Grade_Validity_End': pd.NA }) # 重叠后的区间:若DF1结束晚于重叠结束,保留该段 if row['End_Range'] > row['overlap_end']: split_rows.append({ 'Country': row['Country'], 'City': row['City'], 'Start_Range': row['overlap_end'] + pd.Timedelta(days=1), 'End_Range': row['End_Range'], 'Grade_Validity_Begin': pd.NA, 'Grade_Validity_End': pd.NA }) split_df = pd.DataFrame(split_rows)
步骤3:合并所有结果
# 合并三类行:重叠行、无Grade行、拆分出的无重叠行 final_df = pd.concat([overlap_rows, no_grade_rows, split_df], ignore_index=True) # 按国家、城市、起始时间排序,让结果更规整 final_df = final_df.sort_values(by=['Country', 'City', 'Start_Range']).reset_index(drop=True)
最终结果说明
最终的final_df完全满足需求:
- DF1中无对应Grade的时间区间,
Grade_Validity_Begin和Grade_Validity_End为空; - Grade有效时段与DF1切片重叠的部分,完整保留Grade字段;
- DF1被Grade区间拆分后的剩余无重叠部分,Grade字段为空。
内容的提问来源于stack exchange,提问作者Zara
相关产品推荐
相关产品推荐

