☰
从Spearman相关到KNN:红酒评分建模实战解析
2026/10/17 16:16:09 网站建设 项目流程

简介:一份以红酒数据集为对象的数据分析大作业完整报告,定位清晰,适合统计学、数据科学方向的初学者作为课程作业模板或自学案例。报告系统梳理了从原始数据到结论的完整流程:先介绍红酒评分的背景,再对11项物理属性进行相关性分析,筛选出挥发性酸度、硫酸盐、酒精等关键影响变量;随后建立多元线性回归模型并解释参数与R方,接着引入主成分回归解决变量相关性问题,并展示置信区间与预测区间;最后利用KNN算法对红酒品质进行简单分类。资源包内含1个docx文档,压缩后大小约202KB,类型单一且便于下载。文档附有R与Python的主要操作代码及结果截图,可帮助读者快速复现分析,理解统计建模在真实数据集上的应用。这份成果已有4265人浏览学习,既是一份完整的作业范例,也适合作为数据分析报告写作的参考。

1. 红酒评分:11 个理化指标里真正说话的只有三四个

一份含 11 项理化指标、评分范围 0 到 10 的红酒数据集摆在你面前,直觉反应是先找哪个指标和评分关系最“像样”。结果却反常识:真正和评分显著相关的只有挥发性酸度、硫酸盐、酒精这三项,而 pH、残糖这些听起来很重要的指标,斯皮尔曼相关性低到可以忽略。更反常识的是,把不相关的变量丢掉之后,拟合效果没有变差,反而让所有回归系数都通过了显著性检验。

这个数据集的价值不在于“预测出 100% 准确的评分”,而在于展示了一整条数据分析的链路:相关性分析、特征筛选、多元回归、主成分回归、KNN 分类,每一步环环相扣。搞懂这条链路的关键,是弄清相关系数到底替我们做了哪些判断、这些判断为什么成立又为什么可能翻车,以及最后那个 R² = 0.6589 的模型到底算不算合格。下面从数据进入软件的第一刻开始拆。

2. 相关性分析:为什么 Spearman 一上来就替我们做了降维决定

2.1 从 csv 到 xlsx,再进 R:数据形态决定了分析工具链

原始数据是 csv 格式,先用 Python 的 openpyxl 转成 xlsx,再用 R 读取。这一步看着像流水账,但有个细节值得记录:转换时保留全部 1599 行样本,R 端用read_excel("redwine.xlsx", sheet = 1, col_names = TRUE)读取时,col_names = TRUE表示第一行作为变量名,这决定了后续cor(df)是对列中所有数值型变量直接做相关计算。

代码本身很简单:

install.packages("readxl") library(readxl) df <- read_excel("redwine.xlsx", sheet = 1, col_names = TRUE) cor(df, method = "spearman")

一个容易被忽略的点:cor()默认是 Pearson 相关系数,而这里显式指定了method = "spearman"。两者对数据分布的假设完全不同。Pearson 衡量的是线性关系,对离群点极度敏感,要求变量近似正态;Spearman 则先把两列数据各自排序成秩次,再对秩次做相关计算,捕捉的是单调关系。红酒评分是 3 到 8 的离散整数,大量样本挤在 5 分和 6 分两档,这种分布完全不该假设正态,所以 Spearman 的选择在机制上是合理的。

2.2 相关矩阵读出的关键信号:三组高相关,六组可忽略

用 Spearman 计算后,11 个变量两两之间的相关结构大致可以分成三个层次。直接看评分列时,残糖、游离二氧化硫、pH 这三项的相关系数绝对值非常低,基本在 0.05 以下,属于“沾边但说不出规律”的水平;非挥发性酸度、柠檬酸、氯化物、总二氧化硫、密度的相关性居中;真正挤进高相关区间的只有三项:挥发性酸度呈现明显负相关,硫酸盐和酒精呈现正相关。

变量与变量之间也有可解释的结构。pH 与非挥发性酸度的相关性很高,原因是红酒中非挥发性酸的含量基数大,直接缓冲了 pH 的波动;酒精和密度也有相关性。从化学背景出发这些都能自圆其说,但从建模角度,这里埋下了多重共线性的伏笔——后面线性回归的“翻车”和它直接相关。

相关系数的判断标准要有个度,经验划分大致如下:

相关系数绝对值解释强度本次数据集中的代表变量
0.5 以上强相关,优先保留酒精与评分、挥发性酸度与评分
0.2 到 0.5中等相关,可选硫酸盐与评分
0.2 以下弱相关,可降维丢弃残糖、pH、游离二氧化硫

