简介:一份完整的红酒数据集分析大作业方案,面向需要完成数据分析课程设计或统计建模练习的高校学生。资源包内含一份docx格式报告,共1个文件,压缩包整体仅202KB,内容覆盖从数据预处理到建模分类的全流程。报告以Python将csv数据转换为xlsx,再利用R完成变量相关性分析,识别出挥发酸度、硫酸盐、酒精等对评分影响较大的因素;随后对剩余变量进行多元线性回归,得到评分预测方程,并利用主成分回归优化模型;最后通过KNN对红酒进行简单分类。文中还给出了数据定义、11项物理属性与评分的关系、各变量标准差与均值、置信区间和预测区间的完整输出,便于读者对照复现并理解统计建模思路。已有4265人学习下载,适合作为数据分析课程设计、毕业设计或自学案例的参考模板。
1. 这份红酒数据集分析方案,解决的是“拿到数据不知道从哪下手”
很多同学做数据分析大作业,第一步就卡住了:数据集下载下来了,Excel 打开几百行数据,看着 11 个物理属性加一个评分,不知道先做什么。这篇文章就是一套可以直接照搬的红酒数据集分析方案——它从“评分到底受什么影响”这个实际问题出发,带你走完相关性分析、多元线性回归、主成分回归、KNN 分类的完整链路。方案适用于统计学或数据分析课程的课程设计、毕业设计里的数据分析部分,也适合刚接触 R 和 Python 的初学者照着复现。核心逻辑是:先通过相关性分析筛掉无关变量,再用回归模型解释评分与属性之间的定量关系,最后用 KNN 做分类验证,形成“解释+预测”的完整体验。
数据是公开的红酒数据集,1598 条样本,每条包含 11 个理化指标和 1 个品酒师评分(0-10 分)。这个项目的难点不在于算法有多复杂,而在于:评分是人的主观感受,物理属性与它之间的关系并不线性,怎么在拟合度不够高的情况下仍然得出可信结论,才是这份方案真正的价值。
2. 探索性数据分析:相关性矩阵、变量筛选与降维逻辑
2.1 数据形态与读取:从 csv 到 xlsx 的取舍
原始数据以 csv 格式存储,csv 的好处是体积小、通用性强,但用 R 读取时经常遇到编码问题,尤其是中文表头。我一般会先用 Python 的 openpyxl 库把 csv 转成 xlsx 格式再交给 R 处理,这一步看着多余,实际能省掉后面读数据的一堆麻烦。
读入时有一个细节容易忽略:数据文件第一行是列名,包括 x1 到 x11 这 11 个属性列,最后一列是评分 q,变量名是英文缩写,没有中文编码问题。如果要自己复现,建议直接用 read_excel 指定 sheet 和列名,代码很简短:
install.packages("readxl") library(readxl) df <- read_excel("redwine.xlsx", sheet = 1, col_names = TRUE)这里 col_names 参数指定第一行作为列名,sheet 参数选择工作表。读取后先看一眼数据分布,这个数据集的评分集中在 5 分和 6 分之间,3 分和 8 分是极少数,意味着后续不管是回归还是分类都要面对“数据不平衡”的问题,这个后面 KNN 部分会专门踩到。
2.2 相关性分析:用 Spearman 而不是 Pearson
相关性分析是这个项目的第一步筛选手段。用 R 的 cor 函数,需要指定方法。我建议用 spearman 而不是默认的 pearson。原因是评分数据是离散的整数(3 到 8),不是连续正态分布,Pearson 相关系数对线性关系敏感,但对单调关系不敏感,而 Spearman 是秩相关系数,对分布形态更鲁棒。
cor(df, method = "spearman")输出是一张 12x12 的相关性矩阵。读这张矩阵有个技巧:先看最后一列(评分 q 与其他变量的相关系数),绝对值大于 0.3 的算中等相关,小于 0.1 的可以直接列为候选删除变量。从结果可以看到,x4(残糖)、x6(游离二氧化硫)、x9(pH 值)与评分的相关系数都很低,这三个变量对评分几乎没有解释力,可以丢;x2(挥发性酸度)是负相关且绝对值最高,x10(硫酸盐)、x11(酒精)是正相关最高的两个,这三个变量是后续建模的主力。
变量之间也存在明显的相关性结构,这给后面的回归埋了一个坑。比如 pH 与非挥发性酸的相关性很高,因为红酒中的非挥发性酸直接决定 pH 水平;酒精含量和密度是负相关,因为酒精的密度比水低。这说明 11 个变量之间不是独立的,直接全部扔进回归模型会产生多重共线性,这也就是为什么后面需要主成分回归来处理。
2.3 变量筛选策略:相关性与业务逻辑结合
相关性的数值判断还不够,还要结合业务逻辑。比如残糖和甜度相关,但干红葡萄酒本身残糖就很低,对评分的影响自然很弱;游离二氧化硫主要是防腐保鲜用的,在合理范围内消费者感知不到。这两个变量数值上相关性低、业务上也解释不通,删除后不会有信息损失。
但有一类变量要小心:相关性不高不代表没有作用。比如 pH 值和评分的相关性不高,可能只是因为它与挥发性酸度的信息重叠了,并不是真的无关。这时候如果只按相关性筛变量,会丢掉潜在的解释变量。更稳妥的做法是:第一轮用相关性分析筛掉明显无关的变量,第二轮把剩余变量全部放进回归模型,再看哪些参数不显著、哪些方差膨胀因子过大。这个项目里就是这么做的,先删掉残糖、游离二氧化硫、pH,剩下 8 个变量做回归,结果有部分参数没通过 t 检验,再往下精简。
3. 回归建模:多元线性回归、主成分回归与系数还原
3.1 多元线性回归:从全模型到精简模型
回归部分是整个分析的核心环节。第一步把筛选后的 8 个变量放进模型,用 R 的 lm 函数拟合。这里有个关键操作:summary 输出里看每个系数的 Pr 值。实际操作中,模型里总有几个变量的 p 值大于 0.05,说明这些变量在“控制其他变量不变”的条件下,对评分没有显著的独立贡献。
lm.sol <- lm(q ~ x1 + x2 + x3 + x5 + x7 + x8 + x10 + x11, data = df) summary(lm.sol)一个常见的误区是:既然 p 值不显著就把变量删掉,只要整体 R 方提高就行。但这里更合理的逻辑是——先把相关系数最高的三个变量单独建模,看看拟合度能到什么水平。结果如下:
lm.sol2 <- lm(q ~ x2 + x10 + x11, data = df) summary(lm.sol2)三个变量的 p 值全部通过检验,R 方为 0.6589。说实话,这个值对“主观评分”类数据来说不算差,品酒师打分本身就有很强的随机性,能解释 65.89% 的方差已经是一个可观的结果。回归方程是:
p = -1.22130 x2 + 0.67898 x10 + 0.30920 x11 + 2.61104
系数的符号非常有业务含义:x2 挥发性酸度系数为负,意味着挥发酸越高评分越低——挥发酸含量高时酒会呈现醋味或洗甲水味;x10 硫酸盐和 x11 酒精系数为正,说明酒精度高、含适量硫酸盐的酒更受品酒师青睐。拿出一个样本实测:x2=0.31,x10=0.66,x11=11,实际评分 6,模型预测结果与原数据基本吻合。
3.2 预测区间与置信区间:回归结果怎么解释才有说服力
用 predict 函数对保留的那组数据做预测时,输出里有两个区间值得认真解释。置信区间表示真实的平均评分落在什么范围;预测区间表示一个单一样本的评分会落在什么范围。预测区间一定比置信区间宽,因为单个样本的波动除了模型误差,还包括个体差异。
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")很多人在答辩时只报一个点预测值,这其实是把自己的软肋露出来。正确做法是强调区间的意义:比如预测评分是 6,置信区间是 5.2 到 6.8,预测区间是 4.8 到 7.2。品酒师评分这么主观的东西,能给出区间而不是单点,说服力强得多。
3.3 主成分回归:处理多重共线性的标准解法
多元线性回归还有一个隐患:变量之间的相关性可能造成多重共线性,导致系数估计不稳定。主成分回归是对治多重共线性的经典方法。princomp 做主成分分析时,注意 cor=TRUE 参数,表示将变量标准化后再做主成分,否则量纲大的变量(比如总二氧化硫的数值在几百,pH 才三点几)会主导主成分的提取。
wine.pr <- princomp(wine, cor = TRUE) summary(wine.pr, loadings = TRUE)提取前五个主成分,累计贡献率会达到 85% 以上,然后用这五个主成分对评分做回归:
pre <- predict(wine.pr) z1 <- pre[, 1] z2 <- pre[, 2] z3 <- pre[, 3] z4 <- pre[, 4] z5 <- pre[, 5] redd <- data.frame(q = df[12], z1, z2, z3, z4, z5) lm.solp <- lm(q ~ z1 + z2 + z3 + z4 + z5, data = redd) summary(lm.solp)这里所有系数基本都能通过检验,主成分回归的优越性就在这里:它把相关的原始变量组合成互不相关的综合变量,消除了多重共线性带来的系数不稳定问题。但它的缺点是结果不能直接解释——z1 是个一堆变量的线性组合,“主成分提高一个单位”没有任何业务含义。所以还要做一步关键操作:把主成分回归的系数反变换回原始变量的系数。
3.4 系数还原:把主成分结果翻译回原始变量
这一步是整个项目里最容易算错、也最容易被忽略的地方。还原逻辑是:响应变量对主成分的回归系数,乘以主成分对原始变量的变换系数(即特征向量),除以原始变量的标准差,再减去均值归一化的补偿项。
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)这里踩过的坑是:直接把主成分回归的系数当成原始变量系数,结果和多元线性回归完全对不上。原因是主成分是标准化变量的线性组合,而原始变量有自己的均值和标准差,必须做两步归一化——先除标准差,再用均值修正截距。之前在这个步骤翻过车,出来的系数符号和多元回归相反,最后一步步对中间结果才找到问题。这个坑在第 5 章会展开说。
系数还原后,再对比多元线性回归得到的系数,会发现符号方向基本一致,数值略有差异,这是正常的——两种方法对多重共线性的处理方式不同,本来就该有差异,只要结论方向相同,就说明模型的结论稳健。
4. KNN 分类:特征选择、距离度量与精度瓶颈分析
4.1 为什么用 KNN 而不是别的分类器
KNN 是分类算法里最直观的一种:新样本来了,找训练集里最近的 k 个已知样本,用多数表决决定新样本的类别。选它做红酒分类有两个原因:一是实现简单,不依赖训练过程,适合快速验证;二是和前面的回归分析形成对比——回归告诉你哪些属性影响评分,KNN 告诉你用这些属性能不能直接“匹配”出评分。
它的三个要素是 k 值选择、距离度量和分类决策规则。这三者确定后,新样本的类别唯一确定,没有任何随机性,对答辩时讲清楚原理很有帮助。
4.2 特征选择:五维距离的计算逻辑
特征选择上,这个项目选了五个变量:x2(挥发性酸度)、x5(氯化物)、x7(总二氧化硫)、x10(硫酸盐)、x11(酒精)。这个选择是“相关性筛选之后再加一点平衡”的结果:x2、x10、x11 是回归分析验证过的强相关变量;x5 和 x7 相关性中等,但增加维度能拉大样本点之间的距离,避免所有点在三维空间里挤成一团。
距离用的是欧氏距离,即逐维求差平方、累加后开方:
distance = sqrt((x2_i - x2_new)^2 + (x5_i - x5_new)^2 + (x7_i - x7_new)^2 + (x10_i - x10_new)^2 + (x11_i - x11_new)^2)k 值选 20。这个值不是调参调出来的,而是考虑到数据有 1598 条,评分集中在 5 到 6 分之间,k 值太小容易被单个离群点带偏,k 值太大又会被 5 分和 6 分的大多数淹没。20 是一个居中的选择,在代码里可以用一个变量单独控制,方便后面做敏感性测试。
4.3 Python 实现:读取、距离计算与投票分类
import openpyxl def read_excel(input_file_name, objects): """ 从 xlsx 文件中读取数据,只取指定列 """ workbook = openpyxl.load_workbook(input_file_name) table1 = workbook['Sheet1'] rows = table1.max_row cols = table1.max_column for row in range(1, rows): object = [] for col in range(cols): if (col == 1 or col == 4 or col == 6 or col == 9 or col == 10 or col == 11): data = table1.cell(row + 1, col + 1).value object.append(data) objects.append(object) return objects 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): """ 取距离最近的30个点,多数表决决定评分 """ value = [] d = {} for i in range(30): item = objects[distances.index(min(distances))][5] if item in d: d[item] = d[item] + 1 else: d[item] = 1 distances[distances.index(min(distances))] = 100 for k, v in d.items(): print("vaule is ", k) breakread_excel 函数里那个 col 判断有个细节要注意:Python 的 openpyxl 列索引从 1 开始,所以 col==1 对应的是第 2 列,也就是 x2。注释里明明写着“只取指定列”,但索引偏移很容易搞错,当时我写完后发现读出来的数据怎么都对不上,排查才发现是列索引偏移了一位。建议在实际代码里把列名显式写出来,比如用表头 name 去匹配,而不是用数字索引,能少踩一个坑。
classify 函数里的“距离置 100”这个技巧很朴素的:选出一个最近点后,把它对应的距离设为 100(一个远大于任何正常距离的值),这样下一轮 min 就能找到倒数第二近的点,相当于手写了一个“不放回抽样”。
4.4 精度不高的根因:数据分布与关联强度的双重限制
测试结果确实不太理想。分析下来有三个原因:
第一,数据集严重不平衡。评分 5 和 6 占了绝大多数,即使盲猜也能达到 40% 以上的准确率,但 KNN 的投票结果会被多数类带偏——距离最近的 20 个点里,如果 14 个是 6 分的,不管新样本的实际特征偏向哪边,都会被投成 6 分。
第二,属性与评分的相关性不够强。相关性分析里最强的是挥发性酸度(绝对值 0.5 左右),说明这个数据集里物理属性对评分的影响是“有,但不决定性”。品酒师的评分还受产地、年份、酿造工艺等数据里没有的信息影响,这些信息丢失导致 KNN 的分类上限不高。
第三,k 值与数据分布不匹配。一个对比实验:如果只看距离最近的那一个点(k=1),准确率反而比 k=20 高。原因是 k=1 只看最相似的样本,不容易被多数类淹没;k=20 引入的多数类噪声反而压过了相似性信号。
所以 KNN 在这个项目里的定位不应该是“争准确率的分类器”,而是“验证回归结论一致性的辅助手段”——它证明的是这五个属性确实携带了评分相关的信息,只是强度有限。答辩时主动说清楚这一点,比藏着掖着等评委追问要得体得多。
5. 常见问题与避坑:从数据读取到模型解读的四个翻车点
5.1 相关性不高不等于删除:残糖和 pH 的前世今生
现象:根据相关性矩阵,残糖、游离二氧化硫、pH 与评分相关系数都在 0.1 以下,于是直接删除。但后来发现,pH 与挥发性酸度的相关系数超过 0.5,pH 更像是一个“被抵消的变量”。
原因:pH 对评分的影响被挥发性酸度“代偿”了。红酒的 pH 主要由酸决定,而挥发性酸度是模型中更强的预测变量,两者信息重叠度高,单独看 pH 与评分的相关性不显著。
解决:先用相关性分析做粗筛,再用回归模型的 p 值做细筛。如果一个变量单看相关系数低,但它与某个高相关变量高度相关,不要直接删除,放进回归模型看显著性,或者保留到主成分分析里让算法自动处理。我在这个项目里前几次直接把 pH 删了,回头看其实损失了一部分解释力。
5.2 R 方 0.6589 是不是模型不行
现象:多元线性回归的 R 方只有 0.6589,第一反应是模型拟合不够好,想往上加变量、加交互项、做多项式回归。
原因:评分是人的主观判断,天然有大量噪声。同样的酒给不同品酒师打分可能差 2 分,给同一个品酒师在不同时间打分也可能差 1 分。这个“噪声天花板”是模型本身无法突破的。
解决:不要只盯 R 方。看三个东西——系数符号是否符合常识、参数显著性是否通过、预测区间是否覆盖合理范围。这三个都满足时,即使 R 方只有 0.5 也能给出可靠结论。反过来,R 方 0.9 但系数符号和业务逻辑相反,这个模型反而更不可信。
5.3 KNN 的 k 值不是一个“调出来的数”,而是一个“讲得清的数”
现象:k=20 时准确率较低,改成 k=1 反而更高,于是有人开始遍历 k 值找最优解,最终报告里写“k 取 7 时准确率最高”之类的结论。
原因:k 值确实可以调,但调出来的最优 k 对这份数据没有普适意义。评分分布极度不平衡,任何 k 值都绕不开“5 分和 6 分占大头”这个事实。
解决:k 值选择应基于数据量和分布。样本量 1598、类别集中在两档时,k 取 10-20 是合理区间;答辩时直接说“我选了 20,因为太小会被离群点影响,太大被多数类淹没”,比报一个调参得到的 7 更有说服力。如果想展示严谨性,可以补充一个 k 值敏感性表格——k=5、10、20、30 的准确率变化,但结论是准确率波动不大,说明模型对 k 不敏感。
5.4 主成分系数还原错误:符号反了都不知道
现象:主成分回归跑完,直接把 z 的系数当作结论写进报告,说“第一主成分每增加一个单位评分上升 0.23”。仔细一算是错的——主成分是标准化变量的线性组合,原始变量单位不同(pH 是 3.3,总二氧化硫是 46),系数完全不可比。
原因:princomp 用 cor=TRUE 时,主成分是从标准化变量中提取的。响应变量 q 的单位是“分”,而主成分的单位是“标准差的组合”,系数 0.23 的含义是“主成分值每变动一个标准差,评分变动 0.23 分”,不是“x1 每变动一个单位评分变动 0.23 分”。
解决:用第 3 章那个还原公式,把主成分系数乘以特征向量矩阵并除以原始变量标准差。还原后再对比多元回归的系数,如果符号方向一致,说明两种方法互相印证;如果符号不一致,优先怀疑还原过程而不是模型本身。这个坑我踩过一次后学乖了:凡是结果和业务逻辑冲突,先查代码,不要急着改模型。
另一个容易混淆的点是 predict 的置信区间和预测区间。置信区间是“平均评分”的范围,预测区间是“单个评分”的范围,后者永远更宽。答辩时如果被问“为什么两个区间不一样宽”,答不上来就会显得对基础概念含糊,建议主动把两个区间的含义写进分析结论里。
6. 验收与答辩:三类结论怎么自证有效
数据分析作业的验收,关键是让评审信服三件事:数据读对了、模型选对了、结论讲清楚了。我验收这套方案时会跑一遍清单。
第一项是数据完整性。原始 1598 条样本,转 xlsx 后行数必须一致,列名必须对应 x1 到 x11 加 q。我一般会加一步:输出每列的最小值、最大值和缺失值数量,核对 pH 范围在 2.7 到 4.0 之间、评分范围在 3 到 8 之间,防止读入时文件错位。
第二项是回归结果的合理性检查。三个核心变量的系数符号必须符合业务逻辑——挥发性酸度系数为负、硫酸盐和酒精系数为正。系数符号错了直接打回;符号对了再看 p 值是否全部小于 0.05;之后再看预测区间是否覆盖真实值。这个顺序不能乱,符号错了就没有继续往下看的价值。
第三项是 KNN 的验收标准。准确率不是唯一指标。我会另外统计一个数据:距离最近的一个样本的评分命中率。这个命中率通常比 k=20 的投票准确率高得多,把它写进报告里可以证明“特征选择是有效的,是投票机制受到了分布干扰”。
答辩时有三类问题一定会被问到:
第一类,“为什么 R 方只有 0.66?”答:评分是主观值,存在噪声天花板,回归的目标是解释主导因素,不是穷尽可能因素;并且预测区间验证了模型在个例上也有可靠表现。
第二类,“KNN 准确率不高为什么还要用它?”答:KNN 不是争分类准确率,而是用另一种范式验证属性与评分相关性——最近邻命中率高,说明物理属性确实携带评分信息,只是多数表决被不平衡分布干扰。
第三类,“主成分分析取了五个主成分的累计贡献率是多少?”这个数据要提前查好,一般取前五个主成分贡献率超过 85%,在报告里应该明确写出百分比,不要只说“取前五个”。
这套分析做完后我最大的教训是:数据分析和纯编码不同,每一步都要能回答“所以呢”。相关性矩阵告诉你哪个变量强相关,回归告诉你强多少,KNN 告诉你这些变量能不能直接匹配出评分——每一步都指向一个独立结论,而不是把输出结果罗列一遍。从那以后我每次做这种数据分析作业,都会强制自己先写一句话结论,再倒推需要什么分析来支撑,而不是拿到数据就跑模型。希望这份方案能帮你在同样的数据集上少走几步弯路,把重心放在结论的解读和自证上。
本文还有配套的精品资源,点击获取