如何在Python中对含经纬度的数据集实现最近邻地理聚类?
地理坐标点的最近邻聚类解决方案(Python)
核心注意点
地理坐标是球面坐标,不能直接用欧氏距离计算,必须采用球面距离度量(如Haversine公式)或转换为平面投影坐标系后计算,否则会产生较大误差。
具体实现方案
方案1:scikit-learn + 自定义球面距离(中小数据集)
适合数据量不大的场景,用自定义Haversine距离配合聚类算法:
第一步:实现Haversine距离计算
import numpy as np def haversine_distance(latlon1, latlon2): # 经纬度转弧度 lat1, lon1 = np.radians(latlon1) lat2, lon2 = np.radians(latlon2) # Haversine公式计算球面距离(单位:千米) dlat = lat2 - lat1 dlon = lon2 - lon1 a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2 c = 2 * np.arcsin(np.sqrt(a)) earth_radius = 6371 # 地球平均半径(千米) return c * earth_radius
第二步:用DBSCAN做密度聚类(最近邻聚集)
from sklearn.cluster import DBSCAN from sklearn.preprocessing import StandardScaler import pandas as pd # 假设你的数据集是df,包含latitude/longitude/population列 coords = df[['latitude', 'longitude']].values # 标准化坐标(DBSCAN对尺度敏感) coords_scaled = StandardScaler().fit_transform(coords) # 聚类:eps=2表示聚类内点的最大距离为2千米,min_samples=2表示聚类最少包含2个点 db = DBSCAN(eps=2, min_samples=2, metric=haversine_distance) df['cluster_id'] = db.fit_predict(coords_scaled)
方案2:地理空间库(geopandas+pysal)(大数据集)
数据量较大时,用专业地理库效率更高,先转投影坐标系再计算平面距离:
import geopandas as gpd from shapely.geometry import Point from pysal.cluster import KMeans # 1. 转换为GeoDataFrame(WGS84坐标系) geometry = [Point(xy) for xy in zip(df['longitude'], df['latitude'])] gdf = gpd.GeoDataFrame(df, geometry=geometry, crs="EPSG:4326") # 2. 转换为UTM投影坐标系(将球面坐标转平面,距离计算更准确高效) gdf_proj = gdf.to_crs(gdf.estimate_utm_crs()) # 3. 用加权K-Means聚类(可加入人口权重) # 若要加权,可设置weights参数为df['population'] model = KMeans(n_clusters=5) # 指定聚类数量 gdf_proj['cluster_id'] = model.fit_predict(gdf_proj[['geometry.x', 'geometry.y']]) # 4. 转回原WGS84坐标系 gdf = gdf_proj.to_crs("EPSG:4326")
方案3:BallTree快速查询最近邻
如果只需要查询每个点的最近邻而非聚类,用BallTree更高效:
from sklearn.neighbors import BallTree # 经纬度转弧度 coords_rad = np.radians(df[['latitude', 'longitude']].values) # 构建BallTree,使用haversine度量 tree = BallTree(coords_rad, metric='haversine') # 查询每个点的前2个最近邻(排除自身) distances, indices = tree.query(coords_rad, k=2) # 距离转换为千米 distances = distances * 6371
可选:聚类结果可视化
用folium生成交互式地图验证结果:
import folium # 生成地图 m = folium.Map(location=[df['latitude'].mean(), df['longitude'].mean()], zoom_start=10) # 给不同聚类点上色 colors = ['red', 'blue', 'green', 'purple', 'orange'] for _, row in gdf.iterrows(): folium.CircleMarker( location=[row['latitude'], row['longitude']], radius=5, color=colors[row['cluster_id'] % len(colors)], fill=True, fill_color=colors[row['cluster_id'] % len(colors)] ).add_to(m) # 保存地图 m.save('cluster_result.html')
内容的提问来源于stack exchange,提问作者Ashwin Sarath
相关产品推荐
相关产品推荐