2.3 相关性驱动的降维:省的是维度,欠的是条件

原始分析把弱相关的 x4 残糖、x6 游离二氧化硫、x9 pH 直接丢弃,后面回归只用剩余变量。这个操作本质上是“过滤式特征选择”,优势是计算量小、可解释性强。但它有一个容易被忽视的副作用:在同一个数据集上,先用相关性筛特征再做回归,会让估计的置信区间和检验 p 值略微偏乐观,因为你事实上利用了数据中的信息来选择模型,相当于给模型“开了一次卷”。

这也是为什么后续每个模型的系数标准差都值得多看几眼——如果在相关性筛掉变量之后,剩余变量的 VIF(方差膨胀因子)仍然高,那问题不是筛选本身,而是原始变量结构里本就存在强关联。

3. 多元线性回归:先被全变量模型打脸,再被三变量模型救回

3.1 全变量回归为什么“部分参数没通过检验”

去除弱相关变量后,拟合的是:

lm.sol <- lm(q ~ x1 + x2 + x3 + x5 + x7 + x8 + x10 + x11, data = df) summary(lm.sol)

这里的 x1 到 x11 分别代表 11 项理化指标,q 是评分。从统计机制上看,回归系数检验的原假设是“该系数为零”,t 统计量等于系数估计值除以标准误。标准误大,说明该系数的估计不稳定,而标准误膨胀的常见来源正是自变量之间高度相关。

x1 非挥发性酸度和 x9 pH 高度相关、x11 酒精和 x8 密度高度相关,这两组共线性足以让最小二乘的解在变量空间中变得非常敏感。结果是:模型整体的 R² 勉强能看,但单个系数的置信区间宽到没有实际解释价值。“部分参数没有通过检验”翻译成人话就是:你没法确定这个变量的真实效应是正还是负,也不知道效应有多强。

3.2 三变量模型:系数全通过,但 R² = 0.6589 说明了什么

删掉所有“中间相关”的变量,只保留相关性最强的 x2、x10、x11 后,模型变为:

lm.sol2 <- lm(q ~ x2 + x10 + x11, data = df) summary(lm.sol2)

得到的回归方程:

p = -1.22130 × x2 + 0.67898 × x10 + 0.30920 × x11 + 2.61104

三个系数的符号符合领域常识:挥发性酸度高会使酒闻起来像醋或洗甲水,所以系数为负;硫酸盐承担抗氧化和保鲜功能,酒精支撑酒体和香气,两者系数为正。参数全部通过检验,说明在引入这三个变量后,每个系数的估计标准误都收缩到了可以接受的范围。

这时候出现了一个判断节点:R² = 0.6589,意味着模型解释了评分中约 65.9% 的方差,剩余 34% 的波动归结于未纳入模型的变量和测量噪声。在感官评分的研究里,这个水平不丢人,但也谈不上精确。对这份数据集来说,评分是离散整数且集中分布在 5 到 6 分之间,评分的方差本身就不大,一个只靠三项指标的线性模型能吃掉六成多方差,说明这三个指标确实摸到了评分的主动脉。

3.3 置信区间和预测区间:同一个预测,两个完全不同的回答

数据读取时特意留出一组未知数据用于验证:x2 = 0.31、x10 = 0.66、x11 = 11,真实评分是 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")

interval = "confidence"给出的是回归均值处的置信区间,它只反映“如果反复抽样,样本均值会落在哪个范围”,区间宽度取决于样本量和参数估计精度;interval = "prediction"给出的是新单点预测区间,它额外叠加了残差的随机波动,所以永远比置信区间宽。很多初学者在这两个区间上踩坑:把窄的置信区间当成对新酒评分的预测范围,这是错的;预测单瓶红酒的评分,必须看 prediction 区间。

这份数据的预测结果与真实评分基本吻合,说明三变量模型在局部预测上有实用价值,但也只证明了“靠近均值处的预测靠谱”,越靠近分布边缘,两个区间都会显著变宽。

4. 主成分回归:换一组坐标轴,能否救回被噪声掩盖的拟合

4.1 主成分分析的机制:从相关矩阵出发的坐标旋转

主成分分析的本质不是选变量,而是把 11 个原始变量重新组合成一组互不相关的新变量,这些新变量按方差贡献逐级递减。这份数据集里用princomp进行主成分提取:

wine.pr <- princomp(wine, cor = TRUE) summary(wine.pr, loadings = TRUE)

