哈喽,我是小白~
今儿和大家再来聊聊数据采样方法~
在机器学习实验中,数据采样方法非常重要。它决定了模型训练时数据的多样性和平衡性,避免因数据偏差导致模型泛化能力下降。正确的采样方法可以提升模型的表现,确保实验结果的准确性和可重复性。
涉及到的方法有:
简单随机采样 分层采样 系统采样 过采样 欠采样 自助采样 聚类采样 SMOTE 留一法交叉验证 K折交叉验证
咱们一起来看下~
1. 简单随机采样
简单随机采样是一种基础的数据采样方法。它要求从数据集 中随机抽取 个样本,其中每个样本有相同的概率被抽取,适用于独立同分布(i.i.d.)的数据集。
原理
对于一个包含 个样本的数据集 ,简单随机采样的目标是从中选取一个子集 ,其中 的样本量为 。
核心点
每个样本被选中的概率为:
总体组合数为:
从总体 中随机抽取 个样本,且每个样本的抽取概率为均匀分布 。
如果我们不放回抽样,那么每次选择的样本都会减少总体大小,因此抽取 次的联合概率为:
但这种方法在数据量较大时会产生高时间复杂度。
Python实现
下面是一个关于简单随机采样的案例,数据集将会模拟一个拥有多个属性的虚拟数据集。我们将通过简单随机采样的方式,从这个数据集中提取一个子集,然后对其进行数据分析。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.utils import resample
# 设置随机种子
np.random.seed(42)
# 生成虚拟数据集
n = 1000
data = pd.DataFrame({
'age': np.random.randint(18, 65, n),
'income': np.random.normal(50000, 15000, n), # 正态分布的收入
'experience': np.random.randint(1, 40, n),
'satisfaction_score': np.random.uniform(1, 5, n) # 满意度评分从1到5
})
# 简单随机采样:抽取300个样本
sampled_data = resample(data, n_samples=300, random_state=42)
# 创建子图网格
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle('Data Analysis with Simple Random Sampling', fontsize=16)
# 1. 年龄的直方图
sns.histplot(data=sampled_data['age'], bins=15, kde=True, color='orange', ax=axes[0, 0])
axes[0, 0].set_title('Age Distribution (Sampled)', fontsize=12)
axes[0, 0].set_xlabel('Age')
axes[0, 0].set_ylabel('Frequency')
# 2. 收入的箱线图(全体数据 vs 采样数据)
sns.boxplot(data=[data['income'], sampled_data['income']], ax=axes[0, 1], palette='Set2')
axes[0, 1].set_title('Income Boxplot (Original vs Sampled)', fontsize=12)
axes[0, 1].set_xticklabels(['Original', 'Sampled'])
axes[0, 1].set_ylabel('Income')
# 3. 工作年限与收入的散点图
sns.scatterplot(x='experience', y='income', data=sampled_data, color='purple', ax=axes[1, 0])
axes[1, 0].set_title('Experience vs. Income (Sampled)', fontsize=12)
axes[1, 0].set_xlabel('Experience (Years)')
axes[1, 0].set_ylabel('Income')
# 4. 满意度评分的直方图
sns.histplot(data=sampled_data['satisfaction_score'], bins=10, kde=True, color='green', ax=axes[1, 1])
axes[1, 1].set_title('Satisfaction Score Distribution (Sampled)', fontsize=12)
axes[1, 1].set_xlabel('Satisfaction Score')
axes[1, 1].set_ylabel('Frequency')
# 调整布局
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
生成数据集:首先生成一个包含1000行的虚拟数据集,每列分别为年龄、收入、工作年限和满意度评分。年龄和工作年限是通过 np.random.randint
生成的随机整数,收入是正态分布,满意度评分是均匀分布的随机数。简单随机采样:我们使用 resample
函数,从原始数据中抽取300个样本。创建子图网格:我们创建了2x2的子图网格,方便放置4个不同的分析图。
直方图:绘制年龄的直方图并叠加核密度估计线,颜色为橙色。通过查看年龄的分布情况,我们可以了解数据的总体分布趋势。年龄的直方图有助于我们看到采样后数据的代表性是否依旧覆盖了不同年龄段。 箱线图:绘制收入的箱线图,比较原始数据和采样数据的分布,展示数据的离群点。箱线图可以直观地展示数据的分布情况和离群点,通过对比原始数据和采样数据的箱线图,我们可以评估采样是否导致了数据分布的显著变化。 散点图:展示工作年限与收入的关系,颜色为紫色,分析二者之间的相关性。通过工作年限和收入的散点图,可以分析这两个变量之间的关系,是否存在相关性或者潜在的趋势,这种图形特别适合展示两个连续变量之间的相互关系。 满意度评分分布:绘制满意度评分的分布直方图,并叠加核密度估计线,颜色为绿色。满意度评分是一个均匀分布的数据,通过绘制其分布情况,可以了解随机抽样后数据的代表性是否仍然保留了满意度评分的总体分布特征。
2. 分层采样
分层采样是一种改进的采样方法,尤其适用于数据集中存在多个类别或群体(即非均匀分布)时。它会将数据分为不同的“层”,然后在每一层中按照比例进行随机采样,以确保每一类数据在样本中得到合理的代表。
原理
假设数据集被分成 个层次 ,每个层次有 个样本,总样本量为 ,我们希望抽取总数为 的样本。
核心点
每个层次中的样本比例为:
抽取样本数:
分层采样的目标是确保各层次的数据分布不变。假设层 的样本比例为 ,我们希望抽取的样本数量为 ,并且希望 保持与总体一致:
总体来说,分层采样能够有效降低抽样误差。
Python实现
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
# 生成虚拟数据集
np.random.seed(42)
# 创建一个类别不平衡的数据集,特征为一个连续值,类别为分类
n_samples = 1000
X = np.random.normal(0, 1, size=(n_samples, 1)) # 连续值特征
y = np.random.choice([0, 1, 2], size=n_samples, p=[0.1, 0.3, 0.6]) # 类别不平衡
# 创建DataFrame便于处理
df = pd.DataFrame(data={'Feature': X.flatten(), 'Class': y})
# 分层采样:按类别比例采样
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=42)
# 采样前后的类别分布
def plot_class_distribution(y_before, y_after, title_before, title_after):
fig, ax = plt.subplots(1, 2, figsize=(12, 6))
# 原始数据类别分布
ax[0].hist(y_before, bins=np.arange(y_before.min(), y_before.max()+2)-0.5, edgecolor='black', color='skyblue')
ax[0].set_xticks([0, 1, 2])
ax[0].set_title(title_before)
ax[0].set_xlabel('Class')
ax[0].set_ylabel('Frequency')
# 采样后数据类别分布
ax[1].hist(y_after, bins=np.arange(y_after.min(), y_after.max()+2)-0.5, edgecolor='black', color='salmon')
ax[1].set_xticks([0, 1, 2])
ax[1].set_title(title_after)
ax[1].set_xlabel('Class')
plt.tight_layout()
plt.show()
# 特征分布图
def plot_feature_distribution(X_train, X_test):
fig, ax = plt.subplots(figsize=(8, 6))
# 训练集和测试集特征分布
ax.hist(X_train, bins=20, alpha=0.7, label='Train Set', color='cyan', edgecolor='black')
ax.hist(X_test, bins=20, alpha=0.7, label='Test Set', color='magenta', edgecolor='black')
ax.set_title('Feature Distribution Before and After Sampling')
ax.set_xlabel('Feature Value')
ax.set_ylabel('Frequency')
ax.legend()
plt.tight_layout()
plt.show()
# 绘制类别分布
plot_class_distribution(y, y_train, 'Original Class Distribution', 'Class Distribution After Stratified Sampling')
# 绘制特征分布
plot_feature_distribution(X_train, X_test)
1. 类别分布图:通过 plot_class_distribution
函数,我们可以直观看到原始数据中类别不平衡的现象,以及在分层采样后,类别比例仍然得到了很好的保持。
2. 特征分布图:plot_feature_distribution
函数展示了分层采样前后特征的分布情况。两者基本保持一致,表明采样没有显著改变数据的分布特征。
这两个分析图有助于理解分层采样如何在保证类别分布的同时,也尽量保留特征的代表性。
3. 系统采样
系统采样是一种按固定间隔选取样本的采样方法。在这种方法中,首先从数据集中随机选择一个起点,然后按照预定的间隔进行抽样。这种方法适合数据排序良好或没有明显模式的数据集。
原理
假设数据集中有 个样本,我们需要采样 个样本。系统采样的步长 为:
然后在从 到 中随机选择一个起始点 ,之后按照步长 依次选取样本。
核心点
第 个选中的样本的索引为:
假设我们要从一个有 个样本的数据集中选取 个样本,首先计算步长 ,即两个连续样本的间隔:
然后随机选取一个起点 ,后续每个样本按固定间隔 选取,选取的样本索引为:
系统采样的一个优势在于它实现了等间隔抽样,且时间复杂度较低。
Python实现
该案例使用虚拟的销售数据,并结合系统采样来进行数据的简化和分析。
假设我们有一个包含1000条记录的虚拟销售数据集,包括以下列:
Order ID
: 订单编号Sales Amount
: 销售额Order Date
: 订单日期Region
: 销售区域
我们将使用系统采样方法从该数据集中抽取样本数据~
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 生成虚拟数据集
np.random.seed(42)
num_records = 1000
data = pd.DataFrame({
'Order ID': np.arange(1, num_records+1),
'Sales Amount': np.random.exponential(scale=100, size=num_records),
'Order Date': pd.date_range(start='2021-01-01', periods=num_records, freq='D'),
'Region': np.random.choice(['North', 'South', 'East', 'West'], size=num_records)
})
# 系统采样:每隔 k 条记录抽取 1 条 (k=10)
k = 10
systematic_sample = data.iloc[::k]
# 绘图
plt.figure(figsize=(14, 8))
# 1. 原始数据的销售额分布(直方图)
plt.subplot(2, 2, 1)
sns.histplot(data['Sales Amount'], bins=30, color='blue', alpha=0.7, kde=True)
plt.title('Original Sales Amount Distribution', fontsize=14)
plt.xlabel('Sales Amount', fontsize=12)
plt.ylabel('Frequency', fontsize=12)
# 2. 抽样数据的销售额分布(直方图)
plt.subplot(2, 2, 2)
sns.histplot(systematic_sample['Sales Amount'], bins=30, color='green', alpha=0.7, kde=True)
plt.title('Sampled Sales Amount Distribution', fontsize=14)
plt.xlabel('Sales Amount', fontsize=12)
plt.ylabel('Frequency', fontsize=12)
# 3. 原始数据与抽样数据的销售额箱线图对比
plt.subplot(2, 1, 2)
sns.boxplot(data=[data['Sales Amount'], systematic_sample['Sales Amount']],
palette=['orange', 'purple'])
plt.xticks([0, 1], ['Original Data', 'Systematic Sample'], fontsize=12)
plt.title('Boxplot of Sales Amount: Original vs Sample', fontsize=14)
plt.ylabel('Sales Amount', fontsize=12)
# 调整布局
plt.tight_layout()
plt.show()
原始数据的销售额分布图(直方图):通过查看销售额的分布,我们可以理解原始数据中销售额的主要集中范围,并查看数据是否存在偏态或离群值。
抽样数据的销售额分布图(直方图):通过对比原始数据和抽样数据的销售额分布,评估系统采样是否有效保留了数据的主要分布特征。如果分布形态一致,说明采样较好地代表了整体数据。
原始数据与抽样数据的销售额箱线图对比:箱线图可以直观地展示数据的分布特征(如四分位距、中位数、离群点等),通过并排展示原始数据和抽样数据的箱线图,可以更清楚地对比采样后的数据是否仍然具有类似的统计特征。
4. 过采样
过采样是针对类别不平衡问题的一种处理方法,常用于分类问题中。其目的是通过增加少数类样本的数量来平衡类别分布,通常会通过复制现有的少数类样本或生成新样本来实现。
原理
假设类别 和 的样本量分别为 和 (其中 ),为了平衡类别分布,我们可以通过复制或生成少数类 的样本,使得最终的样本量达到 。
核心点
增加的少数类样本数:
假设我们希望少数类样本数与多数类样本数相等,则需要增加 个少数类样本:
实现方法可以是直接复制少数类样本或利用合成数据生成方法(如SMOTE)来增加新的少数类样本。
Python实现
使用 imbalanced-learn
库中的 SMOTE
(Synthetic Minority Over-sampling Technique)来处理不平衡分类问题。我们创建一个虚拟的分类数据集,其中类别严重不平衡,之后使用过采样技术生成更多的少数类别样本。然后,我们将通过不同的数据分析图形来展示数据集的原始分布与过采样后的分布变化。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
import seaborn as sns
# 设置随机种子以确保可重复性
np.random.seed(42)
# 创建一个不平衡的虚拟数据集
X, y = make_classification(n_samples=500, n_features=2, n_classes=2,
n_informative=2, n_redundant=0, n_repeated=0,
n_clusters_per_class=1, weights=[0.9, 0.1],
class_sep=0.8, random_state=42)
# 查看类别的分布
unique, counts = np.unique(y, return_counts=True)
print(f"原始数据类别分布: {dict(zip(unique, counts))}")
# 进行SMOTE过采样
smote = SMOTE(sampling_strategy='auto', random_state=42)
X_res, y_res = smote.fit_resample(X, y)
# 查看过采样后类别的分布
unique_res, counts_res = np.unique(y_res, return_counts=True)
print(f"过采样后类别分布: {dict(zip(unique_res, counts_res))}")
# 创建画布
fig, axs = plt.subplots(2, 2, figsize=(14, 10))
fig.suptitle("Over-sampling using SMOTE", fontsize=16)
# 1. 类别分布柱状图
sns.barplot(x=unique, y=counts, ax=axs[0, 0], palette='bright')
axs[0, 0].set_title('Original Class Distribution')
axs[0, 0].set_xlabel('Class')
axs[0, 0].set_ylabel('Count')
sns.barplot(x=unique_res, y=counts_res, ax=axs[0, 1], palette='bright')
axs[0, 1].set_title('Resampled Class Distribution')
axs[0, 1].set_xlabel('Class')
axs[0, 1].set_ylabel('Count')
# 2. 原始数据的特征分布图
axs[1, 0].scatter(X[y == 0][:, 0], X[y == 0][:, 1], label='Class 0', alpha=0.5, s=50, c='red')
axs[1, 0].scatter(X[y == 1][:, 0], X[y == 1][:, 1], label='Class 1', alpha=0.5, s=50, c='blue')
axs[1, 0].set_title('Original Data Feature Distribution')
axs[1, 0].set_xlabel('Feature 1')
axs[1, 0].set_ylabel('Feature 2')
axs[1, 0].legend()
# 3. 过采样后数据的特征分布图
axs[1, 1].scatter(X_res[y_res == 0][:, 0], X_res[y_res == 0][:, 1], label='Class 0', alpha=0.5, s=50, c='red')
axs[1, 1].scatter(X_res[y_res == 1][:, 0], X_res[y_res == 1][:, 1], label='Class 1', alpha=0.5, s=50, c='blue')
axs[1, 1].set_title('Resampled Data Feature Distribution')
axs[1, 1].set_xlabel('Feature 1')
axs[1, 1].set_ylabel('Feature 2')
axs[1, 1].legend()
# 调整布局
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
我们使用 make_classification
生成一个包含500个样本的虚拟数据集,其中类别严重不平衡,类别 0 有90%的样本,类别 1 只有10%的样本。使用 SMOTE
方法进行过采样,生成更多的少数类样本,使得类别分布变得均衡。分别绘制原始数据和过采样后数据的类别分布柱状图和特征分布散点图。类别分布柱状图 展示了类别不平衡以及过采样后均衡的变化。特征分布散点图 有助于直观观察数据在特征空间中的分布,展示少数类样本如何通过过采样补充。
5. 欠采样
欠采样也是用于处理类别不平衡的一种方法。与过采样相反,欠采样通过减少多数类样本的数量来平衡数据分布。
原理
假设多数类 和少数类 的样本量分别为 和 (其中 ),欠采样的目标是通过随机减少多数类样本数,使得两类样本的数量相等。
核心点
减少的多数类样本数:
假设我们希望多数类的样本数减少到与少数类相等,那么需要从多数类中随机删除 个样本:
这种方法虽然可以解决类别不平衡问题,但也可能导致信息损失,尤其是删除掉重要样本时。
Python实现
在不平衡数据集中,少数类通常占据较小比例,这可能导致机器学习模型过度偏向多数类。我们可以使用欠采样(undersampling)来解决这个问题。欠采样是通过减少多数类的样本数量来平衡数据集,使得分类器更能够识别少数类。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, roc_curve, auc
from imblearn.under_sampling import RandomUnderSampler
import seaborn as sns
# 生成虚拟不平衡数据集
X, y = make_classification(n_samples=5000, n_features=2, n_informative=2, n_redundant=0,
n_clusters_per_class=1, weights=[0.9, 0.1], flip_y=0, random_state=42)
# 将数据分成训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 欠采样处理不平衡数据
rus = RandomUnderSampler(random_state=42)
X_res, y_res = rus.fit_resample(X_train, y_train)
# 训练逻辑回归模型
model = LogisticRegression()
model.fit(X_res, y_res)
# 模型预测
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]
# 绘制图形
fig, axs = plt.subplots(2, 2, figsize=(14, 12))
fig.suptitle("Undersampling Data Analysis", fontsize=16)
# 图1:数据分布可视化
sns.histplot(y_train, bins=2, ax=axs[0, 0], color='blue', kde=False)
sns.histplot(y_res, bins=2, ax=axs[0, 0], color='orange', kde=False)
axs[0, 0].set_title('Class Distribution: Before and After Undersampling')
axs[0, 0].set_xticks([0, 1])
axs[0, 0].legend(['Before Undersampling', 'After Undersampling'])
axs[0, 0].set_xlabel('Class Label')
axs[0, 0].set_ylabel('Count')
# 图2:混淆矩阵
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", ax=axs[0, 1])
axs[0, 1].set_title('Confusion Matrix')
axs[0, 1].set_xlabel('Predicted Label')
axs[0, 1].set_ylabel('True Label')
# 图3:ROC曲线
fpr, tpr, _ = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
axs[1, 0].plot(fpr, tpr, color='green', lw=2, label=f'ROC curve (area = {roc_auc:.2f})')
axs[1, 0].plot([0, 1], [0, 1], color='gray', linestyle='--')
axs[1, 0].set_xlim([0.0, 1.0])
axs[1, 0].set_ylim([0.0, 1.05])
axs[1, 0].set_title('Receiver Operating Characteristic (ROC)')
axs[1, 0].set_xlabel('False Positive Rate')
axs[1, 0].set_ylabel('True Positive Rate')
axs[1, 0].legend(loc="lower right")
# 图4:样本分布可视化(PCA降维)
axs[1, 1].scatter(X_train[:, 0], X_train[:, 1], c=y_train, cmap='coolwarm', alpha=0.6, edgecolor='k')
axs[1, 1].set_title('Original Data Distribution')
axs[1, 1].set_xlabel('Feature 1')
axs[1, 1].set_ylabel('Feature 2')
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()
图1:数据分布可视化,比较欠采样前和欠采样后的类样本分布,展示欠采样如何平衡类比例。可以直观地看到多数类样本数量大幅减少,达到与少数类相近的平衡。
图2:混淆矩阵,展示模型的分类效果,了解模型分类的正确率和错误率。通过可视化的混淆矩阵,可以看到模型在不同类上分类的表现,尤其是查看少数类的分类是否得到改善。
图3:ROC曲线,评估模型的整体性能,通过绘制ROC曲线来展示模型在不同阈值下的分类效果。观察真阳率(TPR)与假阳率(FPR)的关系,并通过AUC值来评估模型性能,AUC值越接近1,模型效果越好。
图4:样本分布可视化,展示原始数据在特征空间中的分布,查看不同类别样本的分布情况。通过2D的散点图展示数据特征分布,从而可以更好地理解样本在特征空间中的分布规律,尤其是少数类与多数类的区分。
通过这些图形,可以全方位地分析欠采样对数据分布、模型效果和分类性能的影响。
6. 自助采样
自助采样是一种有放回的抽样方法,常用于估计统计量的不确定性,如模型的泛化误差。它通过从数据集中多次抽取子集并训练模型,构建不同的模型来评估模型的稳定性。
原理
对于一个包含 个样本的数据集,我们进行有放回的抽样,每次从中抽取 个样本,即一个样本可能会被多次选中。通常用自助采样法构造多个样本集来进行模型评估。
核心点
某个样本 未被选中的概率为:
当 足够大时:
对于自助采样中的每次抽样,样本 未被选中的概率是 。进行 次有放回抽样后,该样本从未被选中的概率为:
根据极限公式 ,因此,当 足够大时,某个样本未被选中的概率约为 ,即大约 36.8% 的样本在每次自助采样中不会被选中。
Python实现
假设我们有一个数据集,包含了某公司的员工年龄和对应的月薪信息。我们的目标是通过自助采样的方法来估计该公司的员工平均月薪的分布情况,以及不同年龄组之间的收入差异。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 设置随机种子
np.random.seed(42)
# 创建虚拟数据集 (年龄: 20-60岁,月薪: 3000-15000元)
n_samples = 100
ages = np.random.randint(20, 61, n_samples)
salaries = np.random.randint(3000, 15001, n_samples)
# 创建DataFrame
df = pd.DataFrame({
'Age': ages,
'Salary': salaries
})
# 自助采样函数
def bootstrap_sample(data, n_iterations=1000):
bootstrap_means = []
bootstrap_samples = []
for i in range(n_iterations):
sample = data.sample(n=len(data), replace=True)
bootstrap_samples.append(sample)
bootstrap_means.append(sample['Salary'].mean())
return bootstrap_means, bootstrap_samples
# 进行自助采样
bootstrap_means, bootstrap_samples = bootstrap_sample(df)
# 原始数据的平均薪水
original_mean = df['Salary'].mean()
# 创建绘图
fig, axs = plt.subplots(2, 2, figsize=(16, 12))
# 1. 原始数据和自助采样均值对比
axs[0, 0].bar(['Original Data'], [original_mean], color='blue', label='Original Mean', alpha=0.7)
axs[0, 0].bar(['Bootstrap Mean'], [np.mean(bootstrap_means)], color='orange', label='Bootstrap Mean', alpha=0.7)
axs[0, 0].set_title('Comparison of Original and Bootstrap Mean')
axs[0, 0].set_ylabel('Average Salary')
axs[0, 0].legend()
# 2. 自助样本的密度分布图
sns.kdeplot(bootstrap_means, ax=axs[0, 1], color='green', label='Bootstrap Mean Distribution', fill=True)
axs[0, 1].set_title('Density Plot of Bootstrap Means')
axs[0, 1].set_xlabel('Salary')
axs[0, 1].legend()
# 3. 年龄与薪水的关系散点图
sns.scatterplot(x='Age', y='Salary', data=df, ax=axs[1, 0], color='purple')
axs[1, 0].set_title('Scatter Plot: Age vs Salary')
axs[1, 0].set_xlabel('Age')
axs[1, 0].set_ylabel('Salary')
# 4. 自助样本中的年龄组薪资分布箱线图(每10岁为一个年龄段)
df['AgeGroup'] = pd.cut(df['Age'], bins=[20, 30, 40, 50, 60], labels=['20-30', '30-40', '40-50', '50-60'])
sns.boxplot(x='AgeGroup', y='Salary', data=df, ax=axs[1, 1], palette='Set2')
axs[1, 1].set_title('Box Plot: Salary Distribution by Age Group')
axs[1, 1].set_xlabel('Age Group')
axs[1, 1].set_ylabel('Salary')
plt.tight_layout()
plt.show()
原始数据和自助采样的均值对比(条形图):展示了原始数据的平均薪资和通过自助采样得到的平均薪资之间的对比。这样可以帮助我们验证自助采样的结果是否能准确估计总体的均值。 自助样本的密度分布图(核密度估计图):自助样本均值的密度分布图,展示了均值在多次采样下的分布情况,可以帮助我们直观地观察到均值的波动范围以及总体的分布形态。 年龄与薪水的关系散点图:用于探索年龄与薪水之间的关系。通过散点图可以查看是否存在线性或非线性的相关性。 自助样本中不同年龄组的薪资分布箱线图:通过箱线图,我们可以观察到不同年龄组的薪资分布情况,比较各个年龄组薪资的中位数、四分位数及异常值等信息。
通过这些图形,我们可以全面地分析自助采样结果,评估不同年龄组的薪资差异,并对公司的整体薪资结构进行推测和优化。这些图形组合展示了从原始数据和自助采样数据中提取的多维信息。
7. 聚类采样
聚类采样是用于分组数据的一种方法。它首先将数据集分成多个簇(群组),然后从簇中随机抽取若干个群组中的所有数据点。这种方法适合群组间差异较大的情况。
原理
假设数据集分为 个群组,每个群组的样本量不同。聚类采样会从这些群组中随机选择 个群组(),然后在每个群组中选取所有样本进行分析。
核心点
从群组中选择的概率为:
聚类采样的原理类似于简单随机采样,但它在分组基础上进行。每个群组被选中的概率相同,每个群组中的所有样本被包含在抽样数据中。
Python实现
我们使用聚类采样方法对一组虚拟客户数据进行分析,目的是通过聚类算法将客户划分为不同的群体,并基于这些群体进行进一步的数据分析。通过聚类后,我们可以对不同群体进行特征探索,例如每个群体的平均消费额、访问频率等。
我们生成一个虚拟的客户数据集,其中包括以下特征:
age
: 客户的年龄annual_income
: 年收入(单位:K美元)spending_score
: 消费得分(1-100之间的一个评分,反映客户的消费倾向)visits_per_month
: 每月访问次数
分析目标:
聚类客户群体:我们使用 KMeans 对客户数据进行聚类。 可视化聚类结果:绘制客户的年龄、年收入、消费得分和聚类的分布情况,展示不同特征之间的聚类情况。 各群体的特征探索:在聚类基础上,分析各群体的平均消费得分和月访问次数。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
# 1. 生成虚拟数据
np.random.seed(42)
# 创建虚拟客户数据集
n_samples = 500
age = np.random.randint(18, 70, size=n_samples)
annual_income = np.random.randint(20, 120, size=n_samples) # 单位K美元
spending_score = np.random.randint(1, 101, size=n_samples)
visits_per_month = np.random.randint(1, 15, size=n_samples)
# 创建DataFrame
data = pd.DataFrame({
'age': age,
'annual_income': annual_income,
'spending_score': spending_score,
'visits_per_month': visits_per_month
})
# 2. 数据标准化
scaler = StandardScaler()
scaled_data = scaler.fit_transform(data)
# 3. 使用KMeans进行聚类
kmeans = KMeans(n_clusters=4, random_state=42)
clusters = kmeans.fit_predict(scaled_data)
data['cluster'] = clusters
# 4. 可视化聚类结果 - 散点图(年龄 vs 年收入,按聚类分色)
plt.figure(figsize=(14, 10))
plt.subplot(2, 2, 1)
colors = ['red', 'green', 'blue', 'orange']
for cluster in range(4):
clustered_data = data[data['cluster'] == cluster]
plt.scatter(clustered_data['age'], clustered_data['annual_income'],
color=colors[cluster], label=f'Cluster {cluster}', alpha=0.6)
plt.title('Age vs Annual Income (Clustered)')
plt.xlabel('Age')
plt.ylabel('Annual Income (K$)')
plt.legend()
# 5. 各群体的平均消费得分柱状图
plt.subplot(2, 2, 2)
cluster_means = data.groupby('cluster')['spending_score'].mean()
plt.bar(cluster_means.index, cluster_means.values, color=colors)
plt.title('Average Spending Score by Cluster')
plt.xlabel('Cluster')
plt.ylabel('Average Spending Score')
# 6. 各群体的月访问次数柱状图
plt.subplot(2, 2, 3)
cluster_visits = data.groupby('cluster')['visits_per_month'].mean()
plt.bar(cluster_visits.index, cluster_visits.values, color=colors)
plt.title('Average Visits per Month by Cluster')
plt.xlabel('Cluster')
plt.ylabel('Average Visits per Month')
# 7. 绘制年龄和消费得分的聚类散点图
plt.subplot(2, 2, 4)
for cluster in range(4):
clustered_data = data[data['cluster'] == cluster]
plt.scatter(clustered_data['age'], clustered_data['spending_score'],
color=colors[cluster], label=f'Cluster {cluster}', alpha=0.6)
plt.title('Age vs Spending Score (Clustered)')
plt.xlabel('Age')
plt.ylabel('Spending Score')
plt.tight_layout()
plt.show()
生成虚拟数据:使用随机数生成虚拟的客户特征数据,包括年龄、年收入、消费得分和访问频率。 标准化数据:为了更好地进行聚类,首先使用 StandardScaler
对数据进行标准化。聚类分析:使用 KMeans 聚类,将客户分成 4 个群体,并将聚类标签添加到数据中。
散点图 1:展示客户的年龄与年收入的分布,并按聚类群体着色,观察聚类效果。 柱状图 1:展示每个群体的平均消费得分,以便对比不同群体的消费习惯。 柱状图 2:展示每个群体的月访问次数,进一步探讨每个群体的客户特征。 散点图 2:展示客户的年龄与消费得分的关系,并通过颜色区分不同的聚类群体。
8. SMOTE
SMOTE 是一种用于少数类样本的合成过采样技术。它通过生成新的少数类样本,而不是简单地复制现有样本来增加少数类的样本数量。常用于分类问题中的不平衡数据处理。
原理
对于每个少数类样本 ,选择其最近邻 个样本,然后在 和其最近邻之间的线段上生成新的样本。
核心点
生成的新样本为:
其中 是随机数, 是最近邻样本。
通过在少数类样本 和其最近邻 之间的线段上选择点,新的样本生成过程为:
其中 为随机数,确保新样本位于 和 之间。
Python实现
我们生成一个不平衡的二分类数据集,然后使用SMOTE对其进行处理:
原始数据分布图:展示不平衡的类分布。 经过SMOTE后的数据分布图:展示经过过采样后,两类数据点的变化情况。
将这些图合并到一个图中,以清晰对比SMOTE的效果。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
from collections import Counter
from sklearn.decomposition import PCA
# 生成虚拟数据集(不平衡)
X, y = make_classification(n_classes=2, class_sep=2, weights=[0.1, 0.9],
n_informative=3, n_redundant=1, flip_y=0,
n_features=10, n_clusters_per_class=1,
n_samples=200, random_state=10)
# 使用PCA降维到2D便于可视化
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
# 原始数据可视化
plt.figure(figsize=(14, 7))
plt.subplot(1, 2, 1)
plt.title("Original Dataset Distribution", fontsize=14)
plt.scatter(X_pca[y == 0][:, 0], X_pca[y == 0][:, 1], label="Class 0", alpha=0.7, s=50, color='blue')
plt.scatter(X_pca[y == 1][:, 0], X_pca[y == 1][:, 1], label="Class 1", alpha=0.7, s=50, color='orange')
plt.legend()
plt.xlabel("PCA Component 1")
plt.ylabel("PCA Component 2")
# 应用SMOTE
smote = SMOTE(random_state=42)
X_res, y_res = smote.fit_resample(X, y)
# 将过采样后的数据进行PCA降维
X_res_pca = pca.transform(X_res)
# 过采样后的数据可视化
plt.subplot(1, 2, 2)
plt.title("SMOTE Applied Dataset Distribution", fontsize=14)
plt.scatter(X_res_pca[y_res == 0][:, 0], X_res_pca[y_res == 0][:, 1], label="Class 0", alpha=0.7, s=50, color='blue')
plt.scatter(X_res_pca[y_res == 1][:, 0], X_res_pca[y_res == 1][:, 1], label="Class 1", alpha=0.7, s=50, color='orange')
plt.legend()
plt.xlabel("PCA Component 1")
plt.ylabel("PCA Component 2")
# 展示图像
plt.tight_layout()
plt.show()
我们使用 make_classification
生成一个带有严重类不平衡的数据集(10%的数据点属于类0,90%属于类1)。使用 PCA
将高维数据降维到2维,以便能够可视化。SMOTE
被应用于数据集来生成合成的少数类数据,平衡类分布。最终我们通过对比两个子图来展示SMOTE的效果。
9. 留一法交叉验证
留一法交叉验证是一种特殊的交叉验证方法。它将数据集中每个样本都作为一次验证集,剩余的样本作为训练集。因此,如果有 个样本,模型将训练 次,每次用 个样本训练,1 个样本验证。
原理
将数据集分为 份,每次使用 个样本训练模型,剩下的 1 个样本用于验证,重复 次。
核心点
每次的验证误差为:
总误差为:
每次训练时,将数据集分为训练集和验证集。验证误差为对所有单次误差的平均。
Python实现
接下来:
使用一个简单的线性回归模型对数据进行拟合。 使用留一法交叉验证对模型进行验证。 绘制多个图形来分析数据与模型性能。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import LeaveOneOut
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# 生成虚拟数据集
np.random.seed(42)
X = np.linspace(0, 10, 20).reshape(-1, 1) # 特征值
y = 2.5 * X.flatten() + np.random.normal(0, 1, 20) # 目标值 (线性关系加噪声)
# 模型和交叉验证
loo = LeaveOneOut()
model = LinearRegression()
# 保存每次训练的预测结果和残差
predictions = []
mse_list = []
errors = []
# 图1数据:原始数据散点和模型的多次拟合
plt.figure(figsize=(14, 8))
plt.subplot(1, 2, 1)
plt.scatter(X, y, color='blue', label='Data Points')
# LOO交叉验证
for train_index, test_index in loo.split(X):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# 模型拟合与预测
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
predictions.append(y_pred[0])
# 计算残差
errors.append(y_test[0] - y_pred[0])
# 图1:画出每次拟合的线
plt.plot(X, model.predict(X), color='green', alpha=0.2)
# 图1:最终的拟合曲线
model.fit(X, y)
plt.plot(X, model.predict(X), color='red', label='Final Model', linewidth=2)
plt.title("Data Points and Fitted Lines with LOO")
plt.xlabel("X")
plt.ylabel("y")
plt.legend()
# 图2:残差图
plt.subplot(1, 2, 2)
plt.scatter(np.arange(len(errors)), errors, color='magenta', label='Errors (y_true - y_pred)')
plt.axhline(y=0, color='black', linestyle='--', linewidth=1)
plt.title("Residual Plot for LOO Cross-Validation")
plt.xlabel("Test Sample Index")
plt.ylabel("Residuals")
plt.legend()
plt.tight_layout()
plt.show()
# 打印平均残差和误差平方和
mse = mean_squared_error(y, predictions)
print(f"Mean Squared Error of LOO-CV: {mse:.4f}")
原始数据分布与拟合模型的折线图:该图展示了我们在每次留一法交叉验证中,使用不同的训练集对数据进行拟合时,模型对数据点的拟合情况。通过观察不同的拟合线条,我们可以直观地看到数据点的分布情况以及每次拟合的线条变化。这可以帮助分析模型的表现是否具有稳定性,以及整体拟合的效果。
预测值与真实值的残差图:残差图显示了模型的预测值与真实值之间的差异(即残差)。通过观察残差是否围绕零点随机分布,我们可以判断模型的拟合是否存在系统性偏差。此外,残差的大小可以反映出模型的准确性,残差越小模型越好。这个图是衡量模型性能的常用工具。
10. K折交叉验证
K 折交叉验证是一种常用的模型验证方法。它将数据集分成 个不重叠的子集(折),每次使用 个子集作为训练集,剩余的 1 个子集作为验证集,重复 次,最终结果是 次验证结果的平均值。
原理
数据集被划分为 个子集,模型会进行 次训练和验证,每次验证一个子集。
核心点
每次验证误差:
总误差:
将数据集分为 份,训练 次,每次使用不同的子集作为验证集,将所有验证结果取平均。
Python实现
通过 K 折交叉验证来分析一个虚拟数据集。我们选择逻辑回归作为模型,并使用 KFold
进行 K 折交叉验证。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from sklearn.model_selection import KFold
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix, roc_curve, auc
from sklearn.preprocessing import label_binarize
# 生成虚拟数据集
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2, random_state=42)
# KFold设置
kf = KFold(n_splits=5, shuffle=True, random_state=42)
# 准备画布
fig, axes = plt.subplots(3, 5, figsize=(20, 12))
fig.tight_layout(pad=5.0)
# 用于存储结果的列表
accuracies = []
roc_aucs = []
fold_idx = 1
# 迭代每个KFold
for train_index, test_index in kf.split(X):
# 分割数据集
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# 训练模型
model = LogisticRegression()
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1]
# 计算准确率
acc = accuracy_score(y_test, y_pred)
accuracies.append(acc)
# 计算混淆矩阵
cm = confusion_matrix(y_test, y_pred)
# 绘制混淆矩阵
ax_cm = axes[1, fold_idx-1] # 使用第二行的子图
ax_cm.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
ax_cm.set_title(f'Confusion Matrix Fold {fold_idx}')
ax_cm.set_xlabel('Predicted label')
ax_cm.set_ylabel('True label')
# 准备ROC曲线
fpr, tpr, _ = roc_curve(y_test, y_prob)
roc_auc = auc(fpr, tpr)
roc_aucs.append(roc_auc)
# 绘制ROC曲线
ax_roc = axes[2, fold_idx-1] # 使用第三行的子图
ax_roc.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (AUC = {roc_auc:.2f})')
ax_roc.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
ax_roc.set_xlim([0.0, 1.0])
ax_roc.set_ylim([0.0, 1.05])
ax_roc.set_xlabel('False Positive Rate')
ax_roc.set_ylabel('True Positive Rate')
ax_roc.set_title(f'ROC Curve Fold {fold_idx}')
ax_roc.legend(loc="lower right")
fold_idx += 1
# 绘制准确率图(位于第一行第一列)
ax_acc = axes[0, 1] # 将准确率图放在第一行的第二个子图位置
ax_acc.plot(range(1, 6), accuracies, marker='o', color='red', label='Accuracy per Fold')
ax_acc.set_title('Model Accuracy per Fold')
ax_acc.set_xlabel('Fold')
ax_acc.set_ylabel('Accuracy')
ax_acc.legend()
# 显示总AUC(位于第一行第二列)
avg_roc_auc = np.mean(roc_aucs)
ax_auc = axes[0, 2] # AUC图放在第一行的第三个子图位置
ax_auc.bar(range(1, 6), roc_aucs, color='green', label=f'Avg AUC: {avg_roc_auc:.2f}')
ax_auc.set_title('AUC per Fold')
ax_auc.set_xlabel('Fold')
ax_auc.set_ylabel('AUC')
ax_auc.legend()
plt.show()
准确率变化图:该图展示了每个折叠(Fold)的模型准确率。通过查看这张图,我们可以判断模型在各个折叠中的稳定性,是否某一折的表现特别差或好。
混淆矩阵:混淆矩阵展示了每一折的分类结果,帮助我们更深入地分析模型的分类情况。通过这张图可以直观了解哪些类别更容易被误分类。
ROC 曲线:ROC 曲线是分类器性能的一个重要指标。每一折的 ROC 曲线展示了模型在不同阈值下的表现,曲线下面积(AUC)用来衡量分类器的总体性能。
AUC 柱状图:此图展示了每一折的 AUC 值,并计算了平均 AUC。这有助于了解模型在不同数据切分下的表现一致性。
图中综合展示模型的多方面表现。准确率图可以观察模型的整体稳定性,混淆矩阵可以揭示具体的分类问题,而 ROC 曲线和 AUC 则提供了分类器在不同阈值下的表现指标。这种复杂的分析方式可以帮助大家更好地理解模型在不同数据分布下的性能。
最后