简介一份面向高校数据分析课程的完整大作业报告以经典红酒品质数据集作为研究对象系统展示从数据导入、清洗到统计建模与分类的全过程。文档首先介绍红葡萄酒成分与评分的背景针对十一项物理属性非挥发性酸度、挥发性酸度、柠檬酸、残糖、氯化物、游离二氧化硫、总二氧化硫、密度、酸碱度、硫酸盐、酒精与感官评分的关联展开相关性分析筛选出挥发性酸度、硫酸盐、酒精等关键影响因素随后建立多元线性回归模型预测评分并对模型进行置信区间与预测区间检验为处理变量间多重共线性进一步采用主成分回归提取前五个主成分还原为原始变量表达式最后使用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/Lx2挥发性酸度0.1~1.6 g/Lx3柠檬酸0~1.7 g/Lx4残糖0.9~15.5 g/Lx5氯化物0.01~0.6 g/Lx6游离二氧化硫1~72 mg/Lx7总二氧化硫6~289 mg/Lx8密度0.990~1.004 g/cm³x9pH 值2.7~4.0x10硫酸盐0.3~2.0 g/Lx11酒精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()) # 转成 xlsxindexFalse 避免多出无意义的行号列 df.to_excel(redwine.xlsx, indexFalse)这段代码做了三件事读取 csv、打印数据形状和评分分布、写出 xlsx。shape用于确认样本量value_counts用来观察评分分布是否均匀。如果你手里是带分隔符的 txt 或制表符文件只需要在read_csv里加sep\t。如果原始文件第一行不是表头还要加headerNone。另一种写法是用 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 用回归方程预测新酒样置信区间和预测区间作业里留了一组数据没参与建模专门用来验证回归方程的预测效果。这组数据的 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)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, k20): 取距离最近的 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, keyd.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~289x11 酒精才 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算出预测评分区间不用再重跑一遍相关性分析。从那以后我每次跑统计项目都会强制自己走一遍“先看分布、再筛变量、再建模、最后验证”的流程而且验证一定用留出法而不是全部数据。希望帮到你。本文还有配套的精品资源点击获取