You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为伦敦MSOA贫困率Choropleth地图添加清晰区域标识?

问题描述

我正在用英国ONS发布的家庭贫困数据和MSOA地理边界数据制作伦敦地区的分级统计图(Choropleth Map),目前遇到以下问题:

  • 用Matplotlib生成的静态地图因区域粒度太细,无法识别对应区域,难以读取有效信息;尝试添加区域首字母标签又会遮挡地图。
  • 用Plotly制作交互式Choroplethmapbox时,内存占用过大导致电脑卡顿。
  • 用Folium时无法匹配坐标。

另外,我想添加带有地方当局名称的彩色点图例,但不知如何实现。现请教:如何为该分级统计图整洁地添加地方当局信息?

现有代码

Matplotlib 静态地图代码

import pandas as pd
import matplotlib.pyplot as plt
import geopandas as gpd
from mpl_toolkits.axes_grid1 import make_axes_locatable
import folium
import plotly.express as px
import pyproj
plt.style.use('ggplot')
import json

# 数据来源:ONS家庭贫困数据
populationData=pd.read_csv("householdsinpoverty2014.csv")
# 数据来源:ONS MSOA地理边界数据
mapData= gpd.read_file("MSOA.shp")

mapData.drop(columns=['Shape__Are', 'Shape__Len','GlobalID','OBJECTID'],inplace=True)

populationData.drop(columns=['Unnamed: 9'],inplace=True)
populationData2=populationData.iloc[3:].copy()
populationData2.columns=populationData2.iloc[0]
populationDataClean=populationData2.iloc[1:].copy()

populationDataClean.reset_index(inplace=True)
populationDataClean.dropna(inplace=True)

populationDataClean.drop(columns=['MSOA name', 'Local authority code','Percentage of Households Below 60% of the Median Income; (after housing costs); 95% Confidence Interval Lower Limit','Percentage of Households Below 60% of the Median Income; (after housing costs); 95% Confidence Interval Upper Limit'],inplace=True)

populationDataClean.sort_values(by=['Percentage of Households Below 60% of the Median Income; (after housing costs)'],inplace=True)
populationDataClean['PercentBelowMedIncome']=populationDataClean['Percentage of Households Below 60% of the Median Income; (after housing costs)'].astype(float)

# 筛选伦敦地区数据并合并
mapStats=mapData.merge(populationDataClean[populationDataClean['Region name']=='London'],left_on='MSOA21CD',right_on='MSOA code')

fig, ax = plt.subplots(1, figsize=(8, 8))
plt.xticks(rotation=90)

mapStats.plot(column="PercentBelowMedIncome", cmap="Blues", linewidth=0.4, ax=ax, edgecolor=".4",scheme='equalinterval',missing_kwds={
    "color": "lightgrey",
    "edgecolor": "red",
   "hatch": "///",
   "label": "Missing values",
   })
ax.set_title('% of Households Below 60% \n of the Median Income (London)', fontdict={'fontsize': '25', 'fontweight' : '3'})
ax.axis("off")
sm = plt.cm.ScalarMappable(cmap='Blues', norm=plt.Normalize(vmin=6.7, vmax=50))
sm._A = []
cbar = fig.colorbar(sm)

Plotly 交互式地图代码

from plotly import graph_objects as go
fig = go.Figure(
go.Choroplethmapbox(
geojson=json,
featureidkey="properties.MSOA21CD",
locations=mapStats["MSOA21CD"],
z=mapStats['PercentBelowMedIncome'],
zauto=True,
colorscale='Reds',
showscale=True
)
)

fig.update_layout(mapbox_style='carto-positron',
mapbox_zoom=5,
mapbox_center_lon=-2.057852,
mapbox_center_lat=53.404854,
height=700,
width=700)
fig.show()
解决方案

针对你的核心需求,以下是几种不同工具下的实现方案:

一、Matplotlib 静态地图:添加地方当局边界+中心标注

通过叠加地方当局边界、在区域中心标注名称的方式,既不遮挡MSOA级别的贫困数据,又能清晰识别地方当局范围:

import pandas as pd
import matplotlib.pyplot as plt
import geopandas as gpd

# 提取伦敦地方当局的合并边界(MSOA编码以E09开头为伦敦区域)
la_data = mapData[mapData['MSOA21CD'].str.startswith('E09')].dissolve(by='LAD21NM')

# 绘制MSOA贫困分级图
fig, ax = plt.subplots(1, figsize=(12, 12))
mapStats.plot(column="PercentBelowMedIncome", cmap="Blues", linewidth=0.2, ax=ax, edgecolor=".4", 
              scheme='equalinterval', missing_kwds={
                  "color": "lightgrey",
                  "edgecolor": "red",
                  "hatch": "///",
                  "label": "缺失值",
              })

# 叠加地方当局粗边界,区分区域
la_data.boundary.plot(ax=ax, linewidth=1.5, color='black', zorder=3)

# 在每个地方当局几何中心添加名称标注,用半透明白底避免遮挡
for idx, row in la_data.iterrows():
    centroid = row.geometry.centroid
    ax.text(centroid.x, centroid.y, idx, fontsize=8, ha='center', va='center', 
            bbox=dict(facecolor='white', alpha=0.7, pad=1, edgecolor='none'), zorder=4)

# 优化标题和图例
ax.set_title('伦敦地区家庭收入低于中位数60%的比例', fontdict={'fontsize':16, 'fontweight':'bold'})
ax.axis('off')

