环境
开发工具
VSCode
库的版本
numpy==1.26.4
matplotlib==3.10.1
ipympl==0.9.7
教材
本书为《Python数据可视化》一书的配套内容,本章为第8章 使用matplotlib绘制高级图表
本章主要介绍了如何使用matplotlib绘制一些高级图表,包括等高线图、矢量场流线图、棉棒图、哑铃图、甘特图、人口金字塔图、漏斗图、桑基图、树状图、华夫饼图。通过学习本章的内容,希望大家可以了解常用的高级图表的特点,并可以绘制高级图表。
参考
第8章-使用matplotlib绘制高级图表
8.1 绘制等高线图
等高线图是用等高线(地形图上高程相等的各点连成的闭合曲线)表示地面高低起伏的地形图,它会将地面上海拔高度相同的点连成线,之后将这些连线垂直投影到某一平面上,并按照一定的比例缩绘到图纸上。
等高线图包含三个主要的信息,分别为坐标点的x值、坐标点y值以及坐标点的高度
。假设坐标点的高度为h,则h、x、y之间的关系如下所示:
在matplotlib中,使用pyplot模块的contour()、contourf()函数可以绘制和填充等高线图
。contour()函数的语法格式如下所示:
contour([X, Y,]Z, [levels,]**kwargs)
X,Y:表示坐标点的网格数据。
Z:表示坐标点对应的高度数据。
levels:表示等高线的数量。若levels为n,则说明绘制n+1条等高线。
colors:表示不同高度的等高线颜色。
cmap:表示颜色映射表,默认值是’viridis’ 。
linewidths:表示等高线的宽度。
linestyles:表示等高线的线型。
需要注意的是,参数X、Y需要接收网格数据,即以坐标矩阵批量地描述点的位置。numpy模块的meshgrid()函数用于生成网格状数据。
contour() 函数调用成功后会返回QuadContourSet类(基类是ContourSet)的对象,该对象中存储了一组等高线或填充区域。
在matplotlib中,使用pyplot模块的clabel()函数
可以为给定的等高线添加标签
。 clabel()函数的语法格式如下所示:
clabel(CS, levels=None, **kwargs)
CS :要标注的等高线,该参数需要接收ContourSet类的对象。
levels:表示等高线的数量。若levels为n,则说明绘制n+1条等高线。
inline:若为True,则在放置标签的位置移除基础轮廓。
fmt:表示每条等高线的格式,若该参数的值是字符串,则解释为%-样式格式字符串。
fontsize:表示标签文本的字号大小,以点为单位,默认值为10.0。
Axes类的对象也可以使用contour()、contourf()、 clabel()方法绘制等高线图、填充等高线图、标注等高线图。
import numpy as np
import matplotlib.pyplot as plt
# 计算高度
def calcu_elevation(x1, y1):
h = (1-x1/2 + x1 ** 5 + y1 ** 3) * np.exp(-x1** 2 - y1** 2)
return h
n = 256
x = np.linspace(-2, 2, n)
y = np.linspace(-2, 2, n)
# 利用 meshgrid() 函数生成网格数据
x_grid, y_grid = np.meshgrid(x, y)
fig = plt.figure()
ax = fig.add_subplot(111)
# 绘制等高线
con = ax.contour(x_grid, y_grid, calcu_elevation(x_grid, y_grid), 8, colors='black')
# 填充等高线的颜色
ax.contourf(x_grid, y_grid, calcu_elevation(x_grid, y_grid), 8, alpha=0.75, cmap=plt.cm.copper)
# 为等高线添加文字标签
ax.clabel(con, inline=True, fmt='%1.1f', fontsize=10)
ax.set_xticks([])
ax.set_yticks([])
plt.show()
8.2 绘制矢量场流线图
使用pyplot模块的streamplot()函数
可以绘制矢量场流线图。
streamplot(x, y, u, v, density=1, linewidth=None, col=None, cmap=None, norm=None,
arrowsize=1, arrowstyle='-|>', minlength=0.1, transform=None, zorder=None,
start_points=None, maxlength=4.0, integration_direction='both', *, data=None)
x,y:表示间距均匀的网格数据。
u,v:表示(x,y)速率的二维数组。
density:表示流线的密度。
linewidth:表示流线的宽度。
arrowsize:表示箭头的大小。
arrowstyle:表示箭头的类型。
minlength:表示流线的最小长度。
maxlength:表示流线的最大长度。
import numpy as np
import matplotlib.pyplot as plt
y, x = np.mgrid[0:5:50j, 0:5:50j]
u = x
v = y
fig = plt.figure()
ax = fig.add_subplot(111)
# 绘制矢量场流线图
ax.streamplot(x, y, u, v)
plt.show()
8.3 绘制棉棒图
使用pyplot模块的stem()函数
可以绘制棉棒图。
stem([x,] y, linefmt=None, markerfmt=None, basefmt=None, bottom=0,
label=None, use_line_collection=False, data=None)
x,y:表示茎头的x值和茎头的y值。
linefmt:表示茎属性的字符串。
markerfmt:表示茎头属性的字符串。
basefmt:表示基线属性的字符串。
bottom:表示基线的y值。
label:表示应用于图例的标签。
use_line_collection:若设为True,则将棉棒图的所有线段存储到一个LineCollection类对象中;若设为False,则将棉棒图的所有线段存储到列表中。
下面根据表格中的数据,将轿车品牌列的数据作为x轴的标签,将燃料消耗量列的数据作为y轴的数据,使用stem()绘制不同品牌轿车燃料消耗量的棉棒图。
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
x = np.arange(1, 16)
y = np.array([5.9, 6.2, 6.7, 7.0, 7.0, 7.1, 7.2, 7.4,
7.5, 7.6, 7.7, 7.7, 7.7, 7.8, 7.9])
labels = np.array(['宝骏310', '宝马i3', '致享', '焕驰', '力帆530',
'派力奥', '悦翔V3', '乐风RV', '奥迪A1', '威驰FS',
'夏利N7', '启辰R30', '和悦A13RS', '致炫', '赛欧'])
fig = plt.figure(figsize=(10, 6), dpi= 80)
ax = fig.add_subplot(111)
# 绘制棉棒图
markerline, stemlines, baseline = ax.stem(x, y, linefmt='--',
markerfmt='o', label='TestStem')
# 设置棉棒图线段的属性
plt.setp(stemlines, lw=1)
ax.set_title('不同品牌轿车的燃料消耗量', fontdict={'size':18})
ax.set_ylabel('燃料消耗量(L/km)')
ax.set_xticks(x)
ax.set_xticklabels(labels, rotation=60)
ax.set_ylim([0, 10])
for temp_x, temp_y in zip(x, y):
ax.text(temp_x, temp_y + 0.5, s='{}'.format(temp_y), ha='center', va='bottom', fontsize=14)
plt.show()
8.4 绘制哑铃图
除了通过plot()函数创建Line2D类的对象以外,还可以使用Line2D类(位于matplotlib.lines模块中)的构造方法创建Line2D类的对象。
Line2D(xdata, ydata, *, linewidth=None, linestyle=None, color=None, marker=None, markersize=None, …, **kwargs)
xdata,ydata:表示x轴和y轴使用的数据。
color:表示线条的颜色。
下面根据health.xlsx文件的数据,将city列的数据作为y轴的刻度标签,将pct_2014和pct_2013两列的数据作为数据点,再在两个数据点之间添加线条,绘制由数据点和线条组成的哑铃图 。
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
print(os.getcwd())
df = pd.read_excel("./素材/health.xlsx")
df.sort_values('pct_2014', inplace=True)
df.reset_index(inplace=True)
df = df.sort_values(by="index")
def newline(p1, p2, color='black'):
ax = plt.gca() # 获取当前的绘图区域
l = mlines.Line2D([p1[0], p2[0]], [p1[1],p2[1]], color='skyblue')
ax.add_line(l)
return l
fig, ax = plt.subplots(1, 1, figsize=(8, 6))
# 绘制散点
ax.scatter(y=df['index'], x=df['pct_2013'], s=50, color='#0e668b', alpha=0.7)
ax.scatter(y=df['index'], x=df['pct_2014'], s=50, color='#a3c4dc', alpha=0.7)
# 绘制线条
for i, p1, p2 in zip(df['index'], df['pct_2013'], df['pct_2014' ]):
newline([p1, i], [p2, i])
ax.set_title("2013年与2014年美国部分城市人口PCT指标的变化率", fontdict={'size':12})
ax.set_xlim(0, .25)
ax.set_xticks([.05, .1, .15, .20])
ax.set_xticklabels(['5%', '10%', '15%', '20%'])
ax.set_xlabel('变化率')
ax.set_yticks(df['index'])
ax.set_yticklabels(df['city'])
ax.grid(alpha=0.5, axis='x')
plt.show()
8.5 绘制甘特图
甘特图类似于条形图,这两种图表中使用的图形符号都是横向矩形条,但甘特图中每个矩形条的起始位置是不同的。
使用pyplot的barh()函数可以绘制一个甘特图,只需要给该函数的left参数传值,指定每个矩形条的x坐标即可。
import numpy as np
import matplotlib.pyplot as plt
ticks = np.array(['报告提交', '数据分析', '数据录入', '实地执行',
'问卷确定', '试访', '问卷设计', '项目确定'])
y_data = np.arange(1, 9)
x_data = np.array([0.5, 1.5, 1, 3, 0.5, 1, 1, 2])
fig,ax = plt.subplots(1, 1)
ax.barh(y_data, x_data, tick_label=ticks, left=[7.5, 6, 5.5, 3, 3, 2, 1.5, 0], color='#CD5C5C')
[ax.spines[i].set_visible(False) for i in ['top', 'right']]
ax.set_title("任务甘特图")
ax.set_xlabel("日期")
ax.grid(alpha=0.5, axis='x')
plt.show()
8.6 绘制人口金字塔图
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
print(os.getcwd())
df = pd.read_excel('./素材/population.xlsx')
df_male = df.groupby(by='Gender').get_group('Male')
list_male = df_male['Number'].values.tolist() # 将ndarray 转换为 list
df_female = df.groupby(by='Gender').get_group('Female')
list_female = df_female['Number'].values.tolist() # 将ndarray 转换为 list
df_age = df.groupby('AgeGroup').sum()
count = df_age.shape[0]
y = np.arange(1, 11)
labels = []
for i in range(count):
age = df_age.index[i]
labels.append(age)
fig = plt.figure()
ax = fig.add_subplot(111)
# 绘制人口金字塔图
ax.barh(y, list_male, tick_label=labels, label=' 男', color='#6699FF')
ax.barh(y, list_female, tick_label=labels, label=' 女', color='#CC6699')
ax.set_ylabel("年龄段(岁)")
ax.set_xticks([-100000, -75000, -50000, -25000, 0, 25000, 50000, 75000, 100000])
ax.set_xticklabels(['100000', '75000', '50000', '25000',
'0', '25000', '50000', '75000', '100000'])
ax.set_xlabel("人数(个)")
ax.set_title('某城市人口金字塔')
ax.legend()
plt.show()
8.7 绘制漏斗图
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
num = 5
height = 0.5
x1 = np.array([1000, 500, 300, 200, 150]) # 各环节的客户数量
x2 = np.array((x1.max() - x1) / 2)
x3 = [i +j for i, j in zip(x1, x2)]
x3 = np.array(x3)
y = -np.sort(-np.arange(num)) # 倒转y轴
labels=['访问商品', '加购物车', '生成订单', '支付订单', '完成交易']
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111)
# 绘制条形图
rects1 = ax.barh(y, x3, height, tick_label=labels, color='g', alpha=0.5)
# 绘制辅助条形图
rects2 = ax.barh(y, x2, height, color='w', alpha=1)
ax.plot(x3, y, 'black', alpha=0.7)
ax.plot(x2, y, 'black', alpha=0.7)
# 添加无指向型注释文本
notes = []
for i in range(0, len(x1)):
notes.append('%.2f%%'%((x1[i] / x1[0]) * 100))
for rect_one, rect_two, note in zip(rects1, rects2, notes):
text_x = rect_two.get_width() + (rect_one.get_width() - rect_two.get_width()) / 2 - 30
text_y = rect_one.get_y() + height / 2
ax.text(text_x, text_y, note, fontsize=12)
# 隐藏轴脊和刻度
ax.set_xticks([])
for direction in ['top', 'left', 'bottom', 'right']:
ax.spines[direction].set_color('none')
ax.yaxis.set_ticks_position('none')
plt.show()
8.8 绘制桑基图
创建表示桑基图的对象
matplotlib中使用Sankey类的构造方法Sankey()可以创建代表桑基图的对象。
Sankey(ax=None, scale=1.0, unit='', format='%G', gap=0.25, radius=0.1,
shoulder=0.03, offset=0.15, head_angle=100, margin=0.4,
tolerance=1e-06, **kwargs)
ax:若不提供该参数,会创建一个新的绘图区域。
scale:表示流量比例因子比例调整分支的宽度。
unit:表示与流量相关的物理单位。若设为None,则不会做数量标记。
gap:表示进入或离开顶部或底部的分支之间的间距,默认为0.25。
添加桑基图的选项
Sankey类对象可以调用add()方法为桑基图添加诸如数据流量、标签等选项。
add(self, patchlabel='', flows=None, orientations=None, labels='',
trunklength=1.0, pathlengths=0.25, prior=None, connect=(0, 0), rotation=0, **kwargs)
patchlabel:表示位于图表中心的标签。
flows:表示流量数据的数组,其中投入数据为正值,产生数据为负值。
orientations:表示流的方向列表或用于所有流的单个方向,可以取值为0(从左侧输入、右侧输出)、1(从顶部到顶部)或-1(从底部到底部)。
labels:表示流的标签列表或用于所有流的单个标签。
trunklength:表示输入组和输出组的基之间的长度。
返回桑基图绘制完成的对象
Sankey类对象在添加完选项之后需要调用finish()方法完成绘制,该方法会返回包含多个桑基子图的列表。桑基子图包含以下字段:
patch:表示桑基子图的轮廓。
flows:表示流量值(输入为正,输出为负)。
angles:表示箭头角度的列表。
tips:表示流路径的尖端或凹陷位置的数组,其中每一行是 (x, y)。
text:表示中心标签的Text实例。
texts:表示流分支标签的Text实例。
import matplotlib.pyplot as plt
from matplotlib.sankey import Sankey
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 消费收入与支出数据
flows = [0.7, 0.3, -0.3, -0.1, -0.3, -0.1, -0.1, -0.1]
# 流的标签列表
labels = ["工资", "副业", "生活", "购物", "深造", "运动", "其他", "买书"]
# 流的方向
orientations = [1, 1, 0, -1, 1, -1, 1, 0]
# 创建 Sankey 类对象
sankey = Sankey()
# 为桑基图添加数据
sankey.add(flows=flows, # 收入与支出数据
labels=labels, # 数据标签
orientations=orientations, # 标签显示的方向
color="black", # 边缘线条颜色
fc="lightgreen", # 填充颜色
patchlabel="生活消费 ", # 图表中心的标签
alpha=0.7) # 透明度
# 桑基图绘制完成的对象
diagrams = sankey.finish()
diagrams[0].texts[4].set_color("r") # 将下标为 4 的数据标签设为红色
diagrams[0].texts[4].set_weight("bold") # 将下标为 4 的数据标签设为字体加粗
diagrams[0].text.set_fontsize(20) # 将中心标签的字体大小设为20
diagrams[0].text.set_fontweight("bold") # 将中心标签的字体设为加粗
plt.title("日常生活开支的桑基图")
plt.show()
8.9 绘制树状图
dendrogram()函数
dendrogram()函数用于将层次聚类数据绘制为树状图。
dendrogram(Z, p=30, truncate_mode=None, color_threshold=None, get_leaves=True, orientation='top', labels=None, count_sort=False, distance_sort=False, show_leaf_counts=True, **kwargs)
Z:表示编码层次聚类的链接矩阵。
truncate_mode:表示截断的模式,用于压缩因观测矩阵过大而难以阅读的树状图,可以取值为None(不执行截断,默认)、‘lastp’、‘level’。
color_threshold:表示颜色阈值。
labels:表示节点对应的文本标签。
linkage()函数
linkage()函数用于将一维压缩距离矩阵或二维观测向量阵列进行层次聚类或凝聚聚类。
linkage(y, method='single', metric='euclidean', optimal_ordering=False)
y:可以是一维距离向量或二维的坐标矩阵。
method:表示计算类簇之间距离的方法,常用的取值可以为’single’、 ‘complete’、 'average’和’ward‘,其中’single’表示将类簇与类簇之间最近的距离作为类簇间距; ‘complete’ 表示将类簇与类簇之间最远的距离作为类簇间距;'average’表示将类簇与类簇之间的平均距离作为类簇间距;'ward‘表示将每个类簇的方差最小化作为类簇间距。
美国对各个州的谋杀、暴力、爆炸犯罪案件的数量进行了统计,并将统计后的结果整理到USArrests.xlsx文件中。下面使用pandas读取USArrests.xlsx文件的数据,并将犯罪案例数量相似度高的州进行聚类后绘制一个树状图。
import pandas as pd
import matplotlib.pyplot as plt
import scipy.cluster.hierarchy as shc
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
import os
os.chdir(os.path.dirname(os.path.abspath(__file__)))
print(os.getcwd())
df = pd.read_excel('./素材/USArrests.xlsx')
plt.figure(figsize=(10, 6), dpi= 80)
plt.title("美国各州犯罪案件的树状图", fontsize=12)
# 绘制树状图
dend = shc.dendrogram(shc.linkage(df[['Murder', 'Assault', 'UrbanPop']], method='ward'),
labels=df.State.values, color_threshold=100)
plt.xticks(fontsize=10.5)
plt.ylabel('案例数量(个)')
plt.show()
8.10 绘制华夫饼图
matplotlib中并未提供华夫饼图的绘制函数,但可以结合pywaffle包一起使用来绘制华夫饼图。
安装pywaffle
pip install pywaffle
导入测试
from pywaffle import Waffle
pywaffle的使用
pywaffle是Python中专门绘制华夫饼图的包,它提供了一个继承自Figure的子类Waffle,通过将Waffle类传递给figure()函数的FigureClass参数,即可创建一个华夫饼图。关于figure()函数中创建华夫饼图的常用参数的含义如下:
FigureClass:可以是Figure类或Figure子类。
rows:表示华夫饼图的行数。
columns:表示华夫饼图的列数。
values:表示数据,可以接收数组或字典。若values参数接收一个字典,则将字典的键作为华夫饼图的图例项。
colors:表示每个分类数据的颜色列表。
vertical:表示是否按垂直方向绘制华夫饼图,默认为False。
title:表示标题,可以接收一个字典,其中字典的键为title()函数的关键字参数。
legend:表示图例,可以接收一个字典,其中字典的键为legend()函数的关键字参数。
import matplotlib.pyplot as plt
from pywaffle import Waffle
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
# 绘制华夫饼图
plt.figure(FigureClass=Waffle, rows=10, columns=10,
values=[95, 5],vertical=True, colors=['#20B2AA', '#D3D3D3'],
title={'label': '电影《少年的你》上座率'},
legend={'loc': 'upper right', 'labels': ['占座', '空座']}
)
plt.show()
import matplotlib.pyplot as plt
from pywaffle import Waffle
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False
# 绘制华夫饼图
num1 = 1.121
num2 = 1.0
num3 = 0
num4 = 0.1
plt.figure(FigureClass=Waffle, rows=10, columns=10,
values=[92, 5,3],vertical=True, colors=['#20B2AA', '#D3D3D3','#FF6347'],
title={'label': '电影《少年的你》上座率'},
legend={'bbox_to_anchor': (num1, num2),'loc':num3, 'borderaxespad':num4,'labels': ['占座', '空座',"其它"]}
)
# legend = ['占座', '空座',"其它"]
# num1 = 1.121
# num2 = 1.0
# num3 = 0
# num4 = 0.1
# plt.legend( bbox_to_anchor = (num1, num2), loc = num3, borderaxespad = num4,)
# 说明:bbox_to_anchor被赋予的二元组中,num1用于控制legend的左右移动,值越大越向右边移动,num2用于控制legend的上下移动,值越大,越向上移动。用于微调图例的位置。
plt.tight_layout()
plt.show()
输出如下