☰
红酒数据集分析指南:从pandas清洗到sklearn建模验证
2026/10/7 3:48:57 网站建设 项目流程

简介:这份《数据分析大作业-红酒数据集的分析》文档资源面向正在进行数据分析课程设计或统计建模练习的大学生,完整呈现从数据清洗到建模分析的全过程。内容以11项红酒理化属性与感官评分为研究对象,依次开展相关性分析、多元线性回归、主成分回归,并采用KNN算法实现简单分类,结论对红酒品质影响因素挖掘具有参考价值。资源包共1个文件,为docx格式,大小202KB,打开即可查看含图表、代码及结果输出的完整报告。该资源已有4265人学习,适合作为数据分析、机器学习入门及R语言应用的参照范例。读者可从中获取红酒数据集分析的标准流程、特征降维思路、回归模型构建与检验方法,以及主成分回归与原始变量转换的具体实现细节。

1. 红酒数据集该怎么分析:先想清楚大作业到底在考什么

期末数据分析大作业发下来,很多人第一反应是直接把红酒数据集塞进模型,跑出一个准确率截图交差,这种做法在答辩现场往往撑不过三分钟。原因很简单:老师想看的是分析路径,不是模型的黑匣子输出。这里说的红酒数据集,是 UCI 上那套 178 个样本、13 个化学特征、分成 3 个品种类别的经典数据,它体积小、标签干净、规律明显,非常适合完整走一遍数据清洗、描述统计、可视化和建模验证的小样本数据分析案例。这篇笔记只讲一件事:如何把这套数据分析案例做出“能交作业、能答辩、能讲清楚每个选择”的程度。适合正在做课程设计的学生,也适合刚接触 pandas 和 scikit-learn 的从业者照着复现。

2. 数据获取与基础清洗:先拿到一份能分析的 DataFrame

拿到任何数据集,第一步都不是写模型,而是把它读进来,整理成结构清晰的表格。红酒数据集虽然内置在 sklearn 里,但很多大作业要求你自己从 UCI 下载原始文件,两种来源的读取方式不一样,处理细节也不同。这一章把读取、体检、清洗、标签分离一次说清楚。

2.1 红酒数据集的常见来源与读取方式

常见做法有两种:一是直接用 sklearn 的内置接口,适合快速验证流程;二是从 UCI 网站下载.data文件,适合写在报告里展示“数据获取”环节。我一般会优先用load_wine()作为主流程,因为列名是现成的,省去手工命名的麻烦;如果导师指定要解析原始文件,则用pd.read_csv()加header=None处理。

import pandas as pd from sklearn.datasets import load_wine # sklearn 内置读取,返回 Bunch 对象,类似字典 wine = load_wine() print(wine.keys()) print(wine['DESCR'].split('\n')[0])
# 转成 DataFrame,列名直接用 sklearn 给好的 feature_names df = pd.DataFrame(wine['data'], columns=wine['feature_names']) df['target'] = wine['target'] print(df.head())

load_wine()返回的 Bunch 对象里有data、target、feature_names、target_names和DESCR五个部分。data是 178 行 13 列的二维数组,target是长度 178 的一维标签,feature_names存着 13 个化学指标的名称,包括 alcohol(酒精)、malic_acid(苹果酸)、color_intensity(颜色强度)、proline(脯氨酸)等。把标签直接并进 DataFrame 是为了后面做分组统计和可视化时方便,但建模前必须再拆开。

如果你从 UCI 下载原始文件,文件里第一列是类别标签,后面 13 列是特征,没有表头,读取方式略有不同:

df_raw = pd.read_csv('wine.data', header=None, names=['target'] + wine['feature_names'])

参数header=None告诉 pandas 第一行不是列名,names手动指定列名。这两处不写对,后面所有分析都会串列,这是新手最常见的翻车点。

2.2 用 Pandas 完成首轮体检:缺失、重复、类型

数据读完先别急着算统计量,花一分钟确认三个基础问题:有没有缺失值、有没有完全重复的行、每一列的数据类型是否符合预期。红酒数据集本身很干净,但“干净”这个结论不能靠猜,要写在报告里。

print('数据维度:', df.shape) print('数据类型:\n', df.dtypes) print('缺失值总数:', df.isnull().sum().sum()) print('重复行数量:', df.duplicated().sum())

shape输出(178, 14),表示 178 个样本、13 个特征加 1 个目标列。dtypes应该全部是float64或int64,如果出现object,说明某列被读成了字符串,常见原因是原始文件里混入了缺失占位符。isnull().sum().sum()先按列统计缺失数再全部相加,得到全局缺失值总数。duplicated().sum()统计完全重复的行数。

