特征分箱的目的:
从模型效果上来看,特征分箱主要是为了降低变量的复杂性,减少变量噪音对模型的影响,提高自变量和因变量的相关度。从而使模型更加稳定。
数据分桶的对象:
将连续变量离散化
将多状态的离散变量合并成少状态
分箱的原因:
数据的特征内的值跨度可能比较大,对有监督和无监督中如k-均值聚类它使用欧氏距离作为相似度函数来测量数据点之间的相似度。都会造成大吃小的影响,其中一种解决方法是对计数值进行区间量化即数据分桶也叫做数据分箱,然后使用量化后的结果。
分箱的优点:
处理缺失值:当数据源可能存在缺失值,此时可以把null单独作为一个分箱。
处理异常值:当数据中存在离群点时,可以把其通过分箱离散化处理,从而提高变量的鲁棒性(抗干扰能力)。例如,age若出现200这种异常值,可分入“age > 60”这个分箱里,排除影响。
业务解释性:我们习惯于线性判断变量的作用,当x越来越大,y就越来越大。但实际x与y之间经常存在着非线性关系,此时可经过WOE变换。
特别要注意一下分箱的基本原则:
(1)最小分箱占比不低于5%
(2)箱内不能全部是好客户
(3)连续箱单调
以下是对各种数据分桶方法的详细介绍及相应的 Python 代码示例:
一、等宽分箱(Equal Width Binning)
-
原理:
- 将数据的取值范围划分为若干个等宽的区间。
-
步骤:
- 确定数据的取值范围。
- 计算区间宽度。
- 将数据分配到各个区间中。
-
Python 代码示例:
import pandas as pd
data = [22, 25, 30, 35, 40, 45, 50, 55, 60, 65]
df = pd.DataFrame(data, columns=['value'])
# 假设分为 3 个等宽区间
bins = range(20, 70, 15)
labels = ['20-34', '35-49', '50-64']
df['equal_width_binned'] = pd.cut(df['value'], bins=bins, labels=labels)
print(df)
二、等频分箱(Equal Frequency Binning)
-
原理:
- 将数据分成若干个区间,使得每个区间中的数据数量大致相等。
-
步骤:
- 对数据进行排序。
- 根据要划分的区间数量,确定每个区间应包含的数据数量。
- 将数据分配到各个区间中。
-
Python 代码示例:
data = [22, 25, 30, 35, 40, 45, 50, 55, 60, 65]
df = pd.DataFrame(data, columns=['value'])
# 假设分为 3 个等频区间
quantiles = pd.qcut(df['value'], q=3, labels=['low', 'medium', 'high'])
df['equal_frequency_binned'] = quantiles
print(df)
三、分位数分箱(Quantile-based Binning)
-
原理:
- 根据数据的分位数将数据分成若干个区间。
-
步骤:
- 计算特定分位数,如四分位数、五分位数等。
- 以分位数为边界划分区间。
-
Python 代码示例:
data = [22, 25, 30, 35, 40, 45, 50, 55, 60, 65]
df = pd.DataFrame(data, columns=['value'])
# 分为四分位数区间
quartiles = pd.qcut(df['value'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
df['quantile_binned'] = quartiles
print(df)
四、卡方分箱(Chi-Square Binning)
-
原理:
- 基于卡方检验的原理,通过比较不同区间内自变量与因变量的独立性来确定最佳的分箱方式。卡方值越大,说明自变量与因变量的关联性越强。
-
步骤:
- 初步划分区间。
- 计算每个区间的卡方值。
- 按照一定的规则对区间进行合并,直到满足停止条件。
-
Python 实现相对复杂,通常需要借助专门的库。以下是一个简单的示例概念代码,实际应用中可能需要更多的调整和优化:
import numpy as np
from scipy.stats import chi2_contingency
# 假设数据和目标变量
data = [22, 25, 30, 35, 40, 45, 50, 55, 60, 65]
target = [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
# 初步划分区间
bins = [20, 40, 60]
labels = [0, 1, 2]
binned_data = np.digitize(data, bins)
# 计算卡方值并进行合并
while True:
contingency_table = np.zeros((len(bins), 2))
for i in range(len(bins)):
subset = target[binned_data == i]
contingency_table[i, 0] = np.sum(subset == 0)
contingency_table[i, 1] = np.sum(subset == 1)
chi2, _, _, _ = chi2_contingency(contingency_table)
if chi2 < some_threshold:
break
# 合并区间逻辑(这里简化,实际中更复杂)
bins = [bins[0], (bins[1] + bins[2]) / 2]
labels = [0, 1]
binned_data = np.digitize(data, bins)
print(binned_data)
五、WOE 分箱(Weight of Evidence Binning)
-
原理:
- WOE 分箱主要用于信用评分等领域,通过计算每个区间的证据权重(Weight of Evidence)来确定最佳的分箱方式。WOE 反映了自变量在不同区间内对因变量(通常是二分类变量)的预测能力。
-
步骤:
- 初步划分区间。
- 计算每个区间的 WOE 值,公式为:WOE = ln(好样本占比 / 坏样本占比)。
- 基于一定的规则(如最小样本数、单调性等)对区间进行合并和调整,以优化 WOE 值。
-
Python 代码示例(假设数据和目标变量已经准备好):
import numpy as np
import pandas as pd
data = [22, 25, 30, 35, 40, 45, 50, 55, 60, 65]
target = [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
df = pd.DataFrame({'value': data, 'target': target})
# 初步划分区间(这里简化,实际中可能使用其他方法)
bins = [20, 40, 60]
labels = [0, 1, 2]
df['binned'] = np.digitize(df['value'], bins)
# 计算每个区间的好样本和坏样本数量
good_counts = df[df['target'] == 0].groupby('binned')['target'].count()
bad_counts = df[df['target'] == 1].groupby('binned')['target'].count()
total_good = good_counts.sum()
total_bad = bad_counts.sum()
# 计算 WOE
woe_dict = {}
for bin_num in range(len(bins)):
good = good_counts.get(bin_num, 0)
bad = bad_counts.get(bin_num, 0)
woe = np.log((good / total_good) / (bad / total_bad))
woe_dict[bin_num] = woe
# 根据 WOE 进行区间调整(这里简化,实际中更复杂)
#...
print(df)
六、机器学习分箱
-
原理:
- 使用机器学习算法,如决策树、随机森林等,来自动学习最佳的分箱方式。这些算法可以根据数据的特征和目标变量,自动确定分割点和区间。
-
步骤:
- 选择合适的机器学习算法。
- 将数据输入算法进行训练。
- 从训练好的模型中提取分箱信息。
-
Python 代码示例(使用决策树进行分箱的概念示例):
import numpy as np
import pandas as pd
from sklearn.tree import DecisionTreeClassifier
data = [22, 25, 30, 35, 40, 45, 50, 55, 60, 65]
target = [0, 1, 1, 0, 1, 0, 1, 1, 0, 1]
df = pd.DataFrame({'value': data, 'target': target})
X = df[['value']]
y = df['target']
# 训练决策树
clf = DecisionTreeClassifier(max_depth=2)
clf.fit(X, y)
# 提取分割点作为分箱边界
split_points = clf.tree_.threshold[clf.tree_.children_left!= -1]
bins = [np.min(data)] + list(split_points) + [np.max(data)]
labels = [f'{bins[i]}-{bins[i + 1]}' for i in range(len(bins) - 1)]
df['machine_learning_binned'] = pd.cut(df['value'], bins=bins, labels=labels)
print(df)