Python: 使用pyplot可视化

matplotlib数据可视化

基本用法

matplotlib用于将数据呈现为图,它并非python自带的库,需要pip install

生成一条直线的步骤如下,它的原理是将提供的数组中各点连接,所以提供的数组越大,线越光滑

1
2
3
4
5
6
7
8
9
10
11
12
13
import matplotlib.pyplot as plt  # 常用的是pyplot模块
import numpy as np

# x,y可以是数字或字符串,为字符串时默认等距。
x = np.linspace(1,10,500) # linspace:在1-10区间内生成500个等距点,返回数组
y = 2*x + 1 # 直线方程

plt.figure() # 创建新图,方便开多个figure
# plt.figure(num=4) 可以传数字参数num=?,空则默认按顺序命名
# plt.figure(figsize=(5,6)) 自定义宽,高(但实际上可以用鼠标拉长)
plt.title('a line') # 图的名称
plt.plot(x,y) # 生成图,x轴数组为x,y轴数组为y
plt.show() # 将该图展现出来

如图,底栏所示功能编号1-7

  1. 返回首页(即一开始看到的页面)

​ 2-3. 后退/前进

  1. 移动
  2. 放大窗口
  3. 调整图的边界参数
  4. 保存当前窗口

自定义线

如果想在一个figure内画多条线,plt.plot还可以接受其他参数用于区分两条线

1
2
3
4
5
6
y = exp(x)
plt.figure()
plt.plot(x,y)
plt.plot(y,x,color='red',linestyle='--')# 默认蓝色,这里改为红色,虚线
# linestyle可简写为ls,color可简写为c
plt.plot(x,y,linestyle=(0,(1,2,3))) # 元组定义线型

关于元组定义线型,(num,(n1,n2,n3,...))表示以后面元组的值作为单位画线 例如(0,(1,2,3))指的是(1磅实线,2磅空白,3磅实线),因为是奇数个,所以下一组变为(1磅空白,2磅实线,3磅空白),所以一般来说,常用的是偶数个元素的元组 关于num,它用于在显示标签偏移的长度,例如:

plt_linestyle

color参数可用简写如'r'(红色),'g'(绿色),多条曲线不指定颜色时会自动选择不同颜色,其它颜色如下:

  • 'm':洋红色
  • 'w':白色
  • 'k':黑色
  • 'y':黄色
  • 'g':绿色
  • '#000000':自定义RGB颜色字符串->RGB颜色查询对照表

其它常用linestyle如下:

  • '-'/'solid':实线,默认
  • '--'/'doshed':虚线
  • ':'/'dotted':点线
  • '-.'/dashdot:点划线

其它参数:

  • label='name':设置这条线的标签,可用plt.legend()显示
  • linewidth=num:线的宽度,默认为1,可简写为lw
  • alpha:线的透明度,范围0~1
  • 'bo':改为用蓝色实心圈绘制而非连线,常用于绘制单独的点,通过改变首字母来改变颜色,以下标记也可以在前面添加表示颜色的字母,不再赘述
  • '.'/',':点/像素点(很小的点)绘制
  • '<'/'>'/'^'/'v':各种三角m03
  • '1'/'2'/'3'/'4':各种三叉m07
  • '*':星号绘制
  • '+'+号绘制,上面标记都只能跟在数组后且不能重复

如果只提供一个数组,则x会默认为[0,1,...,N-1]N为提供的数组的元素个数

绘图标记

除了改变线型,pyplot还可以用marker更改点的样式

1
2
3
4
plt.plot(x,y,marker='o') # 每个点用实心圆代替(但是线型还是直线)
plt.plot(x,y,marker=r'$\alpha$',markersize=10,label='name') # markersize可简写为ms
plt.plot(x,y,marker='*',mec='r',mfc='k') # mec设置点边框的颜色,mfc设置点内部颜色
# mec:markeredgecolor, mfc:markerfacecolor

除了线型所支持的标记外,点的标记还支持Latex语法

可以用fmt='[mk][line][color]'参数快速设置点、线、色

1
plt.plot(x,y,fmt='o:k')  # 实心圆(点),虚线(线),黑色(色)

自定义坐标轴

设置坐标轴的值

