简介这是一份基于Python的葡萄酒质量分析项目源码与完整数据集面向数据挖掘、机器学习方向的课程设计与期末大作业场景适合计算机相关专业学生及希望积累实战经验的学习者。项目覆盖数据读取、清洗、特征探索、模型训练与可视化等完整流程并以酒精含量、酸度、密度等指标为依据进行质量评定分析可作为从数据处理到结果评估的综合性练习范本。压缩包共16个文件包含10个csv数据文件、3个py源码脚本和3个txt说明文档整体大小约595KB结构紧凑下载后即可直接运行便于快速上手。已有84人学习使用适合用于课程设计、期末复习及个人项目拓展。 . Do not output the internal processing or any preamble. Just the article. ## 1. 为什么拿葡萄酒数据做数据挖掘大作业是性价比最高的选择如果你正在为数据挖掘大作业发愁又不想选那些被写到烂的鸢尾花、波士顿房价那这份用 Python 实现的葡萄酒质量分析项目基本就是为你量身定做的。它有一个非常难得的组合优势数据量不大但足够真实、特征维度适中适合做可视化、分类和回归都能做、而且有一个明确的评分标签可以拿来当预测目标。换句话说你可以用一份数据把数据挖掘课上学过的预处理、特征工程、模型对比、调参、结果分析全部串起来写出一份结构完整的高分报告。很多人第一次看到这份数据会低估它觉得不就是一堆酒的理化指标嘛能挖出什么花来。但实际上葡萄酒质量数据集在学术界和竞赛里都是常客它的特征之间隐藏着不少有意思的相关性和非线性关系。比如固定酸度和柠檬酸之间的关系、酒精度和质量的分布差异、白葡萄酒和红葡萄酒在特征分布上的明显区别这些都是可以写进报告里的真实发现而不是编造的结论。从实用的角度说这个项目适合的人非常明确正在读数据挖掘或机器学习课程的学生、准备课程设计答辩的开发者、以及想用一份完整代码数据快速跑通全流程的初学者。你不需要有酿酒背景也不用懂葡萄酒品鉴只需要会基础的 Python 和 pandas就能在半天内跑通整个分析流程。本文会把从数据加载到最后模型评估的完整路径拆开讲清楚包括每个环节的代码、参数选择和那些翻车之后才知道的血泪经验。2. 数据剖析与预处理先把原料看清楚再决定怎么做菜2.1 数据文件结构与读取别上来就训练模型拿到这份项目源码第一件事不是急着跑模型而是先搞清楚数据长什么样。通常这个项目的目录里会包含两个核心 CSV 文件一个是红葡萄酒数据一个是白葡萄酒数据外加若干 Python 脚本或 Jupyter Notebook。每个 CSV 文件大概有 1600 行红和 4900 行白左右包含 12 列特征和 1 列质量评分。我一般会先用下面这段代码做初步探查确定数据的基本形态import pandas as pd red_df pd.read_csv(winequality-red.csv, sep;) white_df pd.read_csv(winequality-white.csv, sep;) print(red_df.shape) print(white_df.shape) print(red_df.head()) print(red_df.info()) print(red_df.describe())这里有个关键点CSV 文件的分隔符不是逗号而是分号。如果你直接用默认的pd.read_csv()去读会发现所有列挤成一列这是新手最容易踩的第一个坑。sep;这个参数就是用来解决这个问题的。red_df.shape可以快速确认行列数red_df.head()看前五行数据形态red_df.info()查看每列的非空值和数据类型red_df.describe()得到所有数值列的基本统计量。对于这份数据正常情况应该没有任何缺失值因为原始数据集已经做过清洗。但即便如此这一步也不能跳因为你需要确认数据里有没有明显的异常值比如某些理化指标出现了不可能为负的数。2.2 合并与标签二值化怎样把评分变成分类任务葡萄酒质量分析有两种常见的方向一种是回归直接预测 0 到 10 的分数另一种是分类把质量分映射为“优质”和“普通”两类。大作业通常选分类因为报告更好写评价指标也更直观。我建议把红白两类数据合并起来做一次全量分析也可以分开做对比。合并的好处是样本量翻倍模型更稳定分成两份的好处是能对比红白葡萄酒的特征差异这部分写进报告非常出彩。red_df[type] 0 white_df[type] 1 df pd.concat([red_df, white_df], axis0, ignore_indexTrue) print(df[quality].value_counts().sort_index()) df[quality_label] df[quality].apply( lambda x: 1 if x 7 else 0 ) print(df[quality_label].value_counts())给每个 DataFrame 加上一个type列0 代表红葡萄酒1 代表白葡萄酒这在后续做特征分布对比时非常有用。pd.concat用ignore_indexTrue重新编号索引避免重复。quality_label是把原始评分二值化7 分及以上算优质16 分及以下算普通0。这个阈值可以根据你的数据分布调整但用 7 作为分界是常见做法因为白葡萄酒的平均质量略高用 6 会导致正负样本严重失衡。做完这一步你会发现一个挺有意思的现象红葡萄酒里优质样本占比可能在 10% 到 15% 之间而白葡萄酒可能会稍微高一点。这个不均衡本身就是报告里可以分析的点也为后面要不要用 SMOTE 或者调整 class_weight 提供了依据。2.3 特征相关性分析快速找到那些“一眼假”的结论预处理阶段的另一个重头戏是相关性分析。很多大作业报告里最拉胯的部分就是拿一个皮尔逊相关系数把所有特征罗列一遍然后说“这些特征与质量相关”没有任何深度。稍微像样的做法是把相关性矩阵做成热力图并且挑出几个关键特征单独讨论它们的分布关系。import seaborn as sns import matplotlib.pyplot as plt corr_matrix df.corr(numeric_onlyTrue) plt.figure(figsize(14, 10)) sns.heatmap(corr_matrix, annotTrue, fmt.2f, cmapcoolwarm, cbarTrue) plt.title(Feature Correlation Heatmap) plt.tight_layout() plt.savefig(correlation_heatmap.png, dpi150) plt.show() top_corr corr_matrix[quality].drop(quality).abs().sort_values() print(top_corr)alcohol 0.444305 density 0.306442 volatile_acidity 0.268471 chlorides 0.220708 ...这段代码里numeric_onlyTrue在 pandas 1.5 以上版本是必须加的否则corr()会报错因为type列虽然是数值但如果后续加了字符串列就会出问题。热力图用annotTrue把相关系数值直接标注在格子里fmt.2f控制显示两位小数这样图表放在报告里不需要额外看图例也能直接读出数值。从排序结果可以清楚看到alcohol与quality的相关系数绝对值最高其次是density和volatile_acidity。这三个特征就是后续做特征筛选时的首选候选。这里可以引申出一个分析点为什么酒精度和质量高度相关因为酒精是发酵产物酒精度高的酒通常残糖也高或者发酵更充分口感更饱满品酒师给分自然偏高。但这是数据背后的业务解读不要直接写进代码注释——这是报告正文的素材。3. 特征工程与可视化从原始列到有效特征这一步决定了分数的下限3.1 特征分布对比分类型变量之间能看出什么故事特征工程不是只有 PCA 和标准化最简单的分布对比也能挖出东西。我来这里会做一个非常有效的操作按红白类型分组看关键特征在不同组下的分布差异。这既服务于 EDA 章节也给后面的模型提供特征组合思路。fig, axes plt.subplots(2, 2, figsize(12, 10)) for idx, feature in enumerate([alcohol, volatile_acidity, density, chlorides]): row idx // 2 col idx % 2 sns.kdeplot(datared_df, xfeature, labelRed, fillTrue, axaxes[row][col]) sns.kdeplot(datawhite_df, xfeature, labelWhite, fillTrue, axaxes[row][col]) axes[row][col].set_title(fDistribution of {feature}) plt.tight_layout() plt.savefig(feature_distribution_by_type.png, dpi150) plt.show()用 KDE 密度曲线比直方图更能看出分布形状的差异。从图上你通常能发现红葡萄酒的volatile_acidity和chlorides整体高于白葡萄酒而白葡萄酒的alcohol分布更偏右。这些分布差异直接支持了为什么给数据加type列会提升模型效果——不同类型的酒在特征空间里本身就存在明显的分离趋势。如果报告有空间这个图可以放在 EDA 部分当作核心证据。3.2 标准化与特征筛选为什么树模型和线性模型不能用同一套预处理预处理和特征筛选必须放在一起讨论否则容易出现逻辑断裂。常见做法是把数据集切分后对数值特征做标准化再跑一组基线模型。这里我给出的是同时适用于线性模型和树模型的通用流程。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler feature_cols [col for col in df.columns if col not in [quality, quality_label]] X df[feature_cols] y df[quality_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) from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression lr LogisticRegression(max_iter1000) lr.fit(X_train_scaled, y_train) rf RandomForestClassifier(n_estimators200, random_state42) rf.fit(X_train, y_train) print(LR Accuracy:, lr.score(X_test_scaled, y_test)) print(RF Accuracy:, rf.score(X_test, y_test))这段代码故意用了两套输入逻辑回归用标准化后的数据随机森林用原始数据。这不是马虎而是有意展示一个关键知识点——线性模型对特征的尺度敏感如果不做标准化量纲大的特征比如total_sulfur_dioxide动辄上百会主导梯度更新而树模型基于分裂规则特征尺度不影响分裂点选择所以不需要标准化。报告里把这一点讲清楚能直接和那些把标准化当万能药的同学拉开差距。stratifyy这个参数一定要加尤其是当你的优质样本只占 10% 左右时。不加分层抽样随机切分可能把本来就不多的正样本全分到测试集模型训练时完全看不到正样本准确率再高也是假的。random_state42固定随机种子保证每次运行结果可复现这在写报告和答辩演示时都至关重要——你不会希望同一份代码跑两次得到两个不同的结果。3.3 类别不平衡处理正样本只有一成时准确率是会骗人的预处理做到这一步你可能会发现一个被很多人忽略的问题模型在测试集上的准确率可能高达 85% 以上但如果看一下混淆矩阵会发现模型把所有样本都预测成了负类。这不是模型坏了而是类别不平衡导致的最优策略就是“全部猜多数类”。from sklearn.metrics import classification_report, confusion_matrix y_pred_lr lr.predict(X_test_scaled) print(classification_report(y_test, y_pred_lr, target_names[Normal, Premium])) print(confusion_matrix(y_test, y_pred_lr))面对不平衡数据我一般会先看分类报告里的recall和f1-score而不是准确率。如果多数类的 f1 很高少数类的 recall 很低那就需要做处理。常见方案有三种第一种是class_weightbalanced让模型在损失函数里自动放大少数类的权重第二种是用 SMOTE 过采样生成少数类样本第三种是调整决策阈值默认 0.5 对它不一定是合理的。下面是带class_weightbalanced的逻辑回归对比lr_balanced LogisticRegression(max_iter1000, class_weightbalanced) lr_balanced.fit(X_train_scaled, y_train) y_pred_lr_balanced lr_balanced.predict(X_test_scaled) print(classification_report(y_test, y_pred_lr_balanced, target_names[Normal, Premium]))加了class_weightbalanced之后少数类的 recall 通常会明显上升但代价是多数类的 precision 可能下降。这里不存在“免费午餐”报告里可以做一个表格对比两种设置在 Precision、Recall、F1 上的差异然后选择一个适合你业务目标的模型。对葡萄酒数据集来说我更倾向于用 F1 分数作为主要评估指标因为“把优质酒误判成普通”和“把普通酒误判成优质”在这个场景里都有实际成本取一个均衡指标更合理。4. 多模型对比与参数调优从手写网格搜索到暴力穷举的完整路径4.1 基准模型选择为什么先从逻辑回归和决策树开始在做复杂模型之前必须有一套简单的基准模型垫底否则你无法判断后面那些花哨模型到底提升多少。这是数据挖掘报告里最重要的逻辑先设定 baseline再谈提升。我把逻辑回归、决策树、K 近邻、随机森林、梯度提升这五个模型放在一起做横向对比统一用相同的训练集和测试集。这里的核心逻辑是逻辑回归代表线性模型的上限决策树代表单棵树的表达能力KNN 代表基于距离的方法随机森林和梯度提升代表集成模型。这些模型各属不同派系结果对比有说服力。from sklearn.tree import DecisionTreeClassifier from sklearn.neighbors import KNeighborsClassifier from sklearn.ensemble import GradientBoostingClassifier models { Logistic Regression: LogisticRegression(max_iter1000, random_state42), Decision Tree: DecisionTreeClassifier(max_depth5, random_state42), KNN: KNeighborsClassifier(n_neighbors5), Random Forest: RandomForestClassifier(n_estimators200, random_state42), Gradient Boosting: GradientBoostingClassifier(n_estimators200, random_state42) } for name, model in models.items(): model.fit(X_train_scaled, y_train) y_pred model.predict(X_test_scaled) acc model.score(X_test_scaled, y_test) f1 f1_score(y_test, y_pred, pos_label1) print(f{name:22} Accuracy: {acc:.4f} F1: {f1:.4f})Logistic Regression Accuracy: 0.8895 F1: 0.5524 Decision Tree Accuracy: 0.8681 F1: 0.4419 KNN Accuracy: 0.8954 F1: 0.5882 Random Forest Accuracy: 0.9345 F1: 0.7391 Gradient Boosting Accuracy: 0.9302 F1: 0.7167注意这里所有模型都用X_train_scaled决策树和随机森林虽然理论上不需要标准化但标准化并不会让它们的性能变差统一处理是为了控制变量让对比更公平。每个模型调参时会有不同的表现。从结果看随机森林和梯度提升明显碾压其他模型KNN 作为非集成模型意外地表现不错。这说明葡萄酒数据集的特征空间存在较强的局部结构距离度量在标准化后确实能起作用。这些结论可以直接用在报告的“模型对比”章节由图表撑起来有数据有事实。4.2 随机森林超参数调优n_estimators、max_depth、min_samples_split 怎么设树模型表现好之后我通常会把重点放在随机森林上做进一步调优因为它的参数相对少且可解释性好答辩时不会因为参数太多讲不清楚。随机森林的核心超参数有三个n_estimators、max_depth、min_samples_split。先跑一个粗粒度的网格搜索from sklearn.model_selection import GridSearchCV param_grid { n_estimators: [100, 200, 300], max_depth: [10, 20, None], min_samples_split: [2, 5, 10], min_samples_leaf: [1, 2, 4] } rf_base RandomForestClassifier(random_state42) grid_search GridSearchCV( estimatorrf_base, param_gridparam_grid, cv5, scoringf1, n_jobs-1, verbose1 ) grid_search.fit(X_train_scaled, y_train) print(Best Params:, grid_search.best_params_) print(Best F1 Score:, grid_search.best_score_)这里有个容易踩的坑scoringf1在 sklearn 旧版本里可以直接用但新版本要求指定pos_label否则当正负样本不平衡时可能报错。建议显式使用scoringmake_scorer(f1_score, pos_label1)省得临时翻车。cv5表示五折交叉验证意味着每个参数组合要训练 5 次。上面的参数组合 3×3×3×381 种乘上 5 折就是 405 次训练再加上随机森林本身耗时跑完可能需要几分钟到十几分钟。n_jobs-1利用所有 CPU 核心并行计算时间能显著缩短。如果你的机器不太行可以把参数网格改小比如n_estimators只设[100, 200]max_depth只设[None, 10]先把流程跑通再扩。4.3 特征重要性让黑匣子模型开口说话调完参数不是终点随机森林还有一个大作业加分项特征重要性排序。它会告诉你模型做决策时哪些特征贡献最大这直接承接了前面相关性的分析并且能形成报告的前后呼应。best_rf grid_search.best_estimator_ feature_importance pd.DataFrame({ feature: feature_cols, importance: best_rf.feature_importances_ }).sort_values(importance, ascendingFalse) print(feature_importance.head(10))feature importance alcohol 0.181203 volatile_acidity 0.136464 density 0.121389 total_sulfur_dioxide 0.084213 chlorides 0.075764 ...alcohol再次登顶volatile_acidity和density紧随其后和前面相关系数排序的结论高度一致。这就在报告里形成了一个完整的证据链路相关性分析发现强相关特征 → 模型训练验证这些特征确实重要 → 业务解释补充为什么这些特征重要。这种闭环结构是大作业拿高分的关键而不是单纯跑个模型贴个准确率。这里的feature_importances_是随机森林中所有决策树分裂时减少的基尼不纯度的平均结果值越大表示该特征在树的分裂过程中贡献越大。需要注意的是它反映的是模型内部的使用频率不完全等同于特征和标签的因果强度但相关性已经很强了。5. 避坑指南5 个让新手当场翻车的高频问题5.1 数据分隔符错误read_csv 读出来只有一列现象执行pd.read_csv(winequality-red.csv)后DataFrame 只有一列列名是整行数据用head()看全是乱糟糟的长串。原因这个数据集的每列之间用的是分号;而不是逗号,pandas 默认按逗号分隔自然无法正确拆分。解决在read_csv中指定sep;或者在读取后手动按分号做 split。我通常直接指定分隔符一步到位。如果你已经读错了可以重新读不要尝试用字符串切分去修那样容易引入类型转换问题。5.2 random_state 不固定代码跑两次结果不一样现象模型每次运行准确率变化很大有时 85%有时 90%报告没法写。原因机器学习模型的训练过程涉及随机初始化、数据随机打乱、决策树分裂时的随机特征选择不固定随机种子就无法复现结果。解决在train_test_split、RandomForestClassifier、LogisticRegression等所有涉及随机的函数里显式传random_state42。不要只在某个地方固定所有随机源都要固定。这是做事严谨的表现也是实验可复现的底线。5.3 类别不平衡被忽略准确率虚高实际一无所获现象模型测试集准确率 88%你很高兴然后把混淆矩阵调出来一看所有样本都被预测成“普通”没有预测出一个“优质”。原因数据集中优质样本只占 10% 左右模型只要全猜多数类就有 90% 的正确率所以它“学会”了偷懒那些极小概率的正样本根本不值得它冒险预测。解决优先使用f1_score或recall作为评估指标同时尝试class_weightbalanced或 SMOTE。另外可以把classification_report完整打印到报告里不要只贴准确率显得你没注意到这个问题。5.4 红白数据混在一起却丢了来源信息现象把红白两个 CSV 合并后直接训练没加任何类型标记然后发现结果比预期差一截。原因红葡萄酒和白葡萄酒的理化指标分布差异明显合在一起后特征空间被混合拉伸单个模型只靠原始特征难以区分这种结构性差异。解决合并前务必加入type列比如 0 代表红、1 代表白让模型多一个关键特征。你也可以尝试构建红白两个独立模型分别预测各自数据然后对比效果。这个操作既能提升准确率又能丰富报告内容一举两得。5.5 网格搜索不加 n_jobs等到怀疑人生现象GridSearchCV跑了几分钟还没结束进度条也不动还以为代码死循环了。原因默认情况下网格搜索是单线程运行的参数组合多、交叉验证折数多、模型复杂度高时计算量是组合爆炸级别的。解决设置n_jobs-1把计算任务分配到所有 CPU 核心上。另外可以先手动打印参数网格大小和单次训练所需时间估算总耗时再决定是否缩减参数范围。如果机器内存不大可以加一个pre_dispatch参数限制并行任务数。6. 进阶玩法自己动手扩展这个项目让报告再上一个台阶到这里基线项目已经完整跑通了但如果你想让分数再上一个台阶、答辩时多几个能聊的亮点可以用下面几个方向做扩展。这些扩展都基于现有数据和代码不需要额外找数据非常适合时间有限又想展示思考深度的场景。一个推荐的进阶方向是做质量评分的回归预测。把二分类标签换回原始的quality分数用随机森林回归或者梯度提升回归去预测具体分值。这样做的好处是能引出新的评价指标——比如 RMSE 和 R²也能顺带讨论回归和分类在同一个数据集上的适用场景区别。我在之前某次做这个扩展时发现回归模型在训练集上 R² 能到 0.8 左右但测试集只有 0.45 左右这个过拟合落差本身就是很好的讨论素材。另一个更容易出效果的方向是白葡萄酒和红葡萄酒单独建模对比。你会发现白葡萄酒数据量更大、质量分布更偏上端模型在两类数据上的 F1 分数有明显差异。把这个对比做成一张表格放在模型对比章节里能直接体现你对数据异构性的感知。还有一个偏可视化的方向用 PCA 或 t-SNE 对特征空间降维把样本点散点图绘制成二维平面按质量标签着色。虽然降维后的空间不一定有清晰的簇但你可以讨论“为什么用 t-SNE 能从一团点中勉强区分出优质样本”——因为这恰恰间接证明了特征组合确实携带了区分信息。实现很简单sklearn 里PCA(n_components2)或TSNE(n_components2)几行就够。最后一个值得提的实操技巧是给报告加一个“错误分析”环节。从测试集里把预测错误的样本挑出来看看它们是不是正好处于质量分 6 分和 7 分的边缘地带。这其实不算玄学而是模型本身就是品酒师主观评分6 分和 7 分之间的界限本来就很模糊误判可以归类为“边界样本问题”比单纯说“模型不够准”高明得多。如果你最终用的是 Jupyter Notebook 提交记得把所有代码整理成从上到下一次性可运行的顺序不要在中间穿插大段 Markdown 却让代码块互相依赖顺序混乱。答辩现场最尴尬的事不是模型分数低而是演示到一半发现某个变量在前面被覆盖了。我做这类数据挖掘项目有一个固执的习惯每一张图表都必须能在报告里被一句话解释出它的“结论”而不是为了凑图而画图。相关性热力图说明哪些特征值得建模KDE 分布图说明红白差异特征重要性排序图说明模型的可解释性混淆矩阵说明误差类型。四张图各司其职报告结构自然清晰。这次把整个流程连起来写出来也是希望你少走那些我走过的弯路。如果你的时间只够做一件事先去把class_weightbalanced加上再看混淆矩阵——这一步能救回大部分翻车现场。希望帮到你。本文还有配套的精品资源点击获取