本文示例代碼、數據及文件已上傳至我的
Github
倉庫https://github.com/CNFeffery/DataScienceStudyNotes
1 簡介
在上一篇文章中我們詳細學習了geoplot
中較為基礎的三種繪圖API:pointplot()
、polyplot()
以及webmap()
,而本文將會承接上文的內容,對geoplot
中較為實用的幾種高級繪圖API進行介紹。

本文是基於geopandas的空間數據分析系列文章的第7篇,通過本文你將學習geoplot
中的高級繪圖API。
2 geoplot進階
上一篇文章中的pointplot()
、polyplot
以及webmap()
幫助我們解決了在繪制散點、基礎面以及添加在線地圖底圖的問題,為了制作出信息量更豐富的可視化作品,我們需要更強的操縱矢量數據與映射值的能力,geoplot
為我們封裝好了幾種常見的高級可視化API。
2.1 Choropleth
Choropleth圖又稱作地區分布圖或面量圖,我們在系列之前的深入淺出分層設色篇中介紹過其原理及geopandas
實現,可以通過將指標值映射到面數據上,以實現對指標值地區分布的可視化。
在geoplot
中我們可以通過choropleth()
來快速繪制地區分布圖,其主要參數如下:
df:傳入對應的
GeoDataFrame
對象projection:用於指定投影坐標系,傳入
geoplot.crs
中的對象hue:傳入對應df中指定列名或外部序列數據,用於映射面的顏色,默認為None即不進行設色
cmap:和
matplotlib
中的cmap使用方式一致,用於控制色彩映射方案alpha:控制全局色彩透明度
scheme:作用類似
geopandas
中的scheme參數,用於控制分層設色,詳見本系列文章的分層設色篇,但不同的是在geoplot
0.4.0版本之后此參數不再搭配分層數量k共同使用,而是更新為傳入mapclassify
分段結果對象,下文中會做具體演示legend:bool型,用於控制是否顯示圖例
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
hatch:控制填充陰影紋路,詳情見本系列文章前作基礎可視化篇圖7
edgecolor:控制多邊形輪廓顏色
linewidth:控制多邊形輪廓線型
下面我們通過實際的例子來學習geoplot.choropleth
的用法,這里使用到的數據為美國新型冠狀肺炎各州病例數分布,對應日期為2020年5月14日,來自Github
倉庫:https://github.com/CSSEGISandData/COVID-19/tree/master/csse_covid_19_data/csse_covid_19_daily_reports_us;使用到的美國本土各州矢量面數據contiguous-usa.geojson
已上傳到文章開頭對應的Github
倉庫中:


