本文示例代码和数据已上传至我的 GitHub 仓库 https://github.com/CNFeffery/DataScienceStudyNotes
1 简介
在前面的基于 geopandas 的空间数据分析系列文章中, 我们已经对 geopandas 的基础知识, 基础可视化, 以及如何科学绘制分层设色地图展开了深入的学习, 而利用 geopandas+matplotlib 进行地理可视化固然能实现常见的地图可视化, 且提供了操纵图像的极高自由度, 但对使用者 matplotlib 的熟悉程度要求较高, 制作一幅地图可视化作品往往需要编写较多的代码, 而 geoplot 基于 geopandas, 提供了众多高度封装的绘图 API, 很大程度上简化了绘图难度, 就像 seaborn 之于 matplotlib.
图 1
本文是基于 geopandas 的空间数据分析系列文章的第 6 篇, 通过本文你将学习 geoplot 中的基础绘图 API.
2 geoplot 基础
推荐使用 conda install --channel conda-forge geoplot 来安装 geoplot, 可以避免很多恼人的依赖包问题. 首先我们从一个简单的例子来初探一下 geoplot 的基础使用流程:
2.1 从一个简单的例子出发
我们下面所使用到的数据: nyc-boroughs.geojson, 记录了纽约的行政区域面文件:
- import geopandas as gpd
- %matplotlib inline
- # 读入纽约行政区域面文件
- nyc_boroughs = gpd.read_file('geometry/nyc-boroughs.geojson')
- nyc_boroughs.head()
图 2
以及 nyc-collision-factors.geojson, 包含了纽约所发生的车祸记录点以及相关信息数据:
- # 读入纽约车祸记录点文件
- nyc_collision_factors = gpd.read_file('geometry/nyc-collision-factors.geojson')
- nyc_collision_factors.head()
图 3
首先我们使用 geoplot 中的 polyplot 来绘制纽约行政区划, 这里使用 geoplot 自带的 Albers 等面积投影作为投影:
- import geoplot as gplt
- import geoplot.crs as gcrs
- import matplotlib.pyplot as plt
- ax = gplt.polyplot(df=nyc_boroughs,
- projection=gcrs.AlbersEqualArea())
- plt.savefig("图 4.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 4
接着我们使用 geoplot 中的 pointplot 将点叠加到图 4 上:
- ax = gplt.polyplot(df=nyc_boroughs,
- projection=gcrs.AlbersEqualArea())
- ax = gplt.pointplot(df=nyc_collision_factors,
- s=2,
- color='grey',
- alpha=0.2,
- linewidth=0, # 设置轮廓粗细为 0
- ax=ax)
- plt.savefig("图 5.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 5
为了让车祸密集的区域更突出, 我们将点图层换成核密度图层:
- ax = gplt.polyplot(df=nyc_boroughs,
- projection=gcrs.AlbersEqualArea())
- # 叠加核密度图层
- ax = gplt.kdeplot(df=nyc_collision_factors,
- cmap='Reds',
- shade=True,
- shade_lowest=True,
- clip=nyc_boroughs,
- ax=ax)
- plt.savefig("图 6.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 6
从这个简单的例子中我们可以大致了解到, geoplot 在 geopandas 处理好的数据基础上, 针对不同类型图层封装了各自不同的 API, 由用户自主传入对应类型的矢量数据进行图层叠加, 以得到最终结果, 且可以兼容 matplotlib, 譬如上面我们最终使用 plt.savaefig() 对图片进行保存, 下面我们就来详细学习 geoplot 的基础知识.
2.2 geoplot 绘图 API
在 geoplot 中内置了功能丰富的绘图 API, 只需要传入 GeoDataFrame 格式的矢量数据即可进行绘图 (但切记 geoplot 中传入的数据必须为 WGS84 地理坐标系, 所有的投影转换在 geoplot 各绘图函数内部传参实现即可!)
2.2.1 Pointplot
geoplot 中的 pointplot 即为散点图, 其针对点数据进行可视化, 其主要参数如下:
df: 传入对应的 GeoDataFrame 对象
projection: 用于指定投影坐标系, 传入 geoplot.crs 中的对象
hue: 当需要根据 df 中的某列或外部的其他序列数据来映射散点的色彩时, 可传入对应 df 中指定列名或外部序列数据, 默认为 None 即不进行设色
cmap: 和 matplotlib 中的 cmap 使用方式一致, 用于控制色彩映射方案
scheme: 作用类似 geopandas 中的 scheme 参数, 用于控制分层设色, 详见本系列文章的分层设色篇, 但不同的是在 geoplot0.4.0 版本之后此参数不再搭配分层数量 k 共同使用, 而是更新为传入 mapclassify 分段结果对象, 下文中会做具体演示
scale: 用于设定映射散点大小的序列数据, 格式同 hue, 默认为 None 即每个散点等大小
limits: 元组型, 当 scale 不为 None 时, 用于设定散点大小尺寸范围, 格式为 (min, max)
s: 当 scale 设置为 None 时, 用于控制散点的尺寸大小
color: 当 hue 设置为 None 时, 用于控制散点的填充色彩
marker: 用于设定散点的形状
alpha: 控制全局色彩透明度
linewidths: 控制散点轮廓宽度
edgecolors: 控制散点轮廓颜色
legend:bool 型, 用于控制是否显示图例
legend_var: 传入'hue'或 scale, 当设定为 hue 时图例显示色彩映射信息, 当设定为'scale'时图例显示大小映射信息
legend_values:list 型, 用于自定义图例显示的各个具体数值
legend_labels:list 型, 用于自定义图例显示的各个具体数值对应的文字标签, 与 legend_values 搭配使用
legend_kwargs: 字典, 在 legend 参数设置为 True 时来传入更多微调图例属性的参数
extent: 元组型, 用于传入左下角和右上角经纬度信息来设置地图空间范围, 格式为 (min_longitude, min_latitude, max_longitude, max_latitude)
figsize: 元组型, 用于控制画幅大小, 格式为 (x, y)
ax:matplotlib 坐标轴对象, 如果需要在同一个坐标轴内叠加多个图层就需要用这个参数传入先前待叠加的 ax
知晓了上述主要参数之后, 下面我们通过实际案例来学习修改各个参数得到的效果, 使用到的数据为波士顿区划面数据以及波士顿部分地区 Airbnb 房源点数据:
图 7
普通散点分布
首先我们来简单绘制房源分布散点图, 对大小, 色彩, 透明度等基础属性进行简单调整:
- # 简单绘制波士顿行政区划
- ax = gplt.polyplot(df=boston_zip_codes,
- projection=gcrs.AlbersEqualArea(),
- edgecolor='lightgrey',
- linewidths=0.5)
- gplt.pointplot(df=boston_airbnb_listings,
- ax=ax, # 叠加图层
- s=1,
- linewidths=0.1,
- color='grey',
- alpha=0.4)
- plt.savefig("图 8.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 8
通过这样一张简单的图我们是看不出太多信息的, 只能大致看出哪些地方房源分布较多.
映射房源价格到色彩上
将房源价格列作为色彩映射列, 使用 mapclassify 中的分位数法将价格区间等分成五段, 并使用其他的视觉参数和自定义图例参数:
- import mapclassify as mc
- # 解决中文显示问题
- plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
- plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
- # 简单绘制波士顿行政区划
- ax = gplt.polyplot(df=boston_zip_codes,
- projection=gcrs.AlbersEqualArea(),
- edgecolor='lightgrey',
- linewidths=0.5)
- scheme = mc.Quantiles(boston_airbnb_listings['price'], k=5)
- gplt.pointplot(df=boston_airbnb_listings,
- ax=ax, # 叠加图层
- s=1, # 散点大小
- linewidths=0.1, # 散点轮廓宽度
- hue='price', # 以 price 作为色彩映射列
- cmap='Reds', # 色彩方案为 Reds
- scheme=scheme, # 传入 mapclassify 对象
- legend=True, # 开启图例
- legend_kwargs={
- 'loc': 'lower right', # 图例位置
- 'title': '价格区间', # 图例标题
- 'title_fontsize': 8, # 图例标题字体大小
- 'fontsize': 6, # 图例非标题外字体大小
- 'shadow': True, # 添加图例阴影
- },
- legend_labels=['80%-100% 价格房源',
- '60%-80% 价格房源',
- '40%-60% 价格房源',
- '20%-40% 价格房源',
- '前 20% 价格房源'])
- plt.savefig("图 9.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 9
映射房源价格到尺寸上
看完了如何映射颜色, 下面我们来看看如何将值映射到散点大小上, 使用 scale='price'来将房源价格映射到散点大小上, 再配合一些相关参数进行绘图:
- import numpy as np
- # 简单绘制波士顿行政区划
- ax = gplt.polyplot(df=boston_zip_codes,
- projection=gcrs.AlbersEqualArea(),
- edgecolor='lightgrey',
- linewidths=0.5)
- ax = gplt.pointplot(df=boston_airbnb_listings,
- ax=ax, # 叠加图层
- linewidths=0.2, # 散点轮廓宽度
- scale='price', # 以 price 作为色彩映射列
- color=np.array([0., 0., 0., 0.]), # 设置填充色为透明
- edgecolor='grey', # 设置轮廓颜色
- limits=(1, 16), # 设置散点的尺寸范围
- legend=True, # 开启图例
- legend_kwargs={
- 'loc': 'lower right', # 图例位置
- 'title': '价格区间', # 图例标题
- 'title_fontsize': 8, # 图例标题字体大小
- 'fontsize': 6, # 图例非标题外字体大小
- 'shadow': True, # 添加图例阴影
- 'markeredgecolor': 'grey', # 图例标记的轮廓色彩
- 'markeredgewidth': 0.2 # 图例标记的轮廓粗细
- })
- plt.savefig("图 10.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 10
现在我们可以一眼看出那些半径较大的圆圈对应着价格较高的房源, 值得注意的是在我们映射值到散点大小上时, 默认条件下会自动在图例中按照等间距法分出 5 段, 这样得到的图例各个圆圈大小过渡保证了均匀, 当然你也可以自由地通过 legend_values 和 legeng_labels 这两个参数自定义图例内容.
同时映射颜色与尺寸
geoplot 允许用户同时映射色彩和尺寸, 但同一张图中的图例只能显示色彩或尺寸其中之一的信息, 使用 legend_var 参数来选择让哪一种映射信息显示在图例上:
- # 简单绘制波士顿行政区划
- ax = gplt.polyplot(df=boston_zip_codes,
- projection=gcrs.AlbersEqualArea(),
- edgecolor='lightgrey',
- linewidths=0.5)
- scheme = mc.Quantiles(boston_airbnb_listings['price'], k=5)
- gplt.pointplot(df=boston_airbnb_listings,
- ax=ax, # 叠加图层
- scale='price', # 以 price 作为尺寸映射列
- limits=(1, 16), # 设置散点的尺寸范围
- alpha=0.6, # 设置散点透明度
- linewidths=0.1, # 散点轮廓宽度
- hue='price', # 以 price 作为色彩映射列
- cmap='Reds', # 色彩方案为 Reds
- scheme=scheme, # 传入 mapclassify 对象
- legend=True, # 开启图例
- legend_kwargs={
- 'loc': 'lower right', # 图例位置
- 'title': '价格区间', # 图例标题
- 'title_fontsize': 8, # 图例标题字体大小
- 'fontsize': 6, # 图例非标题外字体大小
- 'shadow': True, # 添加图例阴影
- },
- legend_labels=['80%-100% 价格房源',
- '60%-80% 价格房源',
- '40%-60% 价格房源',
- '20%-40% 价格房源',
- '前 20% 价格房源'])
- plt.savefig("图 11.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 11
2.2.2 Polyplot
geoplot 中的 polyplot 用于绘制基础的面数据, 并不像 pointplot 那样带有值映射功能, 其主要参数如下:
df: 传入对应的 GeoDataFrame 对象
projection: 用于指定投影坐标系, 传入 geoplot.crs 中的对象
extent: 元组型, 用于传入左下角和右上角经纬度信息来设置地图空间范围, 格式为 (min_longitude, min_latitude, max_longitude, max_latitude)
figsize: 元组型, 用于控制画幅大小, 格式为 (x, y)
ax:matplotlib 坐标轴对象, 如果需要在同一个坐标轴内叠加多个图层就需要用这个参数传入先前待叠加的 ax
alpha: 控制全局色彩透明度
linewidths: 控制线宽度
edgecolors: 控制线颜色
facecolor: 控制填充颜色
linestyle: 控制线样式, 详情见本系列文章前作基础可视化篇图 5
hatch: 控制填充阴影纹路, 详情见本系列文章前作基础可视化篇图 7
下面我们就对纽约区划面数据进行举例说明:
- gplt.polyplot(df=nyc_boroughs,
- projection=gcrs.AlbersEqualArea(),
- figsize=(10, 10),
- linewidths=0.5,
- linestyle='-.',
- edgecolors='grey',
- facecolor='#d9c09e',
- hatch='--')
- plt.savefig("图 12.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 12
2.2.3 webmap
geoplot 中的 webmap 用来添加在线瓦片地图底图, 使得我们可以在在线地图上图层, 但目前暂时只支持叠加基于点要素的图层. 值得注意的是, 因为常见在线地图如谷歌地图, OpenStreetMap, 高德地图等的投影均为 EPSG:3857 也就是我们常说的 Web Mercator, 所以一旦要使用 webmap, 则投影锁死为 EPSG:3857, 其主要参数如下:
df: 传入对应的 GeoDataFrame 对象
extent: 元组型, 用于传入左下角和右上角经纬度信息来设置地图空间范围, 格式为 (min_longitude, min_latitude, max_longitude, max_latitude)
figsize: 元组型, 用于控制画幅大小, 格式为 (x, y)
ax:matplotlib 坐标轴对象, 如果需要在同一个坐标轴内叠加多个图层就需要用这个参数传入先前待叠加的 ax
zoom:int 型, 控制在线地图底图的缩放级别, 越大越清楚, 同时获取瓦片地图资源从而渲染地图所耗费的时间也越多, 上限由具体所使用的在线地图所决定, 通常情况最大缩放级别为 18
provider:str 型, 用于指定在线地图底图的类型, 下面会举例说明
下面我们将纽约车祸点数据叠加到在线地图上, 这里我们选择 provider 参数为 ST_TERRAIN_LINES, 并设置缩放级别为 11 级:
- ax = gplt.webmap(df=nyc_boroughs,
- provider='ST_TERRAIN_LINES',
- zoom=10)
- ax = gplt.pointplot(df=nyc_collision_factors,
- color='lightyellow',
- edgecolor='yellow',
- alpha=0.4,
- s=1,
- ax=ax)
- plt.savefig("图 13.png", bbox_inches='tight', pad_inches=0, dpi=300)
图 13
如果想要切换底图样式, 可以修改 provider 参数的输入, 目前为止所有可用的地图如下图所示:
图 14
2.3 在模仿中学习
在本系列文章基础可视化篇的最后我们对数据可视化专家用 R 绘制的澳大利亚火灾影响地图进行了模仿, 从而加深对 geopandas 数据可视化的融会贯通, 而本文作为 geoplot 篇的上半篇, 介绍了 geoplot 中最基本的几种数据可视化 API, 使得我们足以完成较为基础的数据可视化作品, 而同样为了加深对上文所介绍知识的理解掌握, 接下来我们再次对其他优秀的数据可视化作品进行模仿.
这次我们要模仿的作品来自 GitHub 仓库 https://github.com/Z3tt/30DayMapChallenge, 是利用 R 进行地理空间数据可视化的一个集锦仓库, 要用 geoplot 来模仿复现的作品如图 15 所示, 展示了柏林所有电动汽车充电桩的分布情况:
图 15
我们主要浮现的是图 15 中柏林地图以及内部元素部分, 使用到的数据在我的 GitHub 仓库对应本文路径下的 Berlin 文件夹中, 其中 ladesaeulen_bnetza_und_be_emobil.xlsx 记录了 EPSG:25833 投影坐标系格式下的充电桩经纬度点信息, gis_osm_roads_free_1.shp 记录了柏林市 OSM 路网信息, Bezirke__Berlin.shp 记录了柏林行政区划信息.
在分析了原图的 R 代码之后, 我们将整幅图拆解分为四个图层, 1 是柏林最边缘的灰色轮廓, 这其实是整个柏林区域面数据向外生成缓冲区之后的效果; 2 是柏林各行政区区划, 3 是柏林内部的部分 OSM 路网, 构成了图中依稀可见的类似纹路的要素, 4 是所有的充电桩点数据, 即图中黄色的半透明散点, 其中除路网线数据可视化以外的其他图层我们均使用 geoplot 来实现.
数据预处理部分分步骤代码较多, 不便在文章中全盘放出, 你可以到文章开头的 GitHub 仓库中对应路径下查看和下载, 下面只贴出绘图部分的代码以方便理解思想:
- # 绘制最底层柏林缓冲区
- ax = gplt.polyplot(df=gpd.GeoSeries([GeometryCollection(berlin_area.geometry.tolist())], crs='EPSG:4326') \
- .buffer(0.005, resolution=100),
- projection=gcrs.WebMercator(),
- facecolor='grey',
- edgecolor='None',
- alpha=0.6)
- # 绘制柏林区划
- ax = gplt.polyplot(df=berlin_area,
- facecolor='black',
- edgecolor='white',
- alpha=0.65,
- ax=ax)
- # 利用 geopandas 绘制内部 OSM 路网
- ax = intersect_roads.plot(ax=ax,
- linewidth=0.1,
- edgecolor='black',
- alpha=0.25)
- # 绘制充电桩散点
- ax = gplt.pointplot(df=df_emobil,
- ax=ax,
- extent=berlin_area.total_bounds,
- color='#edc00d',
- alpha=0.3,
- linewidth=0.2,
- s=4.5)
- # 绘制充电桩中心点
- ax = gplt.pointplot(df=df_emobil,
- ax=ax,
- extent=berlin_area.buffer(0.01).total_bounds,
- color='#d29c14',
- edgecolor='#6f603e',
- linewidth=0.01,
- s=0.7)
- plt.savefig("图 16.png", bbox_inches='tight', pad_inches=0, dpi=600)
最终得到的效果如图 16 所示:
图 16
来源: https://www.cnblogs.com/feffery/p/12779150.html