对这份数据来说,缺失值和重复行都是 0,但这步不能跳过。报告里写“已完成数据清洗,确认无缺失无重复”的前提是你真的跑过这些命令,而不是凭感觉说“数据集是干净的”。

2.3 大作业最容易忽略的一步:标签和特征分离

很多同学建模前直接把整个df扔进fit(),等于把target列也当成特征输入模型,训练时模型已经“看到”了答案,测试集准确率直接逼近 100%。这种事一旦在答辩时被追问,基本没有还手余地。

# 特征矩阵 X 只保留 13 个化学指标 X = df.drop('target', axis=1) # 目标变量 y 单独存放 y = df['target'] print('特征矩阵:', X.shape) print('目标变量:', y.shape)

drop('target', axis=1)按列名删掉目标列,返回新的 DataFrame,原df不受影响。y是 pandas Series,长度与X行数一致,都是 178。后面做标准化、PCA、交叉验证时,始终只传X和y,不再碰带标签的完整表。

3. 描述统计与特征筛查:三个类别差异到底藏在哪里

大作业的得分点从这一章开始真正拉开差距。很多人只贴一个df.describe()就完事,但描述统计不是凑字数,而是用来回答一个具体问题:三类红酒在哪些化学指标上分得开?这一章用分组对比、量纲分析、相关性筛查三组操作,把“该建模时优先选哪些特征”这个结论一步步推出来。

3.1 类别是否平衡:统计前先看 target

做任何统计分析前,先回答“三个类别各有多少样本”。类别不平衡会直接影响后续指标选择,如果某一类样本特别少,准确率就不可信。

# 统计三个类别的样本量 print(df['target'].value_counts().sort_index())

输出结果中类别 0 有 59 个样本,类别 1 有 71 个,类别 2 有 48 个,比例接近 1:1.2:0.8,属于基本平衡。这意味着后面用准确率作为主要指标是安全的,不需要额外做重采样或加权。如果你拿到的是其他数据集且类别比例悬殊,就要考虑StratifiedKFold或者给模型加class_weight参数,不能照搬这套流程。

类别平衡确认后,接下来按类别分组看特征均值和标准差。这是描述统计里最有信息量的一步。

# 按类别分组,统计每个特征的均值和标准差 group_stats = df.groupby('target').agg(['mean', 'std']) print(group_stats.T.round(2))

转置后行是特征名,列是三个类别,可以逐行比较。用color_intensity(颜色强度)举例:类别 0 均值约 4.97,类别 1 约 3.02,类别 2 约 1.69,三个箱体几乎错开,说明这个特征对区分三类酒的贡献很大。再看alcohol,类别 0 约 13.7,类别 1 约 12.3,类别 2 约 13.1,虽然也有差异,但类别 1 和类别 2 之间的差距很小,单独用它分类会有重叠。

这一步的价值在于:读完这个表,你已经知道哪些特征“一眼就能分开”,哪些特征“基本分不开”,后续建模选特征和解释模型都有据可依。

3.2 描述统计里有价值的信息:量纲差异预告标准化

全局describe()看起来平平无奇,但把最大值减最小值算出一个跨度列,就能立刻发现数据的一个关键问题:量纲差异巨大。

# 计算每列的最大最小值跨度 desc = df.describe().T[['mean', 'std', 'min', 'max']] desc['range'] = desc['max'] - desc['min'] print(desc.round(2))

describe()返回的是按特征排列的 DataFrame,取转置后只看均值、标准差、最小值、最大值,再手动增加range列。这时你会看到proline(脯氨酸)的跨度接近 1400,从 278 到 1680,而alcohol的跨度只有 3.8,从 11.0 到 14.8。magnesium的跨度也有 92,从 70 到 162。

如果后续做 PCA 或逻辑回归这类对尺度敏感的模型,不标准化的话,主成分会被脯氨酸这种量纲大的变量带着走,模型学到的不是真实规律,而是尺度假象。所以这个跨度列的存在,就是后面第 5 章“标准化前就做 PCA 会翻车”的实证依据。报告里把这张表放上去,比空写一句“需要标准化”可信得多。

3.3 相关性矩阵:从冗余特征里挑出建模主力

相关性分析解决的是另一个问题:13 个特征之间有没有信息重复?如果有,建模时就不必全部塞进去,既省训练时间,又降低过拟合风险。

# 计算特征间的相关性矩阵,并查看与 target 的相关性排名 corr_matrix = df.corr(numeric_only=True) corr_with_y = corr_matrix['target'].drop('target').abs().sort_values(ascending=False) print('与target相关性最高的3个特征:') print(corr_with_y.head(3))

