17370845950

Pandas: 使用参考数据框的分位数区间对目标数据框进行分箱标注

本文介绍如何基于一个参考数据框(df2)按日期分组计算的 qcut 分位数边界,精准地对另一个目标数据框(df1)中的数值列进行分箱(binning)并生成类别标签(如 0、1),特别适用于跨数据集的一致性分组场景。

在量化分析、风控建模或报表分层统计中,常需保证不同数据集在相同时间维度(如交易日)下使用完全一致的分箱逻辑——例如,用全量历史成交价(df2)计算每日四分位数边界,再将新样本(df1)映射到这些固定区间中。直接对 df1 单独分箱会导致边界漂移,破坏可比性;而 pd.qcut 不支持外部 bin 输入,因此需手动构建分箱流程。

核心思路分为四步:

  1. 从参考数据(df2)提取分位数边界:按 PriceDate 分组,对 Price 列调用 pd.qcut(..., retbins=True) 获取各组的 bin 边界数组;
  2. 关联边界到目标数据(df1):通过 merge 将边界列表(Bins)按日期左连接至 df1;
  3. 扩展边界以覆盖异常值:将原始 bin 的首尾替换为 -∞ 和 +∞,确保所有 Price 值均可被 pd.cut 安全归类;
  4. 按日期分组执行分箱:对合并后的 DataFrame 按 PriceDate 分组,对每组 Price 应用 pd.cut 并返回整数标签。

以下是完整可运行代码:

import pandas as pd
import numpy as np

# 示例数据(已修正 df1 首值为 -4.4 以验证边界外处理)
df1 = pd.DataFrame({
    'Price': [-4.4, 3.6, 9.2, 3.4],
    'PriceDate': ['2025-10-01', '2025-10-01', '2025-10-01', '2025-10-02']
})
df2 = pd.DataFrame({
    'Price': [0.0, 3.6, 9.3, 4.5, 2.9, 3.2, 1.0, 6.7, 8.7, 9.8, 3.4, 0.7, 2.2, 6.5, 3.4, 1.7, 9.4, 10.0],
    'PriceDate': ['2025-10-01']*7 + ['2025-10-02']*11
})

# Step 1: 从 df2 提取每日期的 qcut 边界(2 分位 → 3 个边界点)
ref = df2.groupby('PriceDate')['Price'].apply(
    lambda g: pd.qcut(g, q=2, retbins=True)[1]
).reset_index(name='Bins')

# Step 2: 合并边界到 df1
df_merged = pd.merge(df1, ref,

on='PriceDate', how='left') # Step 3 & 4: 定义安全分箱函数并应用 def assign_bin(group): bins = group['Bins'].iloc[0] # 取当前日期的边界列表 # 扩展边界:保留中间断点,首尾替换为 ±inf(关键!) extended_bins = [-np.inf] + bins[1:-1].tolist() + [np.inf] # 执行分箱,返回整数标签(0-based) return pd.cut(group['Price'], bins=extended_bins, labels=False).astype('Int64') df_merged['Rank'] = df_merged.groupby('PriceDate', group_keys=False).apply(assign_bin) print(df_merged[['Price', 'PriceDate', 'Rank']])

输出结果:

   Price   PriceDate  Rank
0 -4.4  2025-10-01     0
1  3.6  2025-10-01     1
2  9.2  2025-10-01     1
3  3.4  2025-10-02     0

关键注意事项

  • bins[1:-1] 跳过首尾(因 qcut 返回的边界含最小/最大值,而 cut 需内部断点);
  • 使用 [-np.inf, ..., np.inf] 是鲁棒性保障,避免 ValueError: Bin edges must be unique 或 NaN 标签;
  • 若需处理 NaN 价格,可在 assign_bin 中添加 group['Price'].dropna() 或设置 pd.cut(..., include_lowest=True);
  • 对于高基数日期(如万级交易日),建议将 ref 转为字典索引提升 merge 效率:bins_dict = ref.set_index('PriceDate')['Bins'].to_dict(),再用 map 替代 merge。

该方法确保了分箱逻辑完全复用参考分布,是实现跨数据集标准化分组的可靠范式。