基于边界范围对DataFrame区间数据进行分箱的技术问询
解决方案:基于边界规则的DataFrame分箱实现
首先,我先明确咱们的核心分箱规则,确保理解正确:
- 排除项:如果
to_bin的区间完全落在borders的某一个区间内,标记为NaN - 分箱规则:
- 只要
to_bin的区间与某个bin的范围有重叠(不管是start在bin范围内、end在bin范围内,还是跨bin边界),就归入该bin - 循环结构:最后一个
borders区间之后的所有数据,和第一个borders区间之前的数据,都归入bin_1 - 中间的bin对应
borders相邻区间之间的空隙:比如borders第i个区间的end到第i+1个区间的start,对应bin_{i+1}
- 只要
方案一:逻辑清晰的自定义函数方式
这种方式代码可读性高,适合数据量不大的场景:
import pandas as pd import numpy as np # 示例数据(替换成你的真实数据) borders = pd.DataFrame({ 'start': [25000, 85000, 105000], 'end': [30000, 90000, 110000] }) to_bin = pd.DataFrame({ 'start': [3676, 24943, 25010, 29000, 51174, 54224, 58014, 84900, 91987, 117574], 'end': [4686, 25902, 26000, 31000, 52100, 54682, 59024, 85500, 92988, 119637] }) # 1. 构造所有bin的范围 bins_ranges = [] # bin_1的左半部分:到第一个borders的start为止 bins_ranges.append(('bin_1', -np.inf, borders.iloc[0]['start'])) # 中间的bin:相邻borders区间之间的空隙 for i in range(1, len(borders)): bin_name = f'bin_{i+1}' left_bound = borders.iloc[i-1]['end'] right_bound = borders.iloc[i]['start'] bins_ranges.append((bin_name, left_bound, right_bound)) # bin_1的右半部分:最后一个borders的end之后 bins_ranges.append(('bin_1', borders.iloc[-1]['end'], np.inf)) # 2. 定义分箱逻辑函数 def assign_bin(row, borders, bins_ranges): # 先判断是否完全在某个borders区间内 is_inside_border = ((borders['start'] <= row['start']) & (borders['end'] >= row['end'])).any() if is_inside_border: return np.nan # 找到重叠的bin for bin_name, left, right in bins_ranges: # 区间重叠的条件:当前行的start < bin的右边界,且行的end > bin的左边界 if row['start'] < right and row['end'] > left: return bin_name return np.nan # 理论上不会执行到这里 # 3. 应用函数到to_bin的每一行 to_bin['bin'] = to_bin.apply(lambda row: assign_bin(row, borders, bins_ranges), axis=1) print(to_bin)
运行后会得到你预期的结果:
start end bin 0 3676 4686 bin_1 1 24943 25902 bin_1 2 25010 26000 NaN 3 29000 31000 bin_2 4 51174 52100 bin_2 5 54224 54682 bin_2 6 58014 59024 bin_2 7 84900 85500 bin_2 8 91987 92988 bin_3 9 117574 119637 bin_1
方案二:性能更优的向量化方式
如果你的数据量很大,逐行apply效率较低,可以用向量化操作批量处理:
import pandas as pd import numpy as np # 同样先准备示例数据(替换成真实数据) borders = pd.DataFrame({ 'start': [25000, 85000, 105000], 'end': [30000, 90000, 110000] }) to_bin = pd.DataFrame({ 'start': [3676, 24943, 25010, 29000, 51174, 54224, 58014, 84900, 91987, 117574], 'end': [4686, 25902, 26000, 31000, 52100, 54682, 59024, 85500, 92988, 119637] }) # 1. 标记完全在borders区间内的行 is_inside = to_bin.apply( lambda row: ((borders['start'] <= row['start']) & (borders['end'] >= row['end'])).any(), axis=1 ) to_bin['bin'] = np.nan # 2. 处理bin_1的情况:第一个borders之前 或 最后一个borders之后 bin_1_mask = (to_bin['start'] < borders.iloc[0]['start']) | (to_bin['end'] > borders.iloc[-1]['end']) to_bin.loc[bin_1_mask & ~is_inside, 'bin'] = 'bin_1' # 3. 处理中间的bin for i in range(1, len(borders)): bin_name = f'bin_{i+1}' left = borders.iloc[i-1]['end'] right = borders.iloc[i]['start'] # 区间重叠的条件 mask = (to_bin['start'] < right) & (to_bin['end'] > left) & ~is_inside & ~bin_1_mask to_bin.loc[mask, 'bin'] = bin_name print(to_bin)
为什么原代码有问题?
你的原代码只处理了start和end都在bin范围内的情况,没有考虑部分重叠的场景(比如start在边界内但end在边界外,或者反过来),同时循环逻辑也没有覆盖循环结构的完整规则。上面的两种方案都完整覆盖了所有规则,并且处理了所有边界情况。
内容的提问来源于stack exchange,提问作者Saraha




