news 2026/10/17 17:31:56

红酒评分数据分析全流程:R回归与Python KNN实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
红酒评分数据分析全流程:R回归与Python KNN实战解析

简介:一份面向高校数据分析课程的完整大作业报告,以经典红酒品质数据集作为研究对象,系统展示从数据导入、清洗到统计建模与分类的全过程。文档首先介绍红葡萄酒成分与评分的背景,针对十一项物理属性(非挥发性酸度、挥发性酸度、柠檬酸、残糖、氯化物、游离二氧化硫、总二氧化硫、密度、酸碱度、硫酸盐、酒精)与感官评分的关联展开相关性分析,筛选出挥发性酸度、硫酸盐、酒精等关键影响因素;随后建立多元线性回归模型预测评分,并对模型进行置信区间与预测区间检验;为处理变量间多重共线性,进一步采用主成分回归提取前五个主成分,还原为原始变量表达式;最后使用K近邻算法完成红酒品质的简单分类。资源为DOCX格式,共1个文件,压缩包大小202KB,图文结合、附有R与Python代码片段,便于读者按步骤复现。目前已有4265人学习,非常适合正在完成数据分析大作业、撰写课程报告或入门统计建模和机器学习的学生参考。

1. 项目定位:一份能直接复现的红酒数据分析作业

前段时间整理旧硬盘,翻到一份数据分析大作业,主题是红酒数据集的分析。乍看起来就是一篇期末报告:用 R 做相关性分析和回归,用 Python 手写 KNN 分类。但顺着代码跑一遍发现,它把统计建模里最典型的几件事全串起来了——变量相关性分析、多元线性回归、主成分回归、距离分类。真正让我写这篇解析的反直觉结论是:红酒评分跟酸性高度相关,想拿高分,不是堆果香,而是控制挥发性酸、增加硫酸盐和酒精。而且这份材料并不追求漂亮结果,它的多元回归 R 方只有 0.6589,主成分回归的预测还会偏差到 7 分左右,但它把真实分析过程中“拟合得不够好”的状态完完整整保留了下来。对正在做数据分析作业、想复现统计建模完整流程的人来说,这比一个完美模型更有参考价值。

2. 技术栈与数据预处理:从 CSV 转到 xlsx 再进 R 的完整链路

2.1 数据集长什么样:11 个物理变量加一个评分

这份作业用的红酒数据来自公开数据集,原始格式是 csv,包含 1599 条记录。每条记录有 11 个物理属性,外加一个由品酒师给出的评分。评分区间理论上是从 0 到 10,实际数据集中在 3 到 8 分,其中 5 分和 6 分占了绝大多数。这个分布特点在后面直接影响了回归和 KNN 的准确度。

变量含义常见区间
x1非挥发性酸度7~15 g/L
x2挥发性酸度0.1~1.6 g/L
x3柠檬酸0~1.7 g/L
x4残糖0.9~15.5 g/L
x5氯化物0.01~0.6 g/L
x6游离二氧化硫1~72 mg/L
x7总二氧化硫6~289 mg/L
x8密度0.990~1.004 g/cm³
x9pH 值2.7~4.0
x10硫酸盐0.3~2.0 g/L
x11酒精8.4~14.9 %vol

这里有个容易忽略的点:x4 残糖、x6 游离二氧化硫、x9 pH 值跟评分的相关性很低,而 x2 挥发性酸度、x10 硫酸盐、x11 酒精的相关性明显更高。也就是说,真正影响品酒师打分的是挥发酸、硫酸盐和酒精这三个指标,而不是糖分和 pH。相关性分析的意义就在这里:先把与目标无关的变量剔掉,再做回归,能少踩很多共线性的坑。

2.2 Python 侧:把 csv 转成 xlsx 的两种写法

原始数据是 csv 格式,作业里先用了 Python 把它转成 xlsx,再交给 R 的 readxl 包读取。转格式不是炫技,是因为 R 处理 xlsx 在中文列名和单元格类型上更省事,而且 openpyxl 方案可以顺便检查数据读取是否正常。

