(數據科學學習手札83)基於geopandas的空間數據分析——geoplot篇(下)

本文示例代碼、數據及文件已上傳至個人Github倉庫https://github.com/CNFeffery/DataScienceStudyNotescss

1 簡介

  在上一篇文章中咱們詳細學習了geoplot中較爲基礎的三種繪圖API:pointplot()polyplot()以及webmap(),而本文將會承接上文的內容,對geoplot中較爲實用的幾種高級繪圖API進行介紹。html

圖1

  本文是基於geopandas的空間數據分析系列文章的第7篇,經過本文你將學習geoplot中的高級繪圖API。git

2 geoplot進階

  上一篇文章中的pointplot()polyplot以及webmap()幫助咱們解決了在繪製散點、基礎面以及添加在線地圖底圖的問題,爲了製做出信息量更豐富的可視化做品,咱們須要更強的操縱矢量數據與映射值的能力,geoplot爲咱們封裝好了幾種常見的高級可視化API。github

2.1 Choropleth

  Choropleth圖又稱做地區分佈圖面量圖,咱們在系列以前的深刻淺出分層設色篇中介紹過其原理及geopandas實現,能夠經過將指標值映射到面數據上,以實現對指標值地區分佈的可視化。web

  在geoplot中咱們能夠經過choropleth()來快速繪製地區分佈圖,其主要參數以下:json

df:傳入對應的GeoDataFrame對象api

projection:用於指定投影座標系,傳入geoplot.crs中的對象ide

hue:傳入對應df中指定列名或外部序列數據,用於映射面的顏色,默認爲None即不進行設色學習

cmap:和matplotlib中的cmap使用方式一致,用於控制色彩映射方案字體

alpha:控制全局色彩透明度

scheme:做用相似geopandas中的scheme參數,用於控制分層設色,詳見本系列文章的分層設色篇,但不一樣的是在geoplot0.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)

axmatplotlib座標軸對象,若是須要在同一個座標軸內疊加多個圖層就須要用這個參數傳入先前待疊加的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倉庫中:

圖2 圖3

  首先咱們將兩張表中各自對應的州名數據做爲鍵進行鏈接(注意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

  這樣咱們就獲得了圖4,須要注意的是,geoplot.choropleth()只能繪製地區分佈圖,傳入面數據後hue參數必須指定對應映射列,不然會報錯,所以這裏咱們疊加紐約州和新澤西州單獨面圖層時使用的是polyplot()

2.2 Kdeplot

  geoplot中的kdeplot()對應核密度圖,其基於seaborn中的kdeplot(),經過對矢量點數據分佈計算核密度估計,從而對點數據進行可視化,可用來展現點數據的空間分佈狀況,其主要參數以下:

df:傳入對應的存放點數據的GeoDataFrame對象

projection:用於指定投影座標系,傳入geoplot.crs中的對象

cmap:和matplotlib中的cmap使用方式一致,用於控制色彩映射方案

clipGeoSeries型,用於爲初始生成的核密度圖像進行蒙版裁切,下文會舉例說明

extent:元組型,用於傳入左下角和右上角經緯度信息來設置地圖空間範圍,格式爲(min_longitude, min_latitude, max_longitude, max_latitude)

figsize:元組型,用於控制畫幅大小,格式爲(x, y)

axmatplotlib座標軸對象,若是須要在同一個座標軸內疊加多個圖層就須要用這個參數傳入先前待疊加的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')
圖5 圖5

  能夠看到,在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')
圖6 圖6

  能夠看到在增大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')
圖7 圖7

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

圖8 圖8

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

圖9 圖9

2.3 Sankey

  桑基圖專門用於表現不一樣對象之間某個指標量的流動狀況,譬如最多見的航線流向狀況,其本質是對線數據進行可視化,並將指標值映射到線的色彩或粗細水平上,而geoplot中的sankey()能夠用來繪製這種圖,尷尬的是sankey()繪製出的OD流向圖實在太醜,但sankey()中將數值映射到線數據色彩和粗細的特性能夠用來進行與流量相關的可視化,其主要參數以下:

df:傳入對應的GeoDataFrame對象

projection:用於指定投影座標系,傳入geoplot.crs中的對象

hue:傳入對應df中指定列名或外部序列數據,用於映射線的顏色,默認爲None即不進行設色

cmap:和matplotlib中的cmap使用方式一致,用於控制色彩映射方案

alpha:控制全局色彩透明度

scheme:做用相似geopandas中的scheme參數,用於控制分層設色,詳見本系列文章的分層設色篇,但不一樣的是在geoplot0.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)

axmatplotlib座標軸對象,若是須要在同一個座標軸內疊加多個圖層就須要用這個參數傳入先前待疊加的ax

  下面咱們以2015年華盛頓街道路網日平均交通流量數據爲例,其中每一個要素均爲線要素,aadt表明日均流量:

圖10 圖10

  咱們將其流量列映射到線的粗細程度和顏色上來,爲了美觀起見咱們選擇系列文章分層設色篇palettableSunsetDark做爲配色方案:

# 選擇配色方案爲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')
圖11 圖11

2.4 geoplot中的座標參考系

  geoplot中的座標參考系與geopandas中管理起來的方式大相徑庭,由於geopandas基於pyproj管理座標參考系,而geoplot中的crs子模塊來源於cartopy,這一點我跟geoplot的主要開發者聊過,他表示geoplot暫時不支持geopandas中那樣自定義任意投影或使用EPSG投影,而是內置了一系列經常使用的投影,譬如咱們上文中繪製美國區域時頻繁使用到的AlbersEqualArea()即以前咱們在geopandas中經過proj4自定義的阿爾伯斯等面積投影,其餘常見投影譬如Web MercatorRobinson,或者直接繪製球體地圖,如本文開頭的圖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語言實現,對全球主要河流的形態進行優雅地可視化:

圖12 圖12

  針對其河流寬度方面的可視化,咱們基於上文中的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')
圖13 圖13

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

相關文章
相關標籤/搜索