You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于边界范围对DataFrame区间数据进行分箱的技术问询

解决方案:基于边界规则的DataFrame分箱实现

首先,我先明确咱们的核心分箱规则,确保理解正确:

  • 排除项:如果to_bin的区间完全落在borders的某一个区间内,标记为NaN
  • 分箱规则
    1. 只要to_bin的区间与某个bin的范围有重叠(不管是start在bin范围内、end在bin范围内,还是跨bin边界),就归入该bin
    2. 循环结构:最后一个borders区间之后的所有数据,和第一个borders区间之前的数据,都归入bin_1
    3. 中间的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)

为什么原代码有问题?

你的原代码只处理了startend都在bin范围内的情况,没有考虑部分重叠的场景(比如start在边界内但end在边界外,或者反过来),同时循环逻辑也没有覆盖循环结构的完整规则。上面的两种方案都完整覆盖了所有规则,并且处理了所有边界情况。

内容的提问来源于stack exchange,提问作者Saraha

火山引擎 最新活动