import pandas as pd # 读取原始 CSV 文件 df = pd.read_csv("redwine.csv") print(df.shape) print(df["quality"].value_counts().sort_index()) # 转成 xlsx,index=False 避免多出无意义的行号列 df.to_excel("redwine.xlsx", index=False)

这段代码做了三件事:读取 csv、打印数据形状和评分分布、写出 xlsx。shape用于确认样本量,value_counts用来观察评分分布是否均匀。如果你手里是带分隔符的 txt 或制表符文件,只需要在read_csv里加sep="\t"。如果原始文件第一行不是表头,还要加header=None。

另一种写法是用 openpyxl 逐行写入,这种方式适合做单元格级别的清洗,比如把空值替换成 0,或者把某一列强制转成数值类型。pandas 适合整体转换,openpyxl 适合精细加工,两个方案在这个项目场景里都成立。

2.3 R 侧读取与 Spearman 相关性分析

数据准备好之后,作业用 R 读取 xlsx 并调用cor函数做相关性分析。评分本身是有序离散值,不是严格连续变量,所以这里用 Spearman 秩相关而不是 Pearson 相关,这一点在统计上更严谨。

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

sheet = 1指定读取第一个工作表,col_names = TRUE表示把第一行作为列名而不是数据。method = "spearman"指定使用 Spearman 相关。跑完矩阵后可以直观看到:x4、x6、x9 和评分的相关系数绝对值都很低,说明它们对打分的解释力很弱;x2 挥发性酸度与评分显著负相关,x10 硫酸盐、x11 酒精与评分正相关。此外,x1 非挥发性酸度与 x9 pH 值相关性很高,x11 酒精与 x8 密度有较强相关性,这说明原始变量之间本身就存在信息重叠,后面做回归时有必要做变量筛选或主成分压缩。

3. 核心建模拆解:多元回归与主成分回归的完整链路

3.1 先跑全变量回归,再筛选到三个核心变量

得到相关性矩阵后,第一步是把低相关变量剔掉,保留 x1、x2、x3、x5、x7、x8、x10、x11 做多元回归。这一步看起来很合理,但实际结果很典型:有部分参数的 p 值没有通过显著性检验。这说明“和评分相关”和“在回归方程里有贡献”是两回事,变量之间互相干扰。

# 第一次尝试:剔除低相关变量后做回归 lm.sol <- lm(quality ~ x1 + x2 + x3 + x5 + x7 + x8 + x10 + x11, data = df) summary(lm.sol) # 第二次尝试:只保留高相关变量 lm.sol2 <- lm(quality ~ x2 + x10 + x11, data = df) summary(lm.sol2)

第二次回归的参数全部通过检验,得到的方程是:

quality = -1.22130 * x2 + 0.67898 * x10 + 0.30920 * x11 + 2.61104

模型的 R 方为 0.6589,意思是这三个变量能解释评分约 65.89% 的变化。对一个受主观品鉴影响的数据集来说,这个值可以接受,但显然还有 34% 的波动来自没被建模的因素,比如品酒师个人偏好、年份差异。从系数方向看,x2 挥发性酸度每增加 1 个单位,评分平均下降 1.22 分;x10 硫酸盐和 x11 酒精每增加 1 个单位,评分分别上升 0.68 和 0.31 分。这也是“减少挥发酸、增加酒精能提高评分”结论的来源。

3.2 用回归方程预测新酒样:置信区间和预测区间

作业里留了一组数据没参与建模,专门用来验证回归方程的预测效果。这组数据的 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"给出的是单个新样本的取值范围。后者通常会比前者宽很多,因为它把残差波动也算进去了。预测结果和真实评分 6 比较接近,说明这个三元回归方程虽然 R 方不算高,但用于粗粒度预测是没问题的。实际使用中,我更推荐报告预测区间,因为它诚实地反映了模型的不确定性。

3.3 主成分回归:用五个主成分代替原始变量

多元回归的问题在于变量之间还有相关性,比如前面提到的酸度和 pH、酒精和密度。主成分回归的思路是把原始变量线性组合成互不相关的主成分,再拿主成分去做回归。作业里对 11 个变量做主成分分析,取前五个主成分保存下来。

