☰
葡萄酒质量分析Python实战:从数据探索到模型评估的完整方案
2026/10/11 21:36:21 网站建设 项目流程

简介:基于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(x='quality', data=red) 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(ascending=False)) plt.figure(figsize=(10, 8)) sns.heatmap(corr, annot=False, cmap='RdBu_r', linewidths=0.5) plt.title('Correlation Heatmap of Red Wine Features') plt.show()

annot=False 是因为 11 个特征的热力图如果每个格子都标数字会非常拥挤。报告里可以放 annot=True 的 quality 列单独截图,或者只画 top 6 特征的子矩阵。corr() 默认算的是皮尔逊相关系数,适合看线性关系。这里不要忽略一个细节:density 和 residual sugar 之间的相关性通常在 0.8 以上,特征之间存在共线性,后续建模时可以考虑去掉其中一个。

3.3 酒精含量与质量的关系:非线性关系的可视化验证

相关性热力图看的是全局线性关系,但酒精含量与质量的关系更值得用箱线图展示。把 quality 按分数分组,画 alcohol 的箱线图,你会看到 8 分那组的酒精含量中位数明显高于 3 分那组。这个图放在报告里,比任何文字描述都直观。

plt.figure(figsize=(10, 5)) sns.boxplot(x='quality', y='alcohol', data=red) 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'], axis=1) y = red_binary['label'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)

stratify=y 是做分层抽样,让训练集和测试集里好酒和普通酒的比例保持一致。不写这个参数,随机划分时有可能把好酒全分到测试集,导致训练集里几乎没有正样本。random_state=42 是一个约定俗成的固定种子,方便别人复现你的结果,不要省掉。

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_iter=1000, random_state=42) lr.fit(X_train_scaled, y_train) lr_pred = lr.predict(X_test_scaled) # 随机森林 rf = RandomForestClassifier( n_estimators=300, max_depth=5, min_samples_leaf=3, random_state=42 ) 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 我设 300,max_depth 限制为 5,min_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 里用 stratify=y 保证划分时比例一致;第二步,在模型里设置 class_weight='balanced',让模型按类别比例调整权重。逻辑回归和随机森林都支持这个参数。

lr_balanced = LogisticRegression( max_iter=1000, class_weight='balanced', random_state=42 ) rf_balanced = RandomForestClassifier( n_estimators=300, max_depth=5, min_samples_leaf=3, class_weight='balanced', random_state=42 )

设置 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_state=42 声明“所有实验在同一随机种子下复现”,不要反复换种子去挑一个好看的数字,这是学术诚信问题。如果想证明模型稳定,用交叉验证而不是换种子,具体做法看下一章。

6. 冲高分的关键技巧:交叉验证稳定性与 SHAP 特征解释

6.1 用 StratifiedKFold 代替单次训练测试划分

单次 train_test_split 的结果受随机划分影响,模型好坏的评估不够客观。高分报告里我用 StratifiedKFold 做 5 折交叉验证,每一折的评估指标取平均,结果更可信。

from sklearn.model_selection import StratifiedKFold, cross_val_score cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores_rf = cross_val_score( rf_balanced, X, y, cv=cv, scoring='f1' ) scores_lr = cross_val_score( lr_balanced, X_train_scaled, y_train, cv=cv, scoring='f1' ) 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 作为交叉验证的打分指标,因为在这个不均衡数据集上,直接看准确率没有参考价值。shuffle=True 保证每折之前先打乱数据,避免原始数据里有某种顺序导致的划分偏差。标准差越小说明模型在不同数据子集上表现越稳定,这个数值在报告里很有说服力。

需要注意 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_names=X.columns)

TreeExplainer 专门用于树模型,速度比 KernelExplainer 快很多。summary_plot 的输出是一个散点图,横轴是 SHAP 值,纵向按特征排列,颜色表示特征值高低。从这个图上能直接看到:alcohol 特征值越高,SHAP 值越倾向于正方向,说明酒精含量对“好酒”的预测贡献最大。这个结论和相关性分析的结论吻合,报告前后逻辑一致。

我的血泪经验是:SHAP 的火爆程度在课设答辩时远超预期,但很多同学只是在最后一步堆了代码,没有解释图里的含义。你至少要在报告里写三句话:这个图横轴代表什么、每个点代表什么、颜色代表什么,然后指出两个特征的具体模式。答辩老师一看你不仅跑了 SHAP 还读懂了图,分数直接拉开差距。

最后一章说回我的习惯。做葡萄酒质量分析这个项目,我会在模型跑完后刻意退回一步,重新审视质量分布那张图,确认二分类的边界是合理的,而不是因为 7 分以上的样本少就随手切一刀。整个项目最有价值的产出其实不是那几个百分点的准确率,而是你从数据里读出了“酒好不好喝和挥发酸度、酒精含量强相关”这样的业务结论。代码能跑通只是及格线,能把结论讲清楚才是高分线。下次你拿这个数据集写大作业,如果卡在哪一步,想想这篇笔记里提到的分号分隔符、class_weight 和 random_state,这三个坑解决了,你就能省下大量调试时间。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询