在R中按距离聚类空间点:无需指定聚类数实现组内间距≤11km
基于11km阈值的空间点聚类解决方案
核心思路
你需要的是组内任意两点间距不超过11km的聚类,这类需求适合用完全连接(Complete Linkage)的层次聚类,结合地理距离计算,无需提前指定聚类数量。此前的问题大概率是因为用了错误的距离计算方式(比如直接对经纬度算欧氏距离),或是采用了需要指定聚类数的算法(如K-means)。
实现步骤(R语言)
1. 加载依赖包
需要geosphere包计算球面地理距离,层次聚类函数是R自带的:
library(geosphere)
2. 导入数据
假设你的数据框名为df,直接使用你提供的结构:
df <- structure(list(station = c("BE01", "BE02", "BEUWM01", "BL01", "BL02", "PB01", "PB02", "PB03", "PB04", "PB05", "PB06", "PB07", "PB09", "PB10", "PB11", "PB12", "PB13", "PB14", "PB15", "PB16", "PB17", "PB18", "PB19", "PB20", "PB21", "PB22", "PB23", "PB24", "PB25", "PB26", "PB27", "PB28", "PB29", "PB30", "PB4G01", "PB4G02", "PBUWM01", "PBUWM02", "SA01", "SA02", "SA02b", "SA03", "SA04", "SA05", "SA06", "SA07", "SA11", "SAUWM01", "VB01", "VB02", "VB03", "VB04"), longitude = c(71.6546833333333, 71.6748333333333, 71.66293, 72.4337833333333, 72.4347, 71.7342, 71.7632, 71.7992, 71.8092, 71.8326916667, 71.8405, 71.8796, 71.96835, 71.9697666666667, 71.9727, 71.9745666666667, 71.9385075, 71.8685, 71.8524, 71.8414, 71.8294, 71.758275, 71.7578, 71.7468, 71.9733, 71.9795, 71.9741, 71.9209, 71.8959, 71.8228, 71.7498, 71.7323, 71.9068, 71.7474, 71.9398, 71.8329, 71.98115, 71.75197, 72.24793, 72.241, 72.23027, 72.2569, 72.2812, 72.1980666667, 72.2116, 72.221, 72.2636, 72.24754, 72.2155, 72.2405, 72.2156, 72.2488), latitude = c(-5.25671666666667, -5.2662, -5.24915, -5.2579, -5.2432, -5.2815, -5.2459, -5.2461, -5.2448, -5.23439583333, -5.2567, -5.2694, -5.24165, -5.33015, -5.3344, -5.37878333333333, -5.39709575, -5.4271, -5.4229, -5.4308, -5.4406, -5.456505, -5.3823, -5.3512, -5.2695, -5.3039, -5.3521, -5.4126, -5.4243, -5.4644, -5.3957, -5.3181, -5.2668, -5.4261, -5.2582, -5.445, -5.33995, -5.38898, -5.31183, -5.3056, -5.316745, -5.2985, -5.3347, -5.35026666667, -5.3686, -5.3225, -5.3327, -5.30114, -5.5452, -5.5207, -5.5247, -5.546)), row.names = c(NA, -52L), class = "data.frame")
3. 计算地理距离矩阵(单位:km)
不能直接用经纬度计算欧氏距离(经纬度的度单位无法代表实际公里数),必须用球面距离算法:
# 提取经纬度坐标,geosphere要求顺序为经度、纬度 coords <- df[, c("longitude", "latitude")] # 计算所有点对的球面距离,转换为km(默认输出为米) dist_matrix <- distm(coords, fun = distHaversine) / 1000
4. 执行聚类并分配组ID
采用完全连接的层次聚类,保证组内最远两点距离不超过设定阈值,正好满足你的需求:
# 构建层次聚类树,方法为完全连接 hc <- hclust(as.dist(dist_matrix), method = "complete") # 按11km阈值切割聚类树,生成组ID df$group_id <- cutree(hc, h = 11)
5. 验证结果
可以检查分组数量和组内最大距离是否符合要求:
# 查看各分组的点数分布 table(df$group_id) # 验证每个组的最大点间距 lapply(split(df, df$group_id), function(x) { coords_group <- x[, c("longitude", "latitude")] max_dist <- max(distm(coords_group, fun = distHaversine)/1000) cat("分组", unique(x$group_id), "最大间距:", round(max_dist, 2), "km\n") })
此前方法失败的原因
- 若使用K-means等需要指定聚类数的算法,显然不符合无需预设分组数的需求;
- 若直接对经纬度计算欧氏距离,得到的数值无实际地理意义,会导致聚类逻辑完全错误,出现每个点单独分组的情况;
- 若使用DBSCAN等密度聚类算法,其核心逻辑是基于“核心点邻域密度”,和你要求的“组内任意两点间距不超过阈值”逻辑不符。
内容的提问来源于stack exchange,提问作者mikejwilliamson
相关产品推荐
相关产品推荐