首先我們將兩張表中各自對應的州名數據作為鍵進行連接(注意pd.merge
返回的結果類型為DataFrame
,需要轉換回GeoDataFrame
):
# 按照州名列進行連接
usa_plot_base = pd.merge(left=contiguous_usa,
right=usa_covid19_20200513,
left_on='state',
right_on='Province_State')
# 轉換DataFrame到GeoDataFrame,注意要加上crs信息
usa_plot_base = gpd.GeoDataFrame(usa_plot_base, crs='EPSG:4326')
接下來我們將確診數作為映射值,因為美國各州中紐約州和新澤西州確診數量分別達到了34萬和14萬,遠遠超過其他州,所以這里作為單獨的圖層進行陰影填充以突出其嚴重程度:
# 圖層1:除最嚴重兩州之外的其他州
ax = gplt.choropleth(df=usa_plot_base.query("state not in ['New York', 'New Jersey']"),
projection=gcrs.AlbersEqualArea(),
hue='Confirmed',
scheme=mc.FisherJenks(usa_plot_base.query("state not in ['New York', 'New Jersey']")['Confirmed'], k=3),
cmap='Reds',
alpha=0.8,
edgecolor='lightgrey',
linewidth=0.2,
figsize=(8, 8)
)
# 圖層2:紐約州
ax = gplt.polyplot(df=usa_plot_base.query("state == 'New York'"),
hatch='/////',
edgecolor='black',
ax=ax)
# 圖層3:新澤西州
ax = gplt.polyplot(df=usa_plot_base.query("state == 'New Jersey'"),
hatch='/////',
edgecolor='red',
extent=usa_plot_base.total_bounds,
ax=ax)
# 實例化cmap方案
cmap = plt.get_cmap('Reds')
# 得到mapclassify中BoxPlot的數據分層點
bp = mc.FisherJenks(usa_plot_base.query("state not in ['New York', 'New Jersey']")['Confirmed'], k=3)
bins = [0] + bp.bins.tolist()
# 制作圖例映射對象列表,這里分配Greys方案到三種色彩時對應的是[0, 0.5, 1]這三個采樣點
LegendElement = [mpatches.Patch(facecolor=cmap(_ / 2), label=f'{int(bins[_])}-{int(bins[_+1])}')
for _ in range(3)] + \
[mpatches.Patch(facecolor='none',
edgecolor='black',
linewidth=0.2,
hatch='/////',
label='New York: {}'.format(usa_plot_base.query("state == \"New York\"").Confirmed.to_list()[0])),
mpatches.Patch(facecolor='none',
edgecolor='red',
linewidth=0.2,
hatch='/////',
label='New Jersey: {}'.format(usa_plot_base.query("state == \"New Jersey\"").Confirmed.to_list()[0]))]
# 將制作好的圖例映射對象列表導入legend()中,並配置相關參數
ax.legend(handles = LegendElement,
loc='lower left',
fontsize=8,
title='確診數量',
title_fontsize=10,
borderpad=0.6)
# 添加標題
plt.title('美國新冠肺炎各州病例數(截至2020.05.14)', fontsize=18)
# 保存圖像
plt.savefig('圖4.png', dpi=300, pad_inches=0, bbox_inches='tight')

這樣我們就得到了圖4,需要注意的是,geoplot.choropleth()
只能繪制地區分布圖,傳入面數據后hue
參數必須指定對應映射列,否則會報錯,因此這里我們疊加紐約州和新澤西州單獨面圖層時使用的是polyplot()
。
2.2 Kdeplot
geoplot
中的kdeplot()
對應核密度圖,其基於seaborn
中的kdeplot()
,通過對矢量點數據分布計算核密度估計,從而對點數據進行可視化,可用來展示點數據的空間分布情況,其主要參數如下:
df:傳入對應的存放點數據的
GeoDataFrame
對象projection:用於指定投影坐標系,傳入
geoplot.crs
中的對象cmap:和
matplotlib
中的cmap使用方式一致,用於控制色彩映射方案clip:
GeoSeries
型,用於為初始生成的核密度圖像進行蒙版裁切,下文會舉例說明extent:元組型,用於傳入左下角和右上角經緯度信息來設置地圖空間范圍,格式為
(min_longitude, min_latitude, max_longitude, max_latitude)
figsize:元組型,用於控制畫幅大小,格式為
(x, y)
ax:
matplotlib
坐標軸對象,如果需要在同一個坐標軸內疊加多個圖層就需要用這個參數傳入先前待疊加的ax
shade:bool型,當設置為False時只有等值線被繪制出,當設置為True時會繪制核密度填充
shade_lowest:bool型,控制是否對概率密度最低的層次進行填充,下文會舉例說明
n_levels:int型,控制等值線數量,即按照概率密度對空間進行均勻划分的數量
下面我們回到上一篇文章開頭的例子——紐約車禍記錄數據,在其他參數均為默認的情況下,調用kdeplot
對車禍記錄點數據的空間分布進行可視化:
# 圖層1:行政邊界
ax = gplt.polyplot(df=nyc_boroughs,
projection=gcrs.AlbersEqualArea())
# 圖層2:默認參數下的kdeplot
ax = gplt.kdeplot(df=nyc_collision_factors,
cmap='Reds',
ax=ax)
# 保存圖像
plt.savefig('圖5.png', dpi=300, pad_inches=0, bbox_inches='tight')