1
2
3
4
5
6
7
8
9
plt.xlim((0,1))  # 限制x轴的范围
plt.ylim((1,2)) # 限制y轴的范围(也可以用[]括起)
plt.xlabel('label') # 设定x轴的标签,即说明x轴是什么,同理可用plt.ylabel()设置y轴的标签
# loc属性:设置标签的位置,有right/left/center 默认center ---> title(),legend()都有该属性
# ylabel的loc属性有top/bottom/center

plt.xticks(np.linspace(0,1,5)) # 更改x轴的单位,默认每0.5为一个节点,这里设置为将0-1分割为5点
# 后面数组对应前面数组的值,可以改成自己需要的名称
plt.xticks([0,1,5],['none','middle','pretty good'])

此外,后面数组还支持Latex语法,因为可能要用到转义字符等,所以这种情况需要在字符串前加上'r'/'R',将它视为原始字符串(raw string)来解析,例如plt.xticks([0,1,5],[r'$\theta$','$x$',r'$\Theta$'])的效果为:

移动坐标轴

需要用到gca()方法,全称get current axis,一般将返回的对象称为ax,以下是隐藏右轴及上轴的代码

1
2
3
ax = plt.gca() # 获取前面这整张图的坐标轴,有上下左右四条轴
for i in ['right','top']:
ax.spines[i].set_color('none') # 可以通过spines[]更改对应轴的属性

四条轴分别为:top,bottom,left,right

需要移动坐标轴,则要用到set_position()方法,它接受元组参数,效果如下

1
ax.spines['left'].set_position(('data',0)) # 将y轴移动到x=0的位置

一般来说,将坐标轴交点设置在(0,0)处的方法如下

1
2
for i in ['left','bottom']:
ax.spines[i].set_position(('data',0))

网格线

可用grid方法设置网格线:

1
plt.grid(None,which='major',axis='both',...) # 其它参数和设置线型时类似
  • None:是否显示网格线,如果后面接了其它参数,则默认为1,可设置为0
  • which:显示主要/次要网格线,默认只显示主要,可改为minorboth
  • axis:显示某方向的网格线,默认为both,可改为xy

绘制子图

pyplot提供subplot()subplots()方法,它能在一张画布中绘制多张图

subplot

subplot有三个必选参数,从左到右分别是行、列、标号,可以加可选参数例如共享轴、标签、投影类型等,返回一个对象,在subplots里将详细讲述对象作图 或者,把逗号删去,使用一个三位数的参数,例如plt.subplot(111)

子图里可以改变图的投影类型,如:

1
2
3
4
plt.figure()        # 创建画布,或:
fig = plt.figure() # 将画布赋给fig
ax = plt.subplot(111,projection='polar') # 极坐标投影,或:
ax = plt.subplot(111,polar=1) # 等效

其它投影类型还有:

  • aitoff
  • hammer
  • lambert
  • mollweide

这些投影类型需借助其它库,超出了pyplot的范围,且都用作绘制世界地图,故不细讲

注意到这个fig对象,它让切分画布变得便利:

1
ax1 = fig.add_subplot(111)   # 将左上角的区域切分出来

subplots

subplots用于一次性创建多个子图,并返回数组对象方便操作,现在介绍不同于上述用函数作图对象作图方法,这种方法更常用,便于进行精细化操作。当然,如果只需要简单的图形,用函数作图的方式也无妨

它利用subplots返回画布和图对象,通过改变对象的属性,进行对特定画布中特定部分的修改:

1
2
fig, ax = plt.subplots()  # 返回画布,图数组(这里没给参数,默认生成一张图)
ax.plot(x,y)

这里的ax指的是axes,即画布中的一部分。这里因为只生成一张图,所以只有一个axes

这种方法就不需要plt.figure()了,因为subplots()返回时就已创建一张画布了

此外,还可以设置共享轴与投影形式:

1
2
3
4
fig,(ax1,ax2) = plt.subplots(1,2,sharex=1,sharey=1) # 1即所有子图共享,0即所有子图独立
# 改变图形与subplot不同,需要将参数转换为字典传进去
fig,ax3 = plt.subplots(1,1,subplot_kw=dict(projection='polar'))
fig,ax3 = plt.subplots(1,1,subplot_kw=dict(polar=1))

还可设置为col(每列子图共享,列与列间独立),row(每行子图共享,行与行间独立)

其它设置与函数作图类似但部分有所不同:

