
简介这是一份数据分析课程大作业的完整报告围绕经典红酒数据集展开“评分影响因素分析分类建模”的实践适合高校学生、数据分析初学者参考。报告从红酒的11项物理属性出发完整呈现了数据清洗Python转xlsx、R语言相关性分析、变量筛选、多元线性回归、主成分回归以及KNN分类的流程。通过相关性分析识别出与评分高度相关的挥发性酸度、硫酸盐和酒精并给出回归方程、置信区间与预测区间主成分回归部分则展示降维后建模的具体步骤和系数转换方法。压缩包内包含1个docx文档大小仅202KB文字、代码与结果图齐全便于直接查阅。目前已有4265人学习适合用来对照自己的分析思路或快速获取一套可复用的红酒数据分析方案。1. 红酒数据集分析评分最不看 pH看的是这三个因素打开一份红酒数据集多数人的第一反应是先看 pH、柠檬酸这类看起来像品质指标的字段。但相关性分析给出的答案完全相反真正和评分挂钩的是挥发性酸度、硫酸盐与酒精pH 的相关系数低到可以直接忽略。这份项目把完整的数据分析流程走了一遍——CSV 转 XLSX、Spearman 相关性筛变量、多元线性回归拟合评分、主成分回归兜底、再用 KNN 做评分分类R 与 Python 代码都齐。正在写数据分析大作业的人可以直接替换成本地数据跑通想弄明白为什么全变量回归会翻车、主成分系数怎么还原成原始变量的也能在里面找到答案。2. 数据读取与相关性筛检从 CSV 到 Spearman 相关性矩阵2.1 变量构成与格式预处理CSV 转 XLSX 时容易忽略的细节这份数据集的核心结构很清晰11 个理化属性作为自变量评分0 到 10 的整数作为因变量。原始数据以 CSV 格式给出项目里第一步是用 Python 把它转成 XLSX再用 R 读取。转换这一步看着简单但有个细节值得注意——这类红酒数据集的列分隔符不一定是逗号很多版本用的是分号因为部分区域的语言环境把逗号保留给了小数点。import pandas as pd # 注意 sep 参数如果文件里是分号分隔这里必须改成 sep; df pd.read_csv(winequality-red.csv, sep;) df.to_excel(redwine.xlsx, sheet_nameSheet1, indexFalse)转换后打开 Excel 检查两件事列名是否被正确识别成 x1、x2 这类编号评分列是否读成了数值而不是文本。我在实际跑这个项目时遇到过评分列带着引号被读成字符串的情况后续 R 里一旦对这一列做 cor() 就会直接报错。所以转换完之后用df.dtypes快速确认一下数据类型是值得养成的习惯。数据读入后先看评分分布这份数据里评分范围是 3 到 8 分绝大多数集中在 5 分和 6 分之间。这个分布特征会在后面反复影响模型表现——线性回归的 R² 上不去、KNN 分类准确率偏低根源都在这里。变量名含义在后续分析中的记号x1非挥发性酸度回归候选变量x2挥发性酸度最终回归变量x3柠檬酸回归候选变量x4残糖相关性筛除x5氯化物KNN 特征x6游离二氧化硫相关性筛除x7总二氧化硫KNN 特征x8密度回归候选变量x9pH 值相关性筛除x10硫酸盐最终回归变量x11酒精最终回归变量2.2 用 Spearman 相关性给 11 个属性排序相关性分析选择 Spearman 而不是 Pearson 是有道理的评分是离散整数分布又明显偏向 5 分和 6 分不满足 Pearson 对正态性和线性关系的假设。Spearman 基于秩计算对偏态分布更稳。R 里读入 XLSX 后用一行代码就能拿到整个相关性矩阵。install.packages(readxl) library(readxl) df - read_excel(redwine.xlsx, sheet 1, col_names TRUE) cor(df, method spearman)这里的col_names TRUE表示首行是列名sheet 1读第一个工作表。cor() 要求所有列都是数值型所以前面检查类型那一步不能省。相关性矩阵里真正值得关注的结论有两个层面。第一层是与评分的关系x2挥发性酸度、x10硫酸盐、x11酒精与评分相关性较高其中 x2 是负相关x10 和 x11 是正相关x4残糖、x6游离二氧化硫、x9pH 值与评分相关性弱可以先剔除做降维。第二层是变量之间的关系pH 与非挥发性酸度相关性很高因为数据里非挥发性酸含量较高直接主导了 pH 的走向酒精和密度也有一定相关性。这个第二层结论很关键它预告了后面全变量回归会遇到的多重共线性问题。筛选后的处理策略如下表变量与评分的相关性处理建议x2 挥发性酸度负相关较强保留x10 硫酸盐正相关较强保留x11 酒精正相关较强保留x4 残糖、x6 游离二氧化硫、x9 pH弱相关剔除3. 多元线性回归三个变量拟合评分与预测区间3.1 全变量回归的翻车现场为什么参数大面积不显著相关性筛掉 x4、x6、x9 之后剩下的 8 个变量全部丢进线性回归看起来信息更全但结果并不好部分参数的显著性检验 p 值超过了 0.05意味着这些变量对评分的解释贡献在统计上不显著。这一步的教训是——相关性筛选只是第一道过滤变量之间还彼此相关把一堆互相纠缠的变量塞进同一个模型参数估计会被共线性搅浑。lm.sol - lm(q ~ x1 x2 x3 x5 x7 x8 x10 x11, data df) summary(lm.sol)看 summary 输出时我一般先看每个系数的 Pr(|t|) 列再回头看整体 F 统计量。如果整体显著但个别系数不显著多半是共线性在作怪而不是这些变量真的没用。此时不要急着逐个删变量回到相关性矩阵看哪些变量扎堆相关比盲目的逐步回归更有效率。3.2 三变量回归公式、系数含义与 R² 的读法把变量收敛到相关性最强的三个——x2、x10、x11——重新拟合所有参数都通过了显著性检验回归方程也很干净p -1.22130 × x2 0.67898 × x10 0.30920 × x11 2.61104lm.sol2 - lm(q ~ x2 x10 x11, data df) summary(lm.sol2)系数的业务含义很直接挥发性酸度每增加一个单位评分平均下降约 1.22 分这是三个变量里影响最大的硫酸盐每增加一个单位评分上升约 0.68 分酒精每增加一个单位评分上升约 0.31 分。系数大小不能直接跨变量比较重要性因为三个变量的量纲不同但方向和量级已经足够说明问题——挥发性酸度的负向作用远超另外两个正向变量。R² 是 0.6589坦白说这个值不算高。但要结合数据背景看评分是品酒师的主观打分且绝大多数样本挤在 5 分和 6 分之间模型的解释力天然受限。与其死磕 R²不如验证预测区间是否覆盖实际值来得实在。3.3 置信区间与预测区间预留样本怎么用项目里留了一组样本没参与建模专门用来验证x20.31x100.66x1111实际评分是 6。new - data.frame(x2 0.31, x10 0.66, x11 11) lm.conf - predict(lm.sol2, new, interval confidence) lm.pred - predict(lm.sol2, new, interval prediction) print(lm.conf) # 置信区间针对均值估计 print(lm.pred) # 预测区间针对单个新样本这两个区间经常被混用。interval confidence给出的是回归均值估计的不确定性区间适用于大量相同条件的酒平均能得多少分interval prediction给出的是单个新样本的波动范围适用于这一瓶酒大概能得多少分。同一个预测值预测区间明显比置信区间宽因为它还要容纳个体残差的波动。实际看结果时别拿置信区间去对标单瓶酒的预测误差这是新手最容易看错的地方。4. 主成分回归把五维主成分还原成原始变量系数4.1 为什么还要做主成分共线性与信息保留三变量回归虽然通过了检验但代价是丢掉了 x1、x5、x7、x8 这些原始信息。主成分回归的思路完全不同先把 11 个变量做正交变换生成互不相关的多个主成分再用前几个主成分去拟合评分。这样做的好处是能保留大部分变量的变异信息同时避开共线性对参数估计的干扰。用princomp(..., cor TRUE)时cor TRUE表示基于相关矩阵做主成分分析等价于先把所有变量标准化到同一尺度再提取主成分。这一步是必须的——如果直接对原始数据做密度这种数值在 0.99 左右的变量和酒精这种在 10 左右的变量会因量纲差异严重扭曲主成分的方向。wine - read_excel(redwine.xlsx, sheet 1, col_names TRUE) # wine 这里只保留 11 个理化属性列评分列单独存放 wine.pr - princomp(wine, cor TRUE) summary(wine.pr, loadings TRUE)sammary(wine.pr, loadings TRUE)会输出每个主成分的方差贡献率、累积贡献率和载荷向量。项目里取前五个主成分用于后续回归具体累积贡献率以你自己跑出来的输出为准不需要硬记一个固定值。4.2 提取前五个主成分并做回归前五个主成分从 predict() 里取出来拼成新数据框和评分列一起做回归。pre - predict(wine.pr) redd - data.frame( q df$q, z1 pre[, 1], z2 pre[, 2], z3 pre[, 3], z4 pre[, 4], z5 pre[, 5] ) lm.solp - lm(q ~ z1 z2 z3 z4 z5, data redd) summary(lm.solp)这里有个容易踩的坑wine 数据框必须只含 11 个理化属性列如果误把评分列也带进去主成分会被评分污染后续回归的系数方向全变。主成分回归的系数检验基本都能通过因为主成分两两正交不再存在共线性问题。但要注意主成分本身是原始变量的线性组合解释性比直接回归差——这是它的代价。4.3 关键技巧用特征向量反解原始变量系数主成分回归的系数是在主成分空间里的业务上没法直接用。真正的技术点在最后一步把主成分系数还原成原始变量的回归系数。做法是取相关矩阵的特征向量把前几个主成分的回归系数分别与对应特征向量加权求和再除以原始变量的标准差截距项用主成分回归截距减去均值加权项。XX - cor(wine) A - eigen(XX)$vectors beta - coef(lm.solp) X.bar - apply(wine, 2, mean) # 均值 X.sd - apply(wine, 2, sd) # 标准差 coef_orig - (beta[2] * A[, 1] beta[3] * A[, 2] beta[4] * A[, 3] beta[5] * A[, 4]) / X.sd beta0 - beta[1] - sum(X.bar * coef_orig) c(beta0, coef_orig)为什么要除以标准差因为主成分分析是在标准化空间里做的还原到原始量纲时必须把标准化的缩放比例还原回去。还原后的系数方向和量级应该与第 3 章三变量回归的结果大体一致——如果方向都反了说明主成分的符号取向和预期不一致需要检查特征向量的符号约定。这一步是整份资源里最有复用价值的部分做完之后主成分回归就不再是黑匣子了。5. 常见问题与避坑回归和 KNN 的五个实际踩坑记录5.1 全变量回归参数不显著先查相关性矩阵再决定留谁现象把相关性筛完剩下的 8 个变量全部丢进lm()summary 输出里好几个系数的 Pr(|t|) 都大于 0.05整体模型却显著。原因变量之间存在较强的相关关系比如非挥发性酸度与 pH、酒精与密度。多个高度相关的变量进入同一模型后彼此争夺解释力导致单个系数的标准误膨胀显著性被稀释。解决回到相关性矩阵看变量与变量之间的相关性而不是只盯变量与评分的关系。先剔除与评分弱相关的变量再剔除互相强相关的冗余变量。实际项目里通常做两轮筛选第一轮按相关性筛第二轮跑一版全变量回归看 VIF方差膨胀因子VIF 超过 10 的变量优先排除。5.2 三变量回归 R² 只有 0.6589别急着换模型现象三个变量建模后参数全部通过检验但 R² 只有 0.6589怎么看都觉得拟合不够高。原因评分是品酒师的主观判断且数据里 5 分和 6 分占了绝大多数样本本身方差有限。模型能解释的部分已经解释完了剩下的残差更多来自人为评分的不确定性不是变量缺失。解决换模型前先做两个动作第一用留出的样本跑预测看预测区间是否覆盖实际评分预测准比 R² 重要第二看残差图如果残差随机分布在零线附近说明模型没有系统性偏差。R² 0.66 对主观评分类数据来说属于能用但不完美的水平。追求高 R² 可以加交互项但代价是过拟合风险大作业场景里不值得。5.3 主成分回归预测有偏差主成分是黑匣子现象拿一组新数据代入主成分模型预测值和实际评分存在偏差甚至比三变量回归还大。原因主成分回归的系数是在正交空间中估计的前五个主成分只保留了大部分变异信息并非全部丢掉的部分里可能包含与评分相关的细节。另外主成分方向不一定与业务含义对齐预测时任何输入变量的微小异常都会被线性组合放大。解决先看 summary 输出里前五个主成分的累积贡献率如果低于 80%考虑增加主成分数量再看还原后的原始变量系数方向是否和三变量回归一致如果不一致检查是否把评分列误带入了主成分分析。主成分回归适合处理变量多、共线性重的场景不适合当作唯一的预测模型。5.4 KNN 分类准确率上不去评分分布失衡与 k 值双杀现象按项目里的 KNN 实现跑分类准确率明显偏低测试结果和实际评分对不上。原因两个因素叠加。第一评分集中在 5 分和 6 分近邻样本大量来自这两个类别少数类3 分、4 分、8 分几乎被淹没第二k 值取 20 甚至更大时近邻里会混入过多中间分样本把边界样本的投票结果拉向均值。解决降 k 值到 10 左右或者改成距离加权投票——距离越近的样本权重越大减少远端噪声的影响。更实用的做法是把评分分档比如 3-5 分算低分6 分算中分7-8 分算高分把回归问题转成三分类问题准确率会直观很多。这里踩过的坑是分类目标变成了模糊的分档而非精确分值业务上要能接受这个折中。5.5 手工 KNN 的索引越界与重复距离现象Python 手写 KNN 时从 openpyxl 读数据后分类函数里取objects[i][5]直接报 IndexError不报错的时候某些样本会被反复选中结果明显异常。原因读数据时只往列表里放进了 5 个属性值评分没有放进去索引 5 自然越界。另一个坑是distances.index(min(distances))这个写法当多个距离值并列最小时index() 永远返回第一个最小值的位置导致同一个样本被多次选中。解决读取时把评分作为第 6 个元素一起存进对象选最近邻时改成sort()按距离排序再取前 k 个样本投票。openpyxl 的列号从 1 开始和 x1、x2 的编号不是一回事写列索引时先看一眼 Excel 表头避免因为 off-by-one 读错列。6. 留出验证与 KNN 改进让结果经得起复核做完回归和 KNN 之后最容易被忽略的一步是验证。这个项目里预留了一组样本没参与建模这个习惯值得保留——它是整个流程里成本最低、说服力最强的验证方式。我自己的固定做法是建模前先随机切出 10% 的样本锁起来所有模型跑完后统一拿这组数据做预测看预测值和真实值的偏差而不是只用全体数据拟合后自我感觉良好。from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier from sklearn.preprocessing import StandardScaler X df[[x2, x5, x7, x10, x11]] y df[q].apply(lambda v: low if v 5 else (mid if v 6 else high)) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) scaler StandardScaler().fit(X_train) X_train_s scaler.transform(X_train) X_test_s scaler.transform(X_test) model KNeighborsClassifier(n_neighbors20, weightsdistance) model.fit(X_train_s, y_train) print(model.score(X_test_s, y_test))这段代码里有两个改进点供参考。一个是weightsdistance近邻投票时按距离加权比等权投票更抗噪另一个是StandardScaler对 x2、x5、x7、x10、x11 做标准化避免硫酸盐数值范围和总二氧化硫差太多导致距离被大数值变量主导。评分分档也要多说一句5 分以下算 low6 分算 mid7 分以上算 high这个切分不是最优但足够直观样本量允许的话可以按分布分位数来切。跑完这组验证再回头看第 3 章的三变量回归和第 4 章的主成分回归你会发现自己能判断的问题比之前清楚很多回归模型预测连续分值时更平滑KNN 分档后准确率更能反映业务上能不能区分好坏酒的需求。从那以后我每次做类似的数据分析作业都会强制走一遍留出验证 两类模型对比的流程哪怕课题组没要求也照做省下的返工时间远多于多写的那几行代码。希望帮到你。本文还有配套的精品资源点击获取