可以看到,在kdeplot()
主要參數均為默認值的情況下,我們得到了點數據空間分布的概率估計結果及其等高線,譬如圖中比較明顯能看到的兩個點分布較為密集的中心,下面我們調整n_levles
參數到比較大的數字:
# 圖層1:行政邊界
ax = gplt.polyplot(df=nyc_boroughs,
projection=gcrs.AlbersEqualArea())
# 圖層2:kdeplot
ax = gplt.kdeplot(df=nyc_collision_factors,
cmap='Reds',
n_levels=30,
ax=ax,
figsize=(8, 8))
# 保存圖像
plt.savefig('圖6.png', dpi=300, pad_inches=0, bbox_inches='tight')

可以看到在增大n_levels
參數后,圖中等值線的數量隨之增加,下面我們設置shade=True
:
# 圖層1:行政邊界
ax = gplt.polyplot(df=nyc_boroughs,
projection=gcrs.AlbersEqualArea())
# 圖層2:kdeplot
ax = gplt.kdeplot(df=nyc_collision_factors,
cmap='Reds',
shade=True,
ax=ax,
figsize=(8, 8))
# 保存圖像
plt.savefig('圖7.png', dpi=300, pad_inches=0, bbox_inches='tight')

這時圖像等值線間得到相應顏色的填充,使得點分布中心看起來更加明顯,再添加參數shade_lowest=True
,即可對空白區域進行填充:

隨之而來的問題是整幅圖像都被填充,為了裁切出核密度圖像的地區輪廓,將底層行政區面數據作為clip
的參數傳入,便得到理想的效果:

2.3 Sankey
桑基圖專門用於表現不同對象之間某個指標量的流動情況,譬如最常見的航線流向情況,其本質是對線數據進行可視化,並將指標值映射到線的色彩或粗細水平上,而geoplot
中的sankey()
可以用來繪制這種圖,尷尬的是sankey()
繪制出的OD流向圖實在太丑,但sankey()
中將數值映射到線數據色彩和粗細的特性可以用來進行與流量相關的可視化,其主要參數如下:
df:傳入對應的
GeoDataFrame
對象projection:用於指定投影坐標系,傳入
geoplot.crs
中的對象hue:傳入對應df中指定列名或外部序列數據,用於映射線的顏色,默認為None即不進行設色
cmap:和
matplotlib
中的cmap使用方式一致,用於控制色彩映射方案alpha:控制全局色彩透明度
scheme:作用類似
geopandas
中的scheme參數,用於控制分層設色,詳見本系列文章的分層設色篇,但不同的是在geoplot
0.4.0版本之后此參數不再搭配分層數量k共同使用,而是更新為傳入mapclassify
分段結果對象,下文中會做具體演示scale:用於設定映射線要素粗細程度的序列數據,格式同hue,默認為None即每條線等粗
linewidth:當不對線寬進行映射時,該參數用於控制線寬
legend:bool型,用於控制是否顯示圖例
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
下面我們以2015年華盛頓街道路網日平均交通流量數據為例,其中每個要素均為線要素,aadt
代表日均流量:

我們將其流量列映射到線的粗細程度和顏色上來,為了美觀起見我們選擇系列文章分層設色篇中palettable
的SunsetDark
作為配色方案:
# 選擇配色方案為SunsetDark_5
from palettable.cartocolors.sequential import SunsetDark_5
gplt.sankey(
dc_roads,
projection=gcrs.AlbersEqualArea(),
scale='aadt',
hue='aadt',
limits=(0.1, 2), # 控制線寬范圍
scheme=mc.NaturalBreaks(dc_roads['aadt']),
cmap=SunsetDark_5.mpl_colormap,
figsize=(8, 8),
extent=dc_roads.total_bounds
)
plt.savefig("圖11.png", dpi=500, pad_inches=0, bbox_inches='tight')

