从历代皇帝的寿命纵观中国上下五千年
宏观的了解中国古代统治者的历史,可能会对历史的学习有很大的帮助。这个项目给大家准备了两个数据集:
✭统治时间及年号数据集:
这个数据集是从 Wikipedia 上面整理下来的 – List of Chinese Monarchs,包括袁世凯以及它之前具有不同头衔的中国统治者。从周朝到秦朝,统治者通常冠以“国王”的称号(中文:王;拼音:wáng),随着中国分裂成不同的战国,这个称号变得非常普遍。直到中国的统一者,即秦始皇为自己创建了一个新称谓“皇帝”(拼音:huángdì)。皇帝这个称号继续用于剩余中国的朝代历史,一直到 1912 年清朝灭亡。(这个数据集并不完整的包括了所有的帝王或其他统治者,仅作为本项目的数据可视化的实践使用)
✭皇帝寿命数据集:
这个数据集是从《中国帝王皇后亲王公主世系录》当中整理出来的。
1. 统治时间及年号数据集
数据集是从维基百科上整理下来的,所以数据不一定完全的准确,数据也不一定完整,我们仅用这些数据做可视化呈现的实践。 数据存储在 list_of_chinese_monarchs.csv
当中,下面是关于数据集的字段描述:
index
:序号;time_period
:历史时期或朝代;name
:姓名,有部分姓名为汉语拼音;reign_start
:开始统治的年,负数代表公元前,整数代表公元后;reign_period
:统治的年数,由于更换朝代,有的帝王在不同朝代有统治的记录,在数据中会有多行。
In [ ]:
# ... 这里需要您编写 - 任务 1 的代码 ...
# 导入 pandas 模块,并简称为 pd
import pandas as pd
# 加载数据,index_col=0 表示最前面一列为索引
df_monarchs = pd.read_csv('/data/course_data/visualization/list_of_chinese_monarchs.csv', index_col=0)
# 查看数据集的总体信息
df_monarchs
2. 计算直方图所需要的数据
在这个任务中我们需要计算直方图所需要的数据,这个步骤的关键是找到合适的 bin
大小,大致的思路是:
- 使用
df_monarchs['reign_period'].describe()
查看统治时间列的整体情况; - 然后根据经验,猜一个
bin
的值,猜的方法可以是(max - min) / 上四分位值
,上四分位值就是 25% 所表示的值,也可以用任何一种经验方法; - 使用
np.histogram(df_monarchs['reign_period'], bins=30)
计算直方图的数据集。
In [ ]:
# ... 这里需要您编写 - 任务 2 的代码 ...
# 导入 numpy,并简称为 np
import numpy as np
# 查看 reign_period 列中所有值的总体情况
print(df_monarchs['reign_period'].describe())
# 计算直方图所需要的数据集
hist, edges = np.histogram(df_monarchs['reign_period'], bins=30)
hist, edges
3. 绘制关于帝王统治时长的直方图
本项目中,我们会使用 Python 中的 Bokeh 工具包来绘制图形,绘图的脚本主要的步骤包括:
- 导入与绘图相关的 Bokeh 工具包;
- 创建绘图对象,并设置绘图参数;
- 根据第 2 步计算的
hist
和edges
结果数据绘制直方图; - 显示绘图结果。
In [ ]:
# ... 这里需要您编写 - 任务 3 的代码 ...
# 导入绘图组件,figure 是绘图主工具,show 用来显示绘图结果
from bokeh.plotting import figure, show
# 导入一个指定绘图输出方式的工具,
# output_notebook 用来指定 Bokeh 将绘图输出到 Notebook 中
from bokeh.io import output_notebook
output_notebook()
# 创建绘图对象,并设置绘图的标题,指定绘图的宽度和高度
p = figure(
# 绘图标题
title='中国帝王统治时长(年)',
# 绘图宽度
plot_width=600,
# 绘图高度
plot_height=400,
)
# 设置 y 轴的名称(可选)
# p.yaxis.axis_label = '帝王数量(人)'
# 设置 x 轴的名称(可选)
# p.xaxis.axis_label = '统治时间(年)'
# 使用 quad 方法,绘制直方图
p.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:],
fill_color="navy", line_color="white", alpha=0.6)
# 显示绘图
show(p)
4. 分朝代绘制帝王开始统治时间的分布图
学习一个事物,往往先了解他的整体,会帮助学习更加成功。
- 中国历史上有很多朝代,哪些朝代长久、哪些短命?
- 哪些朝代风云变幻、哪些长治久安?
- 哪些朝代是并行的?
要想回答这些问题,数据可视化可以派上用场。我们可以利用数据集中的 reign_start
字段做分朝代的直方图,并且把这些直方图垂直依次排列。
In [ ]:
# 导入批量绘图工具,将多个绘图按照网格方式排布
from bokeh.layouts import gridplot
# ------------------------------------------------------------------- #
# 基于 df_monarchs 中的数据悉数中国历史上有哪些朝代
dynasty = df_monarchs['time_period'].unique()
# ------------------------------------------------------------------- #
# 为每一个朝代绘制帝王开始统治时间的直方图
plots = []
for d in dynasty:
# 创建一个新的绘图
p = figure(tools='save', x_range=[-3100, 2200], min_border_left=30)
# 开始设置:绘图的样式
# 设置整体绘图的外部有一个窄窄的、粗细为 0.5 个像素的边框
p.outline_line_width=0.5
# 隐藏横向的网格,避免线条过多影响观看
p.ygrid.visible = False
# 隐藏 x 坐标轴,让其不显示
p.xaxis.visible = False
# 设置绘图不显示二级刻度,使得绘图的一级刻度更加清晰
p.yaxis.ticker.num_minor_ticks = 0
# 设置 y 轴期望的刻度数量,使得刻度不至于过密影响显示
# 设置的值只是一个期望值,系统会根据实际的数据情况进行微调
p.yaxis.ticker.desired_num_ticks = 2
# 设置完成:绘图的样式
# 开始绘图:为每一个朝代绘制直方图
# 获得朝代名称为变量 d 的数据
df_monarchs_d = df_monarchs[df_monarchs['time_period'] == d]
# 计算绘制直方图所需要的数据
hist, edges = np.histogram(df_monarchs_d['reign_start'], bins=np.linspace(-2500, 2000, 200))
# 绘制直方图中每个条形柱状图
p.quad(top=hist, bottom=0, left=edges[:-1], right=edges[1:],
fill_color="navy", line_color="white", alpha=1)
# 绘制帝王人数和朝代相关的文本数据
p.text(x=-3000, y=0, text=[f'帝王人数:{sum(hist)}'], y_offset=-15, text_font_size='8pt')
p.text(x=-3000, y=0, text=[d], text_font_size='8pt', text_font_style='bold')
# 将当前朝代的直方图添加到 plots 列表当中
plots.append(p)
# ------------------------------------------------------------------- #
# 绘制一个新的绘图,放到所有绘图的最上面:
# - 显示标题:'中国历朝历代帝王开始统治的时间(年)'
# - 显示共享的 x 坐标轴:显示 [-2150, -1500, -1000, -500, 0, 500, 1000, 1500, 2000] 九个年份
# - 隐藏 y 坐标轴
# - 隐藏绘图的网格
p = figure(outline_line_width=0, frame_height=40, x_range=[-3100, 2200], min_border_left=30, tools='save')
p.text(-2150, 0, ['2,150 BCE '], text_align='left', text_baseline='middle')
p.text(2000, 0, ['2,000 CE'], text_align='right', text_baseline='middle')
p.text(0, 0, ['中国历朝历代帝王开始统治的时间(年)'], text_align='center', text_baseline='middle')
p.xaxis.ticker = [-2150, -1500, -1000, -500, 0, 500, 1000, 1500, 2000]
p.yaxis.visible = False
p.grid.visible = False
plots.insert(0, p)
# ------------------------------------------------------------------- #
# 使用 Bokeh 中的 gridplot 工具将所有的绘图排成一列显示:
# - plots:这个参数就是存储了标题绘图和所有朝代绘图的列表
# - ncols:这个参数的意思是所有 plots 中的绘图排成 1 列
# - plot_width:这个参数的意思是每个 plots 中的绘图的宽度都是 800 个像素
# - plot_height:这个参数的意思是每个 plots 中的绘图的宽度都是 40 个像素
grid = gridplot(plots, ncols=1, plot_width=800, plot_height=40)
# 最终显示绘图 grid,grid 中存储着所有已经绘制好的绘图
# 调用 show 方法将 grid 中的,按照 1 列进行排布的绘图显示出来
show(grid)
5. 回答几个小问题
In [ ]:
# ... 这里需要您编写 - 任务 5 的代码 ...
# 在第 4 个任务的绘图中,可以看出只有 1 位帝王的朝代有几个?
answer_1 = ...
# 在第 4 个任务的绘图中,可以看出存在时间最长的朝代是哪个朝代?
answer_2 = ''
# 在第 4 个任务的绘图中,帝王最多的朝代中共有多少位帝王?
answer_3 = ...
6. 皇帝寿命数据集
在这个数据集中,包含了 302 位称号为皇帝的中国古代帝王。我们来用这个数据集学习绘制箱形图,并对箱形图进行排序,获取洞察。
这个数据集的学习,我们将有三个小步骤:
- 加载数据;
- 生成箱形图的数据;
- 绘制箱形图,并对箱形图排序;
- 从极端值中找故事。
我们先来加载皇帝寿命的数据集。
In [ ]:
# ... 这里需要您编写 - 任务 6 的代码 ...
# 导入 pandas 模块,并简称为 pd
import pandas as pd
# 加载数据,index_col=0 表示最前面一列为索引
df_emperor_ages = pd.read_csv(
'/data/course_data/visualization/emperor_ages.csv', index_col=0)
# 查看数据集的总体信息
df_emperor_ages.info()
7. 绘制各朝代皇帝寿命的箱形图
开课吧的工程师们为同学们编写了两个脚本:
generate_box_plot_data
:按照视频中讲解的箱形图的定义,计算中值、四分位值、箱须长度等;bokeh_box_plot
:基于 Bokeh 的基础功能,按照generate_box_plot_data
计算好的数据进行绘制。
本门课程的重点是数据可视化,我们不把重点放在解读代码上,后续会推出专门 Bokeh 数据可视化脚本编写的课程。
让我们先一起「跑」完为大家写好的代码吧!
In [ ]:
# ... 这里需要您编写 - 任务 7 的代码 ...
# 导入提前编写好的绘制箱形图的脚本
import sys
sys.path.append('/data/course_data/visualization')
from dvfe_01_07 import bokeh_box_plot, generate_box_plot_data
# 下面这个脚本用来计算箱形图所需要的绘图数据:
# - df_emperor_ages:数据集
# - '朝代':df_emperor_ages 中的列名,按照朝代对数据进行分组
# - '寿命':df_emperor_ages 中的列名,需要绘制箱形图的数值变量
data_box_plot, data_box_plot_out = \
generate_box_plot_data(df_emperor_ages, '朝代', '寿命')
# data_box_plot, data_box_plot_out 是两个字典类型的数据
# 我们查看一下其中存储了哪些数据
print(data_box_plot.keys())
print(data_box_plot_out.keys())
# 绘制箱形图
p = figure(title='中国历朝历代皇帝寿命',
y_range=data_box_plot['category'],
plot_width=600, plot_height=400,
tools='save', toolbar_location='right')
bokeh_box_plot(p, data_box_plot, data_box_plot_out, all_dot=False)
# 设置绘图的标题、坐标轴等呈现的样式
p.title.text_font_size = '12pt'
p.xaxis.axis_label = '皇帝寿命(年)'
p.axis.axis_label_text_font_size = "10pt"
p.axis.major_label_text_font_size = "8pt"
p.xaxis.ticker.num_minor_ticks = 10
# 显示绘图
show(p)
8. 对箱形图进行排序 => 发现洞察
在观察箱形图的时候,我们通常需要按照「类别」、「中值」、「IQR」排序,在本项目中:
- 按照「类别」排序就是按照
data_box_plot
中的'category'
排序; - 按照「中值」排序就是按照
data_box_plot
中的'q2'
排序; - 按照「IQR」排序就是按照
data_box_plot
中的'iqr'
排序。
请将 sort_by
变量从现在的 'category'
改变为 'q2'
,并重新绘图。
In [ ]:
# ... 这里需要您编写 - 任务 8 的代码 ...
# 定义排序的规则
sort_by = 'category'
# 按照 sort_by 指定的信息,对 data_box_plot 进行排序
df_for_sort = pd.DataFrame(data_box_plot)
df_for_sort.sort_values(by=sort_by, inplace=True)
data_box_plot = df_for_sort.to_dict('list')
# 对箱形图排序,实际上就是改变箱形图绘图时 y 轴的刻度数据
# 将排序后的分类(category)列表,赋值给绘图的 y 轴坐标刻度就好啦
p.y_range.factors = data_box_plot['category']
show(p)
9. 查看极端值
离群值或极端值(Outliers)表示数值异常的数据,是指在数据中有一个或几个数值与其他数值相比差异较大。
- 当出现离群值的时候,要慎重处理,要将专业知识和统计学方法结合起来,首先应认真检查原始数据,看能否从专业上加以合理的解释。
- 如数据存在逻辑错误而原始记录又确实如此,又无法在找到该观察对象进行核实,则只能将该观测值删除。
- 本项目中的数据全部来自于维基百科或者官方史料,我们在这里不追究原始数据的正确还是错误,我们的目的是学习如何试着来解释这些极端值。
In [ ]:
# ... 这里需要您编写 - 任务 9 的代码 ...
outliers = df_emperor_ages.loc[data_box_plot_out['index']]
outliers