wine <- read_excel("redwine.xlsx", sheet = 1, col_names = TRUE, range = "A1:K1559") wine.pr <- princomp(wine, cor = TRUE) summary(wine.pr, loadings = TRUE) pre <- predict(wine.pr) z1 <- pre[, 1] z2 <- pre[, 2] z3 <- pre[, 3] z4 <- pre[, 4] z5 <- pre[, 5] redd <- data.frame(quality = df$quality, z1, z2, z3, z4, z5) lm.solp <- lm(quality ~ z1 + z2 + z3 + z4 + z5, data = redd) summary(lm.solp)

princomp(cor = TRUE)表示在相关矩阵上做主成分,而不是协方差矩阵。这样做的原因是各变量量纲差异太大:二氧化硫的单位是毫克级,密度接近 1,酒精是百分比,如果不做标准化,主成分会被量纲大的变量主导。从输出结果看,前五个主成分基本覆盖了数据的主要信息。原报告里给出第一个主成分的近似表达式:

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

这个表达式其实就是特征向量乘以对应变量,含义是:z1 综合了酸度、柠檬酸和 pH 的信息,代表葡萄酒的“酸度结构”维度。用 z1 到 z5 去回归评分,参数基本能通过显著性检验,拟合效果和之前直接回归 x1~x11 差不多,但模型结构更干净。

3.4 把主成分系数还原成 x1~x11 的原始系数

主成分回归的问题在于,最后得到的方程是quality ~ z1 + ... + z5,无法直接看到 x2、x10、x11 各自的系数。作业最后一步做了换算:通过相关矩阵的特征向量、主成分回归系数、原始变量的均值和标准差,把主成分系数还原成原始变量系数。

XX <- cor(wine) A <- eigen(XX)$vectors beta <- coef(lm.solp) # 原始变量的均值与标准差,从数据中计算得到 X.bar <- c(8.3623, 0.5273, 0.2719, 2.5371, 0.0878, 15.7054, 46.4037, 0.9968, 3.309, 0.6583, 10.4114) X.sd <- c(1.7415, 0.1791, 0.1947, 1.4103, 0.0471, 10.4627, 32.9041, 0.00189, 0.154, 0.1695, 1.0657) 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)

这里要特别注意:原作业代码的coef只把系数乘到了A[,4],也就是漏掉了第五个主成分的贡献。严格来说,主成分回归还原时应该把所有用到的主成分都加进去,也就是beta[2] * A[,1] + ... + beta[6] * A[,5]。这个细节我会放到避坑章节专门展开,因为很多人照着跑会发现还原后的系数符号和直觉不符,多半就是这个原因。

4. KNN 分类实战:用距离投票给新红酒打分

4.1 KNN 三要素与特征选择

KNN 做分类的核心是三要素:k 值、距离度量、分类决策规则。作业里选择 x2 挥发性酸度、x5 氯化物、x7 总二氧化硫、x10 硫酸盐、x11 酒精五个属性作为距离度量的维度,也就是把每个红酒样本映射成一个五维空间里的点。对新数据计算它与所有已知样本的欧氏距离,取距离最近的 k 个点,用这 k 个点中出现次数最多的评分作为预测结果。

这里没有直接调用 sklearn,而是手写了一个简化版 KNN。手写代码的教学价值在于能看清距离计算和投票机制,而且方便调整距离函数。常见的距离度量包括欧氏距离、曼哈顿距离、余弦距离,作业里用的是欧氏距离。

4.2 手写 KNN 的 Python 实现

原作业的 Python 代码用 openpyxl 读取 xlsx,然后自己实现距离计算和投票。我整理后的版本保留了原始思路,只是把函数拆得更清晰,并允许通过参数控制 k 值。

