
简介面向城市规划与数据分析场景这套基于K均值聚类的机器学习项目源码及配套数据文件用于对城市POI数据进行功能复合类型识别与复合程度评估。资源共7个文件压缩包大小约19KB包含3个Python脚本分别负责数据预处理、聚类建模与结果分析、1个CSV地块数据、1个说明文件、1张聚类可视化图及1份Markdown文档可支撑从原始数据清洗到地块功能评分的完整流程。目前已有46人学习下载适合机器学习初学者、城市规划专业学生及相关从业者参考。借助该项目读者能够掌握K均值算法在POI多维度特征聚类中的实际应用思路理解商业-科研机构复合类型、商务区、待开发区、住宅区等不同功能地块的判别方法并可基于附带数据和脚本快速复现实验、调整聚类参数进一步迁移到其他城市数据的功能评估与规划决策中。1. 地址点密度背后藏着城市功能的拼图游戏城市POI数据看起来只是一堆带名字、类别和经纬度的点位但当你把几万个餐饮、公司、小区门口、科研楼宇的坐标叠加到同一张地块上它立刻就变成了城市功能的“体检单”。K均值聚类算法在POI功能复合类型识别这件事上几乎是入门机器学习项目里最实用的一把刀它不需要标注数据只需要特征矩阵就能把地块分成“纯住宅”“商业-科研复合”“待开发区”等隐含类型再配合简单的熵指标就能给每个地块打一个功能复合程度评分。适合刚接触机器学习、手里有一份城市POI数据但不知道从哪下手的从业者也适合想把“地块功能评分”这种模糊概念落成可量化指标的数据分析项目。这篇文章不讲花架子直接告诉你我一般怎么把POI数据变成K均值聚类能吃的样本、K值怎么选不翻车、复合程度评估用什么公式以及真实数据里那些让人头大的坑。核心思路和代码可以照抄但你跑完第一轮结果后大概率还是会回来读一遍第五部分。2. 把经纬度点变成地块画像面向K均值聚类的POI特征工程2.1 从点数据到地块单元的映射逻辑原始POI数据往往是“点”的集合某坐标上有一个“星巴克咖啡”另一个坐标上是“某某大学”。要让K均值聚类起作用得先把这些点归到某个“地块”上。这里的地块不是宗地意义上的规划地块而是一个分析单元。常见做法是用固定边长的网格——比如500米×500米——或者用路网切割生成的街区。我实际项目里更推荐网格因为路网数据不容易拿到干净的。映射逻辑很简单给每个网格一个唯一ID然后把落在网格内的POI按照大类累加计数。为什么不直接把经纬度特征扔进聚类因为K均值对坐标点做聚类得到的是一堆“空间簇”不是功能簇。你要的是“这个地块里有什么业态”不是“哪个区域POI密度高”。所以特征必须是每个地块的业态频数。下面是一个最小可跑的网格映射脚本假设手里有一份POI CSV字段至少包括lng、lat、category三类。import pandas as pd import numpy as np # 读取原始POI数据category请替换为你自己的大类字段 poi pd.read_csv(poi.csv, dtype{category: str}) poi poi.dropna(subset[lng, lat, category]) # 定义网格大小米这里用0.005度近似500米 grid_size 0.005 poi[grid_x] np.floor(poi[lng] / grid_size).astype(int) poi[grid_y] np.floor(poi[lat] / grid_size).astype(int) poi[grid_id] poi[grid_x].astype(str) _ poi[grid_y].astype(str) # 每个网格内统计各类别POI数量NaN填0 pivot poi.pivot_table( indexgrid_id, columnscategory, valueslng, aggfunccount, fill_value0 ) pivot pivot.loc[:, pivot.sum(axis0).sort_values(ascendingFalse).index]逻辑说明pivot_table是这一步的核心valueslng只是占位真正用的是aggfunccount。网格ID用格坐标拼接而成保证经纬度相近的点归到同一个ID。输出矩阵的行是一个地块列是各类POI数量。参数说明grid_size0.005在赤道附近约合550米如果你在北方城市用0.005会略微偏大可以按0.005 * cos(lat)换算但K均值对网格大小不敏感只有特性敏感所以先用固定值跑通再说。2.2 类别粗分与频数标准化决定聚类上限的先手棋拿到POI原始类别后不要直接进入聚类。原始POI类别往往有上百种比如“餐饮服务中餐厅”“餐饮服务西餐厅”这对K均值来说全是稀疏矩阵里的孤立点。必须先将类别粗分为一级大类住宅、商业、餐饮、公司企业、科教文化、医疗、交通、政府、绿地休闲等。粗分后的类别数量控制在815个否则K均值在高维稀疏矩阵上表现极不稳定。我一般按一级类别字段做映射如果没有就用原始分类名的第一段。接下来是频数标准化这一步经常被忽略K均值用欧氏距离计算相似度如果“餐饮”频数普遍几百“科研机构”只有十以内聚类结果会被高频类别完全主导。标准做法是取对数后做Z-Score或者至少做Min-Max归一化。取对数是因为POI数量满足长尾分布直接归一化会被几栋写字楼拉爆。from sklearn.preprocessing import StandardScaler # 取对数避免极端大数支配距离 log_data np.log1p(pivot.values) # log1p处理0值 # Z-Score标准化让每个业态维度的均值0方差1 scaler StandardScaler() scaled_data scaler.fit_transform(log_data) # 保留行列索引方便后续回看 feature_names pivot.columns.tolist() index_names pivot.index.tolist()参数说明np.log1p对0值友好加1后再取对数再小的频数也不会变成负无穷。StandardScaler让每个特征维度在距离计算里等权重。如果你希望住宅类属性权重更大可以在标准化后乘以权重系数但前提是你对这块土地有业务先验。我一般不会一开始就加权重先跑一版无权重聚类再用轮廓系数验证权重只作为后手。2.3 剔除空网格与POI密度极端值给聚类减负网格化之后一定会出现大量“无POI”的空网格它们对应的全零行会让K均值聚出一个“无人区”类别看似合理但没有分析价值。常见处理是直接删除全零行只保留POI有效覆盖的区域。同时还要处理那些POI数量异常大的网格——比如市中心某路口可能有几千个POI它们是同一栋综合体的多个入口会让频数列的方差过大。我的做法是删除行和小于等于0的网格再按每行总量做分位数截断。这块没有标准公式血泪经验是先把每类数量在95百分位以上的值截断防止单个异常点带偏簇中心。注意这只会影响数值不会改变网格编号顺序。3. K均值聚类识别功能复合类型从K值选定到类型命名3.1 使用肘部法则与轮廓系数确定最佳K值K均值必须提前指定K这个K就是“功能复合类型”的数量。K并不是越大越好城市规划里常见的地块功能类型也就610种。确定K我会同时看两个指标簇内误差平方和SSE肘部法和轮廓系数。SSE拐点不明显时以轮廓系数为准。先写一个辅助函数跑1到12的K输出评估指标from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score sse_list [] sil_list [] k_range range(2, 13) for k in k_range: model KMeans(n_clustersk, random_state42, n_init10) labels model.fit_predict(scaled_data) sse_list.append(model.inertia_) sil_list.append(silhouette_score(scaled_data, labels)) # 打印结果人工判断肘部位置 for k, sse, sil in zip(k_range, sse_list, sil_list): print(fK{k:2d} SSE{sse:8.1f} Silhouette{sil:.4f})参数说明n_init10是K均值里最容易忽略的参数它表示用不同初始化中心跑10次取最优开始时用默认的10没问题但如果数据量超过10万行建议把n_init降到5节省时间。random_state42保证结果可复现。这里的inertia_就是SSE越小说明簇内越紧凑但K大了必然会小所以要结合轮廓系数的峰值选K。实际项目中SSE曲线经常是平滑下降没有明显肘点这时候我优先选轮廓系数最大的K但别超过10。如果出现过拟合式的结果——某个簇只包含一两个网格通常是把K定得太大。K均值本质是硬聚类K太大就会捕捉噪声点。一个判断技巧如果最大簇的样本数占总样本数的70%以上说明K太小如果最小簇样本数少于样本总数的2%说明K太大。3.2 用簇中心反向解读功能复合类型的业务含义K值定好后重新训练并得到每个簇的中心向量也就是model.cluster_centers_在标准化前的真实频数参考。这一步必须把数据反标准化回来否则看到的是Z-Score值很难和业务对应。反标准化的代码很简单# 用训练时的scaler做逆变换 center_log scaler.inverse_transform(model.cluster_centers_) center_freq np.expm1(center_log) # 指数还原 # 转成DataFrame方便比较 center_df pd.DataFrame(center_freq, columnsfeature_names)逻辑说明inverse_transform把Z-Score还原成log1p后的值再用np.expm1还原成原始频数。每个簇中心一行每一列表示该簇平均每个网格里对应类别POI的数量。这时去看哪一类占比最高就给这个簇命名。举一个典型结果假设K5第0簇的“住宅小区”中心值为120、“餐饮”为45、“便利店”为20其他都小于5那这个簇就是“纯住宅型”。第1簇“公司企业”中心值为90、“科研机构”为60、“教育”为30那它就是“商业-科研机构复合类型”。第2簇所有特征都接近0它就是“待开发区”。命名这件事机器干不了必须结合中心值和城市规划的常识。我还会做一个交叉验证把每个网格的标签拉出来随机抽20个网格去地图上看真实业态是否和簇名吻合。这一步虽然不能写进自动化流程但能帮你发现是不是因为类别粗分时把“事业单位”并错了导致聚类中心混淆。3.3 复合类型识别结果的可视化与落图识别出复合类型后要落到空间上才直观。最常见的方式是把网格ID经纬度坐标还原成GIS点或直接画散点图。下面是用Matplotlib画彩色散点图的示例import matplotlib.pyplot as plt # 从grid_id拆回网格中心坐标 grid_info pivot.reset_index()[[grid_id]] grid_info[grid_x] grid_info[grid_id].apply(lambda x: int(x.split(_)[0])) grid_info[grid_y] grid_info[grid_id].apply(lambda x: int(x.split(_)[1])) grid_info[lng_center] (grid_info[grid_x] 0.5) * grid_size grid_info[lat_center] (grid_info[grid_y] 0.5) * grid_size # 把聚类标签添加进来 grid_info[cluster_label] labels # 绘制散点图按簇标签着色 plt.figure(figsize(10, 10)) scatter plt.scatter( grid_info[lng_center], grid_info[lat_center], cgrid_info[cluster_label], cmaptab10, s8, alpha0.7 ) plt.colorbar(scatter) plt.xlabel(经度) plt.ylabel(纬度) plt.title(地块功能复合类型聚类结果) plt.show()说明这里的grid_x和grid_y是网格索引加0.5后乘grid_size得到中心点度数再乘所在纬度得到真实坐标的估算。注意如果你的网格是按米而非度数划分这里要换成投影坐标转换。散点图只看个大概真正做规划汇报建议导出GeoJSON或Shapefile。从这一步开始你已经有了“每个地块属于什么功能复合类型”的分类结果下一步才是区分“复合程度”的评分。4. 功能复合程度评估用熵和占比构造地块混合度评分4.1 为什么单独做复合程度评估而不直接用聚类簇判断聚类结果告诉了你“这个地块整体上属于哪一类”但同一个“商业-科研复合类型”里有的地块是商业占80%、科研占20%有的地块是各占50%。前者只是“功能复合的名义分类”后者才是真正的功能混合。如果要给政府报告或规划提供量化评分单靠聚类标签是不够的必须给每个地块算一个连续分数。这里最直观的指标是香农熵。把地块内各类POI的占比看作概率分布熵越大说明分布越均匀功能复合程度越高。熵除以类别总数的对数可以得到01之间的归一化指数——我一般叫它混合度指数。另外还需要一个主功能占比指标避免“所有类别都很少”的低密度地块被误判为高复合。4.2 用香农熵计算混合度指数代码与公式拆解香农熵的计算公式为H -Σ(p_i * log(p_i))其中p_i是第i类POI在该网格中的占比。归一化后H_norm H / log(n_classes)。取对数时必须处理0值占比为0的项直接跳过。这里给出完整的Python实现import numpy as np # pivot是原始频数矩阵未经标准化行是网格列是类别 freq_matrix pivot.values.astype(float) def mixture_entropy(freq_row): 输入一行频数返回归一化混合度指数 total freq_row.sum() if total 0: return 0.0 prob freq_row / total # 只保留大于0的项否则log(0)会报错 prob prob[prob 0] entropy -np.sum(prob * np.log(prob)) # 用类别数做归一化让值落在0-1之间 n_classes freq_row.size if n_classes 1: return 0.0 return entropy / np.log(n_classes) # 应用到每个网格 mixture_scores np.apply_along_axis(mixture_entropy, axis1, arrfreq_matrix) pivot[mixture_score] mixture_scores参数说明total是网格内所有POI数量之和如果某网格只有一条POI那它的熵为0混合度最低这是合理的。n_classes用的是原始类别数而不是“有效类别数”归一化后同样是9类POI的地块富集度会不同。这个指标并没有考虑空间布局只要类别均衡就算高混合度你可以在后续加入“不同类别POI是否在空间上交错”的邻域指标但第一版用熵就足够了。4.3 结合主功能占比构造复合程度评分卡光有熵还不够因为一个只有3个POI、类型刚好各占1/3的小空地熵会接近最高值但这种地块显然不是功能复合区。所以要加一个约束地块POI密度或总量至少达到某个阈值才参与评分。实际项目中我会再加一个主功能占比计算公式为max(p_i)最后组成一个二维评分。常见的做法是做一个评分卡地块特征判定方向建议权重归一化熵混合度越高越好0.6POI总量对数越高越好0.2主功能占比倒数越高越好0.2具体分数可以按百分位截断后线性映射到0100。这样每个地块得到一个混合度得分再结合聚类标签做交叉分析。比如“商业-科研复合类型”里混合度得分高于80的网格就是真正的产城融合高地低于40的网格虽然同簇但实际还是单一功能占主导。我在写评分卡时有一个习惯先不给权重直接把三个指标算出来分别画地图用眼睛看哪个指标和城市认知更吻合再回到权重。比如某城市的老城区熵很高但POI总量也高主功能占比倒数稳定那权重就不用调但如果熵高是因为小餐馆类别细分太碎就要先合并类别再重算。5. 从数据到结论的5个踩坑点这些坑我都替你踩过5.1 网格大小不同导致聚类结果天差地别现象用三百米网格聚类出5类换成八百米网格变成3类且住宅和商业被混在一起。原因网格边界影响POI归属小网格更碎大网格更平滑但面积过大的网格会把功能混合度人为抬高。解决不要追求“唯一正确”的网格大小而是确定业务口径。城市功能区评估普遍采用500米到1公里网格我用500米跑主模型再用250米做敏感性测试。如果两种网格下主要簇中心业态占比差异小于15%说明结果稳定否则就要回头检查类别粗分是否合理。5.2 K均值对“待开发区”这类零值样本产生误判现象没有任何POI的已剔除但只有零星一两个POI的待开发区比如一个加油站在荒地里会被分到“商业型”簇。原因K均值基于欧氏距离零星样本与某个簇中心的距离可能恰好最近但业务上它们是低密度未开发区。解决先按POI总量做密度分档总量小于某个四分位阈值的网格单独标记为“待开发/低密度区”不参与聚类。这也是我在地块功能识别里最常用的一招用密度先做排除法再用K均值做类型划分。5.3 类别分布不均衡导致的“餐饮独大”问题现象某个K代表的地块簇所有类别中心里只有“餐饮”频数突出簇内所有地块都被打上“餐饮区”标签。原因餐饮POI本身数量占比过高且种类多、密度高即使做了Z-Score它内部方差也远大于其他类别。解决在特征工程阶段把餐饮细类合并成“餐饮”一类并在标准化后对餐饮列做额外的降权比如乘以0.7。另外一个更彻底的办法是改用min–max归一化而不是Z-Score但整体效果不如精细粗分。5.4 聚类结果不稳定换random_state就变样现象K均值运行两次轮廓系数相同但某个边缘地块的标签从“住宅”变成“商务”。原因初始中心点随机数据量大或者簇间边界模糊时结果可能落在不同局部最优。解决固定random_state是第一步但不能只固定一个。我一般跑20个不同的随机种子统计每个地块的标签众数作为最终标签只有超过70%次数稳定的地块才认定标签可信。这一步代码量不大却能让后续评分不再被人质疑“是玄学吗”。5.5 将“识别类型”和“复合评分”混为一谈现象有人直接拿聚类标签当作复合程度高低的排序比如认为“商业-科研复合”就是比“商业”更厉害这是业务逻辑错误。原因聚类簇是按特征相似度分组的不代表某个簇天生比另一簇混合度更高。解决始终遵循“标签给类型熵给程度”的原则。同样一个“商业-科研复合”簇内混合度得分排序才是规划部门关心的标签本身只用于地图专题。6. 进阶把聚类结果做成一个可评估的地块功能体检工具到这里你已经完成了从POI到网格画像、从K均值聚类到复合程度评分的全过程。接下来值得做的不是再调参而是把结果固化成一个可重复执行的流程。我一般的做法是把上面所有代码封装成三个函数poi_to_grid、cluster_type、mixture_scoring输入是POI原始表和参数输出是一张含网格ID、类型标签、混合度得分的数据表。封装的好处是换城市或换年份数据时只要重新跑一遍脚本就能得到纵向对比底表。验证这个工具的方法很简单随机抽取当年新增的商业用地或产业园区名录查看它们在聚类结果中是不是被识别为商业或商业-科研复合混合度得分是否高于全市中位数。我曾在一次项目中拿城市当年的招商落地项目名单做验证命中率大约75%剩下25%主要是新开发的待建区POI还没长出来。这提醒我在“待开发区”类型里混合度评分应该默认偏低否则会干扰验证。还有一个进阶方向是引入K均值以外的参照算法。K均值擅长球状簇但城市功能边界是复杂形状所以可以用DBSCAN对网格中心坐标做一次空间聚类把空间上连片的同类地块合并成“功能片区”再做一次复合程度聚合。注意DBSCAN的eps参数要跟网格大小挂钩比如网格是500米eps设为2约1公里比较合适。不过这只是一种补充K均值在这个项目里的核心地位不会动摇它给你一个稳定、可解释、能落表的离散分类而复合程度评估给了连续分数。最后说一个我自己的习惯每次跑完聚类我会把K值、随机种子、网格大小、类别粗分映射表全部写进配置清单然后截个图放在项目笔记里。这不是强迫症而是三个月后有人问你“这个住宅评分怎么来的”时你能把每一步的后悔药都翻出来。功能复合类型识别是城市数据机器学习项目里少有的“投入小、见效快、好解释”的方向希望这次的拆解能帮你在拿到一包城市POI数据时少走几段我走过的弯路。本文还有配套的精品资源点击获取