如何在Python中基于单一Z坐标值进行数据聚类,并优化聚类数量、评估聚类效果及扩展至3D聚类?
Hey Jules, let's break down how to tackle this problem step by step—from prepping your data to evaluating clusters and scaling up to 3D.
1. 数据预处理:提取目标特征
首先你需要把JSON里的Z坐标(以及后续3D用的lat/lng)提取出来,转换成sklearn能处理的格式。这里用pandas会很方便:
import json import pandas as pd # 读取本地JSON文件(先把OneDrive的文件下载到本地) with open('your_markers.json', 'r') as f: marker_data = json.load(f) # 转换成DataFrame,方便后续操作 df = pd.DataFrame(marker_data) # 提取Z坐标(一维聚类用);如果是3D聚类,就提取lat、lng、z三列 z_coords = df[['z']].values # sklearn要求输入是2D数组,所以用双括号保持维度
2. 选择合适的聚类算法
对于你的需求(固定簇数、簇内样本距离相近),K-Means是最适合的选择——它简单高效,核心目标就是最小化簇内样本的平方距离和,完全匹配你“簇内对象彼此距离相近”的要求。
如果你后续想探索基于密度的聚类(比如自动识别异常值),可以试试DBSCAN,但K-Means更适合你当前“5-10个簇”的明确需求。
3. 确定最优簇数(5-10之间)
要找到最优的簇数,推荐两种方法结合使用:
方法1:肘部法则(Elbow Method)
观察簇内平方和(inertia)随簇数增加的变化曲线,曲线的“拐点”就是最优簇数——拐点之后inertia下降速度明显放缓,说明再增加簇数对聚类效果提升不大。
from sklearn.cluster import KMeans import matplotlib.pyplot as plt # 设定簇数范围(你要的5到10) cluster_counts = range(5, 11) inertia_values = [] for n in cluster_counts: kmeans = KMeans(n_clusters=n, random_state=42) # random_state保证结果可复现 kmeans.fit(z_coords) inertia_values.append(kmeans.inertia_) # 绘制肘部曲线 plt.figure(figsize=(8, 4)) plt.plot(cluster_counts, inertia_values, 'bo-', linewidth=2) plt.xlabel('Number of Clusters') plt.ylabel('Inertia (Within-Cluster Sum of Squares)') plt.title('Elbow Method for Optimal Cluster Count') plt.grid(True) plt.show()
方法2:轮廓系数(Silhouette Score)
轮廓系数衡量的是样本与自身簇内样本的相似度,以及与其他簇样本的差异度,取值范围是[-1,1],越接近1说明聚类效果越好。我们可以计算不同簇数下的平均轮廓系数,选最高的那个:
from sklearn.metrics import silhouette_score sil_scores = [] for n in cluster_counts: kmeans = KMeans(n_clusters=n, random_state=42) cluster_labels = kmeans.fit_predict(z_coords) sil_score = silhouette_score(z_coords, cluster_labels) sil_scores.append(sil_score) # 绘制轮廓系数曲线 plt.figure(figsize=(8, 4)) plt.plot(cluster_counts, sil_scores, 'ro-', linewidth=2) plt.xlabel('Number of Clusters') plt.ylabel('Average Silhouette Score') plt.title('Silhouette Score for Optimal Cluster Count') plt.grid(True) plt.show()
4. 评估聚类效果
除了上面两种方法,还有两个常用指标可以辅助判断:
- Calinski-Harabasz指数:值越高说明簇内越紧密、簇间越分散,聚类效果越好
- Davies-Bouldin指数:值越低说明聚类效果越好
示例代码:
from sklearn.metrics import calinski_harabasz_score, davies_bouldin_score # 假设你选了最优簇数n=7 optimal_n = 7 kmeans_opt = KMeans(n_clusters=optimal_n, random_state=42) labels_opt = kmeans_opt.fit_predict(z_coords) print(f"Calinski-Harabasz Score: {calinski_harabasz_score(z_coords, labels_opt):.2f}") print(f"Davies-Bouldin Score: {davies_bouldin_score(z_coords, labels_opt):.2f}")
5. 2D地图上可视化聚类结果
把聚类标签和原数据结合,就可以在2D地图上用颜色、标记或文字标识区分不同Z层级的簇:
# 给原数据添加聚类标签 df['z_cluster'] = labels_opt # 绘制2D地图标记,用颜色区分簇 plt.figure(figsize=(10, 8)) scatter = plt.scatter(df['lng'], df['lat'], c=df['z_cluster'], cmap='viridis', alpha=0.7) plt.xlabel('Longitude (X)') plt.ylabel('Latitude (Y)') plt.title('2D Map Markers Colored by Z-Level Cluster') plt.colorbar(scatter, label='Cluster ID') # 可选:在每个标记旁添加簇编号(适合数据量不大的情况) for idx, row in df.iterrows(): plt.annotate(str(row['z_cluster']), (row['lng'], row['lat']), fontsize=8, ha='center') plt.show()
6. 扩展到3D聚类
如果要基于lat(Y)、lng(X)、z(Z)三个维度聚类,只需要修改特征输入即可,流程和一维聚类完全一致:
步骤1:标准化特征(重要!)
因为lat/lng和Z的数值尺度可能差异很大(比如lat是几十,Z是几百),K-Means对尺度敏感,所以先标准化:
from sklearn.preprocessing import StandardScaler # 提取3D特征 xyz_coords = df[['lng', 'lat', 'z']].values # 标准化特征 scaler = StandardScaler() xyz_scaled = scaler.fit_transform(xyz_coords)
步骤2:3D聚类与可视化
# 用最优簇数进行3D聚类 kmeans_3d = KMeans(n_clusters=optimal_n, random_state=42) df['3d_cluster'] = kmeans_3d.fit_predict(xyz_scaled) # 绘制3D散点图 fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(111, projection='3d') scatter_3d = ax.scatter(df['lng'], df['lat'], df['z'], c=df['3d_cluster'], cmap='viridis', alpha=0.7) ax.set_xlabel('Longitude (X)') ax.set_ylabel('Latitude (Y)') ax.set_zlabel('Altitude (Z)') ax.set_title('3D Clustering of Markers') plt.colorbar(scatter_3d, label='3D Cluster ID') plt.show()
小提示
- 如果你的Z值分布有明显的分层(比如多个离散的高度层),K-Means会自动把这些层分成簇,效果会很好
- 要是数据里有异常值,可以先做简单的清洗(比如移除Z值超出合理范围的样本),避免影响聚类结果
内容的提问来源于stack exchange,提问作者Jules




