恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
从红酒数据集看完整数据分析流程:特征工程、模型对比与避坑指南
首页
资讯中心
/
从红酒数据集看完整数据分析流程:特征工程、模型对比与避坑指南
从红酒数据集看完整数据分析流程:特征工程、模型对比与避坑指南
发布时间:2026/10/13 14:56:47
简介针对“红酒数据集的分析”这一数据分析大作业一套完整的docx实验报告可供数据分析初学者、统计课程学生参考。报告围绕红酒的11项物理属性与010评分展开利用Python完成csv格式转换再借助R软件进行相关性分析、多元线性回归、主成分回归和KNN分类完整呈现数据预处理、特征筛选、建模与评估流程。内容涵盖各变量相关性矩阵、与评分相关较高的挥发性酸度/硫酸盐/酒精的回归结果、主成分表达式及预测区间等便于理解统计方法在实际数据中的应用。资源共1个docx文件压缩包大小约202KB。目前已有4265人学习下载。读者可重点借鉴如何筛选变量、解读R方值以及将主成分回归系数还原为原始变量表达式的方法对完成类似数据分析作业或入门葡萄酒质量研究具有实用价值。1. 红酒数据集分析到底在分析什么一场从表格到结论的完整练习很多人拿到红酒数据集的第一反应是打开 Jupyter 敲一句df.head()然后直接丢给随机森林跑出个准确率就以为大功告成。这个流程不能说错但作为一份数据分析大作业它顶多算把代码跑通了离“分析”两个字还差得远。红酒数据集表面上是 12 列理化指标加一个质量评分本质考的是你对数据分布、类别不平衡、评估指标和数据泄漏这几件事的基本功。下面这条路径会把完整流程走一遍先做探索性分析看清分布再做预处理和特征工程用逻辑回归、随机森林和梯度提升做对比最后拆开五个高频踩坑点。适合正在写课程大作业、想从“能跑”升级到“能讲清楚”的读者。2. 先摸清数据的脾气红酒数据集的三个必看分析角度拿到任何数据集第一个该问的不是“我能用什么模型”而是“这份数据长什么样”。红酒数据集最常见的形态是 CSV 文件每一行是一瓶红葡萄酒的样本每一列是一个理化指标最后一列是品酒师给出的质量评分。质量评分通常是一个 3 到 8 之间的整数不是连续值这一点会在后面影响建模策略。2.1 字段含义与数据类型哪些是连续变量哪些是伪连续先看字段。红酒数据集的 12 个字段里前 11 个是特征最后 1 个是标签。下面这张表列出每个字段的含义以及进入分析时第一时间要注意的点。字段含义类型分析起点fixed acidity固定酸度连续与柠檬酸同属酸度体系注意相关性volatile acidity挥发性酸度连续与质量负相关最强之一重点看citric acid柠檬酸连续存在部分 0 值检查是否为真实 0residual sugar残糖连续红葡萄酒残糖普遍低分布偏右chlorides氯化物连续数值小量纲差异明显free sulfur dioxide游离二氧化硫连续与总二氧化硫高度相关total sulfur dioxide总二氧化硫连续与游离项一起看density密度连续与残糖、酒精相关是共线性来源pHpH 值连续酸性指标与固定酸度取值方向相反sulphates硫酸盐连续与质量存在弱正相关alcohol酒精连续与质量正相关最强特征之一quality质量评分离散3-8 的整数分类还是回归要尽早决定import pandas as pd # 注意分隔符是分号不是逗号这是这个数据集最常见的翻车点 df pd.read_csv(data/winequality-red.csv, sep;) print(df.shape) print(df.info()) print(df.describe().T)这段代码做了三件事。shape告诉你样本量和列数红葡萄酒这份数据大约 1599 行量级不大后面所有模型都能在几秒内跑完。info()帮你看每一列的非空数量如果 count 和行数一致说明没有显式缺失。describe()给出每列的均值、标准差和分位数注意 fixed acidity 和 residual sugar 这类指标极差非常大量纲完全不在一个水平这决定了后面对线性模型必须做标准化。有个细节值得单独说describe()里的 count 只能证明没有 NaN不能证明没有重复值。很多人在这一步看到 count 全齐就跳过重复值检查后面会专门讲这个坑。2.2 相关性矩阵与酒精、挥发酸两个关键特征import matplotlib.pyplot as plt import seaborn as sns # numeric_only 在 pandas 2.0 之后是必填参数老代码直接 df.corr() 会告警 corr df.corr(numeric_onlyTrue) plt.figure(figsize(10, 8)) sns.heatmap(corr, annotTrue, fmt.2f, cmapRdBu_r, center0) plt.title(Red Wine Feature Correlation) plt.tight_layout() plt.savefig(figures/corr_matrix.png, dpi150)相关性矩阵是这份大作业里最值得放进报告的一张图。两个结论对后面的建模方向有直接影响。第一alcohol 与 quality 的相关系数大约 0.48是全部特征里正相关最强的volatile acidity 与 quality 大约 -0.39是负相关最强的。这说明“好酒往往酒精浓度更高、挥发性酸度更低”在数据上是成立的报告里能直接写。第二density 与 residual sugar 相关系数接近 0.56density 与 alcohol 接近 -0.5特征之间存在明显共线性线性模型里系数会互相拉扯这也是为什么后面不用原始特征直接做逻辑回归。这里把相关性矩阵存成图片而不是只在 notebook 里看效果原因是作业报告基本都要配图提前存好 dpi 150 的 PNG后面写结论时直接引用。2.3 质量评分的分布陷阱类别不平衡在动手前就要看见# 看标签分布这一步决定后面用分类还是回归、用准确率还是 AUC quality_counts df[quality].value_counts().sort_index() print(quality_counts) print(好酒占比(6):, (df[quality] 6).mean()) quality_counts.plot(kindbar, figsize(8, 5)) plt.title(Distribution of Quality Scores) plt.tight_layout() plt.savefig(figures/quality_dist.png, dpi150)质量评分集中在 5 和 6 两个档位4 和 7 明显少很多3 和 8 几乎只有零星几条。如果直接用原始评分做多分类等于用一个样本量极不均衡的 6 类问题去训练少数类的 F1 会非常难看。常见做法是转成二分类评分大于等于 6 视为好酒其余视为普通酒。这么切之后好酒占比通常在 45% 到 55% 之间是一个相对平衡但依然有倾斜的分类问题。如果你坚持做回归也不是不行但你要面对一个很难解释的事实质量评分本质是离散整数回归模型输出的 5.4 分在业务上没有任何意义。3.3 节会展开讲为什么二分类是更稳的落点。3. 预处理与特征工程把理化指标变成模型吃得顺的输入第 2 章看的都是“原样数据”。真正进入建模之前还有三步常规操作处理重复值、决定特征尺度、确定标签口径。这三步不复杂但每一步都埋着扣分点。3.1 缺失值与重复值处理红酒数据集的缺失没你想的严重# 先查缺失再查重复两个检查不能互相替代 print(缺失值统计) print(df.isnull().sum()) print(重复样本数, df.duplicated().sum()) df df.drop_duplicates() print(去重后样本量, df.shape[0])红酒数据集这份数据通常没有缺失值所有列的非空数量都和总数一致所以第一步基本是走流程。真正起作用的是第二步去重。这个数据集的采集方式是理化检测同一批酒可能被重复记录去重前的重复样本大约 240 行占总量 15% 左右足够改变交叉验证的分数。drop_duplicates()默认保留第一条出现的样本对这份数据来说足够不需要加 subset 参数因为你要去的是整行完全一致的样本。去重之后样本量从大约 1599 降到 1359 左右不同来源的 CSV 可能有细微差异。注意顺序去重要做在标准化和划分之前否则同样的样本会同时出现在训练集和测试集里导致分数虚高。3.2 特征标准化与哑变量陷阱树模型不怕量纲线性模型怕from sklearn.preprocessing import StandardScaler feature_cols [c for c in df.columns if c ! quality] X df[feature_cols] y df[quality] # StandardScaler 先 fit 再 transform对象要保留测试集还要用 scaler StandardScaler() X_scaled scaler.fit_transform(X) X_scaled pd.DataFrame(X_scaled, columnsfeature_cols) print(X_scaled.describe().round(2))StandardScaler 把每个特征转成均值 0、标准差 1 的分布这一步对逻辑回归和基于距离的算法是必须的。对比一下原始数据chlorides 的均值大约 0.08alcohol 的均值大约 10 出头量纲差了上百倍直接用原始值做逻辑回归正则化会把小数值特征当成不重要的特征这是尺度的锅不是特征的锅。树模型不需要标准化因为决策树只关心分裂点不关心尺度所以第 4 章的随机森林和梯度提升用原始特征跑逻辑回归用标准化后的跑。哑变量陷阱在这里出现的概率不高因为红酒数据集的字段全是数值。如果你后续自己构造了来源、品类这类文本特征再用 one-hot 时记得要么删掉第一列要么确认没有完美的多重共线性。这份数据本身不涉及但报告里可以提一句你考虑过这件事是加分项。3.3 质量分转二分类阈值切在哪里评估指标跟着怎么变# 6 是好酒这是这个数据集最常见的切法 y_binary (df[quality] 6).astype(int) print(y_binary.value_counts(normalizeTrue))阈值切在 6 而不是 5是因为 5 分是中间档把 5 归入坏酒会让“好酒”定义失真。切在 7 也可以那就是“精品酒 vs 其他”正类占比会掉到 15% 以下变成明显的稀有事件建模对只有一千多条的样本量来说难度偏高。所以大作业场景下6 是最容易把故事讲完整的阈值。标签一旦变成 0/1评估指标的选择就跟着变了。准确率在这个任务里会虚高哪怕全部预测为 0如果好酒占比 45%准确率也有 55%。更稳的是 F1 和 AUC这两个指标看的是你在“抓到好酒”和“不误伤普通酒”之间是否平衡直接影响第 4 章模型对比的口径。4. 建模与基线评估先跑通最小可行管道再谈调参预处理做完进入建模。我的习惯是先搭一条最小可行管道划分、交叉验证、三个模型对比、看混淆矩阵。调参放到管道跑通之后因为调参之前你至少要知道基线在哪里。4.1 训练集划分与交叉验证不要用默认的 train_test_split 裸跑from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score # stratify 保证训练集和测试集里好酒比例一致类别不平衡时必须加 X_tr, X_te, y_tr, y_te train_test_split( X_scaled, y_binary, test_size0.2, random_state42, stratifyy_binary ) # 5 折分层交叉验证shuffle 配合 random_state 保证可复现 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) print(f训练集 {X_tr.shape}测试集 {X_te.shape})train_test_split的两个参数值得单独解释。test_size0.2表示留出 20% 的样本做最终测试剩下 80% 用于训练和验证。stratifyy_binary让切割前后正负样本比例保持一致这是类别不平衡任务里绝对不能省的参数不写的话少量级数据集容易出现测试集里好酒占比跌到 30% 的情况。random_state42让结果可复现交作业时如果每次跑分数都不同答辩时解释不清楚。交叉验证用 StratifiedKFold 而不是默认的 KFold理由和stratify一样每一折都尽量保持类别比例。n_splits5对 1359 条样本是常规选择每折大约 270 条不会因为折太少导致验证分数波动大。4.2 三个候选模型的效果对比逻辑回归、随机森林与梯度提升from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier from sklearn.dummy import DummyClassifier # 先放一个瞎猜 baseline所有模型都比它强报告才有说服力 dummy DummyClassifier(strategymost_frequent) models { Dummy: dummy, LogisticRegression: LogisticRegression(max_iter2000), RandomForest: RandomForestClassifier(n_estimators500, random_state42, n_jobs-1), GradientBoosting: GradientBoostingClassifier(random_state42), } for name, model in models.items(): scores cross_val_score(model, X_tr, y_tr, cvcv, scoringroc_auc) print(f{name}: AUC{scores.mean():.4f} (/- {scores.std():.4f}))DummyClassifier 很重要它是所有模型的底线。strategymost_frequent表示永远预测多数类也就是全部预测为“不是好酒”它的 AUC 会稳定在 0.5 附近。如果某个候选模型的 AUC 只有 0.55说明它基本没学到东西这时候调参就是玄学应该先回去看特征。逻辑回归和随机森林在这个任务上的表现通常都在 0.85 到 0.9 之间梯度提升会略高一点但差距不会很大。这个数据集的特征本身就比较干净没有哪种模型能碾压其他报告里直接说“模型间差异在 2 个点以内优先选可解释性强的逻辑回归或随机森林”会比硬吹某个模型更可信。模型是否依赖标准化训练开销可解释性典型 AUC 区间Dummy否极低无意义约 0.5逻辑回归依赖低高系数可直接看0.84-0.88随机森林不依赖中高特征重要性可排序0.86-0.90梯度提升不依赖较高中弱解释性0.86-0.90AUC 区间是经验范围只做参考实际以你当前数据切分为准。4.3 评估指标的选择准确率会骗人F1 与 AUC 更接近真实需求from sklearn.metrics import accuracy_score, f1_score, roc_auc_score, confusion_matrix # 用随机森林作为代表模型在测试集上做最终评估 model RandomForestClassifier(n_estimators500, random_state42) model.fit(X_tr, y_tr) y_pred model.predict(X_te) y_prob model.predict_proba(X_te)[:, 1] print(Accuracy:, round(accuracy_score(y_te, y_pred), 4)) print(F1:, round(f1_score(y_te, y_pred), 4)) print(AUC:, round(roc_auc_score(y_te, y_prob), 4)) print(Confusion Matrix:) print(confusion_matrix(y_te, y_pred))测试集上的评估要区分两个口径。准确率和 F1 用的是 predict 出来的硬分类结果AUC 用的是predict_proba的连续概率。硬分类结果受阈值影响而 AUC 是纯排序指标不受阈值影响。你要写进报告的核心结论应该是模型的排序能力不错AUC 在 0.88 左右但要拿到更高的 F1需要回头调阈值而不是调模型。这时候看混淆矩阵比看准确率有用得多。假设混淆矩阵是[[140, 40], [50, 80]]说明把 40 瓶普通酒误判成了好酒同时漏掉了 50 瓶好酒。到底哪类错误更严重取决于你定义这个任务的人。品酒师更在意漏掉好酒还是误判普通酒这是一个业务问题不是一个代码问题报告里点出这一点会非常加分。5. 红酒数据集建模避坑笔记五个高频问题从特征泄漏到随机种子这章把类似大作业里见过最多的高频问题拆开写每条按现象、原因、解决的结构说明。避开这五个坑你的报告至少能比平均水平高出一个档次。5.1 坑一标准化在划分之前做造成数据泄漏现象交叉验证分数很高但换到测试集上分数明显下滑或者模型分数每次重跑都不稳定。原因很多人习惯读完数据立刻StandardScaler().fit_transform(X)把全部样本的均值和标准差算完再去做train_test_split。问题在于测试集的分布信息已经通过均值、方差泄漏进了训练过程。这是典型的数据泄漏虽然影响没有目标泄漏那么致命但会让交叉验证结果失真被追问一句“你的 scaler 是在划分之前 fit 的吗”就会翻车。解决先划分再标准化。对训练集fit_transform对测试集只transform。最省心的是把标准化和模型绑进同一个 Pipeline交叉验证时每一折都在训练折内部重新拟合 scaler不会偷看验证折。from sklearn.pipeline import Pipeline # 把标准化和模型绑在一起交叉验证不会让 scaler 偷看验证集 pipe Pipeline([ (scaler, StandardScaler()), (clf, LogisticRegression(max_iter2000)), ]) scores cross_val_score(pipe, X_tr, y_tr, cvcv, scoringroc_auc)5.2 坑二重复样本没去干净交叉验证分数虚高现象代码跑出来的准确率和 AUC 高得离谱比同组其他人高出一截但把数据换一份或者换个随机种子分数立刻打回原形。原因红酒数据集里有将近 15% 的完整重复行来源于同一样本的多次物理采样。如果不drop_duplicates()这些完全相同的样本可能同时出现在训练集和测试集里模型在测试集上“见到过”一模一样的数据分数自然虚高。去重前后 AUC 差 1 到 3 个点是常见情况。解决在标准化之前执行drop_duplicates()并直接对比去重前后的交叉验证分数把结果写进报告。df_dedup df.drop_duplicates() # 对比去重前后同一个模型的 AUC数据能说明问题 for label, data in [(before, df), (after, df_dedup)]: X_tmp data[feature_cols] y_tmp (data[quality] 6).astype(int) scores cross_val_score( RandomForestClassifier(n_estimators500, random_state42), X_tmp, y_tmp, cvcv, scoringroc_auc ) print(f{label}: AUC{scores.mean():.4f})5.3 坑三把质量分当回归做结果看起来不错但没意义现象有人用线性回归或 SVR 预测质量评分R² 在 0.3 到 0.4觉得能交差但被问“这个分数怎么用于决策”时答不出来。原因质量评分是 3 到 8 的离散整数不是连续测量值。回归模型输出的 5.7 分不能对应任何一瓶真实存在的酒品酒师也不会认可你的结果是“一瓶 5.7 分的酒”。回归思路不是完全不能做但你需要明确预测残差的意义这对大作业来说解释成本太高。解决二分类是更直接的问题定义。如果导师要求保留回归思路就把输出范围约束到整数档位改成分类问题同时报每个类别的预测概率既利用了排序信息又给出可解释的结论。5.4 坑四随机森林默认参数直接交作业效果平庸现象用RandomForestClassifier()默认参数跑完和认真调过参数的版本相比 AUC 差 2 到 4 个点但很少有人意识到默认参数不是为这个数据集量身定的。原因默认n_estimators100对 1359 条样本来说偏少树的集成效果没有完全发挥。默认max_depthNone让每棵树长到完全深在小样本上非常容易过拟合。两个因素叠加模型方差偏大换一次随机种子分数就跳动。解决先把n_estimators提到 300 到 500再限制max_depth或min_samples_leaf。用一个小范围搜索就够不要在千级样本上做大规模网格搜索。rf RandomForestClassifier( n_estimators500, max_depth12, min_samples_leaf4, random_state42, n_jobs-1, )max_depth12对十几维特征的数据足够表达复杂关系min_samples_leaf4强制叶子节点至少 4 个样本相当于给树加了一点平滑。这两个改动通常能让验证分数稳定很多。5.5 坑五只看准确率模型实际没学到东西现象报告里写“准确率 0.82”但仔细看混淆矩阵模型把所有好酒都预测成了普通酒准确率是靠多数类撑起来的。原因准确率在类别不平衡下天然偏向多数类。好酒占比 45% 时全预测为负类也有 55% 的准确率和瞎猜的区别不大。Precision、Recall、F1 一个都没看等于模型什么都没证明。解决训练阶段用 AUC 做模型选择因为 AUC 对类别比例不敏感最终报告用 F1 和混淆矩阵解释模型的实际表现。附上一句“正类识别率是多少、误伤率是多少”比一个孤零零的准确率有信息量得多。6. 把大作业从“能跑”做到“能讲”一个提升说服力的验证习惯最后这章讲一个具体技巧留出最终测试集并且只对它跑一次。很多人把交叉验证分数和测试集分数混着用导致报告里要么缺一组数要么测试集被跑了五六遍结果失去意义。做法是第 4 章里的X_te只用来做一次最终评估所有调参和模型选择都在训练集内部的交叉验证里完成。这样你写在报告里的每组数字都有明确的含义交叉验证分数代表“调参过程中的平均表现”测试集分数代表“最终模型在新数据上的估计”。两者之间的差距就是模型的泛化余量答辩时直接说“训练集 CV AUC 0.88测试集 AUC 0.86差距在 2 个点以内说明没有严重过拟合”就是一次合格的收尾。结论部分建议配四张图质量分布条形图、相关性热图、模型对比柱状图、混淆矩阵。这四张图是红酒数据集分析最常见也最有解释力的可视化组合。特征重要性可以作为补充看酒精、挥发性酸度和柠檬酸是否排在前列这能佐证 EDA 阶段的结论。import pandas as pd # 特征重要性排序随机森林自带逻辑回归可以用系数绝对值近似 importance pd.Series( model.feature_importances_, indexfeature_cols ).sort_values(ascendingFalse) print(importance.round(4))我自己第一次做这份数据时就是没去重直接拿了 0.87 的 AUC 沾沾自喜被追问一句“你这分数为什么每次重跑都不一样”后当场下不来台。后来慢慢养成了固定 random_state、先查重复再划分、划分后再做标准化的习惯再没在这种基础问题上翻过车。希望帮到你。本文还有配套的精品资源点击获取