df.corr()默认计算 Pearson 相关系数,numeric_only=True确保只对数值列计算。取corr_matrix['target']得到所有特征与目标列的相关性,drop('target')把 target 和自身的相关性 1.0 去掉,abs()取绝对值,只看相关强度不看方向。排序后可以看到与类别相关性最高的特征大概在 0.63 到 0.73 这个区间,具体排序因随机扰动略有浮动,但color_intensity、flavanoids(黄酮类化合物)、proline这几项稳定靠前。

再看特征之间的横向相关性,flavanoids和total_phenols(总酚)的相关系数接近 0.86,flavanoids与od280/od315_of_diluted_wines(稀释葡萄酒的 OD 值)也接近 0.79。这说明这三者描述的是同一类化学信息,建模时保留一个代表即可。

到这里,建模方向基本清晰:优先关注color_intensity、flavanoids、proline这三个判别力强的特征,同时注意特征间的冗余,不必追求“全都要”。这一章的三个小节加在一起,就是数据分析方法里最实用的一条链路:分组对比看判别力,量纲分析看预处理必要性,相关性分析看信息冗余。

4. 可视化:给你的大作业配上能讲出道理的图

大作业报告里最容易被挑刺的就是图。不是因为图丑,而是因为很多图根本解释不了“为什么要画它”。可视化实践的核心原则是:每一张图都要回答前面提出的一个问题,要么展示类别分布,要么展示特征判别力,要么展示特征冗余关系。这一章给出三张必做图,以及每张图对应的解读思路。

4.1 类别分布图和箱线图:先用图验证分组统计的结论

箱线图是你在答辩时最有力的一张图,因为它直接呈现“三个类别在一个特征上的分离程度”,看箱体错开多少比背数字直观得多。

import matplotlib.pyplot as plt import seaborn as sns # 拼接特征,便于 seaborn 按 target 分组绘图 df_plot = df.copy() df_plot['target'] = df_plot['target'].astype(str) # 画类别分布柱状图 plt.figure(figsize=(6, 4)) sns.countplot(data=df_plot, x='target') plt.title('三个类别的样本量分布') plt.show()

countplot按target分组统计样本量,x 轴是三个类别,y 轴是数量,对应第 3 章的value_counts(),用来在报告里可视化“类别平衡”这个结论。

接下来是核心图:箱线图。

# 选择判别力最强的特征画箱线图 fig, axes = plt.subplots(2, 2, figsize=(12, 8)) cols = ['alcohol', 'color_intensity', 'flavanoids', 'proline'] for idx, col in enumerate(cols): ax = plt.subplot(2, 2, idx + 1) sns.boxplot(data=df_plot, x='target', y=col, ax=ax) ax.set_title(f'{col} by target') plt.tight_layout() plt.show()

subplots(2, 2)创建 2x2 的画布,figsize=(12, 8)控制画布大小,保证每个子图不会被压扁。sns.boxplot的x='target'按类别分组,y=col指定特征,箱体的高度表示四分位距,箱子里的横线是中位数,箱子外的须子是数据范围。

从箱线图里能直接读出上一章的统计结论:color_intensity的三个箱子几乎完全不重叠,判别力最强;flavanoids的类别 0 和类别 2 间距明显;alcohol三个箱子重叠较多,判读时要谨慎。答辩时指着这张图说“我据此选择 color_intensity 和 flavanoids 作为主要特征”,比说“模型给我的准确率很高”专业得多。

4.2 散点图矩阵和相关性热力图:把特征冗余画出来

箱线图回答的是“单特征能不能分类”,散点图矩阵回答的是“两个特征放在一起能不能分得更好”。这是从统计到建模之间的最后一块拼图。

# 用 seaborn 的 pairplot 画散点图矩阵 sns.pairplot(df_plot, vars=['alcohol', 'color_intensity', 'flavanoids', 'proline'], hue='target', diag_kind='kde') plt.show()

vars指定参与绘制的特征列表,hue='target'让三个类别的点显示不同颜色,diag_kind='kde'把对角线上的直方图换成核密度曲线,曲线重叠度越低,说明该特征区分度越高。非对角线上的散点图如果颜色聚成三坨,说明这两个特征组合的判别效果好。

相关性热力图则是把第 3 章算出来的相关系数矩阵可视化,让冗余特征一目了然。

plt.figure(figsize=(12, 10)) sns.heatmap(df_plot.corr(numeric_only=True), annot=True, cmap='coolwarm', fmt='.2f', linewidths=0.5) plt.show()

