
简介一套面向数据挖掘课程设计、期末大作业及入门实战的葡萄酒质量分析项目资料基于Python完整实现了从数据读取、清洗、分析到可视化、建模评估的全流程适合计算机专业学生用来直接参考或二次开发。压缩包共16个文件含10个csv数据文件酒精含量、酸度、密度等指标及质量评分、3个py源码文件主程序、分析脚本、辅助工具和3个txt说明文档整体仅595KB结构紧凑。已有84人学习/下载该项目代码经过调试可直接运行降低上手门槛。通过逐行阅读源码与对照数据学习者可以系统掌握Pandas数据处理、特征选择、分类或回归模型训练以及结果评价等要点同时也能快速生成一份组织完整、可视化的课程作业或答辩展示成果。1. 葡萄酒质量分析为什么是数据挖掘作业的黄金选题数据挖掘大作业最怕的不是模型分数低而是选题本身是个坑数据集要么几十万行跑不动要么脏到清洗占掉三分之二时间。相比之下葡萄酒质量分析是这类作业里公认性价比最高的方向——数据规模刚好卡在“单机跑得动、代码秒出结果”的量级字段含义直白质量分数自带业务解释力用 Python 做数据挖掘的标准流程读取、清洗、特征工程、建模、评估能在一套数据上完整走一遍。这篇笔记就围绕这个选题按数据挖掘项目的实际推进顺序讲清楚每一步怎么做、参数怎么定、哪些地方容易翻车让新手能照着复现也让熟手看到边界和细节。2. 拿到数据先别建模数据体检与红白葡萄酒的差异处理2.1 字段含义梳理11个理化指标和一个质量分数这份经典数据集分红酒和白酒两个文件字段结构一致共有 12 列。前 11 列是葡萄酒的理化检测指标最后一列quality是品酒师给出的质量打分3 到 8 分。表 1 列出字段含义便于后面做特征分析时对照。字段名含义对建模的典型作用fixed acidity固定酸度酒石酸为主酸度类特征影响口感volatile acidity挥发性酸度醋酸为主过高会产生醋味与质量负相关citric acid柠檬酸含量与固定酸度配合起清新作用residual sugar残糖含量甜型酒与干型酒的区分chlorides氯化物盐分含量过高可能是不良工艺free sulfur dioxide游离二氧化硫防腐指标有阈值限制total sulfur dioxide总二氧化硫游离加结合态之和density密度与糖分和酒精含量高度相关pHpH 值酸度强弱的直接度量sulphates硫酸盐含量与发酵工艺相关alcohol酒精体积百分比通常与质量正相关quality质量分数3–8目标变量红葡萄酒 1599 条记录白葡萄酒 4898 条两者在残糖、氯化物、二氧化硫等指标的分布上差异明显。常见做法是分开建模或至少单独做统计分析我的习惯是先把两个文件都读进内存体检后对比分布差异再决定后续建模策略。2.2 用 Pandas 快速体检描述性统计与缺失值检查拿到数据的第一步不是直接建模而是先跑describe()看整体分布顺便确认有没有缺失值。分隔符是分号而不是逗号read_csv里要指定sep;这是新手最容易卡住的地方。import pandas as pd # 读取红酒和白酒数据注意分隔符是分号 red pd.read_csv(winequality-red.csv, sep;) white pd.read_csv(winequality-white.csv, sep;) print(red.shape, white.shape) # 描述性统计转置后查看连同缺失值计数一起输出 stats red.describe().T stats[missing] red.isnull().sum() print(stats)这段代码里describe().T会把统计指标转成行方便逐字段查看均值、标准差、最小值、四分位数。isnull().sum()返回每列缺失值数量这里输出全为 0——这个数据集很干净不需要做缺失值填充。如果换成其他脏数据看到非零缺失值时先判断缺失机制随机缺失可以直接删除或均值填充非随机缺失要慎重不能无脑删行。参数上要注意一个细节describe()默认只统计数值列。这份数据恰好全是数值如果以后做其他项目遇到文本特征需要先用select_dtypes过滤再单独对文本字段做频次统计。2.3 分布与相关性速览一张热力图定建模基调数据体检的第二部分是可视化。质量分数是离散的 3 到 8 分用sns.countplot看分布理化指标之间则用相关性热力图快速定位强相关特征对。import matplotlib.pyplot as plt import seaborn as sns # 质量分数分布对比 fig, axes plt.subplots(1, 2, figsize(12, 4)) sns.countplot(xquality, datared, axaxes[0], colorcrimson) axes[0].set_title(Red Wine Quality Distribution) sns.countplot(xquality, datawhite, axaxes[1], colorgold) axes[1].set_title(White Wine Quality Distribution) plt.tight_layout() plt.show() # 相关性热力图以红酒为例 corr red.corr() plt.figure(figsize(10, 8)) sns.heatmap(corr, annotTrue, fmt.2f, cmapRdYlBu_r, linewidths0.5, cbar_kws{label: Pearson Correlation}) plt.title(Red Wine Feature Correlation Heatmap) plt.show()这段代码用了两个 Seaborn 函数countplot画离散变量频数heatmap画相关矩阵。annotTrue会在格子里显示相关系数数值fmt.2f保留两位小数。运行时注意两点第一两个子图的配色是故意区分的方便在报告中直接对比第二相关系数超过 0.7 的特征对比如 density 和 residual sugar、alcohol 之间要特别留意它们会带来多重共线性问题。红白葡萄酒的质量分布形态不同白葡萄酒 6 分的样本占比更高红葡萄酒 5 分和 6 分双峰。这种分布差异会直接影响后续目标变量切分策略和模型评估方式下一章详细展开。3. 特征工程决定分数上限理化指标到质量线索的三步转换3.1 目标变量重编码二分类还是多分类原始质量分数是 3 到 8 的整数直接做回归预测输出会是连续的不好解释直接做多分类类别间有天然的序数关系普通分类器又忽略了这个约束。大作业里最常见且稳妥的做法是转成二分类6.5 分以上算“优质”否则算“普通”这样业务含义清晰模型评估也直观。def label_quality(score): 质量分数 7 视为优质否则视为普通 if score 7: return 1 else: return 0 red[good] red[quality].apply(label_quality) white[good] white[quality].apply(label_quality) # 查看正负样本比例 print(red[good].value_counts(normalizeTrue)) print(white[good].value_counts(normalizeTrue))切分阈值定在 7 而不是 6 或 8是因为 6 分占了样本的大头如果把 6 分划到优质类绝大多数样本都成了正样本模型学不到区分度切在 8 分则正样本太少类别不均衡会很严重。用normalizeTrue直接看比例如果优质酒占比不到 15%后面就要考虑用分层采样或类别权重处理不均衡。如果想让作业更有层次也可以做三分级低/中/高但多分类会引入类别间顺序关系的问题普通模型不擅长利用这种序数信息。我的建议是主体用二分类报告里可提一句“多分类作为扩展实验”体现出思考深度就够了。3.2 标准化与离群值处理不同特征差异化处理葡萄酒理化指标的量纲差异很大酒精含量在 8 到 15 之间二氧化硫总量动辄几十到几百pH 值只有 3 到 4。如果不做标准化逻辑回归这类依赖特征尺度的模型会把注意力全放在数值大的特征上。常用做法是StandardScaler让每个特征均值归 0、方差归 1。需要强调的是标准化要在切分训练集和测试集之后做具体原因放在第五章避坑里详细讲这里是正确顺序from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler features red.drop([quality, good], axis1) target red[good] # stratifyy 让切分前后正负样本比例保持一致 X_train, X_test, y_train, y_test train_test_split( features, target, test_size0.2, random_state42, stratifytarget ) # 先 fit 训练集再 transform 测试集 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) print(X_train_scaled.shape, X_test_scaled.shape)这里stratifytarget是处理类别不均衡最基础的手段如果不加切分可能让训练集里优质样本比例失真加了之后训练集和测试集的正负比例都与原始数据一致。离群值方面先看describe()的最大值和 75% 分位。如果某个指标的最大值远高于 75% 分位大概率存在离群样本。处理方式有两种一是用 IQR 法四分位距界定离群边界再截断二是保留但依赖树模型对离群值的鲁棒性。逻辑回归对离群值敏感随机森林不敏感所以先跑逻辑回归时截断离群值跑随机森林时保留在报告中形成对比。3.3 特征筛选方差阈值与相关性去重数据只有 11 个特征不算高维但特征筛选仍然值得做。一是去掉方差过低的特征它们几乎没有区分能力二是处理高相关特征对避免冗余信息干扰系数解释。常见的做法是先用VarianceThreshold过滤低方差特征再看相关性矩阵手动去重。from sklearn.feature_selection import VarianceThreshold # 对标准化后的特征做低方差过滤 selector VarianceThreshold(threshold0.05) X_train_selected selector.fit_transform(X_train_scaled) X_test_selected selector.transform(X_test_scaled) # 查看被保留的特征索引 print(保留的特征索引:, selector.get_support(indicesTrue))阈值 0.05 的含义是特征方差低于 0.05 的会被移除。这里用的是标准化后的数据方差等于 1 是平均水平0.05 是非常低的区分度阈值。实际调试时如果保留的特征数太少把阈值调低如果目的是精简模型调高一些。相关性去重更依赖业务判断。比如 density 与 residual sugar、alcohol 的相关系数都在 0.6 以上可以把 density 删掉保留后两者因为残糖和酒精有明确业务含义密度只是衍生指标。这样处理后模型输入从 11 维降到 9 维左右解释性更强还避开了共线性对逻辑回归系数的干扰。4. 模型训练与调参逻辑回归到随机森林的对比实验设计4.1 基线模型用逻辑回归跑通完整流程建模的第一个模型建议用逻辑回归原因很简单训练速度快、结果可解释、对线性关系能直接给出系数。跑通整个流程再上随机森林做非线性提升。from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report, confusion_matrix # 初始化逻辑回归max_iter 调高避免收敛警告 lr LogisticRegression(max_iter1000, random_state42) lr.fit(X_train_scaled, y_train) y_pred lr.predict(X_test_scaled) y_proba lr.predict_proba(X_test_scaled)[:, 1] print(classification_report(y_test, y_pred))max_iter1000是必须调的参数。逻辑回归默认 100 次迭代标准化后的高维数据可能不收敛触发 ConvergenceWarning调到 1000 能规避。predict_proba输出每个样本属于优质酒的概率后面画 ROC 曲线或者调阈值时会用到。classification_report输出精确率、召回率、F1 值。此处正负样本不均衡只看 accuracy 毫无意义——模型一直预测“普通类”就能拿到 85% 以上的准确率但召回率是 0必须综合看 F1 和混淆矩阵。4.2 随机森林与网格搜索三个必调参数随机森林是这种量级数据集上最稳的模型不需要复杂调参就能超过逻辑回归。但“不用调参”是指默认参数可用想逼近效果上限还是得做网格搜索。三个关键参数是n_estimators、max_depth、min_samples_split。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200, 300], max_depth: [None, 10, 15, 20], min_samples_split: [2, 5, 10] } rf RandomForestClassifier(random_state42, n_jobs-1) grid GridSearchCV(rf, param_grid, cv5, scoringf1, n_jobs-1) grid.fit(X_train_scaled, y_train) print(最优参数:, grid.best_params_) print(最优 F1:, grid.best_score_) best_rf grid.best_estimator_ y_pred_rf best_rf.predict(X_test_scaled)n_jobs-1让网格搜索并行跑满所有 CPU 核心数据量不大几分钟能出结果。scoringf1这里特意选 F1 而非 accuracy理由和上一节一致类别不均衡场景下 F1 更能反映模型质量。参数说明n_estimators是决策树数量从 100 加到 300 收益递减起步用 100 够了max_depthNone表示树不限制深度这在小数据集上容易过拟合网格搜索会在 None 和有限深度之间权衡min_samples_split控制内部节点再划分所需的最小样本数调大可以抑制过拟合。这三组参数组合共 36 种5 折交叉验证就是 180 次训练在这个数据集上完全跑得动。4.3 混淆矩阵与 ROC 曲线评估指标的选择逻辑大作业里只输出一行accuracy_score是典型扣分项。正确的做法是输出混淆矩阵、精度/召回率、ROC 曲线并且解释每个指标的含义。from sklearn.metrics import roc_curve, auc cm confusion_matrix(y_test, y_pred_rf) print(混淆矩阵:\n, cm) # ROC 曲线数据 fpr, tpr, thresholds roc_curve(y_test, y_proba_rf) roc_auc auc(fpr, tpr) print(fAUC {roc_auc:.3f})混淆矩阵的四象限能直接看出错误类型把普通酒误判为优质酒假阳性和把优质酒误判为普通酒假阴性业务代价不同。品酒检测场景里假阳性的代价是让劣质酒流入市场假阴性的代价是浪费优质酒两者都不是零成本所以要权衡阈值。ROC 曲线的横轴是假阳性率纵轴是真阳性率AUC 越接近 1 越好。如果 AUC 低于 0.8说明特征对质量的解释力不够回到特征工程找问题如果高于 0.9要警惕是不是发生了数据泄漏检查特征里有没有混入目标相关信息。5. 葡萄酒质量分析的避坑指南五个让模型翻车的隐蔽错误5.1 数据泄漏标准化放在切分之前现象训练集交叉验证分数很高但测试集分数突然掉一截或者发现 AUC 跑到 0.99 这种不正常的数值。原因在train_test_split之前对整个数据集做fit_transform缩放器会“看见”测试集的均值和方差等于把测试集信息提前透露给了模型。这不是推导过程泄漏但对模型性能评估是致命的—相当于一个学生提前看了一部分考卷答案模拟考分数虚高真考就崩。解决严格分两步。先用训练集fit_transform再用同一缩放器对测试集只做transform。常见做法是把数据切分、缩放封装进sklearn.pipeline让 pipeline 在每一折交叉验证里自动用训练折拟合缩放器测试折只被转换。这样流程上杜绝了泄漏的可能。5.2 类别不均衡模型只会说“普通酒”现象分类报告里“普通类”精确率很高召回率也很高“优质类”召回率不到 0.2。整个模型变成了一个复读机——永远输出多数类。原因普通酒占比 85% 以上模型发现全猜“普通”也有 85% 的准确率于是偷懒了。分类器默认优化的是准确率不均衡数据下准确率是骗人的。解决三件事分层做。第一切分时用stratify保证训练集和测试集的比例一致第二模型层面给少数类加权重随机森林里设class_weightbalanced让损失函数对少数类分类错误的惩罚更大第三评估时看 F1 和 AUC不看准确率。实在还不够可以用imblearn库的 SMOTE 过采样合成少数类样本但要注意只对训练集做测试集必须保持原始分布。5.3 过拟合随机森林的 max_depth 不是越大越好现象训练集上 F1 接近 0.95测试集只有 0.6 到 0.7网格搜索结果里max_depthNone且min_samples_split2时分数最高但你深知这不对劲。原因max_depthNone让每棵树长到叶子节点只剩一个样本训练集完全被记住泛化能力自然差。小数据集上这种表现尤其明显因为树的深度上限由样本量决定样本太少时树容易把噪声也学进去。解决网格搜索时把max_depth和min_samples_split的候选值范围拉开引导模型选择正则化更强的组合。我的经验是max_depth从 5 到 15 之间搜min_samples_split从 5 到 20 之间搜min_samples_leaf也值得加入。最终选出来的模型可能训练分数略降但测试分数更稳这才是泛化能力。5.4 可视化翻车离散变量画错了图现象报告里的质量分数分布图是一条条竖线看不出分布形态画图时 x 轴标签挤成一团密密麻麻看不清。原因把离散的质量分数当成连续变量画了直方图或者没处理横坐标密度问题。plt.hist默认会对整数数据分箱分箱边界在整数之间导致图形错乱。解决离散变量用countplot而不是直方图坐标轴刻度密度过高时用plt.xticks(rotation45)旋转标签或者手动指定plt.xticks(np.unique(data))只显示出现的整数分数。如果你用的数据中出现了横坐标太密集的问题多半是把连续变量也画成了 bar 图此时回到histplot加bins参数控制分箱数。画图前先问自己这个变量是能取小数还是只能取整数答案决定用哪种图。5.5 结果不可复现随机种子与版本锁定的教训现象换一台机器跑同一份代码ROC 曲线数值不同同一个人在同一个环境里跑两次交叉验证结果都不一样。原因随机森林和交叉验证都有随机过程。train_test_split和RandomForestClassifier不设随机种子每次切分和采样都不一样GridSearchCV的交叉验证也带随机性。这是让作业报告被质疑数据造假的最常见原因。解决在所有可能引入随机性的地方显式设置random_state42把 42 定义成一个常量放代码顶部不要散落各个函数里写死。交叉验证的cv用KFold(n_splits5, shuffleTrue, random_state42)替代cv5因为在GridSearchCV里cv5默认用不置乱的折切分结果稳定但不够随机加shuffleTrue后更需要随机种子来锚定。环境层面在报告开头注明 Python 版本和关键库版本sklearn、pandas、numpy因为 sklearn 不同版本间随机森林和逻辑回归的默认行为有差异版本不锁定别人的复现就是玄学。6. 让大作业从“能跑”到“高分”报告结构与可视化技巧报告得分点不在模型分数而在讲清楚“为什么”。一份高分的数据挖掘报告结构上至少要有这几块问题定义葡萄酒质量预测的业务价值、数据理解字段含义和分布特点、数据准备目标重编码、标准化、特征选择、建模实验基线到优化模型的演进、评估与结论业务建议和局限。每一块配合可视化和代码结果避免大段贴代码却不解释。可视化上几个容易出效果的小技巧。第一报告里所有图表配色统一用同一套色系推荐 Seaborn 的官方主题别一图一个色号第二特征重要性排序图来自随机森林的feature_importances_能直观展示“酒精含量是预测质量最重要的指标”这比放一个大段文字说明有力得多第三模型对比用表格形式呈现列出逻辑回归和随机森林在准确率、F1、AUC 三个指标上的数值一眼看出模型演进的效果。这可以画一个简版的表格模型准确率F1AUC逻辑回归0.870.560.89随机森林调参前0.900.620.92随机森林网格搜索后0.910.660.93数值是示意实际以你的运行结果为准。图表要有标题、坐标轴标签、图例这些细节是阅卷老师最看重的。我自己的教训是第一个版本的报告写了整整五页代码每段代码后面没有一句话解释老师直接批注“这不是报告是脚本合集”。后来血的教训总结出一条习惯——每张图、每段代码后面用两三句话解释“从结果里看到了什么、这个发现对后续步骤有什么影响”把报告写成一个决策链而不是代码流水账。这个习惯让我后来的每一个数据分析项目都少走了很多弯路也希望帮到你。本文还有配套的精品资源点击获取