cor = TRUE非常关键:它说明主成分是从相关矩阵出发计算的,等价于先把所有变量标准化再求主成分。为什么要标准化?因为原始变量的量纲不统一,密度只在 0.99 附近波动,而总二氧化硫能到几百,如果不标准化,第一主成分会被量纲大的变量绑架,所谓“降维”结果将失去化学意义。

取前五个主成分后,第一主成分的表达式大致为:

z1 = 0.49x1 - 0.239x2 + 0.466x3 + 0.149x4 + 0.211x5 + 0.394x8 - 0.437x9 + 0.246x10 - 0.10x11

看到 9 个变量进入同一个主成分,能立刻明白一个信息:原始变量之间不是独立的,这个主成分实质上在捕捉“多种理化指标同时变化的整体状态”。它已经不是某个化学意义上的单一指标,而是一条合成的综合曲线。

4.2 从主成分系数反解原始变量系数

主成分回归的流程是先计算每个样本的前五个主成分得分,再对评分做线性回归:

pre <- predict(wine.pr) z1 <- pre[, 1]; z2 <- pre[, 2]; z3 <- pre[, 3]; z4 <- pre[, 4]; z5 <- pre[, 5] redd <- data.frame(q = df$q, z1, z2, z3, z4, z5) lm.solp <- lm(q ~ z1 + z2 + z3 + z4 + z5, data = redd) summary(lm.solp)

问题来了:主成分回归给出的系数是针对 z1 到 z5 的,但实际问题里人们想知道 x1 到 x11 怎么影响评分。要把系数还原回原始变量尺度,需通过特征向量矩阵:

XX <- cor(wine) A <- eigen(XX)$vectors beta <- coef(lm.solp) coef <- (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) c(beta0, coef)

这里每一步的机制要拆开看。回归系数 beta[2] 到 beta[5] 是评分对标准化主成分的敏感性;A 矩阵的每一列是一个特征向量,代表主成分在原始变量空间里的方向;把 beta 和对应特征向量做加权求和,本质上是把“合成变量层面”的斜率映射回“原始变量层面”。除以 X.sd 是因为主成分从标准化的相关矩阵出发,而我们要回到原始单位;beta0 的修正项则是把截距从标准化空间平移回原始空间。

这套反解操作是主成分回归里最容易被当成黑匣子的部分。很多实现直接跳过反解,只报告主成分回归的 R²,结果是模型能跑,但业务上一个字也解释不了。反解之后系数符号大概率会与普通多元回归的结果有出入,这不是谁错了,而是两者本来就在不同的变量坐标系里说话。

4.3 主成分回归何时真正优于普通回归

这份数据中,主成分回归的验证效果并没有比三变量模型更漂亮,用一组保留样本预测时甚至出现了一定偏差。为什么?因为主成分回归优化的目标是“尽可能多地保留自变量方差”,而不是“尽可能与评分紧密关联”。前五个主成分确实携带了原始数据的大部分信息,但这些信息当中有一部分和评分毫无关系;把这段无关方差纳入回归,反而稀释了模型的预测力。

主成分回归真正的用武之地是多重共线性极其严重的场景:如果两个变量相关系数超过 0.9,普通最小二乘的系数估计会剧烈震荡,此时主成分回归能把共线结构吸收进同一主成分里。在这份红酒数据里,共线性属于“中等严重”,直接砍掉几个变量已经能解决问题,所以主成分回归带来的收益有限,更像是对线性模型的交叉验证。

5. 避坑记录:KNN 的分类准确率为什么上不去,问题并不在 KNN

5.1 KNN 的机制和这份实现里的取舍

K 近邻算法的机制只有三步:选距离度量,选 k 值,定多数表决规则。这份实现里距离定义为五项属性(x2、x5、x7、x10、x11)的欧氏距离,五个属性共同决定“某一瓶酒离另一瓶酒有多远”。就近取 20 个邻居,多数表决得到预测评分。

def knndis(nowa, objects): distances = [] for i in range(len(objects)): distance = 0 for j in range(5): distance += (objects[i][j] - nowa[j]) ** 2 distances.append(distance ** 0.5) return distances def classify(distances, objects): d = {} for i in range(30): item = objects[distances.index(min(distances))][5] d[item] = d.get(item, 0) + 1 distances[distances.index(min(distances))] = 100 return d

这段代码在机制上值得指出两点。第一,distances.index(min(distances))每次从头查找最小距离,再用一个魔法数100标记已选过的点,这种方法在样本量小的时候没问题,但样本量一大性能就会崩,属于只适合教学演示的写法。第二,classify 函数里虽然写了 k = 30 个候选,真正进入投票的是前 20 个,因为这 30 是留出的一小批测试样本,不是近邻数;近邻数由第一个循环控制。这种变量命名混乱在真实项目里很常见,是代码可维护性的隐形坑。