annot=True在每个格子里显示相关系数数值,cmap='coolwarm'用冷暖色区分正负相关,fmt='.2f'控制数字格式,linewidths=0.5给格子加边线防止视觉粘连。红色格子代表正相关,蓝色格子代表负相关,颜色越深相关越强。

这张图的价值在于:你能一眼指出“flavanoids 和 total_phenols 是强相关,建模时只需要其中一个”,这种观察在报告里写出来,比单纯的数表更有说服力。

4.3 可视化指导建模:两张图定下特征清单

可视化做完,需要把观察结论收敛成具体的建模决策。以这份红酒数据集为例,四张图看下来能得出三条可以写进报告的判断:

第一,color_intensity和flavanoids在箱线图和散点图中都表现出一类与两类清晰分离的趋势,是建模时必须保留的特征。第二,alcohol单独判别力弱,但和color_intensity组合后能看到三个类别的分布呈现三个方向,说明它适合作为辅助特征保留。第三,proline的动态范围大、判别力也强,但使用 PCA 或距离模型前必须标准化,否则它会喧宾夺主。

如果报告篇幅允许,可以在这张图上叠加 PCA 之后的主成分散点图,把 13 维压缩到 2 维,观察类别分布。可视化这条链路走完,建模的方向已经被图给“规定”好了,后面训练模型只是验证这些图的判断是否成立。

5. 红酒数据集的避坑与常见问题:四条血泪经验

这份数据集体积小、规律明显,按理说翻车概率不高,但恰恰因为它看起来简单,很多人会跳过基本功,在四个固定位置上踩坑。每条都是实际跑数据时反复遇到的状况,按“现象 → 原因 → 解决”写,方便对照排查。

5.1 把三分类当成二分类做,模型准确率虚高

现象:模型输出的准确率达到 96%,但查看分类报告发现类别 2 的 precision 和 recall 明显低,甚至某一类完全没被识别出来。

原因:红酒数据集的目标变量是 0、1、2 三个类别,对应三种不同栽培品种的红酒,不是二分类问题。新手容易在建模时用逻辑回归默认的二分类模式,或者只看整体准确率,忽略了多分类的本质。

解决:建模前用一行命令确认类别数量和标签含义,不要凭感觉猜。

print(wine['target_names']) # 输出三个类别的原始名称 print(df['target'].value_counts().sort_index()) # 确认每个类别的样本量

如果这一步发现样本量严重不平衡,后续建模要用StratifiedKFold保持每折类别比例,或者给模型加class_weight='balanced'。

5.2 标准化前就做主成分分析,第一主成分被脯氨酸绑架

现象:PCA 降维后画出的散点图,第一主成分方向上三个类别的分布没有明显区分,而且主成分权重几乎全部集中在proline上。

原因:PCA 是方差驱动算法,哪个变量量纲大、方差大,哪个就对主成分贡献大。前面第 3 章算过,proline的跨度接近 1400,alcohol只有 3.8,两者不在一个尺度上,PCA 找到的方向被proline主导,其他 12 个特征的信息被稀释。

解决:先标准化再 PCA,两步必须分开写清楚。

from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCA # 先标准化,再降维 X_scaled = StandardScaler().fit_transform(X) pca = PCA(n_components=2) X_pca = pca.fit_transform(X_scaled) print('主成分方差解释比:', pca.explained_variance_ratio_)

StandardScaler()把每个特征变成均值为 0、标准差为 1 的分布,fit_transform先计算均值和标准差,再套用到数据上。PCA(n_components=2)降到两维,explained_variance_ratio_查看两个主成分各自解释了多少方差,合理区间一般在 50% 到 65% 之间。如果这里不标准化,第一主成分的解释率容易虚高,但实际可视化效果很差。

5.3 随机种子不同,测试集准确率在 80% 到 100% 之间跳动

现象:同一套代码,只改random_state的取值,训练集和测试集划分变了,模型在测试集上的准确率从 0.82 跳到 1.0。

原因:数据一共只有 178 条,测试集通常留出 30 到 40 条。样本量这么小,随机划分的偶然性很大,划分到简单样本则准确率高,划分到难样本则准确率低,模型真实水平被单次划分的运气掩盖。

解决:用交叉验证代替单次 hold-out 划分,报告均值而不是单次结果。

from sklearn.model_selection import StratifiedKFold, cross_val_score from sklearn.linear_model import LogisticRegression cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(LogisticRegression(max_iter=2000), X, y, cv=cv) print(f'交叉验证准确率: {scores.mean():.3f} ± {scores.std():.3f}')

