从Excel到3D图:一份内部数据的K-Means聚类与可视化完整实战记录(避坑xlrd与编码)
从Excel到3D图:K-Means聚类实战全流程解析
当业务部门甩过来一份销售数据Excel表格,要求"明天上班前给出客户分群报告"时,很多数据分析师的第一反应可能是打开Excel的筛选功能手动分类。但面对成百上千条多维数据,传统方法不仅效率低下,还容易受主观判断影响。本文将手把手带你用Python实现从原始Excel到三维可视化的完整分析闭环,特别针对中文编码、数据预处理等实际场景中的高频"坑点"提供解决方案。
1. 环境准备与数据读取
工欲善其事,必先利其器。我们先配置好分析环境:
# 基础环境安装(建议使用conda创建虚拟环境) conda create -n kmeans_analysis python=3.8 conda activate kmeans_analysis pip install numpy pandas scikit-learn matplotlib openpyxl xlrd为什么选择openpyxl+xlrd组合?在读取老版本Excel文件(.xls)时,xlrd从2.0版本开始不再支持xlsx格式,而openpyxl对xlsx的兼容性更好。实际项目中常遇到混合格式的情况,建议同时安装这两个库。
读取数据时的典型问题及解决方案:
| 问题类型 | 错误示例 | 解决方案 |
|---|---|---|
| 中文路径 | UnicodeEncodeError | 路径前加r或使用os.path.normpath |
| 空单元格 | NoneType异常 | 增加if cell_value is not None判断 |
| 日期格式 | 显示为浮点数 | 使用xlrd.xldate_as_datetime转换 |
import os from pathlib import Path def read_excel_safe(file_path): """安全读取含中文路径的Excel文件""" try: file_path = str(Path(file_path).resolve()) if file_path.endswith('.xlsx'): from openpyxl import load_workbook wb = load_workbook(filename=file_path) ws = wb.active return [[cell.value for cell in row] for row in ws.iter_rows()] else: import xlrd wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) return [[ws.cell_value(r, c) for c in range(ws.ncols)] for r in range(ws.nrows)] except Exception as e: print(f"读取失败: {str(e)}") return None2. 数据预处理实战技巧
原始业务数据往往存在各种问题,直接扔进算法就像把没洗的菜下锅——结果可想而知。以下是三个关键预处理步骤:
2.1 缺失值处理的三重境界
- 简单删除:当缺失比例<5%时可用
df.dropna(inplace=True) - 均值/中位数填充:适合数值型特征
from sklearn.impute import SimpleImputer imputer = SimpleImputer(strategy='median') df_filled = imputer.fit_transform(df) - 模型预测填充:利用其他特征建立预测模型
2.2 特征标准化方法对比
业务数据常存在量纲差异(如销售额vs客户年龄),标准化是聚类的必要步骤:
| 方法 | 公式 | 适用场景 | Scikit-learn实现 |
|---|---|---|---|
| MinMax | (x-min)/(max-min) | 已知边界的数据 | MinMaxScaler |
| Z-Score | (x-μ)/σ | 正态分布数据 | StandardScaler |
| Robust | (x-median)/IQR | 含异常值数据 | RobustScaler |
from sklearn.preprocessing import MinMaxScaler # 保留原始数据副本 raw_data = np.array(data) scaler = MinMaxScaler(feature_range=(0, 1)) normalized_data = scaler.fit_transform(raw_data)2.3 分类变量编码策略
遇到地区、产品类别等文本字段时,需要转换为数值:
# 有序分类变量 from sklearn.preprocessing import OrdinalEncoder ordinal_encoder = OrdinalEncoder(categories=[['低', '中', '高']]) df['等级'] = ordinal_encoder.fit_transform(df[['等级']]) # 无序分类变量(避免引入虚假顺序) from sklearn.preprocessing import OneHotEncoder onehot = OneHotEncoder(sparse=False) encoded = onehot.fit_transform(df[['地区']])3. K-Means建模的进阶技巧
3.1 如何科学确定K值
业务部门说"分4类",但数据真的适合分4类吗?三个验证方法:
肘部法则:观察SSE下降拐点
sse = [] for k in range(1, 10): kmeans = KMeans(n_clusters=k) kmeans.fit(data) sse.append(kmeans.inertia_) plt.plot(range(1,10), sse, 'bx-')轮廓系数:评估聚类紧密度
from sklearn.metrics import silhouette_score sil_score = silhouette_score(data, kmeans.labels_)Gap统计量:比较实际数据与参考分布
3.2 处理非凸聚类的小技巧
标准K-Means假设聚类是球形的,当遇到如下情况时:
尝试谱聚类:
from sklearn.cluster import SpectralClustering spec = SpectralClustering(n_clusters=4, affinity='nearest_neighbors')使用核K-Means:
from sklearn.cluster import KMeans from sklearn.metrics.pairwise import rbf_kernel gamma = 0.1 K = rbf_kernel(data, gamma=gamma) kmeans = KMeans(n_clusters=4).fit(K)
3.3 聚类结果评估矩阵
不要只依赖模型输出,要用业务指标验证:
# 计算每个簇的统计量 df['cluster'] = kmeans.labels_ cluster_stats = df.groupby('cluster').agg({ '销售额': ['mean', 'count'], '利润率': 'median' })4. 三维可视化与结果输出
4.1 交互式3D绘图技巧
静态图难以观察数据全貌,推荐使用:
import plotly.express as px fig = px.scatter_3d(df, x='特征1', y='特征2', z='特征3', color='cluster', hover_name='客户ID') fig.update_traces(marker_size=5) fig.show()实用参数调整:
opacity=0.7解决点重叠问题size_max=10控制点大小范围animation_frame='时间字段'制作动态演变图
4.2 结果保存的最佳实践
避免中文乱码的CSV输出方案:
with open('聚类结果.csv', 'w', encoding='utf-8-sig', newline='') as f: writer = csv.writer(f) writer.writerow(['原始索引', '聚类标签'] + feature_names) for i, (orig, label) in enumerate(zip(raw_data, labels)): writer.writerow([i, label] + list(orig))4.3 制作可交互报告
用Pyecharts生成HTML分析报告:
from pyecharts.charts import Scatter3D from pyecharts import options as opts scatter3d = ( Scatter3D() .add("", [(d[0], d[1], d[2]) for d in data], grid3d_opts=opts.Grid3DOpts(width=100, depth=100)) .set_global_opts(title_opts=opts.TitleOpts(title="客户分群3D视图")) ) scatter3d.render("cluster_3d.html")5. 业务落地常见问题
为什么聚类结果不符合业务直觉?
- 检查特征相关性:高相关特征会导致维度浪费
- 尝试特征组合:如"客单价×购买频率"可能比单独使用更有意义
- 考虑非线性关系:先用PCA降维观察数据结构
如何向非技术人员解释聚类结果?
- 为每个簇起业务名称(如"高价值休眠客户")
- 制作雷达图对比簇特征:
from pyecharts.charts import Radar radar = Radar().add_schema(schema).add("簇1", values1) - 提供典型客户案例(不要只用统计数字)
模型迭代策略:
- 定期用新数据验证聚类稳定性
- 设置业务指标监控(如不同簇的转化率变化)
- 建立自动化重训练机制(如每月第一个周一自动运行)