1
2
3
4
5
6
7
8
fig,axs = plt.subplots(2,2)      # 两行两列,返回数组,用坐标访问
axs[0,0].set_title('titleName') # 标题
axs[0,0].plot(x,y,lw=1,c='r',marker='*',...) # plot
axs[0,0].set_xticks(array) # 轴的单位
axs[0,0].set_xlim(-1,1) # 轴的范围
axs[0,0].spines['right'].set_color('none') # 隐藏轴
axs[0,0].axis('off') # 隐藏所有轴,可以用xaxis/yaxis
axs[0,0]1.set_xlabel('labelName') # 给x轴加标注

其它图形

接下来简略介绍其它图形的绘制

散点图(scatter)

除了x、y外,其它参数可以有:marker(改变点的样式),s(点的大小),c(点的颜色),lw等已经讲过的,和:

  • cmap(colormap):调色盘,只有当c是一个浮点数数组时启用,会将这些数值对应到调色盘内的某个颜色。默认为viridis,可以通过以下命令查看颜色条

    1
    2
    3
    4
    # c=y,则y的数值大小可以清晰地映射到颜色条上
    plt.scatter(x,y,c=y,cmap='viridis')
    plt.colorbar() # 函数作图
    ax.figure.colorbar(ax.scatter(x,y,c=y,cmap='viridis')) # 对象作图

    其它可选调色盘:

  • vmin,vmax:调节颜色条的范围,大于vmax的数值将被强制映射到vmax上,vmin同理

  • norm:也是用于调节范围的,优先级高于上一条,参数没搞懂,有如下:

    • linear
    • log
    • symlog
    • asinh
    • logit
    • function
    • functionlog

柱形图(bar/barh)

bar即纵向柱形图,barh即横向柱形图。除x,y外的其它参数有:

  • width:浮点数数组,表示柱子宽度
  • height:浮点数数组,表示横向柱子的宽度,它是barh()的参数
  • bottom:底线值,即y=?,默认0
  • color:颜色,不能缩写为c,可接受颜色数组
  • align:对齐方式,默认center,即值的中间,可改为edge(左对齐),如要实现右对齐,可以传递负数宽度值

饼图(pie)

饼图的参数有点不同,它只需要一个x的参数,且它必须是非负数、非字符串的值。其它参数如下:

  • explode:一个数组,表示各个扇形的间隔。一般来说,没有显式说明的第二个参数默认为它
  • labels:一个列表,表示各个扇形的标签
  • colors:自定义颜色
  • autopct:数值显示形式,与C语言类似,%d表示整数,%0.1f表示一位小数,在后面加上%%表示百分比形式显示
  • labeldistance:标签的位置,默认1.1,即饼图外侧
  • pctdistance:与上一条类似,表示数值的位置
  • shadow:饼图是否有阴影,默认为0
  • radius:饼图半径

其它参数实用性不强,这里不再说明

直方图(hist)

直方图用于统计数的个数,也只需要提供x。其它参数如下:

  • bins:箱数,默认为10。hist将直方图分为bins个等距区间(即箱),每个箱子的柱高表示这个区间内数的个数
  • density:是否以频率显示。默认为0,即默认为统计个数而非频率
  • weights:数组,表示每个箱子的权重,箱子中每有一个数就会乘以这个权重值
  • bottom:与柱形图功能相同
  • histtype:直方图样式,可选参数有(一些没有区别的已省去):
    • bar:默认值
    • step:空心柱

其它装饰

复杂子图

在讲述subplots时,只是提到了一个简便的画图方法,如果需要更加精细地切分画布,这种规规整整的方式就不行了。于是我们需要用到网格:

1
2
3
4
plt.figure()
ax1 = plt.subplot2grid((2,2),(0,0)) # 将画布切分为2行2列,并把(0,0)网格返回给ax对象
ax2 = plt.subplot2grid((2,2),(1,0),colspan=2) # ax2占满第二行
ax3 = plt.subplot2grid((2,2),(1,1),rowspan=2) # ax3占满第二列

也可以用matplotlib.gridspec模块实现:

1
2
3
4
5
6
import matplotlib.gridspec as gridspec
plt.figure()
gs = gridspec.GridSpec(3,3) # 将画布切分成3行3列
ax1 = plt.subplot(gs[0,0])
ax2 = plt.subplot(gs[1:,1])
ax3 = plt.subplot(gs[-1,-1])