StratifiedKFold保证每一折里三个类别的比例跟整体一致,n_splits=5把数据切成 5 份轮流转训练和验证,shuffle=True打乱顺序避免原始数据按类别排好导致的分割偏差。最后输出的均值比单次准确率可信得多。

5.4 只盯着准确率,忽略混淆矩阵里的细节

现象:准确率 96%,看起来模型很好,但把混淆矩阵打出来,发现类别 2 有 20% 的样本被错分到类别 1,只是在整体准确率里被稀释了。

原因:准确率把三个类别的结果汇总成一个数字,掩盖了单个类别上的失败。红酒数据集类别基本平衡所以问题不大,但如果换成类别不平衡的数据,准确率会完全失真。

解决:每次训练完,至少跑一次分类报告和混淆矩阵。

from sklearn.metrics import classification_report, confusion_matrix from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) model = LogisticRegression(max_iter=2000).fit(X_train, y_train) y_pred = model.predict(X_test) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))

classification_report输出每个类别的 precision、recall、f1-score,能直接看出哪一类被模型冷落。confusion_matrix的行是真实类别,列是预测类别,矩阵里的非对角线元素就是错分样本的位置。

一条额外提醒:报告里不要只贴代码和输出,每个数字下面加一句判断,比如“类别 0 的召回率为 1.00,说明这一类基本全部识别正确;类别 2 有 3 条被错分到类别 1,主要集中在 proline 值偏高的样本”。带分析的输出才是老师想看到的东西。

6. 建模验证技巧:把结果从“跑通”提升到“能答辩”

模型本身在这种小数据集上很难翻车,真正区分作业质量的是验证方式是否严谨、解释是否闭环。这一章给出一个直接能用的 Pipeline 对比实验,以及一套把模型结果与前面 EDA 结论串起来的技巧。

6.1 用 Pipeline 把标准化和模型绑定,防止数据泄漏

单次StandardScaler().fit(X_train)之后再transform(X_test)是正确写法,但手写容易乱。更稳的做法是把标准化放进 Pipeline,让交叉验证的每一折自动在训练子集上计算均值方差。

from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.model_selection import StratifiedKFold, cross_val_score X = df.drop('target', axis=1) y = df['target'] cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) log_pipe = make_pipeline( StandardScaler(), LogisticRegression(max_iter=2000) ) scores = cross_val_score(log_pipe, X, y, cv=cv) print(f'逻辑回归: {scores.mean():.3f} ± {scores.std():.3f}')

make_pipeline把标准化和模型串成一条流水线,cross_val_score在每一折里自动执行“先 fit 标准化器,再 fit 模型,再预测验证集”。max_iter=2000是给逻辑回归的优化器一个更宽上限,避免在低样本量下出现收敛警告。

6.2 随机森林对比:不标准化的基线模型

随机森林不受量纲影响,可以直接拿原始特征跑,正好可以当对照模型验证逻辑回归的表现。

from sklearn.ensemble import RandomForestClassifier rf_pipe = make_pipeline( RandomForestClassifier(n_estimators=200, random_state=42) ) rf_scores = cross_val_score(rf_pipe, X, y, cv=cv) print(f'随机森林: {rf_scores.mean():.3f} ± {rf_scores.std():.3f}')

n_estimators=200是随机森林里常用的中度规模,树太少方差大,树太多在小数据集上收益递减,200 在这个场景足够稳定。

两个模型跑完,报告里写下“逻辑回归与随机森林的交叉验证均值都在 0.95 附近,模型差异不大,但逻辑回归在标准化后对特征重要性解释更清晰”,这句话已经把模型对比的结论说透了。

6.3 用特征重要性回扣 EDA 结论

随机森林训练后可以直接读取特征重要性,用它来验证前面可视化的判断。

rf = RandomForestClassifier(n_estimators=200, random_state=42) rf.fit(X, y) for name, imp in zip(X.columns, rf.feature_importances_): print(f'{name}: {imp:.3f}')

训练完成后打印 13 个特征的重要性分数,排名靠前的通常是color_intensity、flavanoids、proline这几项,与前面箱线图、散点图里观察到的强判别特征高度重合。

这个重合就是一份大作业最完整的证据链:先用描述统计和可视化挑出候选特征,再用模型验证这些特征确实重要,最后在报告里写一句“EDA 阶段发现 color_intensity 与 flavanoids 区分度最高,模型特征重要性排序与这一观察一致”。我每次做这类小样本分析都会刻意保留这条证据链,因为它能把报告从“我跑了个模型”变成“我理解这份数据”。希望帮到你。

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

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

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

立即咨询