按组查找ini.1落在ini.2与fin.2区间内的对应行号
如何在分组DataFrame中匹配ini.1到ini.2/fin.2区间并获取对应行号?
嘿,我来帮你解决这个问题!看起来你需要在每个Grp分组里,把每个观测的ini.1值匹配到同组内ini.2和fin.2构成的有效区间里,然后拿到对应行的行号(就是你示例里的1-11那种序号对吧?)。我给你两种可行的方案,你可以根据自己的数据规模和特点选择:
首先先还原你的示例DataFrame(方便代码测试):
import pandas as pd import numpy as np data = { 'Grp': ['A','A','A','A','A','A','B','B','B','B','B'], 'Grp.ind': ['A.1','A.2','A.3','A.4','A.5','A.6','B.1','B.2','B.3','B.4','B.5'], 'ini.1': [0,5,10,15,26,28,0,np.nan,15,20,31], 'fin.1': [5,10,15,26,28,30,15,np.nan,20,31,50], 'ini.2': [0,5,np.nan,np.nan,np.nan,25,0,10,20,25,30], 'fin.2': [5,25,np.nan,np.nan,np.nan,30,10,20,25,30,50] } df = pd.DataFrame(data) # 生成1-based的行号,和你示例里的行号对应 df['row_num'] = df.index + 1
方案1:分组自定义匹配(灵活通用)
这个方案适合所有情况,不管区间是否重叠、有序,都能精准匹配。核心思路是对每个分组单独处理,先筛选出该组内ini.2和fin.2都非空的有效区间,再逐个检查每个ini.1落在哪个区间里:
def match_interval_in_group(group): # 提取当前组的有效区间(排除ini.2或fin.2为NA的行) valid_intervals = group.dropna(subset=['ini.2', 'fin.2'])[['ini.2', 'fin.2', 'row_num']] # 定义单个ini.1值的匹配逻辑 def get_matching_row(ini_val): if pd.isna(ini_val): return np.nan # 筛选满足 ini.2 <= ini_val <= fin.2 的行 matched = valid_intervals[(valid_intervals['ini.2'] <= ini_val) & (ini_val <= valid_intervals['fin.2'])] # 如果有多个匹配,这里返回第一个;如果要返回所有,改成matched['row_num'].tolist() return matched['row_num'].iloc[0] if not matched.empty else np.nan # 给当前组添加匹配结果列 group['matching_row'] = group['ini.1'].apply(get_matching_row) return group # 应用到整个DataFrame,得到结果 result_df = df.groupby('Grp', group_keys=False).apply(match_interval_in_group)
运行后,result_df['matching_row']就是你要的结果:
- 第3行(A.3)的
ini.1=10,落在A组第2行的区间(5,25)里,所以matching_row=2 - 第6行(A.6)的
ini.1=28,落在自身的区间(25,30)里,所以matching_row=6 - 第9行(B.3)的
ini.1=15,落在B组第8行的区间(10,20)里,所以matching_row=8
方案2:用merge_asof高效匹配(适合有序区间)
如果你的分组内区间是不重叠且按ini.2升序排列的,用merge_asof会更高效,尤其是处理大数据量的时候:
# 第一步:提取有效区间并按分组和ini.2排序 interval_df = df.dropna(subset=['ini.2', 'fin.2']).sort_values(['Grp', 'ini.2']) # 第二步:提取需要匹配的ini.1数据并排序 target_df = df.dropna(subset=['ini.1']).sort_values(['Grp', 'ini.1']) # 用merge_asof匹配:找每个ini.1对应的最大的ini.2 <= ini.1的区间 merged = pd.merge_asof( target_df, interval_df[['Grp', 'ini.2', 'fin.2', 'row_num']], on='ini.2', by='Grp', direction='backward' ) # 检查匹配到的区间是否满足ini.1 <= fin.2,不满足的设为NA merged['matching_row'] = np.where(merged['ini.1'] <= merged['fin.2'], merged['row_num'], np.nan) # 把匹配结果合并回原DataFrame result_df = df.merge(merged[['Grp', 'Grp.ind', 'matching_row']], on=['Grp', 'Grp.ind'], how='left')
这个方法的优势是速度快,但依赖区间的有序性,如果你的区间有重叠或者乱序,还是方案1更稳妥。
注意事项
- 如果一个
ini.1匹配多个区间,方案1可以轻松改成返回所有匹配的行号(把return matched['row_num'].iloc[0]换成return matched['row_num'].tolist()) - 示例里用的是1-based行号(和你给出的示例行号一致),如果需要原DataFrame的0-based索引,去掉
df['row_num'] = df.index +1里的+1即可 - 对于
ini.1为NA的观测,两种方案都会返回NA,符合预期
内容的提问来源于stack exchange,提问作者Javmi
相关产品推荐
相关产品推荐

