Python实战:用决策树与聚类分析解析汽车满意度数据集

Python实战:用决策树与聚类分析解析汽车满意度数据集 简介一套基于汽车满意度数据集的分析与建模代码面向机器学习初学者和数据挖掘实践者用于掌握决策树和多种聚类算法的应用对比。包内共五个Python脚本分别实现决策树分类、K均值、均值漂移、层次聚类和密度聚类压缩包仅七KB轻量易读便于直接运行和二次修改。已有四百六十六人浏览学习适合课程设计、毕业设计或算法实战练习。资源覆盖从数据加载、模型训练到结果评估的完整流程并给出关键结论决策树准确率约百分之九十五密度聚类在该数据集上效果不佳可帮助使用者快速理解各算法的适用场景与调参思路。代码按模型分文件组织便于对照运行结果比较各算法差异也可作为算法实验模板进行扩展。 最近整理机器学习练手项目把经典的汽车满意度数据集 Car Evaluation 翻了出来。它来自 UCI虽然只有 1728 条记录特征也全是离散的类别型变量但特别适合用来同时跑通“有监督”和“无监督”两条主线决策树做满意度预测聚类分析做评价模式分群。这篇文章就是我完整记录用 Python 从数据编码、模型训练到结果解读的过程包含踩过的坑和调参心得。如果你刚学完 sklearn 基础或者想找一个能练手又不会太烧脑的小项目这篇应该能给你一份可以直接抄的作业。1. 数据集理解与前置准备1.1 数据集字段和业务含义Car Evaluation 数据集的原始文件可以在 UCI 机器学习仓库下载文件名一般是 car.data文件里没有列名读取时一定要手动指定。数据包含 6 个特征和 1 个目标变量6 个特征描述了一辆车的购买成本和使用属性目标变量是整体满意程度。各字段含义如下表。字段可选值业务含义buyingvhigh / high / med / low购买价格maintvhigh / high / med / low维护成本doors2 / 3 / 4 / 5more车门数量persons2 / 4 / more可载人数lug_bootsmall / med / big后备箱大小safetylow / med / high安全等级classunacc / acc / good / vgood满意度不可接受 / 可接受 / 好 / 很好满意度等级里 unacc 占了绝大多数acc、good、vgood 依次减少是一个典型的类别不平衡数据。所以整个项目不能只看准确率一定要把混淆矩阵、宏平均 F1 拿出来看否则容易被虚高的分数骗了。这也是我特意选这个数据集的原因之一它字段少、语义清楚非常适合作为学习机器学习基础流程的起点。1.2 环境准备与依赖说明我用的 Python 3.10模型部分主要依赖 scikit-learn数据操作靠 pandas画图用 matplotlib 和 seaborn。安装直接在终端执行pip install pandas numpy scikit-learn matplotlib seaborn graphvizAnaconda 用户一般自带前四个库seaborn 没有的话用 pip 装一下就行。graphviz 不仅需要 Python 包系统层面也要安装 Graphviz 软件否则导出决策树图片时会报 ExecutableNotFound 错误。如果只是想快速看树形图用 sklearn 自带的 plot_tree 就够了不装 graphviz 也能跑。因为后面会涉及多个随机算法我会先统一设置随机种子保证每次运行结果一致。这里要说一句随机种子不是随便写个 42 就完事它决定了 KMeans 初始质心、决策树分裂时的随机行为。如果你发现别人的代码跑出来的结果和你不一样先检查是不是随机种子没对齐。import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns random_state 42 np.random.seed(random_state)2. 数据加载与特征工程2.1 读取数据与基本信息读取 car.data 时最容易踩的坑就是缺少列名。我一开始直接用pd.read_csv(car.data)结果第一行被当成了表头后面所有数据都错位。正确做法是指定headerNone再传入列名columns [buying, maint, doors, persons, lug_boot, safety, class] df pd.read_csv(car.data, headerNone, namescolumns) print(数据集形状:, df.shape) print(df.head()) print(df.isnull().sum()) print(df[class].value_counts())运行结果能确认两条信息数据集没有缺失值形状是 1728 行 7 列。类别分布里 unacc 有 1210 条acc 有 384 条good 69 条vgood 65 条。这样的分布决定了后面的处理重点少数类 good 和 vgood 很容易被模型忽略必须靠分层抽样和类别权重来干预。2.2 类别特征编码顺序映射还是 One-Hot数据里所有特征都是字符串类别sklearn 的决策树和 KMeans 都不支持直接吃字符串。编码方式有两种主流选择。第一种是 LabelEncoder 式整体编码简单但会丢失有序语义。例如把 buying 变成 0、1、2、3 时模型会误以为等级之间是等距的。第二种是 One-Hot 编码不会引入顺序假设但会增加特征维度。对于 Car Evaluation 这种只有 6 个特征的小数据One-Hot 也可以接受。我最终选择了手动有序映射原因是这 6 个特征本身都有明确的高低语义价格从低到高、安全从低到高。保留这种有序信息对聚类分析尤为重要因为 KMeans 计算的是欧氏距离有序编码能让“低安全”和“高安全”的距离真正拉开更贴近业务直觉。def encode_car_data(df): order_maps { buying: {low: 0, med: 1, high: 2, vhigh: 3}, maint: {low: 0, med: 1, high: 2, vhigh: 3}, doors: {2: 0, 3: 1, 4: 2, 5more: 3}, persons: {2: 0, 4: 1, more: 2}, lug_boot: {small: 0, med: 1, big: 2}, safety: {low: 0, med: 1, high: 2} } df_encoded df.copy() for col, mapping in order_maps.items(): df_encoded[col] df_encoded[col].map(mapping) return df_encoded df_encoded encode_car_data(df) df_encoded[class] df_encoded[class].map({unacc: 0, acc: 1, good: 2, vgood: 3})注意等到后面做决策树特征重要性时这种映射不会影响树的分裂因为树模型本身对特征顺序不敏感但对聚类来说差别很大。如果不想自己做映射也可以用 sklearn 的 OrdinalEncoder效果是一样的。另外建议在编码后检查一下value_counts()防止原始数据里出现映射字典没覆盖的取值否则 map 会产生 NaN后面模型会报错。3. 决策树实现满意度预测3.1 为什么选决策树决策树是一个非常容易解释的监督学习模型它通过一系列“如果特征 x 小于或大于某个值”的规则把样本不断划分成更纯的子集。在 Car Evaluation 这种小数据集上决策树不仅能给出高准确率还能把判断逻辑用一棵树可视化出来这是逻辑回归和 SVM 难以直接做到的。sklearn 里的 DecisionTreeClassifier 默认使用 CART 算法分裂准则默认是基尼系数。基尼系数比熵计算更快在这类小数据上两者差异很小所以我直接用默认 gini。3.2 数据划分、训练与评估模型训练前先划分训练集和测试集。由于类别不平衡这里必须用stratifyy做分层抽样保证训练集和测试集中各个类别的比例一致。同时我在模型里设置了class_weightbalanced让少数类获得更高权重避免模型把所有样本都猜成 unacc。from sklearn.model_selection import train_test_split from sklearn.tree import DecisionTreeClassifier from sklearn.metrics import classification_report, confidence_matrix, accuracy_score X df_encoded.drop(class, axis1) y df_encoded[class] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_staterandom_state, stratifyy ) model DecisionTreeClassifier(random_staterandom_state, class_weightbalanced) model.fit(X_train, y_train) y_pred model.predict(X_test) print(准确率:, accuracy_score(y_test, y_pred)) print(classification_report(y_test, y_pred, target_names[unacc, acc, good, vgood]))我跑出来的准确率在 94% 左右分类报告里 unacc 的精确率和召回率都很高但 acc、good、vgood 的召回率波动较大。这是不平衡数据加上样本量少时很常见的现象需要结合调参和业务需求做取舍不一定非要追求每个类别都 90% 以上。如果你发现 confusion_matrix 里 good 和 vgood 整行都是 0不要慌先去确认 class_weight 是否生效再看是否需要调整决策阈值。3.3 决策树可视化与特征重要性默认的训练树可能很深直接画出来会非常拥挤。我先用限制深度到 3 的版本来观察树结构再把特征重要性打出来from sklearn.tree import plot_tree plt.figure(figsize(18, 10)) plot_tree(model, max_depth3, feature_namesX.columns, class_names[unacc, acc, good, vgood], filledTrue) plt.show() importance pd.Series(model.feature_importances_, indexX.columns).sort_values(ascendingFalse) print(importance)从特征重要性排序来看safety、buying、persons 通常会出现在前列maint 和 lug_boot 影响相对弱一些。这也很符合常识买车的人首先看重安全性和价格然后是空间是否够坐最后才是后备箱和维护费用。看树的时候可以重点观察第一层分叉用了哪个特征那个特征往往就是全局区分度最强的变量。3.4 网格搜索调参决策树需要重点调的参数主要是 max_depth 控制深度min_samples_leaf 控制叶子节点最少样本数还有 criterion 选择分裂准则。我用 5 折交叉验证和宏平均 F1 作为评分标准这样能兼顾少数类from sklearn.model_selection import GridSearchCV param_grid { criterion: [gini, entropy], max_depth: [None, 3, 5, 7, 9], min_samples_leaf: [1, 3, 5] } grid GridSearchCV( DecisionTreeClassifier(random_staterandom_state, class_weightbalanced), param_grid, cv5, scoringf1_macro ) grid.fit(X_train, y_train) print(最佳参数:, grid.best_params_) print(最佳交叉验证得分:, grid.best_score_)用最优参数重新训练后准确率未必有显著提升但少数类的召回率通常会变好。这里最该避开的坑是max_depthNone在数据量小的时候容易长出一棵无限深的树训练集准确率能到 100%测试集表现却差很多。交叉验证里的 f1_macro 是比 accuracy 更诚实的指标尤其在 Car Evaluation 这种少数类占比极低的情况下。4. 聚类分析汽车满意度分群4.1 无监督聚类的定位聚类分析在这个项目里不是用来预测满意度等级的而是用无监督的方式探索数据里是否天然存在几类“评价模式”。比如某类车虽然是低价格但安全性也很低评价可能普遍很糟糕另一类车安全性高但价格稍高评价却不错。这些模式如果只靠人工看 1728 行数据很难发现聚类可以把相近的样本归到同一个簇里再对照真实满意度去验证簇的实际含义。4.2 KMeans 聚类与最佳 K 值选择KMeans 是最常用的聚类算法它把样本分成 K 个簇使得每个点到所属簇中心的距离平方和最小。在 Car Evaluation 这种低维小样本数据上KMeans 速度快、结果好解释。选择 K 值我用了两个指标惯性inertia肘部法则和轮廓系数。from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score X_cluster X.copy() inertia [] silhouette [] for k in range(2, 11): km KMeans(n_clustersk, random_staterandom_state, n_init10) km.fit(X_cluster) inertia.append(km.inertia_) silhouette.append(silhouette_score(X_cluster, km.labels_)) plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.plot(range(2, 11), inertia, markero) plt.xlabel(K) plt.ylabel(Inertia) plt.subplot(1, 2, 2) plt.plot(range(2, 11), silhouette, markero) plt.xlabel(K) plt.ylabel(Silhouette Score) plt.tight_layout() plt.show()肘部法则看的是下降趋势拐点轮廓系数则是越大越好。实际运行后K3 或 K4 附近轮廓系数较高惯性下降也逐渐平稳我最终选了 4 个簇做后续分析。这里不要机械地选最大轮廓系数的 K因为 K 越大轮廓系数越不稳定还要结合业务可解释性。如果某个簇里面的样本特征均值非常接近说明 K 选大了簇之间没有明显差异。4.3 聚类画像与满意度交叉分析选好 K 后重新拟合把聚类标签合并回数据框分组看各特征均值再和真实满意度做交叉表km KMeans(n_clusters4, random_staterandom_state, n_init10) cluster_labels km.fit_predict(X_cluster) df_cluster df_encoded.copy() df_cluster[cluster] cluster_labels print(df_cluster.groupby(cluster)[X.columns].mean()) print(pd.crosstab(df_cluster[cluster], df[class]))聚类画像通常能清楚区分出几类车一类是低安全低维护成本对应质量较差的代步车满意度几乎都是 unacc一类是高安全高价格对应配置较好的车满意度多在 acc 到 good中间还有一类是均衡型。这个结果和决策树的特征重要性是互相呼应的。画交叉表的时候建议把列名改成可读的满意度标签否则 0、1、2、3 堆在一起很难看出规律。4.4 用 PCA 降维可视化簇分布由于原始维度只有 6 个聚类结果可以直接用 PCA 压到二维平面来观察分布from sklearn.decomposition import PCA pca PCA(n_components2, random_staterandom_state) X_pca pca.fit_transform(X_cluster) plt.figure(figsize(10, 7)) scatter plt.scatter(X_pca[:, 0], X_pca[:, 1], ccluster_labels, cmapviridis, alpha0.6) plt.xlabel(PC1) plt.ylabel(PC2) plt.colorbar(scatter) plt.show()PCA 的可视化只是为了直观感受簇分离程度不能用来解释业务维度。详细维度解释一定要回到原始特征均值表否则很容易被降维后的坐标带偏。我在实际跑的时候发现PC1 和 PC2 大约能保留八成左右的方差信息所以二维图还是有一定参考性的。5. 决策树与聚类结果联合解读5.1 聚类标签作为额外特征一个有意思的实验是把聚类标签当成新特征放进决策树看看监督模型是否能从中获益。我不建议直接覆盖原特征而是把标签加到特征矩阵里做对比X_aug X.copy() X_aug[cluster] cluster_labels X_train_aug, X_test_aug, y_train_aug, y_test_aug train_test_split( X_aug, y, test_size0.3, random_staterandom_state, stratifyy ) model_aug DecisionTreeClassifier(random_staterandom_state, class_weightbalanced) model_aug.fit(X_train_aug, y_train_aug) y_pred_aug model_aug.predict(X_test_aug) print(加入聚类特征后的准确率:, accuracy_score(y_test_aug, y_pred_aug))我的实验里加入聚类特征后准确率变化不大甚至略有下降原因是聚类标签和原始特征高度相关而决策树本身已经足够捕捉这些模式。但这不代表这个尝试没有意义它可以帮你理解有监督和无监督模型的信息边界。如果你把聚类标签换成独热编码再加进去模型可能更平稳一点但依旧不会有质变。5.2 特征重要性如何落地成业务结论结合决策树的特征重要性和聚类画像可以得到几个明确的视图安全性和购买价格主导静态评价维护成本在低端车里会成为压垮骆驼的最后一根稻草载人数量和后备箱大小在决策树里往往排在后半段说明舒适性配置不是满意度分级的首要因素。如果把这个分析套到一个更实际的汽车评价场景中产品团队可以优先看安全配置的缺失率再结合聚类里的“高危群体”去定向优化。比如低安全低价格的簇只要把安全等级从中提到高满意度就可能从 unacc 跳到 acc这个跳跃在决策树的可视化分叉里非常直观。再比如结合聚类均值表你可能会发现某个簇的人均维护成本偏高那就需要从产品策略上考虑降低维修保养费用而不是一味加座位数。6. 常见问题与排查技巧6.1 数据读取类问题问题现象解决方案第一行被当表头df.columns 出现 vhigh 等read_csv 加 headerNone并指定 names中文列名乱码可视化时中文显示方块设置 plt.rcParams[font.sans-serif][SimHei]文件编码报错UnicodeDecodeError读取时指定 encodinglatin1 或 utf-86.2 模型效果类问题决策树训练集 100%、测试集差基本就是过拟合限制 max_depth 和 min_samples_leaf 后有明显改善。分类报告里少数类全为 0是因为类别不平衡太严重除了 class_weight还可以用 imbalanced-learn 的 SMOTE 做重采样不过在这个数据集上不建议因为少数类样本太少合成样本可能引入噪声。聚类结果不稳定是新手最容易困惑的地方。KMeans 初始点是随机的所以运行结果可能不同需要设置 random_state 和 n_init。如果多次运行后簇的归属仍然漂移可以考虑换成 AgglomerativeClustering 层次聚类它没有随机初始点问题但计算复杂度会高一些。还有一点我用的有序映射对欧氏距离有较强假设若用 One-Hot 编码再聚类很可能会得到不同分群建议两种都试选业务上更合理的。6.3 写在最后一点实操体会Car Evaluation 这套数据我第一次跑的时候以为决策树训练完就算结束了后来把聚类的分群结果和树的可视化图放在一起看才发现两个模型的结论如此一致安全性和价格是满意度分级的核心变量维护成本在低端市场里才是压垮评价的最后一根稻草。这个小项目最值得学习的地方不是“跑通代码”而是理解监督学习和无监督学习在同一个数据上的互补视角。后续你可以尝试把随机森林、XGBoost 搬上来做对比或者用聚类结果做一个简单的推荐规则——比如识别出满意度大概率是 unacc 的样本提前给出改进建议。这些都是很有意思的扩展方向。本文还有配套的精品资源点击获取