简介基于Python实现的葡萄酒质量分析项目面向计算机专业学生适用于数据挖掘课程设计、期末大作业以及实战练习。资源包含完整源码和全部数据共十六个文件其中十个csv数据文件涵盖酒精含量、酸度、密度等影响葡萄酒质量的多种变量三个py脚本实现数据读取、清洗、分析及可视化三个txt文档提供使用说明。压缩包仅595KB结构精简源码经过严格调试下载后无需复杂配置即可运行显著降低学习门槛。通过该项目可系统掌握数据处理、特征工程、模型训练与结果评估等数据挖掘全流程是构建课程设计或大作业的实用范本且目录结构清晰便于按模块查阅与二次开发。目前已有84人学习浏览适合希望快速上手并完善项目细节的学习者。1. 葡萄酒质量分析写给数据挖掘大作业的 Python 完整方案葡萄酒质量分析是数据挖掘课程里少有的“数据干净、任务清晰、可视化好看”的选题。它不像电商用户画像那样需要自己爬数据清洗半年也不像图像识别那样吃显卡一份 UCI 的 Wine Quality 数据集加 Python 的 pandas、matplotlib、scikit-learn 就能把数据挖掘的完整流程走一遍数据探索、可视化、特征工程、建模评估、结论报告。很多高分大作业都是在这个题上把报告结构和代码规范做扎实的。这篇笔记按我平时带项目的习惯从数据字段讲到模型评估再给你几条能直接抄的踩坑记录适合正在做数据挖掘大作业、又不想只靠调库糊弄过去的读者。2. 为什么选葡萄酒质量数据集字段解读与大作业选题逻辑2.1 两个数据集先分清红葡萄酒与白葡萄酒的字段差异UCI 的 Wine Quality 数据集分红葡萄酒和白葡萄酒两份红葡萄酒 1599 条白葡萄酒 4898 条。字段都是 11 个理化指标加 1 个质量评分但两份数据的分布差异很大不能上来就 concat。先看字段含义再做后续处理字段名含义对质量的主要影响fixed acidity固定酸度影响口感的清爽度volatile acidity挥发酸度过高会产生醋味通常与质量负相关citric acid柠檬酸适量能提升风味residual sugar残糖甜型葡萄酒该值偏高chlorides氯化物盐分过高代表口感偏咸涩free sulfur dioxide游离二氧化硫防腐抗氧化total sulfur dioxide总二氧化硫白葡萄酒普遍高于红葡萄酒density密度与残糖、酒精含量相关pH酸碱度影响微生物稳定性和口感sulphates硫酸盐适量可提升香气alcohol酒精含量通常与质量评分正相关quality质量评分0-10 的整数实际数据集中在 3-8 分我的习惯是一开始就把红白两份数据分开读分别做 describe()先看两份数据的残糖、总二氧化硫、酒精含量这三个字段的均值差异。白葡萄酒的残糖和总二氧化硫均值明显高于红葡萄酒这说明两份数据来自不同工艺合并前必须想清楚你要回答什么问题。2.2 从数据挖掘课程评分表反推这个题能拿分的四个模块做数据挖掘大作业老师看的不只是模型准确率。我在带人做课设时反复强调一份高分报告要覆盖四个模块数据探索、数据预处理、建模与对比、结论与改进。葡萄酒质量分析这个题正好四个模块都能做出内容。数据探索可以画质量分布直方图、酒精含量与质量的箱线图、相关性热力图这些图在答辩 PPT 里非常占版面。数据预处理可以做标准化、类别转换、样本均衡处理体现你对数据质量有意识。建模部分至少跑两个算法比如逻辑回归和随机森林对比准确率和 F1。最后要有结论——哪些特征对质量影响最大模型有哪些局限。这个选题还有个隐性优势数据是公开的、字段有业务含义答辩时老师问“特征是什么意思”你答得上问“为什么用这个模型”你也有话说。不像有些选题用爬虫抓了一堆数据自己都解释不清字段。2.3 环境准备与数据装载pandas 读入后的第一轮检查环境用 Python 3 加 pandas、numpy、matplotlib、seaborn、scikit-learn 就够了。装库用 pip 安装就可以不要在这里浪费太多时间。数据文件下载后一般是 CSV 格式分隔符是分号而不是逗号这是我第一次跑这个数据集就踩过的坑。import pandas as pd import numpy as np # 注意分隔符是 ; 而不是默认的 , red pd.read_csv(winequality-red.csv, sep;) white pd.read_csv(winequality-white.csv, sep;) print(red.shape, white.shape) print(red.head()) print(red.isnull().sum().sum()) # 检查缺失值 print(red.duplicated().sum()) # 检查重复行sep; 这个参数是关键。UCI 的这份 CSV 用分号做分隔符如果忘记指定pandas 会把整行读成一列后面所有列名操作全乱。缺失值检查我一般用 isnull().sum().sum() 一行带过重复行用 duplicated().sum() 看数量。这个数据集比较干净几乎没有缺失值但重复行是存在的尤其是红葡萄酒部分。处理方式不一定是直接 drop也可以先看重复比例如果不超过 5%保留或删除对结果影响都不大。大作业里把这一步写清楚老师会觉得你做了数据质量审查而不是拿过来就跑模型。3. 用 Python 做探索性分析质量分布、相关性热力图与可视化要点3.1 质量评分的分布先看清这是分类还是回归问题quality 字段是 0-10 的整数但实际数据集中在 3-8 分5 分和 6 分占了大多数。这个分布直接决定了你的建模策略如果把 quality 当连续值做回归绝大多数样本挤在 5-6 分模型会倾向于预测平均值附近极端分数3 分、8 分基本学不到如果做分类又会遇到类别不均衡问题。import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(8, 4)) sns.countplot(xquality, datared) plt.title(Red Wine Quality Distribution) plt.show() # 看具体频数 print(red[quality].value_counts().sort_index())value_counts 的输出会让你直观看到 5 分 681 条、6 分 638 条而 3 分只有 10 条、8 分只有 53 条。这种分布下直接预测 0-10 的每个分数模型容易翻车。常见做法是转成二分类把 quality 大于等于 7 的定义为“好酒”小于等于 5 的定义为“普通酒”6 分可以视情况并入某一类或直接丢弃。这样类别比例接近 1:4 左右虽然还是有一定不均衡但可比 10 分类靠谱得多。大作业里先展示原始分布、再说明转换理由这一步就是拿分点。3.2 相关性矩阵与热力图找出和 quality 最相关的三个特征相关性分析能帮你在答辩时回答“你为什么重点用这几个特征”。用 pandas 的 corr() 算皮尔逊相关系数再用 seaborn 画热力图。红葡萄酒里和 quality 相关性最高的通常是 alcohol正相关、volatile acidity负相关、sulphates正相关。corr red.corr() # 只看每个特征与 quality 的相关性 print(corr[quality].sort_values(ascendingFalse)) plt.figure(figsize(10, 8)) sns.heatmap(corr, annotFalse, cmapRdBu_r, linewidths0.5) plt.title(Correlation Heatmap of Red Wine Features) plt.show()annotFalse 是因为 11 个特征的热力图如果每个格子都标数字会非常拥挤。报告里可以放 annotTrue 的 quality 列单独截图或者只画 top 6 特征的子矩阵。corr() 默认算的是皮尔逊相关系数适合看线性关系。这里不要忽略一个细节density 和 residual sugar 之间的相关性通常在 0.8 以上特征之间存在共线性后续建模时可以考虑去掉其中一个。3.3 酒精含量与质量的关系非线性关系的可视化验证相关性热力图看的是全局线性关系但酒精含量与质量的关系更值得用箱线图展示。把 quality 按分数分组画 alcohol 的箱线图你会看到 8 分那组的酒精含量中位数明显高于 3 分那组。这个图放在报告里比任何文字描述都直观。plt.figure(figsize(10, 5)) sns.boxplot(xquality, yalcohol, datared) plt.title(Alcohol Content by Quality Score) plt.show()注意箱线图的一个坑3 分和 4 分组的样本量很少箱线图的 whisker 会拉得很怪看起来分布很宽其实是样本太少导致的。解释时要注明“低分组样本量仅 10-50 条统计意义有限”不然答辩老师一眼就能看出来你在强行解读。我更推荐的做法是把 0-6 分合并成一组7-10 分合并成一组画两组的酒精含量分布对比图信息更干净也能顺带引出后面的二分类建模思路。可视化不是图越多越好是每一张图都能回答一个问题。4. 特征工程与模型训练从逻辑回归到随机森林的可复现代码4.1 二分类转换把 quality 切成好酒与坏酒再建模前面分析了质量分布现在正式做特征工程。我把 quality 小于等于 5 的归为 0普通酒大于等于 7 的归为 1好酒6 分这个中间分数直接丢弃。这样做的理由是 6 分样本量太大且处于模糊地带模型在这个区间上很难学出稳定规律强行划分只会增加噪声。# 二分类转换去掉6分保留边界清晰的样本 red_binary red[red[quality] ! 6].copy() red_binary[label] (red_binary[quality] 7).astype(int) print(red_binary[label].value_counts()) # label 0: 681, label 1: 53红葡萄酒原始分布大致如此copy() 是为了避免 SettingWithCopyWarning这个警告在 pandas 里虽然不阻断运行但会在答辩演示时看起来很业余。astype(int) 把布尔值转成 0/1后面直接喂给模型。这里你会看到正负样本比例大约 1:13非常不均衡。直接训练逻辑回归的话模型大概率把所有样本都预测成 0准确率看起来很高但没有任何实际意义。处理办法后面说先记住“准确率高不代表模型好”这句话。4.2 特征标准化与训练集划分参数怎么设才不翻车逻辑回归和 SVM 这类模型对特征尺度敏感酒精含量单位是百分比二氧化硫单位是 mg/L数值范围差了几十倍不标准化会让模型把大数值特征当成主要权重来源。随机森林和 XGBoost 这类树模型不需要标准化但标准化了也不影响结果。我的习惯是统一标准化方便后续换模型对比。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X red_binary.drop([quality, label], axis1) y red_binary[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)stratifyy 是做分层抽样让训练集和测试集里好酒和普通酒的比例保持一致。不写这个参数随机划分时有可能把好酒全分到测试集导致训练集里几乎没有正样本。random_state42 是一个约定俗成的固定种子方便别人复现你的结果不要省掉。scaler.fit_transform 和 scaler.transform 的区别是fit 在训练集上计算均值和标准差transform 直接用这套参数转换数据。测试集只能用 transform不能再次 fit否则测试集的信息泄漏到数据预处理里评估结果会虚高。4.3 模型对比与评估指标准确率、F1、混淆矩阵一起看这个数据集上我会先跑逻辑回归作为 baseline再跑随机森林做对比。逻辑回归的优势是可解释性强每个特征的权重系数能直接写进报告随机森林的优势是对特征交互和非线性关系更敏感但调参空间大容易过拟合。from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix # 逻辑回归 lr LogisticRegression(max_iter1000, random_state42) lr.fit(X_train_scaled, y_train) lr_pred lr.predict(X_test_scaled) # 随机森林 rf RandomForestClassifier( n_estimators300, max_depth5, min_samples_leaf3, random_state42 ) rf.fit(X_train, y_train) # 树模型不需要用标准化后的数据 rf_pred rf.predict(X_test) print(Logistic Regression:) print(classification_report(y_test, lr_pred)) print(Random Forest:) print(classification_report(y_test, rf_pred)) print(confusion_matrix(y_test, rf_pred))逻辑回归用标准化后的数据随机森林用原始数据这是我故意这样写的目的就是在报告里说明不同模型对预处理的要求不同。RandomForestClassifier 的 n_estimators 我设 300max_depth 限制为 5min_samples_leaf 设 3这三个参数是防止过拟合的常规组合。max_depth 不限制的话单棵决策树会学得过于精细训练集准确率 100%测试集反而下降。看评估指标时不要只盯 accuracy。正负样本 1:13 的情况下全预测成 0 的准确率也有 90% 以上。要看 precision、recall、F1-score尤其是好酒那一类的 recall 和 F1。混淆矩阵能直接看出模型把多少好酒误判成了普通酒。5. 数据挖掘大作业避坑指南五个高频翻车点与排查方法5.1 红白葡萄酒合并后模型反而变差现象把 red 和 white 两份数据合并成一份用同样的特征训练模型准确率比单独训练红葡萄酒还低。原因红葡萄酒和白葡萄酒的理化指标分布差异很大白葡萄酒的残糖和总二氧化硫含量整体偏高。合并后模型会用这些字段的均值做切分但两类酒的“好酒”标准并不一致混在一起相当于让模型学一个同时适配两种工艺的规则难度反而更高。解决要么分开建模做对比分析要么在合并时加一列 wine_type 作为特征让模型自己学出两类酒的差异。我更推荐分开建模报告里可以对比两个模型的结论内容更丰富。5.2 类别不均衡导致模型完全偏向多数类现象模型预测结果里好酒的比例接近 0所有样本都被判为普通酒F1-score 惨不忍睹。原因好酒只有 53 条普通酒有 681 条模型发现只要全猜 0 准确率就有 90% 以上损失函数最小化的方向就是全部猜多数类。解决两步走。第一步在 train_test_split 里用 stratifyy 保证划分时比例一致第二步在模型里设置 class_weightbalanced让模型按类别比例调整权重。逻辑回归和随机森林都支持这个参数。lr_balanced LogisticRegression( max_iter1000, class_weightbalanced, random_state42 ) rf_balanced RandomForestClassifier( n_estimators300, max_depth5, min_samples_leaf3, class_weightbalanced, random_state42 )设置 class_weight 后少数类的 recall 会明显提升但 precision 可能下降这是正常的权衡。报告里把两个结果都写出来说明你理解了这个 trade-off比只展示一个漂亮数字更可信。5.3 把评分当连续值回归结果被少数极端分数带偏现象用 LinearRegression 对 quality 做回归R² 只有 0.3 左右预测值集中在 5.5-6.0 之间。原因quality 是离散整数且分布严重集中在 5 和 6 分。回归模型学到的规律就是“预测均值”极端分数样本量太少模型根本学不到 3 分和 8 分对应的特征组合。解决大作业里可以同时做回归和分类但结论要说清楚由于质量评分的分布特征分类建模更合适。如果你非要回归可以用 Ordinal Regression有序回归或者把评分映射到连续空间但这不是 scikit-learn 直接支持的功能写起来麻烦不如二分类清晰。5.4 相关性热力图颜色很浅看不出谁和谁相关现象画出来的热力图大部分格子都是浅色只有个别格子颜色深感觉看不出规律。原因两个可能。一是特征之间本身就缺少强相关二是你画图时用了 plt.tight_layout() 但画布大小不对颜色映射被拉伸得没有区分度。解决先检查 corr() 输出的具体数值。像 density 和 residual sugar 的相关性通常在 0.8 以上如果热力图上看不出来说明绘图参数有问题。调整 vmin 和 vmax 让颜色映射聚焦在 -1 到 1 的完整范围或者只画部分特征的相关矩阵不要强行塞 11 个字段。5.5 换 random_state 结果波动很大现象随机森林的准确率在 random_state 取 0 和取 42 时差了 5 个百分点。原因样本量小、类别不均衡随机森林的 bootstrap 抽样和特征子集选择都会受随机种子影响。这不是代码写错了是数据本身的特性。解决报告里固定 random_state42 声明“所有实验在同一随机种子下复现”不要反复换种子去挑一个好看的数字这是学术诚信问题。如果想证明模型稳定用交叉验证而不是换种子具体做法看下一章。6. 冲高分的关键技巧交叉验证稳定性与 SHAP 特征解释6.1 用 StratifiedKFold 代替单次训练测试划分单次 train_test_split 的结果受随机划分影响模型好坏的评估不够客观。高分报告里我用 StratifiedKFold 做 5 折交叉验证每一折的评估指标取平均结果更可信。from sklearn.model_selection import StratifiedKFold, cross_val_score cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores_rf cross_val_score( rf_balanced, X, y, cvcv, scoringf1 ) scores_lr cross_val_score( lr_balanced, X_train_scaled, y_train, cvcv, scoringf1 ) print(RF F1: {:.3f} - {:.3f}.format(scores_rf.mean(), scores_rf.std())) print(LR F1: {:.3f} - {:.3f}.format(scores_lr.mean(), scores_lr.std()))我习惯用 F1 作为交叉验证的打分指标因为在这个不均衡数据集上直接看准确率没有参考价值。shuffleTrue 保证每折之前先打乱数据避免原始数据里有某种顺序导致的划分偏差。标准差越小说明模型在不同数据子集上表现越稳定这个数值在报告里很有说服力。需要注意 cross_val_score 里的模型不能预先在完整数据上 fit 过它会自己在每折内部做训练和验证。我之前犯过的错是在交叉验证前先 fit 了模型导致数据泄漏分数虚高得离谱。6.2 用 SHAP 解释模型报告里多两页干货答辩时最常见的问题是“你的模型为什么这么判断”。随机森林很难直观回答这个问题但 SHAP 可以。SHAP 是一个基于博弈论的特征归因方法能算出每个特征对每个样本预测结果贡献了多少。import shap # 用随机森林模型和对应的训练数据做解释器 explainer shap.TreeExplainer(rf_balanced) shap_values explainer.shap_values(X_train) shap.summary_plot(shap_values, X_train, feature_namesX.columns)TreeExplainer 专门用于树模型速度比 KernelExplainer 快很多。summary_plot 的输出是一个散点图横轴是 SHAP 值纵向按特征排列颜色表示特征值高低。从这个图上能直接看到alcohol 特征值越高SHAP 值越倾向于正方向说明酒精含量对“好酒”的预测贡献最大。这个结论和相关性分析的结论吻合报告前后逻辑一致。我的血泪经验是SHAP 的火爆程度在课设答辩时远超预期但很多同学只是在最后一步堆了代码没有解释图里的含义。你至少要在报告里写三句话这个图横轴代表什么、每个点代表什么、颜色代表什么然后指出两个特征的具体模式。答辩老师一看你不仅跑了 SHAP 还读懂了图分数直接拉开差距。最后一章说回我的习惯。做葡萄酒质量分析这个项目我会在模型跑完后刻意退回一步重新审视质量分布那张图确认二分类的边界是合理的而不是因为 7 分以上的样本少就随手切一刀。整个项目最有价值的产出其实不是那几个百分点的准确率而是你从数据里读出了“酒好不好喝和挥发酸度、酒精含量强相关”这样的业务结论。代码能跑通只是及格线能把结论讲清楚才是高分线。下次你拿这个数据集写大作业如果卡在哪一步想想这篇笔记里提到的分号分隔符、class_weight 和 random_state这三个坑解决了你就能省下大量调试时间。希望帮到你。本文还有配套的精品资源点击获取