从本期开始,我会陆续推出系列空间插值的推文教程,包括常见的「Kriging(克里金插值法)、Nearest Neighbor(最近邻点插值法)、Polynomial Regression(多元回归法)、Radial Basis Function(径向基函数法)」 等多种空间插值方法。探索空间可视化带给我们的视觉魅力。
由于自己摸索前进(好在已经走通全程),更新进度可能会有延迟。
还会继续推出R-Python 的基础图表绘制推文系列。
可能会根据粉丝的需求或者感兴趣图表进行专门的推文教程,大家可以给我发私信,我们会针对需求较多的图表绘制要求进行专门的推文教程。
好了,下面我们就开始今天的推文内容,本期推文主要包括:
geopandas 绘制空间地图及裁剪操作
colorbar定制化操作参考代码
scipy.stats.gaussian_kde()函数进行核密度估计计算
plotnine 绘制插值结果
在上期推文中Python-geopandas 中国地图绘制 中,我们使用了geopandas实现了中国地图的绘制,也相应分享了绘图数据(数据分享链接失效,本期会补上链接)。当然也有人私信我说安装geopandas太麻烦了,下面我们说明一下,如下:
针对geopandas的安装问题,最好使用 conda install --channel conda-forge geopandas 进行安装。但考虑到科学上网的问题,这一步就难住了很多人。大多人还是采用pip安装geopandas以及其依赖包,这里给出官网链接,大家可根据需求进行下载安装。geopandas官网。读取geojson 地图文件、散点数据及基础绘图代码如下:
散点数据预览如下:
具体绘图代码如下:
import?numpy?as?np
import?pandas?as?pd
import?geopandas?as?gpd
import?matplotlib.pyplot?as?plt
#统一修改绘图字体
plt.rcParams["font.family"]?=?"Roboto?Condensed"
js?=?gpd.read_file(r"江苏省.json")
nj_data?=?pd.read_excel("pmdata.xlsx")
fig,ax?=?plt.subplots(figsize=(6,4),dpi=130)
cm?=?plt.cm.get_cmap('Spectral_r')
vmin?=?nj_data["PM2.5"].min()
vmax?=?nj_data["PM2.5"].max()
js.plot(fc="none",ec="black",ax=ax,lw=.5,zorder=2)
scatter?=?ax.scatter(nj_data['经度'],nj_data["纬度"],c=nj_data["PM2.5"],s=50,ec="k",lw=0.5,cmap=cm,vmin=vmin,vmax=vmax)
#默认的colorbar?无法满足要求,这里进行定制化操作
scatter_bar?=?plt.colorbar(scatter,shrink=0.75,label="PM2.5")
#设置colorbar的outline(edgecolor)颜色
scatter_bar.outline.set_edgecolor('none')
#s设置网格
ax.grid(which="both",axis="both",c="gray",linewidth=.8,alpha=.6)
ax.set_axisbelow(True)
#轴脊设置
for?spine?in?['top','left','right','bottom']:
????ax.spines[spine].set_visible(None)?#隐去轴脊
ax.text(.5,1.1,"Map?Charts?in?Python?Exercise?01:Map?point",transform?=?ax.transAxes,ha='center',?
????????va='center',fontweight="bold",fontsize=13)
ax.text(.5,1.03,?"processed?map?charts?with?geopandas",
????????transform?=?ax.transAxes,ha='center',?va='center',fontsize?=?10,color='black')
ax.text(.83,-.07,'\nVisualization?by?DataCharm',transform?=?ax.transAxes,
????????ha='center',?va='center',fontsize?=?7,color='black')
plt.savefig(r'scatter_example.png',width=7,height=5,dpi=900,bbox_inches='tight')
可视化效果如下:
上面绘图代码中这里我们定制化了colorbar,代码如下:
#默认的colorbar?无法满足要求,这里进行定制化操作
scatter_bar?=?plt.colorbar(scatter,shrink=0.75,label="PM2.5")
#设置colorbar的outline(edgecolor)颜色
scatter_bar.outline.set_edgecolor('none')
此外,我还收集了多个关于设置colorbar的代码语句,如下:
#?COLORBAR
#?set?colorbar?label?plus?label?color
cb.set_label('colorbar?label',?color=fg_color)
#?set?colorbar?tick?color
cb.ax.yaxis.set_tick_params(color=fg_color)
#?set?colorbar?edgecolor?
cb.outline.set_edgecolor(fg_color)
#
cbar?=?plt.colorbar()
cbar.ax.tick_params(color="red",?width=5,?length=10)
#####?two?new?lines?####
color_bar.outline.set_color('w')???????????????????#set?colorbar?box?color
color_bar.ax.yaxis.set_tick_params(color='w')??????#set?colorbar?ticks?color?
#####?two?new?lines?####
clb.set_label('label',?labelpad=-40,?y=1.05,?rotation=0)
#cb=colorbar(cs,?drawedges=True)
cb.outline.set_color('white')
cb.outline.set_linewidth(2)
cb.dividers.set_color('white')
cb.dividers.set_linewidth(2)
cb.outline.set_edgecolor('white')
##?以前文章内容
cbar?=?plt.colorbar(ax=ax,ticks=[0,10,20,30,40],drawedges=False)
#cbar.ax.set_ylabel('Frequency',fontdict=colorbarfontdict)
cbar.ax.set_title('Counts',fontdict=colorbarfontdict,pad=8)
cbar.ax.tick_params(labelsize=12,direction='in')
cbar.ax.set_yticklabels(['0','10','20','30','>40'],family='Times?New?Roman')
希望可以大家更好的使用matplotlib魔改颜色条(colorbar)设置。
在系列插值之前,我们先绘制核密度估计的插值图,在Python中物品们可以借用scipy.stats.gaussian_kde()实现空间核密度插值计算,大家也可参考scipy官网关于gaussian_kde() 的用法:高斯核密度估计参考官网。接下来我们使用该函数将散点插值到南京地图的范围之内,这里先给出代码再对应给出解释:
这一步是为了获取插值所需要的范围,使用geopandas的total_bounds()方法即可获取:
js_box?=?js.geometry.total_bounds
这里直接给出代码,如下:
#生成插值网格
#导入核密度估计包
import?scipy.stats?as?st
#根据获取的地图文件范围设置插值网格的大小范围
xmin?=?js_box[0]
xmax?=?js_box[2]
ymin?=?js_box[1]
ymax?=?js_box[3]
x_valuse?=?nj_data["经度"].values
y_valuse?=?nj_data["纬度"].values
#?这里注意下,也没过多解释,直接看代码即可
X,?Y?=?np.mgrid[xmin:xmax:400j,?ymin:ymax:400j]
positions?=?np.vstack([X.flatten(),Y.flatten()])
values?=?np.vstack([x_valuse,?y_valuse])
kernel?=?st.gaussian_kde(values)
Density?=?kernel(positions)
Density
注意:
np.mgrid[xmin:xmax:400j, ymin:ymax:400j]则是更具地图文件的范围插入400x400的网格,当然你也可以设置不同值。
X.flatten() 是将数据扁平化处理。
np.vstack() 按垂直方向(行顺序)堆叠数组构成一个新的数组,堆叠的数组需要具有相同的维度
接下来,我们将结果转换形状即可:
#reshape
Density_re?=?np.reshape(Density.T,?X.shape)
这一步,我们将结果存成pandas的df类型,方便后续的制图操作,如下:
#将插值网格数据整理
df_grid?=pd.DataFrame(dict(long=X.flatten(),lat=Y.flatten()))
df_grid["kde"]?=?Density
结果如下(部分):
这里的可视化绘制,我们直接使用语法和ggplot2相似的python包:plotnine,感兴趣的小伙伴可以自行搜索。绘图代码如下:
import?plotnine
from?plotnine?import?*
plotnine.options.figure_size?=?(5,?4.5)
kde_map_grid?=?(ggplot()?+?
???????????geom_tile(df_grid,aes(x='long',y='lat',fill='kde'),size=0.1)?+
???????????geom_map(js,fill='none',color='gray',size=0.3)?+?
???????????scale_fill_cmap(cmap_name='Spectral_r',name='kde_value',
???????????????????????????breaks=[0.05,0.10,0.15,0.2,0.25],
???????????????????????????)+
???????????scale_x_continuous(breaks=[117,118,119,120,121,122])+
???????????labs(title="Map?Charts?in?Python?Exercise?01:?Map?point?kde",
????????????????)+
???????????#添加文本信息
???????????annotate('text',x=116.5,y=35.3,label="processed?map?charts?with?plotnine",ha="left",
???????????????????size=10)+
???????????annotate('text',x=120,y=30.6,label="Visualization?by?DataCharm",ha="left",size=9)+
???????????theme(
???????????????text=element_text(family="Roboto?Condensed"),
???????????????#修改背景
???????????????panel_background=element_blank(),
???????????????axis_ticks_major_x=element_blank(),
???????????????axis_ticks_major_y=element_blank(),
???????????????axis_text=element_text(size=12),
???????????????axis_title?=?element_text(size=14),
???????????????panel_grid_major_x=element_line(color="gray",size=.5),
???????????????panel_grid_major_y=element_line(color="gray",size=.5),
????????????))
kde_map_grid
最终可视化效果如下:
一般的绘图教程到这里也就结束了,但往往忽略了大多人人关注的“裁剪”操作,在经历过不断探索后,我们最终使用geopandas.clip() 方法完美解决此问题。
在将gaussian_kde()转换成pandas df类型的数据转换成geopandas数据类型后,就可使用geopandas.clip() 方法对geodf对象进行裁剪操作,具体代码如下:
df_grid_geo?=?gpd.GeoDataFrame(df_grid,?geometry=gpd.points_from_xy(df_grid["long"],?df_grid["lat"]),crs="EPSG:4326")
#根据之前的js?geojson格式的地图文件进行裁剪
js_kde_clip?=?gpd.clip(df_grid_geo,js)
注意: 在使用gpd.clip()操作之前,请确保geopandas 安装成功,要不然 crs="EPSG:4326" 无法准确设置,进而导致无法裁剪。个人建议: pyproj must version 2.2.0 or later
再使用plotnine 对裁剪之后的js_kde_clip 数据进行绘图即可,代码和上述绘图代码一样,即数据更改而已,这里就直接放出可视化结果:
注意: 该裁剪方法只限于geopandas + plotnine 组合绘制空间可视化作品。
最为第一篇插值文章,介绍的可能有些啰嗦,后续其他插值的方法我们将更为精简,希望大家可以好好看看本篇文章,下期推文使用Basemap(虽然停止维护,但还有好多优秀功能可以使用,也有对应不同 python 版本的whl文件可供下载安装)绘制此图,当然,也还有「更加实用的裁剪操作方法」。