如何高效实现Pandas中基于地理区域的DataFrame模糊匹配合并
高效实现带地理层级与日期约束的左连接方案
针对你遇到的地理区域匹配不一致、性能低下的问题,可通过区域标准化+分层级优先级匹配的方案替代apply,大幅提升效率,同时满足所有匹配规则:
一、预处理:统一区域命名与层级标记
首先解决区域命名不一致的问题,同时明确每条数据的区域层级:
- 区域标准化:
- 对两张表的省、市、区列做统一清洗,去掉冗余后缀,用映射字典修正别名。示例代码:
# 定义标准化映射 province_map = {"北京市": "北京", "上海市": "上海", "京": "北京", "沪": "上海"} # 应用到DataFrame left_df['province_std'] = left_df['province'].replace(province_map).str.replace(r'省|自治区|特别行政区', '', regex=True) right_df['province_std'] = right_df['province'].replace(province_map).str.replace(r'省|自治区|特别行政区', '', regex=True) # 市、区列做同样处理 left_df['city_std'] = left_df['city'].str.replace(r'市|地区|自治州', '', regex=True).fillna('') right_df['city_std'] = right_df['city'].str.replace(r'市|地区|自治州', '', regex=True).fillna('') left_df['district_std'] = left_df['district'].str.replace(r'区|县|旗', '', regex=True).fillna('') right_df['district_std'] = right_df['district'].str.replace(r'区|县|旗', '', regex=True).fillna('')
- 对两张表的省、市、区列做统一清洗,去掉冗余后缀,用映射字典修正别名。示例代码:
- 标记区域层级:
- 给每条数据标记层级,方便后续匹配逻辑判断:
def get_level(row): if row['district_std']: return '三级' elif row['city_std']: return '二级' else: return '一级' left_df['level'] = left_df.apply(get_level, axis=1) right_df['level'] = right_df.apply(get_level, axis=1)
- 给每条数据标记层级,方便后续匹配逻辑判断:
- 统一日期格式:确保两张表的日期列转为
datetime类型:left_df['left_date'] = pd.to_datetime(left_df['date']) right_df['right_date'] = pd.to_datetime(right_df['date'])
二、分层级优先级匹配(核心逻辑)
按精确层级优先的顺序匹配,每次匹配后标记已匹配的行,避免重复计算,同时严格满足right_df.date <= left_df.date的条件:
1. 初始化匹配标记与结果容器
left_df['matched'] = False result = pd.DataFrame(columns=left_df.columns.tolist() + ['right_id', 'right_listed_date'])
2. 三级匹配(省+市+区完全对应)
优先匹配最精确的三级区域,仅保留日期符合条件的记录:
# 筛选未匹配的三级数据 left_unmatched = left_df[~left_df['matched'] & (left_df['level'] == '三级')] # 匹配right_df中对应的三级数据 match_3 = pd.merge( left_unmatched, right_df[right_df['level'] == '三级'], left_on=['province_std', 'city_std', 'district_std'], right_on=['province_std', 'city_std', 'district_std'], how='left' ) # 过滤日期条件 match_3 = match_3[match_3['right_date'] <= match_3['left_date']] # 更新匹配标记 left_df.loc[match_3.index, 'matched'] = True # 合并到结果 result = pd.concat([result, match_3[left_df.columns.tolist() + ['right_id', 'right_listed_date']]])
3. 二级匹配(省+市对应)
匹配次精确的二级区域,包括其中一方无区县信息的情况:
left_unmatched = left_df[~left_df['matched'] & (left_df['level'] == '二级')] right_candidates = right_df[(right_df['level'] == '二级') | (right_df['district_std'].isna())] match_2 = pd.merge( left_unmatched, right_candidates, left_on=['province_std', 'city_std'], right_on=['province_std', 'city_std'], how='left' ) match_2 = match_2[match_2['right_date'] <= match_2['left_date']] left_df.loc[match_2.index, 'matched'] = True result = pd.concat([result, match_2[left_df.columns.tolist() + ['right_id', 'right_listed_date']]])
4. 一级匹配(省对应,排除中央)
匹配省级区域,包括其中一方无市、区县信息的情况:
left_unmatched = left_df[~left_df['matched'] & (left_df['level'] == '一级') & (left_df['province_std'] != '中央')] right_candidates = right_df[(right_df['level'] == '一级') | (right_df['city_std'].isna())] match_1 = pd.merge( left_unmatched, right_candidates, left_on=['province_std'], right_on=['province_std'], how='left' ) match_1 = match_1[match_1['right_date'] <= match_1['left_date']] left_df.loc[match_1.index, 'matched'] = True result = pd.concat([result, match_1[left_df.columns.tolist() + ['right_id', 'right_listed_date']]])
5. 中央区域匹配(全国范围)
处理left_df中一级区域为“中央”的情况,匹配所有日期符合条件的right_df记录:
left_unmatched = left_df[~left_df['matched'] & (left_df['province_std'] == '中央')] if not left_unmatched.empty: # 笛卡尔积后过滤日期 match_center = pd.merge(left_unmatched, right_df, how='cross') match_center = match_center[match_center['right_date'] <= match_center['left_date']] result = pd.concat([result, match_center[left_df.columns.tolist() + ['right_id', 'right_listed_date']]])
三、最终整理结果
将结果按left_df原始索引排序,确保保留全量left_df数据(未匹配到的行对应right字段为NaN):
result = result.set_index(left_df.index.names).reindex(left_df.index) # 清理临时列 result = result.drop(['matched', 'level'], axis=1, errors='ignore')
四、性能优化建议
- 建立索引:对标准化后的区域列和日期列建立索引,大幅提升merge速度:
left_df = left_df.set_index(['province_std', 'city_std', 'district_std']) right_df = right_df.set_index(['province_std', 'city_std', 'district_std']) - 批量处理:如果数据量超千万级,可使用
dask.dataframe替代pandas,实现分布式计算 - 避免冗余:预处理时过滤right_df中无效数据(如日期远大于left_df最大日期的记录),减少匹配数据量
内容的提问来源于stack exchange,提问作者Constantine Theodore
相关产品推荐
相关产品推荐

