经常看我直播的小伙伴应该知道,小编最近在复现一些商业网站上优秀可视化作品,也有很多同学让我直播进行讲解,这边和小伙伴们说一下:我们安排都是先在公众号发文,然后再进行情况选择是否进行直播讲解。希望大家在看过公众号推文之后多留言、多互动哈。好了,话不多说,今天小编给大家带来的是复现FiveThirtyEight网站的一幅可视化作品,详细介绍如下:
FiveThirtyEight 原作品介绍
今天索要复现的作品为FiveThirtyEight网站的一幅商业可视化作品,其原图如下:
FiveThirtyEight官网原图
关于更多这副作品介绍的详细内容,大家可阅读:FiveThirtyEight官网原图详情[1]
Python-Matplotlib 复现
使用Matplotlib进行上述作品的复现,可以看出需要解决以下两个问题:
- 多子图的绘制
- 不同样式(加粗)文本的添加
- 指示箭头的绘制
接下来,小编带大家一一进行解决,详细内容如下:
多子图的绘制
在Python-Matplotlib中使用plt.subplots() 模块就可以进行多子图的绘制,其详细介绍内容可参考:plt.subplots()方法介绍[2]。这里我们首先进行单个子图的绘制,详细绘制代码如下:
代码语言:javascript复制import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 绘制规则图形
import matplotlib.patches as patches
plt.rcParams['font.family'] = ['Franklin Gothic Book']
data_01 = [80,42]
x_index = np.arange(len(data_01))
width = 0.7
fig,ax = plt.subplots(figsize=(5.2,5),dpi=100,facecolor="w")
ax.bar(x=x_index,height=data_01,color="#DF7373",width=width)
# 绘制链接形状
x = [0 width/2,1-width/2,1-width/2,0 width/2]
y = [0,0,42,80]
ax.add_patch(patches.Polygon(xy=list(zip(x,y)),color="#DF7373",alpha=.5))
ax.set_xlim(left=-.6,right=1.6)
ax.set_ylim(bottom=0,top=100)
ax.grid(axis="y",linestyle="-",color="gray",alpha=.6)
ax.set_yticklabels(labels = ['0', '20 ', '40 ', '60 ', '80 ', '100%'])
ax.tick_params(which='major',direction='in',labelsize=17,labelcolor="gray",bottom=False,
labelbottom=False,left=False,pad=2)
ax.set_ylabel("Share",fontsize=18)
for spine in ["top","left","right"]:
ax.spines[spine].set_visible(False)
ax.spines['bottom'].set_linewidth(2)
ax.set_axisbelow(True)
# 添加文本信息
ax.text(x=0,y=88,s="Lownpotential",va="center",ha="center",fontsize=18,)
ax.text(x=1,y=50,s="Highnpotential",va="center",ha="center",fontsize=18,)
单独一个子图绘制
不同样式(加粗)文本的添加
在Python中要想实现这样的文字小瓜哦,需要借助Python-flexitext库进行绘制,首先,我们举一个小例子进行介绍,绘制代码如下:
代码语言:javascript复制import matplotlib as mpl
import matplotlib.pyplot as plt
from flexitext import flexitext
mpl.rcParams['figure.facecolor'] = 'w'
fig, ax = plt.subplots(figsize=(9, 6))
text = "Normal text"
ax.text(0.5, 0.7, text, size=24, ha="center")
text = "<weight:bold, size:24>Bold text</>"
flexitext(0.5, 0.6, text, ha="center")
text = "<style:italic, size:24>Italic text</>"
flexitext(0.5, 0.5, text, ha="center")
text = "<weight:bold, size:24>Bold and</> <style:italic, size:24>italic too!</>"
flexitext(0.5, 0.4, text, ha="center");
flexitext绘制样例参考
更多关于flexitext库的详细内容看阅读:flexitext官网[3]
接下来,我们使用flexitext库对我们绘制的可视化结果进行优化,完整代码如下:
代码语言:javascript复制from flexitext import flexitext
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 绘制规则图形
import matplotlib.patches as patches
plt.rcParams["font.family"] = ["Public Sans"]
#Public Sans
data_01 = [80,42]
x_index = np.arange(len(data_01))
width = 0.7
fig,ax = plt.subplots(figsize=(5.2,5),dpi=100,facecolor="w")
ax.bar(x=x_index,height=data_01,color="#DF7373",width=width)
# 绘制链接形状
x = [0 width/2,1-width/2,1-width/2,0 width/2]
y = [0,0,42,80]
ax.add_patch(patches.Polygon(xy=list(zip(x,y)),color="#DF7373",alpha=.5))
ax.set_xlim(left=-.6,right=1.6)
ax.set_ylim(bottom=0,top=100)
ax.grid(axis="y",linestyle="-",color="gray",alpha=.6)
ax.set_yticklabels(labels = ['0', '20 ', '40 ', '60 ', '80 ', '100%'])
ax.tick_params(which='major',direction='in',labelsize=17,labelcolor="gray",bottom=False,
labelbottom=False,left=False,pad=2)
ax.set_ylabel("Share",fontsize=18)
for spine in ["top","left","right"]:
ax.spines[spine].set_visible(False)
ax.spines['bottom'].set_linewidth(2)
ax.set_axisbelow(True)
# 添加文本信息
ax.text(x=0,y=88,s="Lownpotential",va="center",ha="center",fontsize=18,)
ax.text(x=1,y=50,s="Highnpotential",va="center",ha="center",fontsize=18,)
# 添加flexitext字体
text = (
"<size:17>81% of jobs withn</>"
"<weight:bold, size:17>low work fromnhome potentialn</>"
"<size:17>are held by peoplen</>"
"<weight:bold, size:17>without a degree</>"
)
flexitext(.52,.82 , text,)
ax.annotate(text="",
xy=(0 width/2, 80), xycoords='data',xytext=(.52, 90),va="center",ha="left",
arrowprops=dict(arrowstyle="->",
fc="k", ec="k",
relpos=(0, .9),
connectionstyle="angle3,angleA=-5,angleB=-90"))
字体加粗后的单个图表
指示箭头的绘制
这里小伙伴们可能看出来了,我们使用ax.annotate() 方法进行绘制,需要注意的是:为了更好的显示注释信息,这里在ax.annotate()方法中文本内容设置为空,目的是结合上一行中flexitext()方法更灵活的实现文本添加。下面给出绘制箭头的部分代码:
代码语言:javascript复制ax.annotate(text="",
xy=(0 width/2, 80), xycoords='data',xytext=(.52, 90),va="center",ha="left",
arrowprops=dict(arrowstyle="->",
fc="k", ec="k",
relpos=(0, .9),
connectionstyle="angle3,angleA=-5,angleB=-90"))
「提醒」:这一部分我们也在直播中进行了讲解下,完整视频也以上传到B站哈,大家可以在B站搜索DataCharm 进行观看哈。
多子图完整绘制代码
最后,我们可以给出绘制完整多字体的代码:
代码语言:javascript复制plt.rcParams["axes.axisbelow"] = True
#绘制多个
data_01 = [80,42]
data_02 = [61,40]
data_03 = [55,68]
data_04 = [42,60]
x_index = np.arange(len(data_01))
width = 0.7
fig,axs = plt.subplots(2,2,figsize=(7,6),sharey=True,dpi=100,facecolor="w")
axs[0,0].bar(x=x_index,height=data_01,color="#DF7373",width=width)
# 绘制链接形状
x = [0 width/2,1-width/2,1-width/2,0 width/2]
y = [0,0,data_01[1],data_01[0]]
axs[0,0].add_patch(patches.Polygon(xy=list(zip(x,y)),color="#DF7373",alpha=.5))
axs[0,0].set_xlim(left=-.6,right=1.6)
axs[0,0].set_ylim(bottom=0,top=100)
axs[0,0].grid(axis="y",linestyle="-",color="gray",alpha=.6)
axs[0,0].set_yticklabels(labels = ['0', '20 ', '40 ', '60 ', '80 ', '100%'])
axs[0,0].tick_params(which='major',direction='in',labelsize=15,labelcolor="gray",bottom=False,
labelbottom=False,left=False,pad=2)
axs[0,0].set_ylabel("Share",fontsize=18)
for spine in ["top","left","right"]:
axs[0,0].spines[spine].set_visible(False)
axs[0,0].spines['bottom'].set_linewidth(2)
# 添加文本信息
bar_text_size = 14
axs[0,0].text(x=0,y=92,s="Lownpotential",va="center",ha="center",fontsize=bar_text_size,
bbox=dict(facecolor='w', edgecolor='w',pad=1))
axs[0,0].text(x=1,y=54,s="Highnpotential",va="center",ha="center",fontsize=bar_text_size,
bbox=dict(facecolor='w', edgecolor='w',pad=1))
text = (
"<size:12>81% of jobs withn</>"
"<weight:bold, size:12>low work fromnhome potentialn</>"
"<size:12>are held by peoplen</>"
"<weight:bold, size:12>without a degree</>"
)
flexitext(.52,.84 , text,ax=axs[0,0])
axs[0,0].annotate(text="",
xy=(0 width/2, 80), xycoords='data',xytext=(.52, 94),va="center",ha="left",
arrowprops=dict(arrowstyle="->",
fc="k", ec="k",
relpos=(0, .9),
connectionstyle="angle3,angleA=-5,angleB=-90"))
# 添加标题标题文本
axs[0,0].text(.5,1.1,"No college degree",transform=axs[0,0].transAxes,fontsize=15,fontweight="bold",
ha="center")
axs[0,0].text(-.4,.5,"Ability to work from home",transform=axs[0,0].transAxes,fontsize=15,fontweight="bold",
va="center",rotation="vertical")
axs[0,1].bar(x=x_index,height=data_02,color="#DEA060",width=width)
# 绘制链接形状
x = [0 width/2,1-width/2,1-width/2,0 width/2]
y = [0,0,data_02[1],data_02[0]]
axs[0,1].add_patch(patches.Polygon(xy=list(zip(x,y)),color="#DEA060",alpha=.5))
axs[0,1].set_xlim(left=-.6,right=1.6)
#axs[1].set_ylim(bottom=0,top=100)
axs[0,1].grid(axis="y",linestyle="-",color="gray",alpha=.6)
axs[0,1].tick_params(which='major',direction='in',labelsize=15,labelcolor="gray",bottom=False,
labelbottom=False,left=False,pad=2)
for spine in ["top","left","right"]:
axs[0,1].spines[spine].set_visible(False)
axs[0,1].spines['bottom'].set_linewidth(2)
axs[0,1].text(x=0,y=73,s="Lownpotential",va="center",ha="center",fontsize=bar_text_size,
bbox=dict(facecolor='w', edgecolor='w',pad=1))
axs[0,1].text(x=1,y=53,s="Highnpotential",va="center",ha="center",fontsize=bar_text_size,
bbox=dict(facecolor='w', edgecolor='w',pad=1))
# 添加标题文本信息
axs[0,1].text(.5,1.1,"Below median income",transform=axs[0,1].transAxes,fontsize=15,fontweight="bold",
ha="center")
axs[1,0].bar(x=x_index,height=data_03,color="#A8A3C7",width=width)
# 绘制链接形状
x = [0 width/2,1-width/2,1-width/2,0 width/2]
y = [0,0,data_03[1],data_03[0]]
axs[1,0].add_patch(patches.Polygon(xy=list(zip(x,y)),color="#A8A3C7",alpha=.5))
axs[1,0].set_xlim(left=-.6,right=1.6)
#axs[1].set_ylim(bottom=0,top=100)
axs[1,0].grid(axis="y",linestyle="-",color="gray",alpha=.6)
axs[1,0].tick_params(which='major',direction='in',labelsize=15,labelcolor="gray",bottom=False,
labelbottom=False,left=False,pad=2)
for spine in ["top","left","right"]:
axs[1,0].spines[spine].set_visible(False)
axs[1,0].set_ylabel("Share",fontsize=18)
axs[1,0].spines['bottom'].set_linewidth(2)
axs[1,0].text(x=0,y=68,s="Lownpotential",va="center",ha="center",fontsize=bar_text_size,
bbox=dict(facecolor='w', edgecolor='w',pad=1))
axs[1,0].text(x=1,y=80,s="Highnpotential",va="center",ha="center",fontsize=bar_text_size,
bbox=dict(facecolor='w', edgecolor='w',pad=1))
# 添加标题文本信息
axs[1,0].text(-.4,.5,"Physical proximity",transform=axs[1,0].transAxes,fontsize=15,fontweight="bold",
va="center",rotation="vertical")
axs[1,1].bar(x=x_index,height=data_04,color="#527B91",width=width)
# 绘制链接形状
x = [0 width/2,1-width/2,1-width/2,0 width/2]
y = [0,0,data_04[1],data_04[0]]
axs[1,1].add_patch(patches.Polygon(xy=list(zip(x,y)),color="#527B91",alpha=.5))
axs[1,1].set_xlim(left=-.6,right=1.6)
#axs[1].set_ylim(bottom=0,top=100)
axs[1,1].grid(axis="y",linestyle="-",color="gray",alpha=.6)
axs[1,1].tick_params(which='major',direction='in',labelsize=17,labelcolor="gray",bottom=False,
labelbottom=False,left=False,pad=2)
for spine in ["top","left","right"]:
axs[1,1].spines[spine].set_visible(False)
axs[1,1].spines['bottom'].set_linewidth(2)
axs[1,1].text(x=0,y=54,s="Lownpotential",va="center",ha="center",fontsize=bar_text_size,
bbox=dict(facecolor='w', edgecolor='w',pad=1))
axs[1,1].text(x=1,y=72,s="Highnpotential",va="center",ha="center",fontsize=bar_text_size,
bbox=dict(facecolor='w', edgecolor='w',pad=1))
s = "60% of job with high physicalnproximity to others are held by peoplenwho earn below the median income"
text = (
"<size:12>60% of job with <weight:bold, size:12>high physicaln</></>"
"<weight:bold, size:12>proximity to others </><size:12>are held by peoplen</>"
"<size:12>who earn <weight:bold, size:12>below the median income</></>"
)
flexitext(.52,.98, text,va="center",ha="center",ax=axs[1,1])
axs[1,1].annotate(text="",
xy=(.8, 75), xycoords='data',xytext=(.5, 85),va="center",ha="left",
arrowprops=dict(arrowstyle="->",
fc="k", ec="k",
relpos=(0, .2),
connectionstyle="angle3,angleA=90,angleB=10"))
fig.text(x=.05,y=1,s="Vulnerable population tend to have the high-risk jobs",fontweight='bold',
fontsize=19)
fig.suptitle(x=.05,t='Share of workers among high and low likelihoods of being able to worknfrom home
and high and low physical proximity to others work',ha="left",fontsize=14)
plt.tight_layout()
FiveThirtyEight可视化作品复现
总结
今天小编使用Python-Matplotlib实现了对FiveThirtyEight 网站可视化作品的复现,也实现了在Maaplotlib中对文本进行定制化操作,希望大家可以学习到一些可视化小技巧~~我们在以后也是会推出R-ggplot2版本的哈,大家尽请期待哈!!
参考资料
[1]
FiveThirtyEight官网原图地址: https://fivethirtyeight.com/features/the-americans-who-suffered-when-the-economy-shut-down-are-also-in-more-danger-as-it-reopens/。
[2]
plt.subplots()方法介绍: https://matplotlib.org/stable/api/figure_api.html?highlight=subplots#matplotlib.figure.Figure.subplots。
[3]
flexitext官网: https://tomicapretto.github.io/flexitext/。