
做数据汇报的时候我经常需要把不同州的指标数据展示给团队看。人口密度、销售额、客单价这些数据放在表格里很枯燥一旦变成一张在线绘制带颜色标注的美国地图信息密度立刻上去了——哪个州深、哪个州浅扫一眼就明白。这篇文章要解决的就是这个需求怎样快速做出一张带颜色标注的美国地图并且能在线查看、分享和嵌入到网页里。适合做业务分析、市场运营、学术展示又不太想碰重型GIS工具的朋友。1. 先拆需求颜色地图到底在解决什么问题1.1 颜色标注的本质是数据编码很多人第一次做这种图会以为“给地图涂颜色”是美工活其实不是。带颜色标注的地图专业术语叫分级统计图Choropleth Map它的核心逻辑是用颜色深浅或色相变化表达某个数值从小到大、从低到高的分布。比如人口密度越高的州颜色越深销售额越低的州颜色越浅。这里最关键的是理解“颜色是数据的编码”。你不能随手选一个看着顺眼的颜色而要让每一档颜色和数值区间严格对应。就好比体温计上的红色不代表“好看”而是代表“发烧”。做图的时候先要确定你的数据是连续数值比如人口密度、GDP总量还是分类标记比如东部、西部、中部不同数据类型对应完全不同的配色方案。数值型数据推荐用单一色相渐变比如浅蓝到深蓝或者浅黄到深绿。分类数据则适合用离散色板每种颜色代表一个类别彼此之间要有足够的区分度。这两种情况一旦搞混做出来的图看起来漂亮实际上会误导阅读者。1.2 三种实现路线零代码、轻工具、可编程围绕“在线绘制”这个需求市面上的方案基本可以分三条路方案类型代表工具优势代价零代码在线工具Datawrapper、Flourish上手快不需要装环境几分钟出一张图样式定制有限高级功能要付费轻量地图模板MapChart配色和排版精美适合报告插图静态图没有交互功能可编程方案Python Folium、Leaflet GeoJSON自由度高可自动化更新支持交互需要写代码有一定学习成本我个人的建议是如果你只需要一两次展示、数据量不大直接用零代码工具如果你要把图表嵌入业务系统、希望每次跑完数据自动生成地图那就老老实实走代码路线。第三条路线里我最常用的是Python的Folium库。它底层是Leaflet.js生成的是HTML文件双击就能在浏览器里打开部署到任何静态托管平台就能变成“在线地图”非常符合标题里“在线绘制”的场景。2. 工具选型不同需求选不同方案的实操细节2.1 零代码快速方案Datawrapper 五步出图Datawrapper是我用过的最省心的在线地图工具没有之一。它专门做新闻数据可视化单张图免费地图类型支持细分到行政区。具体操作流程可以归纳为五步第一进入工具网站选择Choropleth Map类型。第二创建地图并选择美国州级地图模板。第三把准备好的CSV数据粘贴进去左边是州名右边是数值。第四在颜色设置里选择渐变方案和分箱方式。第五预览无误后导出链接或者直接复制嵌入代码。这里有一个容易踩的坑州名必须是工具认可的格式。Datawrapper默认支持州全称、两位缩写和FIPS编码但不支持你随便写“New York City”这种城市名。我最常遇到的问题是同事把“Washington DC”和“Washington State”搞混DC在州地图里通常是一个独立区域你要在数据里单列出来否则它会被默默忽略。嵌入到网页是Datawrapper的优势它会生成一个自适应宽高的iframe代码直接贴进公司内网系统就能展示。免费版生成的链接带平台署名但作为内部数据看板完全够用。2.2 MapChart适合一次性精美出图如果你要的不是交互网页而是PPT里的插图MapChart会更合适。它本质是一个在线涂色工具你选择美国地图模板给每个州填上颜色最后导出PNG或SVG。这个工具特别适合非技术人员因为它不需要理解数据结构和分箱算法。你只需要在各州的设置面板里指定颜色比如销售额TOP10用深红色剩下用浅灰色一张带颜色标注的地图就完成了。但MapChart的缺点是颜色和数值没有自动绑定关系全手动操作。如果你的数据有50个州手动填一遍会很痛苦。所以我一般只在“只展示几个突出州”的时候用它比如标出公司重点客户的分布区域并不适合做全量数据的分级展示。2.3 可编程方案为什么我最终推荐 Python Folium聊到长期使用我还是强烈推荐Python加Folium这条路线。原因有三点第一自动化。我的数据源可能是Excel、数据库或者API接口每次用零代码工具上传下载效率太低。写成脚本以后只要运行一遍就能重新生成最新的地图HTML。第二交互性强。Folium默认支持悬停提示、缩放、图层切换这些交互特性在零代码工具的免费版里往往被限制。第三可控性高。你可以完全控制颜色阈值、图例位置、初始地图视角甚至嵌入自定义标记点。下文的实操我都以这个路线为准。如果完全没碰过Python也不用担心代码就十几行先复制运行看效果再逐步改成自己的数据。3. 核心细节数据准备、颜色映射与图例设计3.1 州数据的标准化格式全名、缩写还是FIPS做地图最容易翻车的一步不在画图而在数据准备。美国地图的空间数据、各州的名称标识和你手头业务表里的州字段三者之间经常对不上。我强烈建议数据里的州标识统一使用FIPS编码。FIPS是美国联邦信息处理标准编码50个州加华盛顿特区一共51个每个对应一个两位数数字比如加州是06纽约是36。因为它是纯数字不存在大小写问题也不会把Washington DC和Washington州搞混。如果你的业务表里只是中文名称或英文缩写也没关系准备一个映射表手动转换。用Python的pandas库做这个操作非常直接import pandas as pd # 原始数据只包含州英文缩写 df pd.read_csv(sales_data.csv) # FIPS映射字典实际应用中要补全51条 fips_map { AL: 01, AK: 02, AZ: 04, AR: 05, CA: 06, CO: 08, CT: 09, DE: 10, DC: 11, FL: 12, # ... 其余州自行补全 } df[FIPS] df[state_code].map(fips_map) df.to_csv(state_data_fips.csv, indexFalse)这里有个细节如果你的数据源本身就不规范比如有人在Excel里填了“New York, NY”、“NY”和“纽约”三种写法我建议在导入之后先用value_counts()检查一下所有唯一值把脏数据清理干净再继续。地图上少一个州很容易发现但多一个拼写错误往往让人排查半天。3.2 连续数值的颜色分箱区间怎么定数值型数据映射为颜色时系统默认会做“连续渐变”但这会导致一个问题如果数据集中在很小的范围内而个别州数值特别高大部分州都会呈现差不多的浅色图就失去了区分度。解决办法是分箱。所谓分箱就是把连续的数值切成几个区间每个区间对应一种深浅。实际操作中我一般用三种策略等距分箱数值范围平均切5段适合数据分布均匀的情况。分位数分箱每档包含相同数量的州适合数据偏态严重的情况。手动断点根据业务含义设置比如低于基准线、达标、优秀。举例说明假如展示各州人口密度人/平方英里数据从怀俄明的6到华盛顿特区的11000跨度相差接近2000倍用等距分箱根本没法看。我以前做过一次结果前四档颜色几乎一模一样只有DC一骑绝尘图完全报废。后来改用对数变换或者分位数分箱效果立刻好转。在Folium里可以传入bins参数手动指定分箱边界比如bins [0, 20, 50, 100, 200, 500, 1000]这样即使有极端值浅色和深色之间也有明显的灰度梯度一眼就能看出区域差异。3.3 图例与标注的平衡不要什么都往地图上堆地图上的空间是有限的颜色标注的本质是辅助阅读而不是替代表格。图例的标题要把单位写清楚比如“单位万元”或“人口密度人/平方英里”否则看图的人要猜半天。悬停提示我个人很推荐默认的展示信息包括州名和数值是最有效的信息组合。点击弹窗则可以放更多背景信息比如同比变化、排名、负责人。不过要克制弹窗内容超过三行阅读体验就会明显下降。在地图下方我通常会放一行简短说明写清楚统计口径和数据截止日期。比如“数据来源某平台2025年Q1订单统计不含海外仓”。这样做的好处是传播出去的截图即使在几周后被人看到也能追溯数据时效性避免误读。4. 手把手实现用 Python 在线生成带颜色标注的美国地图4.1 安装环境与获取边界数据Folium的安装非常轻量在终端执行pip install folium pandas它默认自带从GeoJSON获取空间边界的能力不需要单独安装GDAL这类重型GIS库。我们还需要一份美国州级边界的GeoJSON文件在Folium官方示例仓库里就有一份us-states.json可以直接下载到本地。这份数据的特征是每个州的id字段用的是FIPS编码正好对接我们数据分析表里的FIPS字段。另外提一句如果公司网络访问外网不便下载好之后把GeoJSON文件和代码放在同一目录用相对路径读取即可整个流程完全可以离线完成。4.2 加载数据并做JOIN前的检查假设我们手头有一份整理好的数据文件state_data.csv包含两列FIPS和value。加载代码很简单import folium import pandas as pd df pd.read_csv(state_data.csv) print(df.head()) print(df.info())这一步我要额外啰嗦几句info()很重要。如果FIPS列被读成了整数而GeoJSON里的id是字符串后续匹配会失败。稳妥的做法是强制统一成字符串df[FIPS] df[FIPS].astype(str).str.zfill(2)zfill(2)会把“6”补成“06”把“42”保持为“42”保证和GeoJSON的id格式完全一致。这个小步骤能为你省下最让人头疼的“地图大半空白”调试时间。4.3 绘制Choropleth地图核心绘图代码是这样的m folium.Map(location[39.8283, -98.5795], zoom_start4, tilesCartoDB positron)注中心点位置用的是美国大陆的几何中心附近。folium.Choropleth( geo_dataus-states.json, namechoropleth, datadf, columns[FIPS, value], key_onfeature.id, fill_colorYlGnBu, fill_opacity0.7, line_opacity0.2, line_colorwhite, bins[0, 20, 50, 100, 200, 500, 1000], legend_name人口密度 (人/平方英里) ).add_to(m) m.save(us_map.html)逐项解释几个容易混淆的参数geo_data边界文件路径可以是本地文件也可以是远程URL。data业务数据DataFrame。columns指明哪一列是地理标识哪一列是数值。key_onGeoJSON里用于和columns匹配的字段。这里写feature.id指的是每个州边界对象的id属性。fill_color色板名称可选YlGnBu、BuPu、OrRd等常见顺序就是浅色到深色。bins分箱边界如果省略Folium会按等间距自动生成。生成的us_map.html直接用浏览器打开就能看到一个完整交互式的美国地图鼠标悬停在任意州上方会显示信息。4.4 增加悬停提示和具体数值展示Choropleth默认的悬停提示通常只显示州名不显示业务数值。要显示数值需要额外添加一个GeoJSON图层tooltip folium.GeoJsonTooltip( fields[name, value], aliases[州, 数值], localizeTrue, stickyFalse, labelsTrue, stylebackground-color: white; ) folium.GeoJson( us-states.json, namelabels, tooltiptooltip, style_functionlambda x: {fillColor: transparent, color: transparent} ).add_to(m) folium.LayerControl().add_to(m) m.save(us_map_interactive.html)这里比较取巧的是我们把us-states.json再加载了一次但这层的填充色和边框都设为透明只用来挂载悬停事件所以视觉上不会覆盖底下已经上色的Choropleth图层。注意GeoJSON工具提示里能显示字段的前提是GeoJSON的properties里包含对应的字段。如果官方GeoJSON没有value字段需要先做一步数据合并。我通常把数据合并转换成新的GeoJSON文件或者直接给GeoJSON的properties动态赋值。如果只是快速看一下可以简化处理在GeoJsonTooltip里只显示name数值留给Choropleth自己的提示。4.5 从本地HTML到“在线”展示生成了HTML以后“在线绘制”还差一步部署。最省事的方案是把HTML文件传到任意静态资源托管平台上几分钟后就能获得一个公网链接。日常工作中我习惯用以下两种方式一种是公司内部网络直接把HTML文件放到内网服务器或对象存储的静态网站桶里内网同事访问链接即可。另一种是公开项目用GitHub Pages这类平台把us_map_interactive.html推到仓库的对应分支就能得到一个https://用户名.github.io/仓库名/us_map_interactive.html这样的地址。需要注意地图HTML引用的是远程在线底图所以在部署环境需要能正常访问底图服务的网络。如果你把文件下载到完全离线的电脑上双击打开底图可能会加载失败但各州颜色和Tooltip数据仍然正常因为底图是瓦片图片数据是本地的。若要真正离线使用需要额外下载底图瓦片到本地这个优化一般只在涉密内网场景才需要做。5. 常见问题与排查技巧实录5.1 地图上大片州显示为“透明/灰色”这个是所有新手最常遇到的问题地图加载出来了但只有零星几个州有颜色其他全是底色。原因基本只有一个业务数据的州标识和GeoJSON里的id匹配不上。排查方法也很简单用一个循环对比两个集合geo_ids set() # 这里需要借助json库解析GeoJSON文件 import json with open(us-states.json) as f: data json.load(f) for feature in data[features]: geo_ids.add(feature[id]) data_ids set(df[FIPS]) print(匹配不上的ID, data_ids - geo_ids) print(缺失的ID, geo_ids - data_ids)拿输出结果和Excel表比对基本立刻就能定位问题要么是ID字符串类型不一致要么是你数据里有两个州的数据用了相同的编码要么是出现了非州级的编码比如把整个国家填了一个数值。5.2 颜色出来了但和图例对不上这种情况通常是分箱参数和数据类型不匹配。举例来说你的数据里有NaN值Folium默认会把NaN处理成一个额外分类颜色和正常数值明显不同。如果你看到某个州颜色异常先去df[df[value].isna()]查一下哪些州是空值。另一个常见原因是数值数量级太大比如从1到100000自动分箱会生成非常不直观的区间。我一般先跑describe()看最小值、分位数、最大值再决定用对数分箱还是自定义bins。记住图例不只是装饰它是读者理解颜色含义的唯一通道区间设置必须让人一眼看出“高、中、低”的梯度。5.3 悬停提示一片空白Choropleth的悬停提示字段依赖GeoJSON自身的properties。我之前犯过一个蠢错误为了省事直接用原生Choropleth显示提示结果它默认显示FIPS编码业务同事根本看不懂。后来我把业务数值合并进了GeoJSON的properties里再用GeoJsonTooltip单独指定字段。合并方法不复杂先加载GeoJSON为字典遍历每个feature根据feature[id]到业务DataFrame里取数值写进feature[properties][value]最后重新dump成JSON文件。这个方法虽然比直接用Folium的choropleth数据参数多几行代码但在交互提示上的可控性是最好的。5.4 部署到线上后地图只有一半能显示如果你部署后发现某些区域底图缺失往往是地图瓦片请求被阻断或者浏览器的网络策略限制了跨域资源。这种情况在国内常见的是底图服务被防火墙拦截但我这里不展开讲网络问题只从技术侧给一个稳妥建议不要把底图源定死成某一个在线服务。多用几套备选瓦片tiles_list [OpenStreetMap, CartoDB positron, CartoDB dark_matter]如果一套瓦片加载不出来就换一套。更重要的是地图的矢量边界和颜色逻辑都在本地HTML里底图瓦片缺失不耽误数据阅读最多背景有点单调不影响核心功能。5.5 避坑清单汇总问题根源解决大片州没有颜色州标识匹配失败统一FIPS编码并转成字符串补零颜色和图例不符NaN或分箱不当检查空值自定义bins提示框无数据properties缺字段合并数据到GeoJSON再用GeoJsonTooltip地图加载慢在线瓦片太多缩小初始显示范围降低缩放级别部署后样式丢失相对路径错乱确认HTML同目录下的GeoJSON路径正确这几天我反复做这种地图最大的体会是绘图本身不复杂真正吃时间的是数据清洗和ID对齐。只要把州名、FIPS编码、数值这三列准备干净哪怕用最简陋的工具也能画出清晰的地图。如果你后续还要做类似的国家级、省级地图这套流程完全可以复用只需要换成对应区域的GeoJSON文件和行政区划编码。最后提醒一句保存地图时把GeoJSON和HTML放在同一个目录下省得以后换电脑打开地图的时候到处找文件。