统计DataFrame单元格值在另一DataFrame中的出现次数(含容差)
问题:基于1%容差统计DataFrame价格出现次数,能否避免迭代?可用merge_asof+分组吗?
现有两个结构一致的DataFrame:
dfA:
| Index | Price |
|---|---|
| 0 | 10.21 |
| 1 | 12.21 |
dfB:
| Index | Price |
|---|---|
| 0 | 10.21 |
| 1 | 10.24 |
| 2 | 11.32 |
| 3 | 12.21 |
需求:给dfA新增Occurrences列,统计每个Price值在dfB中满足1%容差(即Price×0.99 ≤ 目标Price ≤ Price×1.01)的出现次数,预期结果如下:
| Index | Price | Occurrences |
|---|---|---|
| 0 | 10.21 | 2 |
| 1 | 12.21 | 1 |
请问能否避免迭代实现?是否可使用merge_asof结合分组统计的方法?
回答
完全可以避免迭代,merge_asof结合分组的思路可行,同时还有性能更优的批量统计方案,以下是具体实现:
方案1:高效批量统计(无迭代,适合大数据量)
利用排序后的二分查找快速定位容差区间,直接计算匹配次数,全程无循环:
import pandas as pd # 构造示例数据 dfA = pd.DataFrame({'Price': [10.21, 12.21]}) dfB = pd.DataFrame({'Price': [10.21, 10.24, 11.32, 12.21]}) # 对dfB的Price排序(二分查找的必要前提) dfB_sorted_prices = dfB['Price'].sort_values().values # 计算dfA每个Price的1%容差上下限 dfA['lower'] = dfA['Price'] * 0.99 dfA['upper'] = dfA['Price'] * 1.01 # 用searchsorted定位区间边界,计算匹配次数 dfA['Occurrences'] = [ dfB_sorted_prices.searchsorted(upper, side='right') - dfB_sorted_prices.searchsorted(lower, side='left') for lower, upper in zip(dfA['lower'], dfA['upper']) ] # 整理得到结果 result = dfA[['Price', 'Occurrences']]
方案2:merge_asof+分组(满足需求的思路)
merge_asof本身是有序近邻匹配工具,要实现计数需要先关联所有符合条件的记录,再分组统计:
# 排序数据(merge_asof要求左右表按连接键排序) dfA_sorted = dfA.sort_values('Price').reset_index() dfB_sorted = dfB.sort_values('Price').reset_index(drop=True) # 计算容差范围 dfA_sorted['lower'] = dfA_sorted['Price'] * 0.99 dfA_sorted['upper'] = dfA_sorted['Price'] * 1.01 # 交叉连接所有记录,过滤出符合容差的条目 cross_join = dfA_sorted.merge(dfB_sorted, how='cross') filtered_records = cross_join[(cross_join['Price_y'] >= cross_join['lower']) & (cross_join['Price_y'] <= cross_join['upper'])] # 按dfA原索引分组统计匹配次数 counts = filtered_records.groupby('index')['Price_y'].count().rename('Occurrences') # 合并回原dfA得到结果 result = dfA.join(counts)
方案3:分组优化(针对dfA有重复Price的场景)
如果dfA存在重复Price值,先按Price分组计算每组的匹配次数,再合并回原表:
dfB_sorted_prices = dfB['Price'].sort_values().values # 对dfA按Price分组,计算每组的容差匹配次数 price_counts = dfA.groupby('Price').apply( lambda x: dfB_sorted_prices.searchsorted(x['Price'].iloc[0]*1.01, side='right') - dfB_sorted_prices.searchsorted(x['Price'].iloc[0]*0.99, side='left') ).rename('Occurrences').reset_index() # 合并回原dfA得到结果 result = dfA.merge(price_counts, on='Price', how='left')
结论
- 所有方案都完全避免了迭代操作,效率远高于循环遍历。
merge_asof更适合一对一的近邻匹配场景,交叉连接+分组的方式可行但数据量大时内存占用较高;二分查找+区间统计是性能最优的方案。
内容的提问来源于stack exchange,提问作者cksrc
相关产品推荐
相关产品推荐