5.2 踩坑一:分类准确度上不去,先查类别分布而不是调 k

现象:测试时 KNN 的整体准确率不高,看起来像模型失效。

原因:评分集中在 5 分和 6 分,两类样本合计占到数据集的大头。任何一个把所有新样本都判成 5 分的“无脑分类器”,准确率也不会过低。KNN 在这个分布上很容易把边界附近的 5 分、6 分互相分错,表现得“平庸”是数据结构决定的。

解决:不看总体准确率,改看混淆矩阵。如果 5 分和 6 分之间的错分占了大部分,说明模型真正的问题是在相邻分值上无法区分;如果连 3 分和 8 分都混在一起,那才是特征选择或距离度量出了问题。

5.3 踩坑二:把置信区间当成预测区间用

现象:用三变量模型对保留数据预测时,置信区间看起来窄,给人“预测很精确”的错觉,但换到 prediction 区间后宽度显著增大。

原因:置信区间只刻画均值估计的不确定性,而预测区间要额外叠加残差方差;数据噪声越大,两个区间的差距越大。

解决:凡是面向单瓶新酒的预测场景,只看 prediction 区间。只有面向评分均值这类群体推断时,confidence 区间才有意义。

5.4 踩坑三:主成分回归反解后系数符号“不科学”

现象:反解后的原始变量系数里,某个在化学意义上应该为正的变量符号变成了负,导致不敢对外解释。

原因:主成分是多个变量的线性组合,它的载荷方向本来就是任意翻转的,符号本身不固定;更关键的是,主成分回归把高相关的变量绑定在一起后,单一变量的单独效应被分散到多个主成分里,反解出的系数在某些分量上可能完全抵消。这个问题从数学上无解,是主成分的可解释性代价。

解决:不要强行解释主成分回归的单个变量系数,报告主成分方向和载荷,在业务层面直接说“合成指标对评分有正向作用”。

5.5 踩坑四:只看 KNN 最近点准确率高,就想吹整体效果

现象:如果只看距离测试点最近的那个点的评分,准确度看起来还可以,于是误以为整个分类器表现优秀。

原因:这是典型的幸存者偏差。最近邻点天然有更高概率落在密集的 5 分 6 分区,而 KNN 的多数表决是把 20 个近邻一起算,边界上的分歧会被放大。

解决:用分层抽样的方式验证,在 4 分以下、7 分以上的样本里单独看 KNN 的表现,那才是这项特征工程的真正试金石。

6. 回到工程:R² = 0.6589 到底算不算一个能交代的结果

现在回到那个整个项目里最值得反复咀嚼的问题:0.6589 的 R² 够不够用。评判标准要看数据本身能提供多少信息量。红酒评分是离散整数,大量样本挤在 5 分 6 分两档,评分的总方差本来就不大。在这种情况下,三个物理指标就能解释六成五的方差,相当于模型已经抓住了评委判断酒的主要依据。拿这个模型去做精确的单瓶酒评分预测,肯定会被预测区间打脸;但拿它去指导“降低挥发酸、适当控制硫添加量、保证发酵得到的酒精度”,方向是站得住的。

要验证这个结论靠不靠谱,有一个比 R² 更硬的思路:零模型对比。先用一个只预测平均分的常量模型算出基准误差,再用三变量模型算出同样的误差,如果三变量模型没有在每一折交叉验证中稳定压过零模型,那 R² 再高也只是过拟合的幻觉。实践里我会把回归模型、主成分回归和 KNN 三者放在同一份测试集上,比较它们在评分 3 到 4 分段和 7 到 8 分段的误差差异,这才是真正暴露模型短板的地方。

这套分析链条里的每个环节,本质上都是一种取舍:Spearman 换取了单调关系却丢掉了精确的线性尺度;三变量回归换取了全部系数通过检验却牺牲了解释方差的上限;主成分回归换取了稳定却失去了单个变量的解释力;KNN 换取了非线性容错却败在了类别不平衡上。没有一种方法同时赢下所有维度。从那以后,我再拿到类似数据集,都会强制先跑一遍“零模型基线 + 相关性矩阵 + 类别分布统计”,用这三板斧把数据的天花板摸清楚,再决定要不要上复杂模型。这比直接拿算法怼上去要安心得多,希望帮到你。

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

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

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

立即咨询