import openpyxl def read_excel(input_file_name): """从 xlsx 读取数据,返回每个样本的特征和评分""" objects = [] workbook = openpyxl.load_workbook(input_file_name) table = workbook['Sheet1'] rows = table.max_row cols = table.max_column for row in range(1, rows): obj = [] for col in range(cols): # 选取 x2, x5, x7, x10, x11 这五个特征 if col in (1, 4, 6, 9, 10): data = table.cell(row + 1, col + 1).value obj.append(data) obj.append(table.cell(row + 1, 12).value) # quality 评分 objects.append(obj) return objects def knndis(nowa, objects): """计算新样本与所有已知样本的欧氏距离""" distances = [] for item in objects: distance = sum((item[j] - nowa[j]) ** 2 for j in range(5)) ** 0.5 distances.append(distance) return distances def classify(distances, objects, k=20): """取距离最近的 k 个样本,按评分多数表决""" d = {} tmp = distances[:] for _ in range(k): idx = tmp.index(min(tmp)) label = objects[idx][5] d[label] = d.get(label, 0) + 1 tmp[idx] = float('inf') # 已访问的样本距离置为无穷大 return max(d, key=d.get)

read_excel里的range(1, rows)是从第 2 行开始读取,因为第一行是列名;if col in (1, 4, 6, 9, 10)是 openpyxl 的 0 基索引,分别对应 Excel 里的第 2、5、7、10、11 列。classify函数里把已选中的最近邻距离改成无穷大,相当于“排除已选”,这种写法直观但效率不高;样本量到万级时,建议用heapq.nsmallest替代。

4.3 测试效果分析:准确率不高是数据本身的问题

作业里对这个 KNN 的测试结论很直白:算法准确度不高。原因有三个。第一,评分 5 和 6 占了数据的大部分,即使随机猜,猜中 5 或 6 的概率也不低,但想精确猜出 4、7、8 这类少数评分很难。第二,选出的五个属性与评分的相关性并不都很高,比如 x5 氯化物的 Spearman 相关系数并不突出,距离计算时贡献了大量噪声。第三,未做标准化处理,x7 总二氧化硫的取值范围是 6~289,x11 酒精才 8~15,在欧氏距离里二氧化硫会主导距离,相当于无形中给这个变量加了更高的权重。

不过有一个细节值得注意:如果只看距离测试点最近的那一个点,准确度反而还可以。这说明红酒评分存在一定的局部相似性,只是用多数投票后,误差被中间分数稀释了。改进方向也很清楚:对特征做 z-score 标准化,或者用加权距离,提高相关性强变量的权重。

5. 避坑排查:做数据分析大作业最容易翻车的五个地方

5.1 读 Excel 时把表头当成了数据

现象:R 读入数据后,cor函数报错,提示变量不是 numeric;或者相关性矩阵里出现一列都是字符串的“变量”。

原因:read_excel在col_names = TRUE时会把第一行作为列名,但如果数据文件里第一行本身就是数据,或者表头有合并单元格,就会把列名读成样本,导致后续计算失败。

解决:先读一行看head(df),再检查str(df)。如果发现第一行被当成数据,重新用col_names = FALSE读取,并手动指定列名。这个坑在做任何真实数据集时几乎都会遇到一次。

5.2 相关性矩阵出现 NA 或直接报错

现象:cor(df, method = "spearman")返回的矩阵里有很多 NA,或者直接报'x' must be numeric。

原因:数据里混入了文本列,比如红酒产地、编号,或者某些单元格是空值。Spearman 相关对秩计算比较敏感,缺失值会导致结果变成 NA。

解决:先df <- df[, sapply(df, is.numeric)]只保留数值列,再用na.omit(df)去掉缺失行。如果不想丢数据,可以用cor(df, method = "spearman", use = "complete.obs")让 R 自动按行剔除缺失值。

5.3 回归系数不显著还硬解释

现象:summary(lm.sol)里某个变量的Pr(>|t|)大于 0.05,但还是把它留在模型里,并且解释成“有一定影响”。

原因:变量与评分本身相关,但放进回归方程后它的解释方差被其他变量吸收了;或者变量之间存在共线性,导致回归系数不稳定。

解决:先做相关性分析,筛掉与目标变量相关系数过低的变量,再逐步回归。作业里从 8 个变量缩到 3 个,所有参数才通过检验。硬保留不显著变量会导致模型解释力虚高,换一组数据马上就翻车。

5.4 预测区间宽到怀疑人生

现象:用predict(..., interval = "prediction")得到的新样本预测区间是 3 到 9,几乎覆盖了整个评分范围。

