原来 Matplotlib 绘图也可以这么漂亮,这次真的是学习到了!
网易数读
、DT财经
的各种精美可视化图,又限于自己手残学不会AdoeIllutrator。一、简介
Python
的方式模仿复刻图2所示作品:2.1 观察原作品
「1 坐标系部分」
matplotlib
中极坐标系的基础上想方法隐藏部分参考线,不如逆向思维,从构造参考线的角度出发,自己组织构造参考线,会更加的自由和灵活。「2 颜色填充」
居住自由指数
折线为中线,在购房自由指数
折线与租房自由指数
折线之间的颜色填充区域,但困难的是这里当购房自由指数
高于租房自由指数
时对应的颜色为浅蓝绿色,而反过来则变为灰色,与购房自由指数
、租房自由指数
的颜色相呼应。2.2 开始动手!
matplotlib
+geopandas
+shapely
操纵几何对象和绘制调整图像的方便快捷性,来完成这次的挑战。2.2.1 构建坐标系统
geopandas
中的投影变换向设定好的「正射投影」进行转换,再作为平面坐标进行绘图即可。譬如按照这个思路来创建东经10度到东经220度之间,以及南纬-90度到-80度之间,对应的5条纬度线和对应38个城市的经线:import geopandas as gpd
from shapely.geometry import LineString, Point, Polygon
import matplotlib.pyplot as plt
import numpy as np
import warnings
plt.rcParams['font.sans-serif'] = ['SimHei'] # 解决matplotlib中文乱码问题
plt.rcParams['axes.unicode_minus'] = False # 解决matplotlib负号显示问题
warnings.filterwarnings('ignore')
# 设置中心点在南极点的正射投影
crs = '+proj=ortho +lon_0=0 +lat_0=-90'
# 构建经度线并设置对应经纬度的地理坐标系
lng_lines = gpd.GeoDataFrame({
'geometry': [LineString([[lng, -90], [lng, -78]]) for lng in np.arange(10, 220, 210 / 38)]},
crs='EPSG:4326')
# 构建纬度线并设置为对应经纬度的地理坐标系
lat_lines = gpd.GeoDataFrame({
'geometry': [LineString([[lng, lat] for lng in range(10, 220)]) for lat in range(-90, -79, 2)]},
crs='EPSG:4326')
GeoDataFrame
转换到设置好的「正射投影」crs
上,再作为不同图层进行叠加绘制:2.2.2 绘制指标折线
居住自由指数
的具体数值,其他两个指标未提供,因此我们可以结合这3个数值的相互关系,推断出每个城市的购房自由指数
与租房自由指数
1个比自身的居住自由指数
高,1个比居住自由指数
低的规律来「伪造」数据:def fake_index(value):
fake = []
fake.append(value+np.random.uniform(5, 10))
fake.append(value-np.random.uniform(5, 10))
return np.random.choice(fake, size=2, replace=False).tolist()
data['购房自由指数'], data['租房自由指数'] = list(zip(*data['居住自由指数'].apply(fake_index)))
# 修正伪造数据中大于100和小于0的情况
data.loc[:, '居住自由指数':] = data.loc[:, '居住自由指数':].applymap(lambda v: 100 if v > 100 else v)
data.loc[:, '居住自由指数':] = data.loc[:, '居住自由指数':].applymap(lambda v: 0 if v < 0 else v)
data.head()
# 为每个城市生成1条经线
lng_lines = gpd.GeoDataFrame({
'geometry': [LineString([[lng, -90], [lng, -78]]) for lng in np.arange(10, 220, 210 / data.shape[0])]},
crs='EPSG:4326')
# 居住自由指数对应的折线
line1 = gpd.GeoDataFrame({
'geometry': [LineString([(lng, lat) for lng, lat in zip(np.arange(10, 220, 210 / data.shape[0]),
data['居住自由指数_映射值'])])]},
crs='EPSG:4326')
# 居住自由指数对应的折线上的散点
scatter1 = gpd.GeoDataFrame({
'geometry': [Point(lng, lat) for lng, lat in zip(np.arange(10, 220, 210 / data.shape[0]),
data['居住自由指数_映射值'])]}, crs='EPSG:4326')
# 购房自由指数对应的折线
line2 = gpd.GeoDataFrame({
'geometry': [LineString([(lng, lat) for lng, lat in zip(np.arange(10, 220, 210 / data.shape[0]),
data['购房自由指数_映射值'])])]},
crs='EPSG:4326')
# 购房自由指数对应的折线上的散点
scatter2 = gpd.GeoDataFrame({
'geometry': [Point(lng, lat) for lng, lat in zip(np.arange(10, 220, 210 / data.shape[0]),
data['购房自由指数_映射值'])]}, crs='EPSG:4326')
# 租房自由指数对应的折线
line3 = gpd.GeoDataFrame({
'geometry': [LineString([(lng, lat) for lng, lat in zip(np.arange(10, 220, 210 / data.shape[0]),
data['租房自由指数_映射值'])])]},
crs='EPSG:4326')
# 租房自由指数对应的折线上的散点
scatter3 = gpd.GeoDataFrame({
'geometry': [Point(lng, lat) for lng, lat in zip(np.arange(10, 220, 210 / data.shape[0]),
data['租房自由指数_映射值'])]}, crs='EPSG:4326')
fig, ax = plt.subplots(figsize=(8, 8))
# 绘制经度线与纬度线
ax = lng_lines.to_crs(crs).plot(ax=ax, linewidth=0.4, edgecolor='lightgrey')
ax = lat_lines.to_crs(crs).plot(ax=ax, linewidth=0.75, edgecolor='grey', alpha=0.8)
ax = line1.to_crs(crs).plot(ax=ax, color='black', linewidth=1)
ax = scatter1.to_crs(crs).plot(ax=ax, color='black', markersize=12)
ax = line2.to_crs(crs).plot(ax=ax, color='#00CED1', linewidth=0.6)
ax = scatter2.to_crs(crs).plot(ax=ax, color='#00CED1', markersize=4)
ax = line3.to_crs(crs).plot(ax=ax, color='lightgrey', linewidth=0.6)
ax = scatter3.to_crs(crs).plot(ax=ax, color='lightgrey', markersize=4)
ax.axis('off'); # 关闭坐标轴
fig.savefig('图11.png', dpi=500, inches_bbox='tight', inches_pad=0)
2.2.3 绘制填充区域
购房自由指数
与租房自由指数
之间的折线,并且要按照「填充较大值对应色彩」的原则来处理,接下来我们需要用到一点简单的拓扑学知识,首先我们分别构造购房自由指数_映射值
和租房自由指数_映射值
引入南极点后所围成的多边形:购房自由指数_映射值
与租房自由指数_映射值
之间彼此高低起伏交错而形成的填充区域对应着上面两个多边形之间的什么关系?没错!就是就是两者去除掉彼此重叠区域后各自剩余的部分!geopandas
中的difference
即可轻松实现:fig, ax = plt.subplots(figsize=(8, 8))
# 绘制经度线与纬度线
ax = lng_lines.to_crs(crs).plot(ax=ax, linewidth=0.4, edgecolor='lightgrey')
ax = lat_lines.to_crs(crs).plot(ax=ax, linewidth=0.75, edgecolor='grey', alpha=0.8)
ax = line1.to_crs(crs).plot(ax=ax, color='black', linewidth=1)
ax = scatter1.to_crs(crs).plot(ax=ax, color='black', markersize=12)
ax = line2.to_crs(crs).plot(ax=ax, color='#00CED1', linewidth=0.6)
ax = scatter2.to_crs(crs).plot(ax=ax, color='#00CED1', markersize=4)
ax = line3.to_crs(crs).plot(ax=ax, color='lightgrey', linewidth=0.6)
ax = scatter3.to_crs(crs).plot(ax=ax, color='lightgrey', markersize=4)
ax = polygon1.difference(polygon2).plot(ax=ax, color='#00CED1', alpha=0.2)
polygon2.difference(polygon1).plot(ax=ax, color='lightgrey', alpha=0.6)
ax.axis('off'); # 关闭坐标轴
fig.savefig('图13.png', dpi=500, inches_bbox='tight', inches_pad=0)
2.2.4 补充文字、标注等元素
评论