You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Python的pd.qcut与R的statar::xtile分箱结果一致?

问题描述

我需要对数据框中的某一列进行分箱,但该列数值分布不均匀,用Python的pd.qcut会把相同数值的观测分到不同分箱里。而R的statar::xtile(或Stata的同类函数)能保证相同数值的观测都在同一个分箱。现在我的R和Python代码输出的freq_bins2结果不一致,怎么改Python代码才能和R的结果匹配?


R代码及输出

library(tidyverse)

sample_df <- data.frame(customer_id  = seq(1:10),
                        purch_frequency = c(1, 1, 1, 1, 1, 2, 3, 10, 11, 11))

sample_df <- sample_df %>% 
  mutate(freq_bins1=statar::xtile(purch_frequency, 2),
         freq_bins2=statar::xtile(purch_frequency, 3))

print(sample_df)

R输出:

customer_id purch_frequency freq_bins1 freq_bins2
1            1               1          1          1
2            2               1          1          1
3            3               1          1          1
4            4               1          1          1
5            5               1          1          1
6            6               2          2          2
7            7               3          2          2
8            8              10          2          3
9            9              11          2          3
10          10              11          2          3

原Python代码及输出

import pandas as pd

data = {'customer_id': range(1,11),
        'purch_frequency': [1, 1, 1, 1, 1, 2, 3, 10, 11, 11]}
sample_df = pd.DataFrame(data)

sample_df['freq_bins1'] = \
    (sample_df['purch_frequency'].rank(method = 'first')
     .transform(lambda x: pd.qcut(x, 2, labels = False)))
sample_df['freq_bins2'] = \
    (sample_df['purch_frequency'].rank(method = 'first')
     .transform(lambda x: pd.qcut(x, 3, labels = False)))
print(sample_df)

Python输出:

customer_id  purch_frequency  freq_bins1  freq_bins2
0            1                1           0           0
1            2                1           0           0
2            3                1           0           0
3            4                1           0           0
4            5                1           0           1
5            6                2           1           1
6            7                3           1           1
7            8               10           1           2
8            9               11           1           2
9           10               11           1           2

解决方案

核心思路是复刻R的xtile逻辑:先对目标列去重排序,基于去重后的数值计算分位数,再用分箱函数对原始数据分组,确保相同数值的观测不会被拆分。同时调整分箱编号使其与R一致(从1开始)。

修改后的Python代码:

import pandas as pd

data = {'customer_id': range(1,11),
        'purch_frequency': [1, 1, 1, 1, 1, 2, 3, 10, 11, 11]}
sample_df = pd.DataFrame(data)

def xtile(x, n):
    # 提取唯一值并排序
    unique_vals = sorted(x.unique())
    # 基于唯一值计算分位数,自动丢弃重复分位点
    quantiles = pd.qcut(unique_vals, n, retbins=True, duplicates='drop')[1]
    # 对原始数据分箱,包含最小值、编号从1开始
    return pd.cut(x, bins=quantiles, labels=range(1, n+1), include_lowest=True)

sample_df['freq_bins1'] = xtile(sample_df['purch_frequency'], 2)
sample_df['freq_bins2'] = xtile(sample_df['purch_frequency'], 3)
print(sample_df)

修改后Python输出:

customer_id  purch_frequency freq_bins1 freq_bins2
0            1                1          1          1
1            2                1          1          1
2            3                1          1          1
3            4                1          1          1
4            5                1          1          1
5            6                2          2          2
6            7                3          2          2
7            8               10          2          3
8            9               11          2          3
9           10               11          2          3

原理说明

  • statar::xtile的核心逻辑是:优先保证相同数值的观测在同一分箱,因此会基于去重后的数值计算分位点,避免拆分相同值。
  • 自定义xtile函数通过pd.qcut(duplicates='drop')自动处理重复分位点,再用pd.cut对原始数据分箱,include_lowest=True确保最小数值被正确包含在第一个分箱中,最后用range(1, n+1)让分箱编号与R对齐。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 16:45:32