如何基于含逗号分隔ID的列实现DataFrame合规连接与过滤
优化基于逗号分隔PlaceID的DataFrame连接逻辑
需求背景
需要基于AgeGroup和PlaceID连接两个DataFrame,但用户DataFrame的PlaceID以逗号分隔、单个值、null或空值的形式存储在SuitablePlaces列中,需遵循以下规则完成连接:
- 若
SuitablePlaces为null或空值,直接移除该行 - 单个PlaceID值:匹配
AgeGroup+PlaceID组合,成功则新增PlaceName列,否则移除该行 - 逗号分隔的多个PlaceID:拆分后取第一个匹配
AgeGroup+PlaceID组合的记录,新增对应PlaceName,否则移除该行
用户DataFrame结构
| Name | Sex | AgeGroup | SuitablePlaces |
|---|---|---|---|
| User1 | Male | 30 | 1,4 |
| User2 | Female | 40 | 5 |
| User3 | Male | 50 | 1,3 |
| User4 | Male | 40 | 1,3,4 |
| User5 | Female | 30 | [null] |
| User6 | Female | 50 | |
| User7 | Male | 30 | 3 |
地点映射DataFrame结构
| AgeGroup | PlaceID | PlaceName |
|---|---|---|
| 30 | 1 | Place1 |
| 30 | 5 | Place2 |
| 40 | 3 | Place3 |
| 40 | 5 | Place4 |
| 50 | 4 | Place5 |
| 50 | 2 | Place6 |
现有代码问题
原实现用apply逐行处理,效率低下(尤其是数据量大时),且直接替换了SuitablePlaces列,不符合“新增PlaceName列”的需求。
优化后的地道Pandas实现
采用向量式操作替代逐行循环,利用Pandas内置的explode、merge等优化方法提升效率,同时保留原列并新增目标列:
import pandas as pd def map_user_places_optimized(user_df, places_df): # 复制输入数据避免修改原表 user_df = user_df.copy() places_df = places_df.copy() # 1. 过滤空值:将空字符串转为NaN后删除对应行 user_df['SuitablePlaces'] = user_df['SuitablePlaces'].replace('', pd.NA) user_df = user_df.dropna(subset=['SuitablePlaces']) # 2. 拆分逗号分隔的PlaceID为整数列表,保留原始顺序 user_df['PlaceID_List'] = user_df['SuitablePlaces'].str.split(',')\ .apply(lambda x: [int(pid.strip()) for pid in x]) # 3. 将列表拆分为多行,保留原行索引用于后续分组取第一个匹配项 exploded_df = user_df.explode('PlaceID_List', ignore_index=False)\ .rename(columns={'PlaceID_List': 'PlaceID'}) # 4. 统一数据类型,避免连接时类型不匹配 exploded_df[['AgeGroup', 'PlaceID']] = exploded_df[['AgeGroup', 'PlaceID']].astype(int) places_df[['AgeGroup', 'PlaceID']] = places_df[['AgeGroup', 'PlaceID']].astype(int) # 5. 内连接地点表,只保留匹配成功的记录 merged_df = exploded_df.merge(places_df, on=['AgeGroup', 'PlaceID'], how='inner') # 6. 按原行分组,取第一个匹配的PlaceName(因为explode保留了列表顺序) result_df = merged_df.groupby(merged_df.index).first().reset_index(drop=True) # 7. 整理输出列:保留原用户字段 + 新增的PlaceName result_df = result_df[['Name', 'Sex', 'AgeGroup', 'SuitablePlaces', 'PlaceName']] return result_df
代码说明
- 空值过滤:统一处理空字符串和null,确保只保留有效数据
- 列表拆分与展开:将逗号分隔的PlaceID转为列表后拆分为多行,保证后续能按顺序匹配
- 类型统一:避免因数据类型不一致导致连接失败
- 内连接:自动过滤掉匹配失败的记录
- 分组取首项:利用原索引分组,取每组第一条记录即实现“第一个匹配”的需求
测试示例
# 构造测试数据 user_data = [ ['User1', 'Male', 30, '1,4'], ['User2', 'Female', 40, '5'], ['User3', 'Male', 50, '1,3'], ['User4', 'Male', 40, '1,3,4'], ['User5', 'Female', 30, pd.NA], ['User6', 'Female', 50, ''], ['User7', 'Male', 30, '3'] ] user_df = pd.DataFrame(user_data, columns=['Name', 'Sex', 'AgeGroup', 'SuitablePlaces']) place_data = [ [30, 1, 'Place1'], [30, 5, 'Place2'], [40, 3, 'Place3'], [40, 5, 'Place4'], [50, 4, 'Place5'], [50, 2, 'Place6'] ] places_df = pd.DataFrame(place_data, columns=['AgeGroup', 'PlaceID', 'PlaceName']) # 执行函数并输出结果 result = map_user_places_optimized(user_df, places_df) print(result)
输出结果
| Name | Sex | AgeGroup | SuitablePlaces | PlaceName |
|---|---|---|---|---|
| User1 | Male | 30 | 1,4 | Place1 |
| User2 | Female | 40 | 5 | Place4 |
| User4 | Male | 40 | 1,3,4 | Place3 |
(注:User3、User7因无匹配记录被移除,User5、User6因空值被过滤)
内容的提问来源于stack exchange,提问作者Saurabh Khirwal
相关产品推荐
相关产品推荐