原因:模型 R 方只有 0.6589,残差标准差大;预测区间本身就把残差波动加进去了,所以会比置信区间宽很多。

解决:如果目的是说明模型能判断“好酒还是差酒”,报告置信区间就够了,它反映的是均值估计的精确度。如果想报告单个新样本的预测区间,就需要接受这个模型只能做粗粒度排序,不能精确打分。

5.5 主成分回归还原系数时漏掉一个主成分

现象:主成分回归的参数都显著,但还原成原始变量系数后,x2 挥发性酸度的系数竟然变成了正数,和单变量回归的方向相反。

原因:原作业在还原公式里只用了前四个主成分的特征向量,漏掉了beta[6] * A[,5]这一项。A[,5]代表第五主成分在原始变量上的载荷,丢掉后会让系数失真。

解决:把还原公式改成完整加权和:

coef <- (beta[2] * A[, 1] + beta[3] * A[, 2] + beta[4] * A[, 3] + beta[5] * A[, 4] + beta[6] * A[, 5]) / X.sd

写完这个坑之后,我养成了一个习惯:凡是公式转换,一定要把维度对齐检查一遍,主成分个数要和回归系数个数严格对应。

6. 二次开发:把一次性分析改造成可复用的红酒评分工具

如果要把这份作业改造成能反复使用的工具,核心不是把代码写得更花哨,而是把“读取、特征筛选、建模、预测”四个环节拆成独立函数,并把硬编码的列索引替换成列名。

def load_and_prepare(file_path): df = pd.read_excel(file_path) features = ["volatile_acidity", "sulphates", "alcohol"] X = df[features].values y = df["quality"].values return X, y

这是最小改造,把数据集结构变化和建模逻辑解耦。第一步先验证:用留出法把数据按 7:3 切分,在训练集上重新拟合三元回归,在测试集上计算均方误差和 R 方。这样能真实评估这套方法在新样本上的表现,而不是只靠一组手挑数据验证。

第二步是加上标准化。原作业在 KNN 里没做标准化,导致总二氧化硫主导距离;如果改成 sklearn 的StandardScaler,距离度量会更均衡。回归部分也可以对比一下“标准化后的系数”和“原始系数”,确认方向是否一致。

第三步是把模型持久化。joblib.dump(lm_sol2, "wine_model.pkl")保存训练好的方程,下次遇到新红酒数据,直接调用predict算出预测评分区间,不用再重跑一遍相关性分析。

从那以后我每次跑统计项目,都会强制自己走一遍“先看分布、再筛变量、再建模、最后验证”的流程,而且验证一定用留出法而不是全部数据。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 17:28:29

AI管理体系ISO 42001:从HLS骨架到风险双轨落地

简介&#xff1a;ISO/IEC DIS 42001:2022《信息技术 人工智能 管理体系》是国际标准化组织与国际电工委员会联合发布的人工智能管理体系国际标准草案&#xff0c;面向AI治理、合规与标准化从业者&#xff0c;也适合企业AI项目负责人及高校相关专业师生。草案围绕组织责任、风险…

作者头像 李华
网站建设 2026/10/17 17:26:32

Transformer模型详解:从注意力公式到能跑通的极简实现

简介&#xff1a;这份《Transformer 模型详解》PDF 面向自然语言处理初学者、深度学习入门者及需要系统梳理注意力机制的算法工程师&#xff0c;帮助读者从零理解 Transformer 的整体架构与核心原理。资源为单个 PDF 文件&#xff0c;压缩包约 2.01MB&#xff0c;内容以图文结合…

作者头像 李华
网站建设 2026/10/17 17:22:28

逆向基础:文件读写与二进制数据生命周期训练

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 17:18:46

MySQL事务详解:从ACID到JDBC实操,避开事务失效的坑

1. 先从一次转账说起&#xff1a;事务到底是什么1.1 一个让人后怕的线上场景我之前在某公司的交易系统里排查过一个悬案&#xff1a;用户发起提现&#xff0c;后台一查&#xff0c;钱扣了&#xff0c;但账户余额增加了&#xff0c;两边对不上。一开始怀疑是并发扣款&#xff0c…

作者头像 李华