广东省主要城市空气质量数据可视化
呼吸,贯穿人的一生。正因为如此,城市空气质量的监督、检测、报告才吸引着每一位市民的目光。本实验通过对广东空气质量日报 2001 年 1 月在本省各大主要城市各监测站采集的空气质量数据的分析及可视化,向读者介绍了广东省的空气质量情况。
在 Jupyter Notebook 首行输入并执行魔法命令 %matplotlib inline,后序在调用 Matplotlib 相关接口进行绘图的时候,或者生成画布的时候,可以直接在输出行中生成图像。
出现在代码块中 plt.rcParams 相关代码是对图对象的初始定义,相关定义内容详见代码注释。
import matplotlib.pyplot as plt
%matplotlib inline
plt.rcParams['legend.frameon'] = False # 不显示图例边框
plt.rcParams['axes.spines.top'] = False # 图顶部坐标轴显示
plt.rcParams['axes.spines.right'] = False # 图右侧坐标轴显示
plt.rcParams['axes.spines.bottom'] = False # 图底部坐标轴显示
plt.rcParams['ytick.labelsize'] = 20 # y 坐标轴刻度标签字号
plt.rcParams['axes.labelsize'] = 20 # 坐标轴标题字号
plt.rcParams['axes.titlesize'] = 20 # 子图标题字号
数据准备
数据集介绍
本数据集包括 7 个特征,其中 5 个离散型数据、1 个时间序列、1 个连续性数据,针对本文的数据集,主要分析了以下几方面的内容:
各空气质量状况在统计期内出现的频数
空气污染指数分位数分布可视化
空气污染指数的统计估计
空气质量级别聚类可视化
主要城市空气质量状况优秀占比
导入数据并查看头部 5 行数据。
import pandas as pd
air = pd.read_excel(
'https://labfile.oss.aliyuncs.com/courses/3023/guangdong-air.xls')
air.head()
各空气质量状况在统计期内出现的频数
对空气质量状况各元素进行聚合并查看聚合结果。
air_condition = air.groupby(['空气质量状况']).size()
air_condition
字体设置
import matplotlib
import os
# ----------linux 系统下通过以下方式安装开源中文字体 ---------#
# 下载字体到当前目录
!wget -nc "https://labfile.oss.aliyuncs.com/courses/3023/NotoSansCJK.otf"
fpath = os.path.join("./NotoSansCJK.otf")
myfont = matplotlib.font_manager.FontProperties(fname=fpath, size=20)
# ---------- windows 环境下 ---------#
# wintows 环境下,则没有这么多的麻烦,只需要通过如下代码即可使用中文字体
# plt.rcParams['font.family'] = 'SimHei' # 微软雅黑
本实验主要用到的可视化对象为条形图,作为描述性统计的主要可视化形式,条形图直观地向读者呈现出数据系列内部各要素的对比差异,几乎占据了 PPT、融资报告、行业分析报告、券商研报的半壁江山(如果你有兴趣阅读任泽平博士定期更新的恒大研究院行业分析报告的话,更会发现条形+折线图几乎是该研报的主要可视化形式)。
绘制条形图的主要接口为 plt.bar,需要传入的数据为 x 和 height (两者数据长度须保持一致),分别代表条形图的横坐标和条形图柱子的高度,本例将数据集聚合得到的 air_condition 传入接口,可视化了各空气质量状况的频数分布,可见,广东省大多数时候空气质量为良。
plt.rcParams['figure.figsize'] = (8, 6) # 画布大小
plt.bar(x=air_condition.index, height=air_condition)
plt.xlabel('空气质量状况', fontproperties=myfont)
plt.ylabel('频数', fontproperties=myfont)
plt.title('空气质量状况频数分布图', fontproperties=myfont)
# 获取当前子图
ax = plt.gca()
# 设置子图 x 轴上的中文标签字体
ax.xaxis.set_ticklabels(ticklabels=air_condition.index, fontproperties=myfont)
空气污染指数分位数分布可视化
空气污染指数(Air Pullution Index,API),将常规监测的几种空气污染物浓度简化成为单一的概念性指数值形式,用来分级表征空气污染程度和空气质量状况。我们常听到广播或者电视播报当天的 API 指数,那么当你听到当天的指数时,是否感兴趣该指数在全年(或者一定时间范围)的统计期的分位数呢?比如当天的 API 为 50,那么一个统计期内有多少比例的 API 指数高于 50,又有多少比例的低于 50?本例通过调节条形图柱子宽度(width)及条形图数据序列,实现了空气污染指数分位数分布的可视化。
通过 gt_p 和 lt_p 函数计算高于/低于当前 API 值的数据比例,并生成新的特征列。为更直观地可视化高/低的情况分布,将低于当前 API 比例的数据集进行了负数处理(数据可视化的一些小技巧,试想如果都在坐标轴的同一侧,必然带来一定程度的面积重叠,并不能很好地展现数据分布特征)。
import numpy as np
def gt_p(x, array):
return np.sum(array >= x)/len(array)
def lt_p(x, array):
return -1*np.sum(array < x)/len(array)
air['gt_air'] = air['API值'].apply(lambda x: 100*gt_p(x, array=air['API值']))
air['lt_air'] = air['API值'].apply(lambda x: 100*lt_p(x, array=air['API值']))
air = air.sort_values(by=['gt_air'], ascending=False)
air.head()
运行以下代码,从输出图形可以看出,当 API 指数为 0 时,高于/低于其值的比例分别为 100%,0%。说明此处已经是最低的 API;与此相反,在 API 在 140 至 180 的区间,高于其值的比例已经寥寥无几,因此当我们听到当天 API 为 140 时,应该有所警觉,当天的空气污染已经是统计期内相对严峻的时候了。
API 指数在 25 - 85 曲线范围内呈线性变化,两端呈现明显的拖尾效应。50% 的分位值大约对应 API 为 65 左右,说明统计期内,广东省大致的平均污染指数应该在 65 左右。如当天 API 播报在 65 ± 30 范围,可认为当天空气质量相对正常。
plt.rcParams['figure.figsize'] = (8, 6)
plt.bar(x='API值', height='gt_air', data=air, width=1.0)
plt.bar(x='API值', height='lt_air', data=air, width=1.0)
# 设置坐标轴刻度及标签
yticks = np.arange(-100, 120, 40)
plt.yticks(ticks=yticks, labels=['%.0f' % label for label in yticks])
plt.xticks(ticks=np.arange(0, 200, 20))
# 设置坐标轴标题
plt.xlabel('Air Pollution Index')
plt.ylabel('Percent/%')
# 获取当前子图
ax = plt.gca()
ax.spines['bottom'].set_position(('data', 0))
# 添加文本
ax.text(0.6, 0.7, 'Above', size=30, transform=ax.transAxes)
ax.text(0.1, 0.2, 'Below', size=30, transform=ax.transAxes)
# 添加标题
plt.title('The precent of greater or less than current API value')
空气污染指数的统计估计
对于聚合结果的可视化,条形图常常是显得单调的(因为只有一根柱子),往往仅能反应某一个聚合结果(例如,均值、汇总值等)。统计上,我们更多关心的是围绕聚合结果的置信水平,例如柱形图显示的均值为 50,那么其 95% 置信度下的置信区间是多少?对于此类问题的可视化,主要有两种方式实现:
方式一:手工求聚合均值及聚合标准偏差,以添加误差线的形式向柱形图增加标准偏差线;
方式二:通过 seaborn 调用 barplot 接口,直接在特征数据集上进行聚合均值及聚合标准偏差的计算并可视化。
本例采用第二种方式对广东省主要城市和监测点空气污染指数的平均水平和其 95% 置信度区间进行了可视化。
置信区间的统计学原理,可参考 置信区间。
按城市统计估计
运行以下代码,从输出结果可以看出,湛江的空气污染指数平均水平最低,分布也较为狭窄( 95%置信度下,置信区间分布区间较窄,体现在误差线线段很短)。
import seaborn as sns
plt.rcParams['figure.figsize'] = (8, 5)
# 城市顺序:按照均值升序排列
order = air.groupby(['所属城市'], as_index=False)[
'API值'].mean().sort_values(['API值'])['所属城市']
sns.barplot(x='所属城市', y='API值', order=order, palette='Greens', data=air)
# 获取子图并在出现中文的地方设置中文字体
ax = plt.gca()
ax.set_xlabel(ax.get_xlabel(), fontproperties=myfont)
ax.set_ylabel(ax.get_ylabel(), fontproperties=myfont)
ax.xaxis.set_ticklabels(ticklabels=ax.get_xticklabels(), fontproperties=myfont)
ax.set_title('广东主要城市空气污染指数分布', fontproperties=myfont)
按城市及空气质量状况统计估计
运行以下代码,从输出结果可以看出,空气质量状况主要集中在优、良两种状态,同样其分布也相对狭窄。最差的佛山和南海均出现了轻度污染的质量状况。
plt.rcParams['figure.figsize'] = (20, 6)
# 城市顺序:按照均值升序排列
order = air.groupby(['所属城市'], as_index=False)[
'API值'].mean().sort_values(['API值'])['所属城市']
sns.barplot(x='所属城市', y='API值', hue='空气质量状况',
order=order, palette='Greens', data=air)
# 字体设置
ax = plt.gca()
ax.set_xlabel(ax.get_xlabel(), fontproperties=myfont)
ax.set_ylabel(ax.get_ylabel(), fontproperties=myfont)
ax.xaxis.set_ticklabels(ticklabels=ax.get_xticklabels(), fontproperties=myfont)
ax.set_title('广东主要城市空气污染指数分布', fontproperties=myfont)
ax.legend(prop=myfont, frameon=False)
按监测点统计估计
运行以下代码,从输出结果可以看出,不同监测点的空气污染指数明显不同,影院、学校等地区污染指数普遍较低,而工业区等地相对较高,置信区间也相对较宽。
至于市政协等地为什么最高,可能需要对数据集获取点进行实地考察,观察其附近是否有显著污染源。
plt.rcParams['figure.figsize'] = (20, 6)
# 监测站顺序:按照均值升序排列
order = air.groupby(['监测站名称'], as_index=False)[
'API值'].mean().sort_values(['API值'])['监测站名称']
sns.barplot(x='监测站名称', y='API值', palette='Greens', order=order, data=air)
# 字体设置
ax = plt.gca()
ax.set_xlabel(ax.get_xlabel(), fontproperties=myfont)
ax.set_ylabel(ax.get_ylabel(), fontproperties=myfont)
ax.xaxis.set_ticklabels(ticklabels=ax.get_xticklabels(),
fontproperties=myfont, rotation=90)
ax.set_title('广东主要城市空气污染指数分布', fontproperties=myfont)
ax.set_title('广东主要监测站空气污染指数分布', fontproperties=myfont)
空气质量级别聚类
城市空气质量等级,是据城市空气环境质量标准和各项污染物的生态环境效应及其对人体健康的影响,该级别由相应的污染物浓度限值确定。空气质量级别与空气污染指数根据国家标准有相应的对照关系,通常空气污染指数在 0 ~ 50 时,对应的空气质量级别为 Ⅰ 级。本例将统计期内空气质量级别进行聚类展示,可视化了其与空气污染指数的对照关系。
运行以下代码,从输出结果可以看出,空气污染指数 0~ 50,对应,空气质量级别 Ⅰ 级;空气污染指数 51 ~ 100,对应,空气质量级别 Ⅱ 级;与国家标准相对应。从分布面积可以看出,统计期内,广东省常年空气质量等级为 Ⅱ 级以上,Ⅲ 级很少,是很宜居的地方。
plt.rcParams['figure.figsize'] = (10, 6)
start = 0
# 簇间距离
cluster_distance = 20
# 将每个空气质量登记单独绘图成为一个簇
for level in ['Ⅰ级', 'Ⅱ级', 'Ⅲ1级', 'Ⅲ2级']:
air_level = air.loc[air['空气质量级别'] == level]
air_level = air_level.sort_values(['API值'])
nums = len(air_level)
plt.bar(x=np.arange(start=start, stop=start+nums, step=1),
height=air_level['API值'], width=1.0, label=level)
start += nums+cluster_distance
plt.xticks(ticks=[])
plt.ylabel('Air Pollution Index')
plt.title('The Relationship Between Air Pollution Index and Air Pollution Level')
# 设置图例
ax = plt.gca()
ax.legend(prop=myfont, frameon=False)
主要城市空气质量状况优秀占比
即使是对离散型变量,我们也常研究其分层采样状态下的分布情况。挑剔的决策者常常只关心数据的头部特征,例如,哪个城市空气质量状况优秀的占比最高,对于此类问题归一化的纵向堆积条形图是可视化的首选。
将空气质量状况为“优”的,在新的特征('excellent')中赋予特征值 Excellent ,否则赋值为 Nonexcellent,并以特征 所属城市 为行,excellent 为列,日期 为值进行数据透视,生成交叉表,随后将交叉表各列数据归一化到 100%,并新增特征 城市。
air.loc[air['空气质量状况'] == '优', 'excellent'] = 'Excellent'
air.loc[air['空气质量状况'] != '优', 'excellent'] = 'Nonexcellent'
air_ex = air.pivot_table(values='日期', index='所属城市',
columns='excellent', aggfunc='count')
freqs = air_ex.sum(axis=1)
for col in air_ex.columns:
air_ex[col] = air_ex[col]/freqs*100
air_ex['city'] = air_ex.index
air_ex
运行以下代码,从输出结果可以看出,临近广州湾的湛江在统计期内空气质量优秀的占比情况远大于其他几个城市,湛江临海,常年亚热带季风气候,且市内又有几个大型湿地森林公园,空气质量可谓得天独厚地好。
plt.rcParams['figure.figsize'] = (10, 6)
plt.bar(x='city', height='Nonexcellent', data=air_ex, width=1.0, label='非优秀')
plt.bar(x='city', height='Excellent', bottom='Nonexcellent',
data=air_ex, width=1.0, label='优秀')
# 调整x,y轴范围
plt.xlim((-0.5, len(air_ex)-0.5))
plt.ylim((0, 100))
ax = plt.gca()
ax.set_xlabel(ax.get_xlabel(), fontproperties=myfont)
ax.set_ylabel('占比/%', fontproperties=myfont)
# 设置 x 轴刻度标签文本
ax.xaxis.set_ticklabels(ticklabels=air_ex['city'], fontproperties=myfont)
ax.set_title('广东省主要城市空气质量状况优秀占比情况', fontproperties=myfont)
ax.legend(prop=myfont, bbox_to_anchor=(1.0, 0.9), frameon=False)
以下从地理区位角度进一步可视化数据集中几个城市的空气质量优秀比例,引入地图可视化数据集中城市的空气质量状况优秀占比,通过颜色表示占比高低。
!pip install pyecharts==1.7.1
from pyecharts.charts import Map
from pyecharts import options as opts
m = Map()
m.add(
series_name="",
maptype='广东',
data_pair=[list(z) for z in zip(air_ex['city'].apply(
lambda x:x+'市'), air_ex['Excellent'])],
label_opts=opts.LabelOpts(is_show=False),
is_map_symbol_show=False,
)
m.set_global_opts(
title_opts=opts.TitleOpts(
title="统计期内广东省主要城市空气质量状况优秀占比",
subtitle="单位:%",
pos_left="center",
pos_top="top",
title_textstyle_opts=opts.TextStyleOpts(
font_size=25
),
),
visualmap_opts=opts.VisualMapOpts(
min_=0,
max_=100,
range_text=["High", "Low"],
range_color=["red", "green"],
),
)
m.render_notebook()
从数据各个角度均能看出,湛江的空气质量在被分析城市中是最好的,上图可以看出,湛江具备独一无二的地理区位优势,表现在:
相对其他几个城市,湛江紧邻广州湾,常年的亚热带季风季候,海面的新鲜空气与城市空气不断置换,污染物被迅速稀释;
湛江市内包含了几个面积较大的湿地森林公园,植被对空气的过滤效果显著。在广州主要城市中,湛江的空气真可谓是一枝独秀,相比而言,空气质量次好的是珠海市。(本节的地图可视化的绘制将在之后的章节做更详细地讲解,此处只是为了细化本案例的表达内容,可只做了解)。
将以上数据横向堆积,可实现横向堆积条形图。
plt.rcParams['figure.figsize'] = (10, 6)
air_ex = air_ex.sort_values(['Excellent'])
xs = np.array(range(len(air_ex['city'])))
width = 0.4
plt.bar(x=xs-width/2, height=air_ex['Nonexcellent'], width=width, label='非优秀')
plt.bar(x=xs+width/2, height=air_ex['Excellent'], width=width, label='优秀')
plt.ylim((0, 100))
plt.xticks(ticks=xs, labels=air_ex['city'])
ax = plt.gca()
ax.set_xlabel(ax.get_xlabel(), fontproperties=myfont)
ax.set_ylabel('占比/%', fontproperties=myfont)
ax.xaxis.set_ticklabels(ticklabels=ax.get_xticklabels(), fontproperties=myfont)
ax.set_title('广东省主要城市空气质量状况优秀占比情况', fontproperties=myfont)
ax.legend(prop=myfont, frameon=False)
交互式条形图
pyecharts 渲染的图增加了一定的交互式功能,如上图所示,图初次被渲染后并不显示非优秀比例系列柱子,需要手动点击图例,该系列数据才会动态加载。
import matplotlib.colors as cs
from pyecharts.charts import Bar
font_size = 20
bar = Bar()
# 添加x轴数据
bar.add_xaxis(list(air_ex.city))
# 添加y轴数据
bar.add_yaxis(
series_name='优秀比例/%',
yaxis_data=list(air_ex.Excellent.apply(lambda x: round(x, 2))),
is_selected=True,
color=cs.TABLEAU_COLORS['tab:blue'],
)
# 添加y轴数据
bar.add_yaxis(
series_name='非优秀比例/%',
yaxis_data=list(air_ex.Nonexcellent.apply(lambda x: round(x, 2))),
is_selected=False, # 此系列设置为未选中,渲染后须手动选中该序列
color=cs.TABLEAU_COLORS['tab:orange'],
)
bar.set_global_opts(
# 设置标题
title_opts=opts.TitleOpts(title='广东省主要城市空气质量状况优秀占比情况',
pos_left="25%",
title_textstyle_opts=opts.TextStyleOpts(font_size=25)),
# 设置图例
legend_opts=opts.LegendOpts(
pos_top='10%', textstyle_opts=opts.TextStyleOpts(font_size=font_size)),
# 设置y轴
yaxis_opts=opts.AxisOpts(
name='比例/%',
name_textstyle_opts=opts.TextStyleOpts(font_size=font_size),
axislabel_opts=opts.TextStyleOpts(font_size=font_size),
),
# 设置x轴
xaxis_opts=opts.AxisOpts(
axislabel_opts=opts.TextStyleOpts(font_size=font_size),
)
)
bar.render_notebook()
中国历代朝代
在很多情况下,需要制作横向的条形图,横向条形图绘制的主要接口为 plt.barh
,需要传入的数据为 x 和 width (两者数据长度须保持一致),分别代表条形图的纵坐标和条形图柱子的宽度,其大多数用法与 plt.bar
基本相同,此处不再赘述,本例通过一个堆积条形图的案例可视化了中国古代各封建王朝的时序。
本数据集一共包含 4 个特征,分别是朝代名称,开始时间,结束时间和朝代的时长。在可视化上,将开始时间和时长分别传入 width 和 left 参数,制造出阶梯的效果,这种阶梯类型的图叫 甘特图,由亨利·劳伦斯·甘特(Henry Laurence Gantt)提出,并以其名字命名,常用作项目进度和阶段的目视化管理。
china = pd.read_excel(
'https://labfile.oss.aliyuncs.com/courses/3023/china-ancient.xlsx')
china['时长'] = china['结束']-china['开始']
china.head()
plt.rcParams['figure.figsize'] = (10, 15)
color = china['开始'].apply(lambda x: 'tab:blue' if x > 0 else 'tab:red')
plt.barh(y='朝代', width='时长', left='开始', data=china, color=color)
plt.xticks([])
for y, time in enumerate(china['结束']):
plt.text(time, y, '公元前%d年' % abs(time) if time < 0 else '%d年' %
time, fontproperties=myfont, fontsize=20, va='center')
ax = plt.gca()
# 设置 y 刻度标签
ax.yaxis.set_ticklabels(china['朝代'], fontproperties=myfont, rotation=0)
# 添加文本注释
ax.text(0.05, 0.9, '中国古代王朝时间顺序表', fontsize=30,
fontproperties=myfont, transform=ax.transAxes)
poem = '''
夏商与西周\n
东周分两段\n
春秋和战国\n
一统秦两汉\n
三分魏蜀吴\n
二晋前后延\n
南北朝并立\n
隋唐五代传\n
宋元明清后\n
皇朝至此完\n
'''
# 添加文本注释
ax.text(0.15, 0.88, poem, fontsize=25, va='top',
fontproperties=myfont, transform=ax.transAxes)