网格坐标写法有如下:

  1. start:end:从start开始占到end,例如[1:,1]为占了第一列的第二行到最后一行
  2. -num:倒数第num

这还是有点方正了,如果想随心所欲改变图的长宽,可以用update方法或plt.subplots_adjust,它们的参数是类似的:

1
2
gs1 = gridspec.GridSpec(3,3)
gs1.update(left=0,right=0.5) # 限定gs1网格只占画布的左边

其它参数:

  • wspace:横向子图的间距,默认0.2
  • hspace:纵向子图的间距,默认0.2
  • top,bottom:上边界和下边界

中文显示

需要在开头加上:

1
plt.rcParams["font.family"]="SimHei" # 将字体名称设置为"SimHei"(黑体)

关于其它字体的设置:链接

但是在正则表达式中似乎无效

tick_params()

通过这个方法可以让刻度线消失:

1
2
ax.tick_params(length=0) # 直接设置刻度线的长度,或
ax.tick_params(bottom=0) # 不显示底轴的刻度线

实例

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
def main():
x = np.linspace(-5,5,30)
xSin = np.linspace(0, 2*np.pi,30)
theta = np.linspace(0, 2*np.pi,30)
yExp = np.exp(x)
ySin = np.sin(xSin)
rHeart = np.sin(theta)
plt.figure()
# 左网格
gsL = gridspec.GridSpec(2,2)
gsL.update(left=0.05,right=0.4,wspace=0.4)
axSin = plt.subplot(gsL[0,0])
axPol = plt.subplot(gsL[0,1], polar=1) # 心形线极坐标映射
axExp = plt.subplot(gsL[1,:]) # 占满第二行

axSin.plot(xSin,ySin)
axSin.set_xticks([0,np.pi,2*np.pi],[0,r'$\pi$',r'$2\pi$'])
axSin.set_title('y=sinx',backgroundcolor='skyblue')

axPol.plot(theta, rHeart)
axPol.set_title(r'$\rm r=sin\theta$',backgroundcolor='skyblue')

axExp.plot(x,yExp,label=r'$y=e^x$')
axExp.plot(yExp,x,ls=(0,(0,1,2,1)),label='y=lnx')
axExp.plot([0,1],[1,0],'ko')
axExp.set_xlim(-0.5,2)
axExp.set_ylim(-5,10)
axExp.set_xticks(np.linspace(0,2,3))
axExp.set_yticks(np.linspace(-5,10,4))
axExp.tick_params(length=0)
axExp.set_title(r'$\rm y=e^x\ &\ y=\ln x$',backgroundcolor='pink')
plt.legend(loc='upper right')
for i in ['right','top']:
axSin.spines[i].set_color('none')
axExp.spines[i].set_color('none')
for i in ['left','bottom']:
axSin.spines[i].set_position(('data',0))
axExp.spines[i].set_position(('data',0))
# 右网格
gsR = gridspec.GridSpec(2,2,left=0.5,right=0.9,hspace=0.3)
axDens = plt.subplot(gsR[0,0])
axNums = plt.subplot(gsR[0,1])
axPie = plt.subplot(gsR[1,:])

rng = np.random
xNums1 = rng.randn(1000)
xNums2 = rng.randn(1000)
axDens.hist(xNums1,alpha=0.7,color='skyblue',
bins=100,weights=[np.array(2)for _ in range(1000)],density=1)
axDens.hist(xNums2,alpha=0.7,color='pink',bins=100,density=1)
axDens.tick_params(length=0)
axDens.set_title('two groups\'s density',backgroundcolor='orange')

axNums.hist(xNums1,alpha=0.7,color='skyblue',
bins=100,weights=[np.array(2)for _ in range(1000)])
axNums.hist(xNums2,alpha=0.7,color='pink',bins=100)
axNums.tick_params(length=0)
axNums.set_title('two groups',backgroundcolor='red')

axPie.pie([40,30,20,10],colors=['#a4f9e4','#f9c5a4','#c9a4f9','#d1f9a4'],
autopct="%d%%",radius=1.5,
labels=['yorushika','atarayo','zutomayo','yoasobi'],
labeldistance=0.7,
pctdistance=0.3,
shadow=1,
explode=[0.1,0,0,0])
axPie.set_title("The four 'YoRu's in my mind")

plt.show()

if __name__ == '__main__':
main()

run

plt_run