简介这份数据分析大作业资料围绕红酒数据集展开面向高校数据科学、统计学课程的作业提交者与自学者帮助完成从数据清洗到建模的完整分析流程。压缩包内共1个docx文档约202KB以文字与代码截图形式呈现分析报告便于直接参考或改写为课程作业。内容涵盖红酒11项物理属性与评分的关联性分析、Spearman相关性检验、多元线性回归拟合、主成分回归降维以及KNN分类的初步尝试并给出R方值、回归方程与预测区间等具体结果。读者可借此掌握数据格式转换、变量筛选、主成分提取与模型评估的实操思路理解如何用统计方法解释红酒评分的影响因素。目前已有4264人学习下载适合需要一份结构完整、步骤清晰的数据分析案例作为参考的读者。1. 红酒数据集分析从数据清洗到回归与分类的完整落地红酒数据集是 UCI 机器学习库中被引用最多的多变量数据集之一包含 1599 条红葡萄酒样本11 个理化特征固定酸度、挥发性酸度、柠檬酸、残糖、氯化物、游离二氧化硫、总二氧化硫、密度、pH、硫酸盐、酒精以及 1 个感官评分输出quality0-10 整数。很多数据分析大作业拿到手第一反应是直接df.describe()然后画个热力图交差但真正做过的人知道这个数据集有几个非常刁钻的地方quality 分布严重不平衡大部分集中在 5 和 6特征之间存在多重共线性比如固定酸度和 pH 的相关系数接近 -0.68而且直接拿 quality 做多元线性回归会得到一个 R² 只有 0.35 左右的“翻车”结果。这篇笔记不讲空话按实际做作业的顺序把数据清洗、探索性分析、多元线性回归、KNN 分类、模型评估这条链路完整走一遍每一步给出可复现的代码和参数解释。适合正在做数据分析大作业、想用红酒数据集练手回归和分类、或者想搞清楚“为什么我的模型效果总是不行”的读者。2. 数据加载与清洗先把黑匣子打开2.1 加载数据与第一轮体检拿到红酒数据集第一步不是急着画图而是把数据加载进来做一次全面体检。常见做法是用 pandas 读取 CSV然后依次检查形状、缺失值、重复值、数据类型和基本统计量。我一般会写一个inspect_data函数把这几项一次性打出来避免来回敲命令。import pandas as pd import numpy as np # 加载红酒数据集分隔符是分号注意 encoding df pd.read_csv(winequality-red.csv, sep;, encodingutf-8) def inspect_data(data): print(形状:, data.shape) print(\n缺失值:\n, data.isnull().sum()) print(\n重复行数:, data.duplicated().sum()) print(\n数据类型:\n, data.dtypes) print(\n描述统计:\n, data.describe().T) inspect_data(df)这段代码的逻辑很直接shape告诉你样本量和特征数红酒数据集标准是 1599 行 12 列isnull().sum()逐列统计缺失值这个数据集通常没有缺失但作业里如果是从 Kaggle 下载的变体版本可能会有duplicated().sum()检查完全重复的行红酒数据集有约 240 条重复记录这是后面要处理的一个点describe().T转置后方便看每个特征的均值、标准差和分位数。参数上唯一需要注意的是sep;UCI 原始文件用分号分隔如果错用逗号会读成一整列。2.2 重复值与异常值的处理策略重复行怎么处理取决于你的分析目标。如果做回归预测重复行会导致模型过拟合到某些特定组合建议去重如果做分类且关注类别分布去重前要先看重复行是否集中在某个 quality 值上。我一般会先去重然后对特征做异常值检测。红酒数据集中几个容易出异常的特征是残糖、氯化物和硫酸盐它们的分布右偏严重。# 去重 df_clean df.drop_duplicates().reset_index(dropTrue) print(f去重后样本数: {len(df_clean)}) # 用 IQR 方法标记异常值但不直接删除先看看比例 def iqr_outlier_ratio(series): q1, q3 series.quantile(0.25), series.quantile(0.75) iqr q3 - q1 lower, upper q1 - 1.5 * iqr, q3 1.5 * iqr return ((series lower) | (series upper)).mean() for col in df_clean.columns[:-1]: ratio iqr_outlier_ratio(df_clean[col]) if ratio 0.02: print(f{col}: 异常值比例 {ratio:.2%})IQR 方法的核心是用四分位距定义正常范围超出 1.5 倍 IQR 的标记为异常。这里没有直接删除是因为红酒数据集的异常值很多是真实的极端样本比如高残糖的甜酒删掉反而损失信息。我一般会保留但在回归时考虑用鲁棒缩放。参数上1.5 是经典阈值如果你想更激进可以用 3 倍 IQR 只标记极端异常。注意df_clean.columns[:-1]排除了 quality 列因为 quality 是离散的类别标签不适合用 IQR 判断。2.3 特征缩放与 quality 的两种用法红酒数据集的 11 个特征量纲差异很大密度在 0.99 左右总二氧化硫可以到 289酒精在 8-15 之间。做 KNN 或任何基于距离的算法之前必须缩放否则密度这个特征几乎不起作用。常见做法是 StandardScaler 或 MinMaxScaler我一般用 StandardScaler因为后续做回归时对正态性有一定帮助。from sklearn.preprocessing import StandardScaler feature_cols [c for c in df_clean.columns if c ! quality] X df_clean[feature_cols] y_reg df_clean[quality] # 回归目标原始 quality 值 y_clf (df_clean[quality] 6).astype(int) # 分类目标二分类6 分及以上为好酒 scaler StandardScaler() X_scaled scaler.fit_transform(X) X_scaled pd.DataFrame(X_scaled, columnsfeature_cols)这里的关键决策是把 quality 用两次回归任务直接用原始 0-10 的整数分类任务转成二分类标签6 为 1否则为 0。为什么这么转因为 quality 的分布中 5 和 6 占了约 80%直接做多分类会导致模型把所有样本都预测成 5 或 6准确率看起来有 60% 但毫无意义。二分类后正负样本比例大约 4:6虽然仍不平衡但可接受。astype(int)把布尔值转成 0/1这是 sklearn 分类器要求的格式。StandardScaler 的fit_transform只在训练集上 fit测试集要用transform这个坑后面会细说。3. 探索性分析热力图和分布图里藏着什么3.1 相关性热力图与多重共线性识别探索性分析不是走形式红酒数据集的相关性热力图能直接告诉你哪些特征该保留、哪些该组合。用 seaborn 画热力图是最快的方式但要注意只画下三角避免重复信息。import seaborn as sns import matplotlib.pyplot as plt plt.figure(figsize(12, 10)) corr df_clean.corr() mask np.triu(np.ones_like(corr, dtypebool)) sns.heatmap(corr, maskmask, annotTrue, fmt.2f, cmapRdBu_r, center0, squareTrue, linewidths0.5) plt.title(红酒数据集特征相关性矩阵) plt.tight_layout() plt.show() # 打印与 quality 相关性最高的特征 print(corr[quality].sort_values(ascendingFalse))这段代码里mask np.triu(...)生成上三角的布尔掩码把重复的上三角遮掉只保留左下角。annotTrue在格子里显示数值fmt.2f保留两位小数center0让颜色以 0 为中心对称。从结果你会看到酒精与 quality 正相关最高约 0.48挥发性酸度负相关最高约 -0.39固定酸度和 pH 强负相关-0.68游离二氧化硫和总二氧化硫强正相关0.67。这意味着做线性回归时固定酸度和 pH 不能同时放进去否则系数会不稳定这就是多重共线性的典型表现。3.2 品质分布与特征分组对比quality 的分布必须画出来否则你无法理解为什么回归效果差。用 countplot 看分布再用箱线图看不同 quality 下酒精和挥发性酸度的差异。fig, axes plt.subplots(1, 3, figsize(18, 5)) # quality 分布 sns.countplot(xquality, datadf_clean, axaxes[0], paletteviridis) axes[0].set_title(Quality 分布) # 酒精 vs quality sns.boxplot(xquality, yalcohol, datadf_clean, axaxes[1], paletteviridis) axes[1].set_title(酒精含量 vs Quality) # 挥发性酸度 vs quality sns.boxplot(xquality, yvolatile acidity, datadf_clean, axaxes[2], paletteviridis) axes[2].set_title(挥发性酸度 vs Quality) plt.tight_layout() plt.show()从第一张图你会看到 quality5 和 6 的柱子远高于其他3、4、8 的样本极少。第二张图显示 quality 越高酒精的中位数越大趋势明显。第三张图显示 quality 越高挥发性酸度越低。这两张图验证了相关性分析的结果也提示我们如果做二分类好酒/普通酒酒精和挥发性酸度应该是强特征。参数上paletteviridis只是配色不影响分析结论但建议用色盲友好的配色。3.3 用 pairplot 快速筛查特征组合当特征多于 5 个时pairplot 会变得很慢且拥挤。我一般只选与 quality 相关性最高的 4 个特征做 pairplot加上 hue 区分二分类标签。top_features [alcohol, volatile acidity, citric acid, sulphates, quality] sns.pairplot(df_clean[top_features], huequality, palettecoolwarm, diag_kindkde, plot_kws{alpha: 0.5, s: 15}) plt.show()huequality会按 quality 值给点上色diag_kindkde在对角线画核密度曲线而不是直方图alpha0.5让点半透明避免重叠看不清。从 pairplot 里你能直观看到酒精和硫酸盐的组合对高 quality 样本有一定区分度但重叠区域很大这意味着单靠线性边界很难完美分类KNN 这类非线性方法可能更合适。这一步的产出是给你后面选模型提供依据不是单纯画图好看。4. 多元线性回归为什么你的 R² 只有 0.354.1 建模流程与交叉验证多元线性回归是红酒数据集最常被布置的任务但很多人直接LinearRegression().fit(X, y)然后看 R²得到 0.35 左右就不知道怎么办了。正确的做法是先划分训练测试集用交叉验证评估稳定性再看系数。from sklearn.model_selection import train_test_split, cross_val_score from sklearn.linear_model import LinearRegression from sklearn.metrics import r2_score, mean_squared_error X_train, X_test, y_train, y_test train_test_split( X_scaled, y_reg, test_size0.2, random_state42 ) lr LinearRegression() lr.fit(X_train, y_train) y_pred lr.predict(X_test) print(f测试集 R²: {r2_score(y_test, y_pred):.4f}) print(f测试集 RMSE: {np.sqrt(mean_squared_error(y_test, y_pred)):.4f}) # 5 折交叉验证 cv_scores cross_val_score(lr, X_scaled, y_reg, cv5, scoringr2) print(f交叉验证 R²: {cv_scores.mean():.4f} ± {cv_scores.std():.4f})train_test_split的random_state42保证每次划分一致test_size0.2是常用比例。cross_val_score做 5 折交叉验证输出均值和标准差如果标准差很大说明模型不稳定。红酒数据集上线性回归的交叉验证 R² 通常在 0.30-0.35 之间RMSE 约 0.65。这个结果说明11 个理化特征只能解释 quality 约 35% 的变异剩下的 65% 来自感官评分的个体差异、酿造工艺等未记录因素。这不是你的代码写错了是数据本身的局限。4.2 系数解读与多重共线性诊断线性回归的系数告诉你每个特征对 quality 的边际影响但有多重共线性时系数不可靠。用 VIF方差膨胀因子诊断。from statsmodels.stats.outliers_influence import variance_inflation_factor import statsmodels.api as sm X_with_const sm.add_constant(X_scaled) vif_data pd.DataFrame() vif_data[feature] X_with_const.columns vif_data[VIF] [variance_inflation_factor(X_with_const.values, i) for i in range(X_with_const.shape[1])] print(vif_data.sort_values(VIF, ascendingFalse))VIF 大于 10 通常认为存在严重共线性。红酒数据集里固定酸度和 pH 的 VIF 会很高因为两者相关性 -0.68。解决办法是删掉其中一个或者用 PCA 降维。我一般会先看 VIF如果只有一对特征高就删掉对 quality 相关性更低的那个。sm.add_constant是给设计矩阵加一列常数项否则 VIF 计算会出错。variance_inflation_factor的第二个参数是列索引循环里遍历所有列。4.3 用 statsmodels 看完整回归报告sklearn 的 LinearRegression 只给系数和截距要看 p 值、置信区间得用 statsmodels。model sm.OLS(y_reg, X_with_const).fit() print(model.summary())model.summary()会输出一张大表重点看三列coef系数、P|t|p 值、[0.025, 0.975]95% 置信区间。p 值小于 0.05 的特征统计显著。在红酒数据集上酒精、挥发性酸度、硫酸盐通常显著而柠檬酸、氯化物的 p 值可能大于 0.05。这不意味着它们没用只是在线性模型里贡献不显著。如果你要做特征选择可以基于 p 值逐步剔除但要注意剔除后 R² 可能下降不多模型更简洁。5. KNN 分类从 k 值选择到类别不平衡处理5.1 KNN 建模与 k 值调优KNN 是红酒数据集分类任务的另一个高频考点。它的核心参数是 k邻居数k 太小容易过拟合k 太大欠拟合。用网格搜索找最优 k。from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import GridSearchCV from sklearn.metrics import classification_report, confusion_matrix X_train_c, X_test_c, y_train_c, y_test_c train_test_split( X_scaled, y_clf, test_size0.2, random_state42, stratifyy_clf ) param_grid {n_neighbors: range(3, 31, 2), weights: [uniform, distance], metric: [euclidean, manhattan]} knn KNeighborsClassifier() grid GridSearchCV(knn, param_grid, cv5, scoringf1, n_jobs-1) grid.fit(X_train_c, y_train_c) print(f最优参数: {grid.best_params_}) print(f最优 F1: {grid.best_score_:.4f})stratifyy_clf保证训练集和测试集的类别比例一致这对不平衡数据很重要。param_grid里 k 从 3 到 30 步长 2weights可选 uniform等权或 distance按距离加权metric可选欧氏或曼哈顿距离。scoringf1而不是 accuracy因为类别不平衡时 accuracy 有欺骗性。n_jobs-1用所有 CPU 核心并行。红酒数据集上最优 k 通常在 15-25 之间distance 加权略好于 uniform。5.2 分类报告与混淆矩阵解读训练完必须看分类报告和混淆矩阵不能只看一个准确率。best_knn grid.best_estimator_ y_pred_c best_knn.predict(X_test_c) print(classification_report(y_test_c, y_pred_c, target_names[普通酒, 好酒])) print(confusion_matrix(y_test_c, y_pred_c))classification_report输出 precision、recall、f1-score 和 support。在红酒二分类上普通酒的 recall 通常高于好酒因为普通酒样本多。混淆矩阵的四个格子分别是 TN、FP、FN、TP。如果 FN 很高把好酒预测成普通酒说明模型对好酒的特征学习不足可以考虑增加好酒样本的权重或调整分类阈值。target_names按 0、1 的顺序对应别写反了。5.3 类别不平衡的三种处理方式红酒数据集中好酒占比约 40%虽然不算极端不平衡但 KNN 仍会偏向多数类。三种常见处理方式过采样、欠采样、调整 class_weight。KNN 没有内置 class_weight所以用 imblearn 的 SMOTE 过采样。from imblearn.over_sampling import SMOTE smote SMOTE(random_state42) X_train_res, y_train_res smote.fit_resample(X_train_c, y_train_c) knn_res KNeighborsClassifier(n_neighborsgrid.best_params_[n_neighbors], weightsgrid.best_params_[weights]) knn_res.fit(X_train_res, y_train_res) y_pred_res knn_res.predict(X_test_c) print(classification_report(y_test_c, y_pred_res, target_names[普通酒, 好酒]))SMOTE 的原理是在少数类样本之间插值生成新样本fit_resample只对训练集做测试集绝对不能动否则评估结果会虚高。参数random_state保证可复现。用了 SMOTE 后好酒的 recall 通常会提升 5-10 个百分点但 precision 可能略降。这是典型的召回率与精确率权衡选哪个取决于你的业务目标如果作业要求“尽可能找出好酒”就看重 recall如果要求“预测为好酒的确实是好酒”就看重 precision。6. 避坑与排查红酒数据集分析中最容易翻车的五个点6.1 坑一StandardScaler 在划分数据集之前 fit现象交叉验证 R² 比测试集 R² 高很多或者 KNN 准确率异常高。原因先对全量数据做fit_transform再划分训练测试集导致测试集的信息泄露到训练过程中。解决先train_test_split再对训练集fit_transform测试集只用transform。# 错误做法 X_scaled_wrong StandardScaler().fit_transform(X) X_train_w, X_test_w, y_train_w, y_test_w train_test_split(X_scaled_wrong, y_reg) # 正确做法 X_train, X_test, y_train, y_test train_test_split(X, y_reg, test_size0.2, random_state42) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)6.2 坑二把 quality 当连续值做回归后直接四舍五入当分类现象回归预测出 5.7、6.3 这种值四舍五入后分类准确率很低。原因回归优化的是均方误差不是分类边界预测值集中在均值附近。解决分类任务用分类器回归任务用回归器不要混用。如果非要转换用np.clip(np.round(y_pred), 3, 8)限制范围但效果通常不如直接上 KNN 或逻辑回归。6.3 坑三忽略重复行导致数据泄露现象训练集和测试集出现完全相同的样本测试集准确率虚高。原因红酒数据集有约 240 条重复记录train_test_split随机划分时可能把重复样本分到两边。解决先drop_duplicates()再去重或者用groupby按特征组合分组后再划分。6.4 坑四KNN 的 k 值设为偶数导致平票现象二分类时出现两个类别概率都是 0.5predict 返回其中一个但不确定。原因k 为偶数时可能平票。解决k 取奇数或者用weightsdistance让近邻权重更大打破平局。6.5 坑五用 accuracy 评估不平衡分类现象模型把所有样本预测为普通酒accuracy 仍有 60%。原因普通酒占比高多数类基线准确率就是 60%。解决看 f1-score、AUC-ROC 或 balanced_accuracy。classification_report里的 macro avg 和 weighted avg 都要看macro avg 对少数类更敏感。7. 进阶技巧用 XGBoost 和特征重要性收尾当你把线性回归和 KNN 都跑通后可以试试 XGBoost 做二分类它在这个数据集上通常能比 KNN 高 3-5 个百分点的 F1。更重要的是XGBoost 能输出特征重要性帮你验证前面的相关性分析结论。from xgboost import XGBClassifier from sklearn.metrics import roc_auc_score xgb XGBClassifier(n_estimators200, max_depth4, learning_rate0.05, subsample0.8, colsample_bytree0.8, scale_pos_weight1.5, random_state42, use_label_encoderFalse, eval_metriclogloss) xgb.fit(X_train_c, y_train_c) y_pred_xgb xgb.predict(X_test_c) y_prob_xgb xgb.predict_proba(X_test_c)[:, 1] print(classification_report(y_test_c, y_pred_xgb, target_names[普通酒, 好酒])) print(fAUC-ROC: {roc_auc_score(y_test_c, y_prob_xgb):.4f}) # 特征重要性 importance pd.DataFrame({ feature: feature_cols, importance: xgb.feature_importances_ }).sort_values(importance, ascendingFalse) print(importance)参数上n_estimators200是树的数量max_depth4控制每棵树深度防止过拟合learning_rate0.05是学习率subsample0.8和colsample_bytree0.8是行采样和列采样比例scale_pos_weight1.5给正类更高权重处理不平衡。use_label_encoderFalse和eval_metriclogloss是新版 XGBoost 的要求不加会报警告。从特征重要性你会看到酒精、挥发性酸度、硫酸盐排在前三和相关性分析一致。AUC-ROC 在 0.80 左右说明模型有不错的区分能力。我做完这个作业最大的习惯是每次跑完模型先把classification_report和混淆矩阵存成文本文件再画一张特征重要性图。因为作业报告里光写“准确率 0.78”没人信附上混淆矩阵和特征重要性才有说服力。另外红酒数据集虽然小但把回归和分类两条线都走一遍对理解 sklearn 的 API 设计和评估指标非常有帮助。希望帮到你。本文还有配套的精品资源点击获取