
简介面向城市规划与机器学习交叉领域提供一套基于K均值聚类算法实现城市POI数据功能复合类型识别与复合程度评估的微型项目。项目针对城市地块功能评分、商业与科研机构复合类型识别、商务区/待开发区/住宅区划分等场景给出从数据预处理、特征聚类到结果评估的完整代码流程。包内共7个文件以3个Python脚本为核心分别承担数据处理、聚类建模和结果可视化另含CSV示例数据集、说明文档、项目README及聚类效果图整体仅19KB轻量易读适合数据分析师、GIS爱好者及城市规划相关研究者参考。目前已有46人学习下载。通过该项目可掌握K均值聚类在POI空间数据中的典型应用思路了解如何筛选特征维度、调整聚类数并解读复合功能类型为扩展至更大规模的城市功能分析提供可复用的实验框架。1. 用K均值聚类给城市地块算功能复合一个能直接落地的分析思路拿到一批城市POI点数据餐饮、购物、科教文化、公司企业、住宅小区这些带经纬度的点最想回答的问题往往是“这块地到底是干什么的”。肉眼能看出写字楼密集但“商业与科研机构复合”“商务区边界在哪”“待开发区怎么界定”这类结论靠人盯地图标点既慢又主观还容易吵架。标题里这个项目的思路非常直接把城市切成地块网格统计每个网格里各类POI的数量构成用K均值聚类算法给每个地块打上功能复合类型标签再用信息熵或占比公式算出复合程度评分最后落到地块功能评分表上。整个过程不需要深度学习的算力和标注数据pandas加scikit-learn就能跑完适合做城市规划数据分析、机器学习算法入门以及土地功能建模的从业者。2. POI数据清洗与地块特征矩阵构建把散点数据变成K均值能吃的特征表2.1 POI原始数据长什么样字段、坐标系与三个常见的脏数据POI数据最常见的交付格式是CSV或Excel字段一般包含名称、大类名称、地址、经度、纬度。有些数据源还会带区划代码和电话。大部分人拿到手的第一步不是聚类而是先处理脏数据。我经手过的POI数据里最容易出问题的有三个地方。第一个是坐标系混用。国内POI数据商的坐标系五花八门有的给WGS84GPS原始坐标有的给GCJ-02火星坐标还有给BD-09百度坐标的。如果不同来源的数据直接拼在一起切网格同一个位置的点会漂移几百米跨过网格边界后特征统计就是错的。处理方法很简单在清洗阶段先统一转成WGS84GCJ-02转WGS84有公开的纠偏公式BD-09需要先转GCJ-02再转WGS84两步都是纯数学运算不涉及任何外部服务。第二个是类别体系不一致。同一个“星巴克”一个数据源给的是“餐饮服务咖啡厅”另一个给的是“美食咖啡厅”还有一个干脆归到“生活服务”。这种粒度差异会在聚合时把特征向量搞乱。我在做这类项目时一般先把细分类映射到七到八个大类商业、科研、公司、住宅、公共服务、开放空间、工业其余归入其他。映射表单独存成一个CSV后面改起来方便。第三个是重复点和无效坐标。同一家店一个月被采集两三次很常见经纬度完全相同的点直接去重经纬度为0或者在国界范围外的记录直接删掉。注意不要用dropna一把梭有的数据源会把无效坐标写成“0.000000”而不是空值dropna根本发现不了。import pandas as pd import numpy as np # 原始POI数据至少包含 lng, lat, category 三列 poi_df pd.read_csv(poi_raw.csv, dtype{category: str}) print(原始记录数:, len(poi_df)) # 1. 坐标清洗剔除经纬度越界或为空的记录 # 中国范围大致是 lng 73~135, lat 3~53超出这个范围的基本是脏数据 poi_df poi_df.dropna(subset[lng, lat]) poi_df poi_df[ (poi_df[lng] 73) (poi_df[lng] 135) (poi_df[lat] 3) (poi_df[lat] 53) ] # 经纬度为0的记录虽然不越界但明显是无效数据单独剔除 poi_df poi_df[(poi_df[lng] ! 0) (poi_df[lat] ! 0)] print(坐标清洗后:, len(poi_df)) # 2. 去除完全重复的记录同一坐标、同一大类 poi_df poi_df.drop_duplicates(subset[lng, lat, category]) print(去重后:, len(poi_df)) # 3. 细分类归并为7个大类映射表单独维护方便后续调整 category_map { 餐饮服务: 商业, 购物服务: 商业, 金融保险: 商业, 科教文化: 科研, 公司企业: 公司, 商务住宅: 住宅, 体育休闲: 开放空间, 绿地广场: 开放空间, 政府机构: 公共服务, 医疗保健: 公共服务, 交通设施: 公共服务 } poi_df[cat_group] poi_df[category].map(category_map) # 无法归类的记录保留但标记为“其他”不直接删后面分析时单独观察 poi_df[cat_group] poi_df[cat_group].fillna(其他)这段代码里的category_map是归并的核心。判断一份POI数据能不能用先看映射到每个大类后的记录数分布如果某个大类占了90%以上说明数据源本身类别偏科聚类结果会非常难解释。另外drop_duplicates的subset只取经纬度和大类不包含名称因为同一地点不同品牌门店比如一个商场里有多家奶茶店是真实存在的POI不该被去重误伤。2.2 地块网格划分500米格网的参数逻辑POI是点数据点本身没有“地块”的概念。做功能识别前得先把城市切分成一个个空间单元。这块有两个路线真正的路网地块用地红线或街区边界和规则网格。路网地块业务上最准但拿路网数据要额外找数据源而且不同城市的路网密度差异大切割出来的地块面积方差也大特征归一化时很麻烦。规则网格不需要额外数据经纬度一算就出来可复现性强是多数项目的首选。网格尺寸的选择直接决定分析粒度。100米网格太碎一个商场会被切成好几块POI被拆散1000米网格太粗混合功能被平均掉看不出复合类型。我一般先用500米差不多就是城市中心一个街区的尺度POI点能聚起来特征区分度也够。网格投影方式上用地理坐标直接切会有轻微的面积变形但做功能识别不是面积量算这个误差可以接受如果项目要求更严格再用UTN投影转一次。# 4. 按500米格网给每个POI打上网格编号 # 纬度方向 0.005 度约等于 500 米经度方向在高纬度会收缩这里按固定度数切 grid_size 0.005 # 约500米 poi_df[cell_x] np.floor(poi_df[lng] / grid_size) poi_df[cell_y] np.floor(poi_df[lat] / grid_size) poi_df[cell_id] ( poi_df[cell_x].astype(int).astype(str) _ poi_df[cell_y].astype(int).astype(str) ) # 5. 构建特征矩阵每个网格内各POI大类的数量 feature_table poi_df.groupby([cell_id, cat_group]).size().unstack(fill_value0) # 按行归一化为占比消除网格间POI总量差异 feature_table feature_table.div(feature_table.sum(axis1), axis0).fillna(0) print(地块网格数:, feature_table.shape[0]) print(feature_table.head())归一化这一步是特征工程的关键。如果不做占比归一化市中心每个网格POI总量天然比郊区大K均值聚类结果会把“市中心”和“郊区”分成两类而不是把“商业混合”和“住宅”分成两类。归一化成占比后特征向量描述的是“这个网格的功能结构”而不是“这个网格的热闹程度”这正是复合类型识别要的。2.3 待开发区和低密度区域的特殊处理标题里明确提到“待开发区”这类地块在特征矩阵里表现很特殊POI总量极少甚至为0。占总比归一化后如果这个网格恰好有1个POI占比就是100%会被模型当成极度单一的纯功能区这跟业务认知完全不符。我一般用两个办法处理一是设置最小POI量阈值网格POI总数小于等于2的直接标记为“待开发/无数据”不参与聚类二是保留这些网格但加一个特征列“POI总量”参与聚类让模型自己学出密度维度。从实际项目效果看先剔除再聚类更干净因为“待开发区”本来就不靠POI结构定义靠的是密度阈值。阈值设在5以下比较合理——5个POI基本是街边几个小店谈不上功能结构。3. K均值聚类识别功能复合类型为什么选K均值、K等于几、标签怎么贴3.1 对比DBSCAN与高斯混合K均值在“地块功能”任务上的三个优势特征矩阵建好后选聚类算法。K均值是这个任务的默认选择不是因为最先进而是因为最合适。第一特征矩阵是连续数值型K均值期望的就是这种输入计算快几千个网格分分钟跑完。第二K均值产出“簇中心”这个中心向量就是一类地块的“平均功能结构”直接读出“商业60%、科研25%、住宅15%”这样的信息业务人员能看懂。第三K均值实现门槛低scikit-learn一行调用不需要处理DBSCAN的邻域参数调优。DBSCAN在这个任务上并不合适密度聚类适合发现任意形状的簇但地块功能是固定数量的“典型结构”不存在形状问题而且DBSCAN对eps参数非常敏感网格点密度分布不均匀时要么全连成一个簇要么满地都是噪声点。高斯混合模型在理论上更优雅允许一个地块同时属于多个簇的概率但输出是概率分布业务上贴标签反而多一步。3.2 标准化、K值选择与随机种子先跑一批实验再定参数选K是整个流程里最像“玄学”的部分。肘部法看inertia的拐点轮廓系数看簇的分离度两者结合能给一个合理范围但最终K值还得结合业务常识判断——你预期这个城市的典型功能类型有几种。常见城市地块大概有纯商业、纯住宅、商业-科研复合、公司聚集商务区、工业区、待开发区这么几类K取4到6之间是常态。如果肘部法给出8先别急着用8看中间K值的簇中心是否可解释选可解释性最好的那个。from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score # K均值对特征的量纲敏感先做标准差标准化 X feature_table.values scaler StandardScaler() X_scaled scaler.fit_transform(X) # 在K2到9之间跑肘部法和轮廓系数实验 K_range range(2, 10) experiment [] for k in K_range: km KMeans(n_clustersk, random_state42, n_init10, max_iter300) labels km.fit_predict(X_scaled) sil silhouette_score(X_scaled, labels) experiment.append({K: k, inertia: km.inertia_, silhouette: sil}) print(fK{k}, inertia{km.inertia_:.2f}, silhouette{sil:.3f})这里的关键参数是n_init10和random_state42。K均值的初始质心是随机选的n_init表示用10组不同初始质心跑10次取最优结果random_state固定随机种子保证结果可复现——否则每次运行聚类结果都不一样后续业务分析根本没法推进。max_iter300是迭代上限特征矩阵维度不高时实际迭代几十次就会收敛。轮廓系数在K2到4之间通常会有一个峰值但要注意不要让轮廓系数主导决策。POI功能识别的经验是轮廓系数反映的是“簇内紧凑、簇间分离”的几何指标功能地块往往有天然的过渡带轮廓系数不会特别高0.3到0.5就是常见区间。K的选择优先级是“簇中心可解释 业务常识 轮廓系数”。3.3 聚类结果到业务标签先看簇中心再贴类型名聚类输出的是数字编号0、1、2...编号本身没有业务含义。贴标签时不要臆断先打印每个簇的中心向量标准化后的簇中心要反标准化回原始占比看每一列占比最大的类别是什么再组合成业务名称。# 选定K5示例值按实验曲线定 k_final 5 km KMeans(n_clustersk_final, random_state42, n_init10, max_iter300) kmeans_labels km.fit_predict(X_scaled) # 反标准化回原始占比坐标系直接看物理含义 cluster_centers scaler.inverse_transform(km.cluster_centers_) centers_df pd.DataFrame(cluster_centers, columnsfeature_table.columns) centers_df[cluster_id] range(k_final) centers_df[地块数] pd.Series(kmeans_labels).value_counts().sort_index().values print(centers_df.round(3))反标准化这步我见过不少人跳过直接看标准化的簇中心结果就是中心向量里出现负值没法读。scaler.inverse_transform会把标准化空间的值还原成占比空间的值。看中心表格时注意如果某个簇中心所有类别占比都比较均匀比如每个都在10%到20%之间那就是高度复合型地块如果某一列超过0.6那就是单功能主导地块贴“纯商业”“纯住宅”标签。标题里的“商业-科研机构复合类型”就是这样来的——簇中心里商业占比和科研占比同时排前两位且各自都大于0.2。标签定好后把kmeans_labels回填到地块表这一步产出物就是“每个地块一个功能复合类型”可以接GIS工具画专题图了。4. 复合程度评估与地块功能评分从类型标签到连续分值的计算路径4.1 两种评估路线信息熵比与最大占比反推聚类给的是离散类型但“复合程度”是连续量。同样挂着“商业-科研复合”标签的地块一个是商业60%、科研40%另一个是商业90%、科研10%复合程度完全不同。要量化这个差异有两条常用路线。路线一是信息熵。把每个地块的类别占比看成离散概率分布熵越大说明分布越均匀、复合程度越高。直接用scipy.stats.entropy算出来的是原始熵数值范围和特征维度有关最好除以类别数的对数值做归一化让结果落在0到1之间0代表单一功能1代表完全均匀。路线二是最大占比反推占比最大的类别如果是1.0复合程度为0如果占比最大的类别接近1/类别数复合程度接近1。两条路线结果基本一致但信息熵对“次主导类型”更敏感。商业60%科研40%和商业90%科研10%最大占比反推出来一个是0.33一个是0.11信息熵算出来差距更大。做评估时优先用熵值法和业务直觉更吻合。from scipy.stats import entropy # 特征表里每行是一个地块列是各POI大类的占比 def composite_entropy(row): # 剔除占比为0的类别只保留实际存在的功能 p row[row 0].values if len(p) 1: return 0.0 # 单一功能或空地块复合程度为0 # 除以类别数的对数值归一化到 [0, 1] return entropy(p, baselen(p)) feature_table[composite_entropy] feature_table.apply(composite_entropy, axis1) # 最大占比反推法作为交叉验证指标 def composite_from_max(row): p row[row 0].values if len(p) 1: return 0.0 p_max p.max() # 线性映射p_max1/n 时得1p_max1时得0 return (1 - p_max) / (1 - 1 / len(p)) if len(p) 1 else 0.0 feature_table[composite_max] feature_table.apply(composite_from_max, axis1)entropy(p, baselen(p))里base参数传的是实际存在的类别数不是全部7类。这个细节很关键一个只有两类POI的地块最大可能熵是log2一个七类齐全的地块最大可能熵是log7。如果不按实际类别数归一化七类齐全但分布不均的地块会被误判为高复合因为原始熵值大。4.2 地块功能评分加权求和与分档阈值复合程度评估是“功能是不是混合”功能评分是“混合起来值多少钱”。评分要做的是给不同POI大类配权重然后按占比加权求和。权重怎么定不是算法问题是业务导向问题。如果项目关注的是商业活力商业类权重拉高关注的是产学研融合科研权重拉高。我一般先用等权重算一个基线分再按项目需求调整。评分结果的输出不只是单个分值还要分档。0到1的连续分可以切成低、中、高三档也可以用自然断点法自动分。分档的意义在于让“商务区”“住宅区”“待开发区”这些地块级别标签和“评分”对得上——商务区通常是高评分高复合住宅区是中评分中低复合待开发区是低评分低复合。# 三类核心功能的评分权重其余类别给低权重 score_weights { 商业: 0.35, 科研: 0.25, 公司: 0.20, 住宅: 0.10, 公共服务: 0.05, 开放空间: 0.05 } # 权重需要和特征表的列完全对齐缺失的类别自动按0计算 score_cols list(score_weights.keys()) feature_table[function_score] 0.0 for col in score_cols: if col in feature_table.columns: feature_table[function_score] feature_table[col] * score_weights[col] # 按评分分档0.4以下低分0.4-0.7中分0.7以上高分 bins [0, 0.4, 0.7, 1.0] labels [低, 中, 高] feature_table[score_level] pd.cut(feature_table[function_score], binsbins, labelslabels)pd.cut的阈值可以按数据分布调整。如果项目落在新区整体评分偏低硬套0.4和0.7会把所有地块都分到低档这时改成用分位数切30%以下低分、30%到70%中分、70%以上高分能更清楚看出对比关系。评分不是物理量是相对排序工具。4.3 商业-科研复合类型与商务区、待开发区的典型画像把K均值聚类得到的类型、复合程度评分和地块网格三张表一合并就能读出几类典型地块的画像。商业-科研复合型地块的典型特征是商业占比和科研占比都在0.2到0.4之间公司占比也比较高常见于大学科技园周边和产学研一体化片区。这类地块复合程度评分通常在0.7以上功能评分在中高分档。商务区的典型画像则是公司类占比最高商业次之科研很低。待开发区则是POI总量低于阈值聚类阶段被剔除评分也归入低档。住宅区的典型特征是住宅类占比超过一半商业占两成左右科研占比接近0。这些画像可以用来做聚类结果的业务校验——如果某个簇中心显示“商业40%、科研35%、住宅15%”但对应的网格在地图上散布在城市各个方向互不相连要小心这个簇是不是把多个不同功能混在一起的“垃圾筐”得回头调整K值或特征列。5. 避坑K均值在POI功能识别中常见的五个翻车现场5.1 现象每次运行聚类结果都不一样地块标签变来变去原因K均值初始质心是随机选择的每次运行可能收敛到不同的局部最优解。有人换一台机器跑结果完全不同就怀疑代码写错了实际是随机种子没固定。解决KMeans(n_clustersk, random_state42, n_init10)。固定random_state并让n_init不要低于10。如果是做参数实验所有实验用同一个随机种子K值之间的结果才有可比性。5.2 现象跑了K5结果一半地块被归到同一个簇原因特征矩阵里大量网格的POI结构很相似比如整个城市规划了成片的单一住宅区K均值把多数样本塞进一个“平均化”的簇里这个簇中心向量看起来每个类别都在实际上没有任何地块符合它。解决检查特征矩阵的分布统计占比超过80%的单一类别地块数量。如果单功能地块占比过高考虑先把“单一功能地块”单独分出来只对剩余地块做聚类避免K均值被大样本拉偏。5.3 现象聚类结果里“待开发区”跟“住宅新区”混在一起原因待开发区POI少但住宅新区POI也少归一化成占比后待开发区可能因为有一两个便利店被归到“商业”住宅新区因为有一两个底商也被归到“商业”结果混成一体。解决聚类前先按POI总量阈值比如小于等于5筛出待开发区不参与聚类直接标成“待开发”。住宅新区只要POI总量够参与聚类后会被住宅占比主导和待开发区分开。5.4 现象商业-科研复合类型在聚类结果里怎么也分不出来原因POI大类归并时把“科教文化”和“公司企业”混在一个映射组里或者数据源里科研类POI本身数量极少占比被商业稀释簇中心里科研列始终低于0.1。解决检查类别映射表和原始数据分类别统计。如果科研类POI总数不足商业的十分之一就不要指望K均值能分出科研复合簇。可以先按“公司企业”和“科教文化”两类合并成“科创”再和商业组合复合簇就能出来。这是数据分布问题不是算法参数问题。5.5 现象轮廓系数很高但聚类结果业务上没法解释原因轮廓系数高说明数据里确实存在几何上分得很开的簇但几何分离不等于业务类型。比如特征列里“开放空间”占主导的网格在地理上聚成一簇轮廓系数高但业务上你并不想要“公园簇”和“绿地簇”分开它们都是低开发区域。解决选K的时候用“业务可解释性”做最终裁决轮廓系数只当参考。对中心向量做人工判断每个簇是否有明确的形象纯住宅、商业-科研复合、商务区如果有两个簇中心高度相似只差一个类别占比0.05的差异合并它们再说。不要为了追求轮廓系数峰值而盲目增加K值。6. 用轮廓系数与可视化复合类型结果让模型输出能写进项目报告6.1 交叉验证的落地技巧把聚类结果按真实地块边界叠加聚类跑完、评分算完最后一步是验证。光看轮廓系数不够过瘾拿真实路网边界或行政区边界叠加聚类结果一眼就能看出逻辑对不对。具体做法是把聚类标签按网格中心点转成GeoDataFrame跟城市的功能规划图叠加。如果某块的网格被划成“商业-科研复合”但规划图上这块是纯二类居住用地要么是POI数据不准要么是规划滞后值得拉出来单独排查。这也是项目汇报里最有说服力的一页图。6.2 一张图看全结果网格变颜色深浅代表复合程度可视化不要只画聚类颜色要把复合程度评分用颜色深浅叠在一起。离散的聚类标签用色相区分不同颜色代表不同复合类型连续的复合程度评分用颜色深浅表示同一色相从浅到深。用matplotlib的scatter按经纬度画散点图就能完成地块网格化后每个网格取中心点画点即可。import matplotlib.pyplot as plt # feature_table 里已有 cell_id、复合程度和聚类标签 plot_df feature_table.reset_index() # 从cell_id里把格网左下角坐标拆出来加半个格网边长得到中心点 plot_df[lng_center] (plot_df[cell_x] 0.5) * grid_size plot_df[lat_center] (plot_df[cell_y] 0.5) * grid_size # 用散点图按聚类标签着色点的尺寸映射功能评分 fig, ax plt.subplots(figsize(10, 8)) scatter ax.scatter( plot_df[lng_center], plot_df[lat_center], cplot_df[kmeans_label], cmaptab10, splot_df[function_score] * 200, alpha0.7 ) ax.set_aspect(1.2) ax.set_title(POI功能复合类型分布点大小功能评分) plt.colorbar(scatter, label聚类标签) plt.show()点大小的映射让高分高复合的地块在图上自然“跳出来”商务区和商业-科研复合区是大点住宅和待开发区是小点整张图信息量比单独画聚类颜色大得多。做汇报时再叠一张透明底图领导和规划同事一眼就能看懂重点在哪。6.3 一个评分校准技巧用业务先验重设权重别让模型替你背锅最后分享一个踩过两次坑的经验权重设置不是模型的事是项目目标的事。同一个地块用商业0.35的权重和用科研0.4的权重功能评分排名会明显不同如果项目报告里把评分当作地块价值的结论权重依据必须写明——“基于商业活力导向”或“基于科研创新导向”。我现在的习惯是先在配置里放两套权重活力导向和创新导向各跑一遍评分输出两张图让业务方自己选。不要试图找一套“通用正确”的权重那既不存在也不可信。希望“K均值聚类识别POI功能复合”这个方案能帮你把手里的点数据变成决策时能有据可依的地块档案少一点拍脑袋多一点可复现的定量分析。本文还有配套的精品资源点击获取