2.4 geoplot中的坐標參考系
geoplot
中的坐標參考系與geopandas
中管理起來的方式截然不同,因為geopandas
基於pyproj
管理坐標參考系,而geoplot
中的crs
子模塊來源於cartopy
,這一點我跟geoplot
的主要開發者聊過,他表示geoplot
暫時不支持geopandas
中那樣自定義任意投影或使用EPSG
投影,而是內置了一系列常用的投影,譬如我們上文中繪制美國區域時頻繁使用到的AlbersEqualArea()
即之前我們在geopandas
中通過proj4自定義的阿爾伯斯等面積投影,其他常見投影譬如Web Mercator、Robinson,或者直接繪制球體地圖,如本文開頭的圖1就來自官方示例(https://residentmario.github.io/geoplot/gallery/plot_los_angeles_flights.html#sphx-glr-gallery-plot-los-angeles-flights-py),關於geoplot
坐標參考系的細節比較簡單本文不多贅述,感興趣的讀者可以前往官網(https://residentmario.github.io/geoplot/api_reference.html#projections)查看。
2.5 在模仿中學習
又到了最喜歡的“復刻”環節啦,本文要模仿的地圖可視化作品來自https://github.com/Z3tt/30DayMapChallenge/tree/master/contributions/Day26_Hydrology,同樣是用R
語言實現,對全球主要河流的形態進行優雅地可視化:

針對其河流寬度方面的可視化,我們基於上文中的sankey()
來實現,由於原圖中南極洲區域實際上是誇大了的,其R
源碼中設置的緯度范圍達到了-110度,這是原作者為了放得下標題內容,所以在圖像下部區域虛構了一篇區域,而geoplot
中的extent
參數嚴格要求經度必須在-180到180度之間,緯度在-90到90度之間。因此在原圖的基礎上我們進行微調,將標題移動到居中位置,具體代碼如下:
from palettable.cartocolors.sequential import Teal_7_r
import matplotlib.font_manager as fm
from shapely.geometry import box
# 讀入世界主要河流線數據
world_river = gpd.read_file('geometry/world_rivers_dSe.geojson')
# 讀入世界海洋面數據
world_ocean = gpd.read_file('geometry/world_ocean.shp')
# 圖層1:世界范圍背景色,基於shapely.geometry中的bbox來生成矩形矢量
ax = gplt.polyplot(df=gpd.GeoDataFrame({'geometry': [box(-180, -90, 180, 90)]}),
facecolor='#000026',
edgecolor='#000026')
# 圖層2:世界海洋面圖層
ax = gplt.polyplot(world_ocean,
facecolor='#00003a',
edgecolor='#00003a',
ax=ax)
# 圖層3:世界主要河流線圖層
ax = gplt.sankey(world_river,
scale='StrokeWeig',
hue='StrokeWeig',
scheme=mc.Quantiles(world_river['StrokeWeig'], 7),
cmap=Teal_7_r.mpl_colormap,
limits=(0.05, 0.4),
figsize=(8, 8),
extent=(-180, -90, 180, 90),
ax=ax)
# 添加標題
ax.text(0, 0, 'The Rivers of the World',
fontproperties=fm.FontProperties(fname="AlexBrush-Regular.ttf"), # 傳入Alex Brush手寫字體文件
fontsize=28,
color=Teal_7_r.mpl_colors[-1],
horizontalalignment='center',
verticalalignment='center')
# 添加作者信息及數據來源
ax.text(0, -15, 'Visualization by CNFeffery - Data by Natural Earth',
fontproperties=fm.FontProperties(fname="AlexBrush-Regular.ttf"),
fontsize=8,
color='#599bae',
horizontalalignment='center',
verticalalignment='center')
plt.savefig('圖13.png', dpi=600, pad_inches=0, bbox_inches='tight')

以上就是本文的全部內容,我將在下一篇文章中繼續與大家一起探討學習geoplot
中更高級的繪圖API。如有疑問和意見,歡迎留言或在我的Github
倉庫中發起issues與我交流。