用Python手把手教你搞定K-Means聚类从Excel数据读取到三维可视化附完整代码当你面对一堆看似杂乱无章的三维数据时是否曾想过如何从中发现隐藏的模式K-Means聚类算法就是解决这类问题的利器。本文将带你从零开始用Python实现一个完整的K-Means聚类项目涵盖数据读取、预处理、算法实现到三维可视化的全流程。无论你是数据分析新手还是希望巩固基础的开发者这篇保姆级教程都能让你收获满满。1. 环境准备与数据读取在开始之前确保你的Python环境已安装以下必要库pip install numpy pandas matplotlib xlrd scikit-learn我们将使用xlrd库来读取Excel数据。假设你有一个包含三维数据的Excel文件格式如下特征1特征2特征31.23.45.62.34.56.7.........读取数据的Python代码如下import xlrd import numpy as np def read_excel_data(file_path): workbook xlrd.open_workbook(file_path) sheet workbook.sheet_by_index(0) data [] for row_idx in range(1, sheet.nrows): # 跳过标题行 row_data [ sheet.cell_value(row_idx, 0), sheet.cell_value(row_idx, 1), sheet.cell_value(row_idx, 2) ] data.append(row_data) return np.array(data) # 使用示例 raw_data read_excel_data(your_data.xlsx) print(f数据形状: {raw_data.shape})注意如果数据量较大建议使用pandas的read_excel函数它在处理大型文件时效率更高。2. 数据预处理标准化是关键原始数据往往存在量纲不一致的问题这对距离敏感的K-Means算法影响很大。我们使用MinMaxScaler将数据缩放到[0,1]范围from sklearn.preprocessing import MinMaxScaler def normalize_data(data): scaler MinMaxScaler(feature_range(0, 1)) normalized_data scaler.fit_transform(data) return normalized_data, scaler # 标准化数据 normalized_data, scaler normalize_data(raw_data)为什么标准化如此重要考虑以下两个特征特征A范围0-100特征B范围0-1如果不标准化特征A在距离计算中的权重会远大于特征B导致聚类结果失真。3. 从零实现K-Means算法虽然sklearn提供了现成的K-Means实现但自己编写能加深对算法的理解。以下是核心实现import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D class KMeansCustom: def __init__(self, n_clusters3, max_iter300, tol1e-4): self.n_clusters n_clusters self.max_iter max_iter self.tol tol self.centroids None self.labels None def _initialize_centroids(self, X): # 随机选择初始质心 random_idx np.random.choice(X.shape[0], self.n_clusters, replaceFalse) self.centroids X[random_idx] return self.centroids def _compute_distances(self, X): distances np.zeros((X.shape[0], self.n_clusters)) for i, centroid in enumerate(self.centroids): distances[:, i] np.linalg.norm(X - centroid, axis1) return distances def _assign_clusters(self, distances): self.labels np.argmin(distances, axis1) return self.labels def _update_centroids(self, X): new_centroids np.zeros((self.n_clusters, X.shape[1])) for i in range(self.n_clusters): new_centroids[i] np.mean(X[self.labels i], axis0) return new_centroids def fit(self, X): self._initialize_centroids(X) for _ in range(self.max_iter): distances self._compute_distances(X) self._assign_clusters(distances) new_centroids self._update_centroids(X) # 检查收敛 centroid_shift np.linalg.norm(new_centroids - self.centroids) if centroid_shift self.tol: break self.centroids new_centroids return self def predict(self, X): distances self._compute_distances(X) return self._assign_clusters(distances)算法核心步骤随机初始化聚类中心计算每个点到各中心的距离将点分配到最近的中心重新计算中心位置重复2-4步直到收敛4. 三维可视化让结果一目了然可视化是理解聚类结果的关键。我们使用matplotlib的3D绘图功能def plot_3d_clusters(data, labels, centroidsNone): fig plt.figure(figsize(10, 8)) ax fig.add_subplot(111, projection3d) # 为每个聚类设置不同颜色 colors [r, g, b, c, m, y, k] for i in range(len(np.unique(labels))): cluster_data data[labels i] ax.scatter( cluster_data[:, 0], cluster_data[:, 1], cluster_data[:, 2], ccolors[i % len(colors)], labelfCluster {i}, alpha0.6 ) # 绘制质心 if centroids is not None: ax.scatter( centroids[:, 0], centroids[:, 1], centroids[:, 2], marker*, s300, ck, labelCentroids ) ax.set_xlabel(Feature 1) ax.set_ylabel(Feature 2) ax.set_zlabel(Feature 3) ax.legend() plt.title(3D Cluster Visualization) plt.show() # 使用自定义K-Means kmeans KMeansCustom(n_clusters3) kmeans.fit(normalized_data) labels kmeans.labels # 可视化 plot_3d_clusters(normalized_data, labels, kmeans.centroids)5. 关键参数调优与评估5.1 如何选择K值K值的选择是K-Means的核心问题。常用的方法是肘部法则(Elbow Method)def find_optimal_k(data, max_k10): distortions [] for k in range(1, max_k 1): kmeans KMeansCustom(n_clustersk) kmeans.fit(data) # 计算每个点到其质心的平均距离 mean_distortion 0 for i in range(k): cluster_data data[kmeans.labels i] if len(cluster_data) 0: mean_distortion np.mean(np.linalg.norm( cluster_data - kmeans.centroids[i], axis1 )) distortions.append(mean_distortion / k) plt.plot(range(1, max_k 1), distortions, bx-) plt.xlabel(Number of clusters (k)) plt.ylabel(Average Distortion) plt.title(The Elbow Method showing the optimal k) plt.show() find_optimal_k(normalized_data)5.2 评估聚类质量常用的内部评估指标包括轮廓系数(Silhouette Score)from sklearn.metrics import silhouette_score def evaluate_clusters(data, labels): score silhouette_score(data, labels) print(f轮廓系数: {score:.3f}) return score evaluate_clusters(normalized_data, labels)轮廓系数范围在[-1,1]之间接近1表示样本与同类样本距离近与其他类样本距离远接近0表示样本在两个类的边界上接近-1表示样本可能被分配到了错误的类6. 完整项目实战用户行为分析案例假设我们有一组用户行为数据包含三个维度页面停留时间(秒)点击次数购买金额(元)# 模拟数据生成 np.random.seed(42) n_samples 300 # 生成三个簇的数据 cluster1 np.random.normal(loc[0.2, 0.2, 0.2], scale0.05, size(n_samples, 3)) cluster2 np.random.normal(loc[0.5, 0.8, 0.3], scale0.1, size(n_samples, 3)) cluster3 np.random.normal(loc[0.8, 0.3, 0.7], scale0.07, size(n_samples, 3)) user_data np.vstack([cluster1, cluster2, cluster3]) # 聚类分析 kmeans KMeansCustom(n_clusters3) kmeans.fit(user_data) # 评估 evaluate_clusters(user_data, kmeans.labels) # 可视化 plot_3d_clusters(user_data, kmeans.labels, kmeans.centroids)在实际项目中你可能需要尝试不同的初始化方法如k-means处理异常值结合业务知识解释聚类结果7. 常见问题与解决方案7.1 空簇问题有时某个簇可能不包含任何样本导致计算新质心时出错。解决方法def _update_centroids_safe(self, X): new_centroids np.zeros((self.n_clusters, X.shape[1])) for i in range(self.n_clusters): if np.sum(self.labels i) 0: # 检查簇是否为空 new_centroids[i] np.mean(X[self.labels i], axis0) else: # 为空时重新随机初始化 new_centroids[i] X[np.random.choice(X.shape[0])] return new_centroids7.2 处理不同形状的簇K-Means假设簇是凸形的且大小相似。对于非凸形簇可能需要考虑其他算法如DBSCAN。7.3 大数据集优化对于大型数据集可以考虑使用Mini-Batch K-Means降维后再聚类分布式实现from sklearn.cluster import MiniBatchKMeans mbkmeans MiniBatchKMeans(n_clusters3, batch_size100) mbkmeans.fit(large_data)8. 进阶技巧与扩展8.1 特征工程好的特征能显著提升聚类效果。可以尝试添加多项式特征使用PCA降维结合领域知识构造新特征from sklearn.decomposition import PCA # 降维可视化 pca PCA(n_components2) reduced_data pca.fit_transform(normalized_data) plt.scatter(reduced_data[:, 0], reduced_data[:, 1], clabels) plt.title(PCA Reduced Cluster Visualization) plt.show()8.2 半监督学习如果有少量标签数据可以尝试约束聚类from sklearn.semi_supervised import LabelSpreading # 假设我们有5%的标签数据 n_total len(normalized_data) n_labeled int(0.05 * n_total) labeled_indices np.random.choice(n_total, n_labeled, replaceFalse) labels_partial -np.ones(n_total) # -1表示无标签 labels_partial[labeled_indices] labels[labeled_indices] label_prop_model LabelSpreading(kernelknn, n_neighbors10) label_prop_model.fit(normalized_data, labels_partial) refined_labels label_prop_model.predict(normalized_data)8.3 实时聚类对于流式数据可以实现增量式K-Meansclass IncrementalKMeans: def __init__(self, n_clusters3, decay0.9): self.n_clusters n_clusters self.decay decay # 控制旧质心的影响衰减速度 self.centroids None self.counts np.zeros(n_clusters) # 每个簇的样本计数 def partial_fit(self, X): if self.centroids is None: self.centroids X[:self.n_clusters] for sample in X: distances np.linalg.norm(sample - self.centroids, axis1) closest np.argmin(distances) # 更新计数和质心 self.counts[closest] 1 learning_rate 1 / self.counts[closest] self.centroids[closest] (1 - learning_rate) * self.centroids[closest] learning_rate * sample return self