关键词:分层抽样、scikit-learn、Python、数据采样、机器学习、数据预处理、统计学
摘要:本文深入探讨了分层抽样在数据科学和机器学习中的应用。我们将从统计学基础出发,详细讲解分层抽样的原理、优势以及实现方法。通过Python和scikit-learn库的实际代码示例,展示如何在不同场景下应用分层抽样技术。文章还涵盖了分层抽样的数学模型、实际应用案例以及常见问题的解决方案,为读者提供全面的分层抽样知识体系。
分层抽样是一种重要的数据采样技术,广泛应用于统计学、机器学习、市场调研等领域。本文旨在全面介绍分层抽样的概念、原理和实现方法,特别是如何使用Python和scikit-learn库进行高效的分层抽样操作。
本文适合以下读者:
文章首先介绍分层抽样的基本概念和原理,然后深入探讨其数学基础和算法实现。接着通过实际代码示例展示具体应用,最后讨论相关工具、资源和未来发展方向。
分层抽样的核心思想是将总体划分为若干个同质的子群体(层),然后在每个层内进行抽样。这种方法能够确保样本更好地代表总体结构,特别是当总体中存在明显的子群体时。
分层抽样与简单随机抽样的主要区别在于:
分层抽样的优势:
scikit-learn提供了StratifiedShuffleSplit
和train_test_split
(带有stratify参数)来实现分层抽样。
from sklearn.model_selection import train_test_split
# 假设X是特征矩阵,y是目标变量
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
stratify=y, # 按y的分布进行分层
random_state=42
)
from sklearn.model_selection import StratifiedShuffleSplit
sss = StratifiedShuffleSplit(n_splits=5, test_size=0.2, random_state=42)
for train_index, test_index in sss.split(X, y):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# 进行模型训练和评估
对于更复杂的分层需求,可以自定义分层抽样函数:
import numpy as np
import pandas as pd
from sklearn.utils import check_random_state
def stratified_sample(df, strata, size=None, seed=None):
# 参数检查
if not isinstance(df, pd.DataFrame):
raise TypeError("df必须是pandas DataFrame")
if not isinstance(strata, (list, tuple, np.ndarray, pd.Series)):
raise TypeError("strata必须是列表、元组、numpy数组或pandas Series")
if size is not None and not isinstance(size, (int, float)):
raise TypeError("size必须是整数或浮点数")
# 设置随机种子
random_state = check_random_state(seed)
# 计算各层大小
strata_counts = df[strata].value_counts()
# 确定样本量
if size is None:
# 默认按比例抽样
sample_counts = strata_counts
elif isinstance(size, float) and 0 < size < 1:
# 按比例抽样
sample_counts = (strata_counts * size).round().astype(int)
elif isinstance(size, int):
# 固定样本量,按比例分配
proportions = strata_counts / strata_counts.sum()
sample_counts = (proportions * size).round().astype(int)
else:
raise ValueError("size必须是(0,1)之间的浮点数或正整数")
# 进行分层抽样
samples = []
for stratum, count in sample_counts.iteritems():
stratum_df = df[df[strata] == stratum]
if len(stratum_df) > count:
sample = stratum_df.sample(count, random_state=random_state)
else:
sample = stratum_df.copy()
samples.append(sample)
# 合并样本
sample_df = pd.concat(samples)
return sample_df
总体均值 μ \mu μ的分层估计量为:
μ ^ s t = ∑ h = 1 L W h y ˉ h \hat{\mu}_{st} = \sum_{h=1}^{L} W_h \bar{y}_h μ^st=h=1∑LWhyˉh
其中:
分层抽样估计量的方差为:
V ( μ ^ s t ) = ∑ h = 1 L W h 2 ( 1 − n h N h ) S h 2 n h V(\hat{\mu}_{st}) = \sum_{h=1}^{L} W_h^2 \left(1 - \frac{n_h}{N_h}\right) \frac{S_h^2}{n_h} V(μ^st)=h=1∑LWh2(1−Nhnh)nhSh2
其中:
n h = n × N h N n_h = n \times \frac{N_h}{N} nh=n×NNh
考虑层内变异性和抽样成本:
n h = n × W h S h ∑ h = 1 L W h S h n_h = n \times \frac{W_h S_h}{\sum_{h=1}^{L} W_h S_h} nh=n×∑h=1LWhShWhSh
假设一个总体分为3层,各层信息如下:
层(h) | 层大小(N_h) | 层均值(μ_h) | 层标准差(σ_h) |
---|---|---|---|
1 | 1000 | 50 | 10 |
2 | 2000 | 60 | 15 |
3 | 3000 | 70 | 20 |
总样本量n=600,计算比例分配和内曼分配的样本量:
比例分配:
n 1 = 600 × 1000 6000 = 100 n 2 = 600 × 2000 6000 = 200 n 3 = 600 × 3000 6000 = 300 n_1 = 600 \times \frac{1000}{6000} = 100 \\ n_2 = 600 \times \frac{2000}{6000} = 200 \\ n_3 = 600 \times \frac{3000}{6000} = 300 \\ n1=600×60001000=100n2=600×60002000=200n3=600×60003000=300
内曼分配:
首先计算 W h S h W_h S_h WhSh:
W 1 S 1 = 1000 6000 × 10 ≈ 1.6667 W 2 S 2 = 2000 6000 × 15 = 5 W 3 S 3 = 3000 6000 × 20 = 10 ∑ W h S h ≈ 16.6667 W_1 S_1 = \frac{1000}{6000} \times 10 \approx 1.6667 \\ W_2 S_2 = \frac{2000}{6000} \times 15 = 5 \\ W_3 S_3 = \frac{3000}{6000} \times 20 = 10 \\ \sum W_h S_h \approx 16.6667 \\ W1S1=60001000×10≈1.6667W2S2=60002000×15=5W3S3=60003000×20=10∑WhSh≈16.6667
然后计算各层样本量:
n 1 = 600 × 1.6667 16.6667 ≈ 60 n 2 = 600 × 5 16.6667 ≈ 180 n 3 = 600 × 10 16.6667 ≈ 360 n_1 = 600 \times \frac{1.6667}{16.6667} \approx 60 \\ n_2 = 600 \times \frac{5}{16.6667} \approx 180 \\ n_3 = 600 \times \frac{10}{16.6667} \approx 360 \\ n1=600×16.66671.6667≈60n2=600×16.66675≈180n3=600×16.666710≈360
推荐使用以下环境进行分层抽样实验:
安装命令:
pip install pandas numpy scikit-learn matplotlib
import pandas as pd
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
# 加载数据
data = pd.read_csv('creditcard.csv')
# 查看类别分布
print(data['Class'].value_counts(normalize=True))
# 可视化类别分布
data['Class'].value_counts().plot(kind='bar')
plt.title('Class Distribution')
plt.show()
# 分层抽样
X = data.drop('Class', axis=1)
y = data['Class']
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.3,
stratify=y,
random_state=42
)
# 检查抽样后的分布
print("训练集分布:")
print(y_train.value_counts(normalize=True))
print("\n测试集分布:")
print(y_test.value_counts(normalize=True))
import pandas as pd
import numpy as np
# 创建模拟数据
np.random.seed(42)
size = 1000
data = pd.DataFrame({
'age': np.random.randint(18, 70, size),
'income': np.random.normal(50000, 15000, size).astype(int),
'education': np.random.choice(['High School', 'Bachelor', 'Master', 'PhD'], size),
'target': np.random.choice([0, 1], size, p=[0.7, 0.3])
})
# 创建分层变量 - 结合年龄分段和教育程度
data['age_group'] = pd.cut(data['age'],
bins=[18, 30, 40, 50, 70],
labels=['18-29', '30-39', '40-49', '50+'])
data['strata'] = data['age_group'].astype(str) + "_" + data['education']
# 自定义分层抽样
def multifactor_stratified_sample(df, strata_cols, size=0.2, random_state=None):
# 创建分层变量
df['strata'] = df[strata_cols].apply(lambda x: '_'.join(x.astype(str)), axis=1)
# 计算各层大小
strata_counts = df['strata'].value_counts()
# 确定样本量
if isinstance(size, float) and 0 < size < 1:
sample_counts = (strata_counts * size).round().astype(int)
elif isinstance(size, int):
proportions = strata_counts / strata_counts.sum()
sample_counts = (proportions * size).round().astype(int)
else:
raise ValueError("size必须是(0,1)之间的浮点数或正整数")
# 进行分层抽样
samples = []
for stratum, count in sample_counts.items():
stratum_df = df[df['strata'] == stratum]
if len(stratum_df) > count:
sample = stratum_df.sample(count, random_state=random_state)
else:
sample = stratum_df.copy()
samples.append(sample)
# 合并样本并移除临时列
sample_df = pd.concat(samples)
sample_df = sample_df.drop('strata', axis=1)
return sample_df
# 应用多因素分层抽样
sampled_data = multifactor_stratified_sample(
data,
strata_cols=['age_group', 'education'],
size=0.3,
random_state=42
)
# 检查抽样结果
print("原始数据分布:")
print(data[['age_group', 'education']].value_counts(normalize=True))
print("\n抽样后分布:")
print(sampled_data[['age_group', 'education']].value_counts(normalize=True))
信用卡欺诈检测案例:
train_test_split
的stratify
参数确保训练集和测试集保持相同的类别比例多变量分层抽样案例:
multifactor_stratified_sample
实现了灵活的多因素分层抽样pd.cut
创建年龄分段,便于分层分层抽样在以下场景中特别有用:
不平衡分类问题:
小总体中的子群体分析:
地理空间抽样:
市场调研:
A/B测试:
时间序列数据:
分层抽样作为一种经典抽样技术,在未来仍将发挥重要作用,但也面临新的挑战和发展机遇:
发展趋势:
挑战:
未来,分层抽样技术将继续与机器学习、大数据技术深度融合,发展出更智能、更高效的变体,以满足日益复杂的数据分析需求。
Q1: 什么时候应该使用分层抽样而不是简单随机抽样?
A1: 当总体中存在明显的子群体结构,且这些子群体在目标变量上表现不同时,应该使用分层抽样。特别是当某些子群体占比很小时,分层抽样能确保它们有足够的代表。
Q2: 如何选择分层变量?
A2: 选择与目标变量相关性高的变量作为分层变量。可以通过探索性数据分析(EDA)或计算变量与目标的相关性来确定。
Q3: 分层抽样会导致过拟合吗?
A3: 正确实施的分层抽样不会导致过拟合。实际上,它能提高模型的泛化能力,特别是在不平衡数据集中。但要注意不要创建过多的层,导致某些层的样本量过小。
Q4: 如何处理某些层样本量过小的问题?
A4: 可以采取以下策略:
Q5: 分层抽样与交叉验证如何结合使用?
A5: 可以使用分层K折交叉验证(StratifiedKFold),确保每一折都保持原始数据的类别分布。这在scikit-learn中很容易实现。
通过本文的全面介绍,读者应该能够理解分层抽样的原理、掌握Python实现方法,并能在实际项目中合理应用这一技术。分层抽样是数据科学家工具箱中的重要工具,正确使用可以显著提高数据分析的质量和可靠性。