# 配置颜色条
sm = plt.cm.ScalarMappable(cmap='Blues', norm=plt.Normalize(vmin=mapStats['PercentBelowMedIncome'].min(), 
                                                           vmax=mapStats['PercentBelowMedIncome'].max()))
sm._A = []
cbar = fig.colorbar(sm, ax=ax, fraction=0.03, pad=0.02)
cbar.set_label('贫困家庭比例(%)', fontsize=12)

# 添加缺失值图例
ax.legend(handles=[plt.Rectangle((0,0),1,1, color='lightgrey', edgecolor='red', hatch='///')], 
          labels=['缺失值'], loc='lower right', fontsize=10)

plt.tight_layout()
plt.show()

二、Plotly 交互式地图:解决内存问题+添加地方当局信息

内存优化方案:

  • 只保留伦敦地区的MSOA数据,不要加载全英范围
  • 简化MSOA几何形状(降低坐标精度)

添加地方当局信息:

  • 在hover提示框中显示地方当局名称
  • 可选叠加地方当局边界层
import plotly.express as px
import geopandas as gpd

# 简化MSOA几何形状,减少内存占用(tolerance值越大,简化程度越高)
mapStats_simplified = mapStats.copy()
mapStats_simplified['geometry'] = mapStats_simplified['geometry'].simplify(tolerance=100)

# 转换为GeoJSON格式
london_geojson = mapStats_simplified.__geo_interface__

# 绘制交互式地图,hover显示地方当局名称(假设mapStats包含LAD21NM字段)
fig = px.choropleth_mapbox(
    mapStats_simplified,
    geojson=london_geojson,
    locations='MSOA21CD',
    featureidkey='properties.MSOA21CD',
    color='PercentBelowMedIncome',
    color_continuous_scale='Blues',
    mapbox_style='carto-positron',
    zoom=10,
    center={'lat': 51.5074, 'lon': -0.1278},  # 伦敦中心坐标
    hover_data={
        'MSOA21CD': False, 
        'PercentBelowMedIncome': ':,.1f', 
        'LAD21NM': True  # 显示地方当局名称
    },
    title='伦敦地区家庭贫困比例'
)

# 可选:添加地方当局边界层
# la_data_simplified = la_data.copy()
# la_data_simplified['geometry'] = la_data_simplified['geometry'].simplify(tolerance=200)
# la_geojson = la_data_simplified.__geo_interface__
# fig.add_trace(px.choropleth_mapbox(
#     la_data_simplified,
#     geojson=la_geojson,
#     locations=la_data_simplified.index,
#     featureidkey='properties.LAD21NM',
#     color_discrete_sequence=['black'],
#     showscale=False,
#     hover_data={'LAD21NM': True},
#     marker_line_width=1.5
# ).data[0])

fig.update_layout(height=800, width=800)
fig.show()

三、Folium 地图:匹配坐标+添加地方当局信息

Folium默认使用WGS84坐标系(EPSG:4326),需要将你的GeoDataFrame转换到该坐标系,再添加地方当局边界和tooltip:

import folium
import geopandas as gpd

# 将数据转换为WGS84坐标系(Folium默认)
mapStats_wgs84 = mapStats.to_crs(epsg=4326)
la_data_wgs84 = la_data.to_crs(epsg=4326)

# 创建地图实例,以伦敦为中心
m = folium.Map(location=[51.5074, -0.1278], zoom_start=10, tiles='CartoDB Positron')

# 添加MSOA贫困分级图层
folium.Choropleth(
    geo_data=mapStats_wgs84,
    name='贫困家庭比例',
    data=mapStats_wgs84,
    columns=['MSOA21CD', 'PercentBelowMedIncome'],
    key_on='feature.properties.MSOA21CD',
    fill_color='Blues',
    fill_opacity=0.7,
    line_opacity=0.2,
    legend_name='贫困家庭比例(%)'
).add_to(m)

# 添加地方当局边界图层,hover显示名称
folium.GeoJson(
    la_data_wgs84,
    name='地方当局边界',
    style_function=lambda x: {'color': 'black', 'weight': 1.5, 'fillOpacity': 0},
    tooltip=folium.GeoJsonTooltip(fields=['LAD21NM'], aliases=['地方当局名称'])
).add_to(m)

# 添加图层控制器,切换显示
folium.LayerControl().add_to(m)

# 保存为HTML交互式地图
m.save('london_poverty_map.html')

四、地方当局彩色点图例实现

如果需要用彩色点标记地方当局并添加图例,可以在Matplotlib中实现:

import matplotlib.patches as Patch

# 给每个地方当局分配唯一颜色
la_colors = plt.cm.tab20.colors[:len(la_data)]
la_color_map = dict(zip(la_data.index, la_colors))

# 在地方当局中心绘制彩色点
for idx, row in la_data.iterrows():
    centroid = row.geometry.centroid
    ax.scatter(centroid.x, centroid.y, color=la_color_map[idx], s=50, zorder=5)

# 创建图例元素
legend_elements = [Patch.Patch(facecolor=la_color_map[la], label=la) for la in la_data.index]
# 将图例放在图外,避免遮挡
ax.legend(handles=legend_elements, loc='lower left', bbox_to_anchor=(1, 0), fontsize=6, title='地方当局')

plt.tight_layout()
plt.show()

内容的提问来源于stack exchange,提问作者user13948

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 20:43:08