Matplotlib初相識
認識matplotlib
Matplotlib是一個Python 2D繪圖庫,能夠以多種硬拷貝格式和跨平臺的互動式環境生成出版物質量的圖形,用來繪制各種靜態,動態,互動式的圖表
一個最簡單的繪圖例子
matplotlib的影像都是畫在對應的figure上,可以認為是一個繪圖區域,而一個figure又可以包含一個或者多個axes,可以認為是子區域,這個子區域可以指定屬于自己的坐標系,下面通過簡單的實體進行展示:
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
fig, ax = plt.subplots() # 該函式創建一個包含1個axes的figure,并將兩者進行回傳
ax.plot([1,2,3,4],[1,4,2,3])

那么也可以用更為簡單的方式來進行創建:
line = plt.plot([1,2,3,4],[1,4,2,3])
這是因為如果未指定axes,那么會自動創建一個,因此可以簡化,
figure的組成
通常,一個完成的matplotlib影像會包括四個層級(容器):
- Figure:頂級層,用來容納所有繪圖元素
- Axes:matplotlib宇宙的核心,容納了大量元素用來構造一幅幅的子圖,一個figure可以由1個或者多個子圖構成
- Axis:axes的下層,用來處理所有與坐標軸、網格相關的元素
- Tick:axis的下層,用來處理所有和刻度相關的元素

兩種繪圖介面
matplotlib提供了兩種最常用的繪圖介面:
- 創建figure和axes,然后在此之上呼叫繪圖方法
- 依賴pyplot自動創建figure和axes來繪圖
就像是上小節所展示的那樣兩種創建圖的方法,
通用繪圖模板
Datawhale提供了一個通常的繪圖模板,可以根據實際需要對該模板進行修改了補充:
# 先準備好資料
x = np.linspace(0, 2, 100)
y = x**2
# 設定繪圖樣式(非必須)
mpl.rc('lines', linewidth=4, linestyle='-.')
# 定義布局
fig, ax = plt.subplots()
# 繪制影像
ax.plot(x, y, label='linear')
# 添加標簽,文字和圖例
ax.set_xlabel('x label')
ax.set_ylabel('y label')
ax.set_title("Simple Plot")
ax.legend() ;

思考題
-
請思考兩種繪圖模式的優缺點和各自適合的使用場景
- 我覺得先創建figure和axes再進行繪圖的方式更適用于你對圖的規劃比較清晰,或者你想要畫多個子圖,這樣在同一個figure上作畫會簡潔方便;而pyplot模型更實用于你當前只需要畫一個圖,那么把所有元素都加到當前這個圖上就可以了
-
在第五節繪圖模板中我們是以OO模式作為例子展示的,請思考并寫一個pyplot繪圖模式的簡單模板
-
plt.plot(x,y,label='linear') plt.xlabel("x label") plt.ylabel("y label") plt.title("simple plot") plt.legend()
-
藝識訓筆見乾坤
先準備待會兒要用到的庫
import numpy as np
import pandas as pd
import re
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
from matplotlib.patches import Circle, Wedge
from matplotlib.collections import PatchCollection
概述
matplotlib的三層api
matplotlib的原理或者說基礎邏輯是,用Artist物件在畫布(canvas)上繪制(Render)圖形,因此跟人作畫類似,需要三個步驟:
- 準備一個畫圖
- 準備畫筆、顏料
- 作畫
因此可以認為matplotlib有三層的API:
matplotlib.backend_bases.FigureCanvas代表了繪圖區,所有的影像都是在繪圖區完成的matplotlib.backend_bases.Renderer代表了渲染器,可以近似理解為畫筆,控制如何在 FigureCanvas 上畫圖,matplotlib.artist.Artist代表了具體的圖表組件,即呼叫了Renderer的介面在Canvas上作圖,
因此我們大部分是利用Artist類來進行繪圖,
Artist的分類
Artist有兩種型別:primitives 和containers:
- primitive是基本要素,包含一些我們要在繪圖區作圖用到的標準圖形物件,例如曲線、文字、矩形等等,
- container是容器,可以認為是用來放置基本要素的地方,包括圖形figure,坐標系axes和坐標系axis
基本元素primitives
primitives主要有以下幾種型別,我們按照順序介紹,
2DLines
其中常見的引數主要有:
- xdata:橫坐標的取值,默認就是range(1,len(data)+1)
- ydata:縱坐標取值
- linewidth:線條的寬度
- linestyle:線型
- color:線條的顏色
- marker:點的標注樣式
- markersize:標注的大小
如何設定引數屬性
對于上面提到的各個引數有三種修改方法:
-
在plot函式里面進行設定
x = range(0,5) y = [2,5,7,9,11] plt.plot(x,y,linewidth = 10) -
獲取線物件,對線物件進行設定
x = range(0,5) y = [2,5,7,8,10] line, = plt.plot(x, y, '-') # 這里等號坐標的line,是一個串列解包的操作,目的是獲取plt.plot回傳串列中的Line2D物件,回傳是一個串列型別 line.set_antialiased(False); # 關閉抗鋸齒功能,呼叫線物件的函式 -
獲取線屬性,使用setp函式設定
x = range(0,5) y = [2,5,7,8,10] lines = plt.plot(x, y) plt.setp(lines, color='r', linewidth=10);
如何繪制lines
那我們常見的功能是繪制直線line,以及繪制errorbar誤差折線圖,下面對這兩種分別進行介紹,
繪制line
可以采用兩種方法來繪制直線:
1、plot方法
x = range(0,5)
y1 = [2,5,7,8,10]
y2= [3,6,8,9,11]
fig,ax= plt.subplots()
ax.plot(x,y1)
ax.plot(x,y2)
print(ax.lines);

列印為:
<Axes.ArtistList of 2 lines>
可以看到創建了2個lines物件,
2、Line2D物件繪制
x = range(0,5)
y1 = [2,5,7,8,10]
y2= [3,6,8,9,11]
fig,ax= plt.subplots()
lines = [Line2D(x, y1), Line2D(x, y2,color='orange')] # 顯式創建Line2D物件,但是現在還沒有在哪里展示
for line in lines:
ax.add_line(line) # 使用add_line方法將創建的Line2D添加到子圖中,才會展示
ax.set_xlim(0,4)
ax.set_ylim(2, 11);

繪制errorbar誤差折線圖
是利用pyplot中的errorbar類來實作,其引數為:
- x:橫坐標
- y:縱坐標
- yerr:指定在y軸水平的誤差
- xerr:指定在x軸水平的誤差
- fmt:指定折線圖中某個點的顏色、形狀、線條風格等
- ecolor:指定errorbar的顏色
- elinewidth:指定errorbar的線條寬度
那么具體的繪制方法就是將plot更改為errorbar即可:
fig = plt.figure()
x = np.arange(10)
y = 2.5 * np.sin(x / 20 * np.pi)
yerr = np.linspace(0.05, 0.2, 10)
plt.errorbar(x,y+3,yerr=yerr,fmt='o-',ecolor='r',elinewidth=2);

patches
這個類是二維圖形類,它最常見的可以用來繪制矩形、多邊形、楔形,
矩形
Rectangle矩形類比較簡單,主要是通過xy來控制錨點,然后控制矩形的高寬即可,
最常見的矩形圖是hist直方圖和bar條形圖
hist-直方圖
其函式為plt.hist(),那么引數為:
- x:資料集,直方圖將會對這個資料集進行統計
- bins:統計的區間分布,我們可以指定區間進行統計,例如按照([0,10],[11,20])區間進行統計
- range:tuplt,顯示的區間
- density:是否顯示頻數統計結果
- histtype:可選{'bar', 'barstacked', 'step', 'stepfilled'}之一,默認為bar,step使用的是梯狀,stepfilled則會對梯狀內部進行填充,效果與bar類似
- align:可選{'left', 'mid', 'right'}之一,默認為'mid',控制柱狀圖的水平分布,left或者right,會有部分空白區域,推薦使用默認
- log:y軸是否采用指數刻度
- stacked:是否為堆積狀圖
x=np.random.randint(0,100,100) #生成[0-100)之間的100個資料,即 資料集
bins=np.arange(0,101,10) #設定連續的邊界值,即直方圖的分布區間[0,10),[10,20)...
fig = plt.figure(figsize = (6,12))
plt.subplot(311)
plt.hist(x,bins,color='fuchsia',alpha=0.5, density = True, histtype="step",
align = "left")#alpha設定透明度,0為完全透明
plt.xlabel('scores')
plt.ylabel('count')
plt.xlim(0,100); #設定x軸分布范圍 plt.show()
plt.subplot(312)
plt.hist(x,bins,color='fuchsia',alpha=0.5, density = True, histtype="step",
align = "mid")
plt.subplot(313)
plt.hist(x,bins,color='fuchsia',alpha=0.5, density = True, histtype="step",
align = "right")
這里對比了一下引數align的區別:

bar-柱狀圖
同樣,也是采用plt.bar()函式,其引數為:
- left:x軸的位置序列,一般采用range函式產生一個序列,但是有時候可以是字串
- height:y軸的數值序列,也就是柱形圖的高度,一般就是我們需要展示的資料
- alpha:透明度,值越小越透明
- width:柱形的寬度
- color或者facecolor:柱形填充的顏色
- edgecolor:柱形邊緣顏色
- label:標簽
y = range(1,17)
plt.bar(np.arange(16), y, alpha=0.5, width=0.5, color='yellow', edgecolor='red', label='The First Bar', lw=2);
# lw是柱形描邊的線寬度

多邊形
Polygon類是多邊形類,其引數主要是繪制的多邊形的頂點坐標,
那么這個類中最常用的是fill類,它是基于頂點坐標繪制一個填充的多邊形,例如:
x = np.linspace(0, 5 * np.pi, 1000)
y1 = np.sin(x)
y2 = np.sin(2 * x)
plt.fill(x, y1, color = "g", alpha = 0.3);

楔型(餅狀圖)
一個楔型是以坐標xy為中心,半徑r,從角度1掃到角度2,最常用是繪制餅狀圖plt.pie()
其引數為:
- x:楔型的形狀,一維陣列,可以看成是掃過角度的大小
- explode:如果不是None,那么就是一個len(x)的陣列,用來指定每塊的偏移
- labels:指定每個塊的標簽,串列或者none
- colors:指定每個塊的顏色,串列或者none
- startangle:餅狀圖開始繪制的角度
labels = ['Frogs', 'Hogs', 'Dogs', 'Logs']
sizes = [15, 30, 45, 10]
explode = (0, 0.1, 0, 0)
fig1, ax1 = plt.subplots()
ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.1f%%', shadow=True, startangle=90)
ax1.axis('equal'); # 設定axes為等高寬比,這樣才能夠確保畫出來為圓形

collections
這個類是用來繪制一組物件的集合,那么最常見的是用來繪制散點圖,即scatter方法,根據xy繪制不同大小或者顏色標記的散點圖,
其主要的引數如下:
- x和y
- s:散點的尺寸大小
- c:顏色
- marker:標記型別
x = [0,2,4,6,8,10]
y = [10]*len(x)
s = [20*2**n for n in range(len(x))]
plt.scatter(x,y,s=s) ;

image
這是繪制影像的類,最常用的imshow可以根據陣列繪制成影像(數值是各個像素值),
使用imshow畫圖時首先需要傳入一個陣列,陣列對應的是空間內的像素位置和像素點的值,interpolation引數可以設定不同的差值方法,可以理解為不同像素之間的處理手段:
methods = [None, 'none', 'nearest', 'bilinear', 'bicubic', 'spline16',
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']
grid = np.random.rand(4, 4)
fig, axs = plt.subplots(nrows=3, ncols=6, figsize=(9, 6),
subplot_kw={'xticks': [], 'yticks': []})
for ax, interp_method in zip(axs.flat, methods):
ax.imshow(grid, interpolation=interp_method, cmap='viridis')
ax.set_title(str(interp_method))
plt.tight_layout() # 自動調整子圖使其填充整個影像

物件容器-Object container
前面我們介紹的primitives基礎元素,是包含在容器里面的,當然容器還會包含它自身的屬性,
Figure容器
figure是最頂層的一個容器,它包含了圖中的所有元素,而一個圖表的背景可以認為就是在figure中添加的一個矩形,
當我們向圖表中添加add_subplot或者add_axes時,這些元素會被添加到figure.axes串列中:
fig = plt.figure()
ax1 = fig.add_subplot(211) # 作一幅2*1的圖,選擇第1個子圖
ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3]) # 再添加一個子圖位置引數,四個數分別代表了(left,bottom,width,height)
ax3 = fig.add_axes([0.2,0.1,0.3,0.4]) # 添加第三個子圖
print(ax1)
print(fig.axes) # fig.axes 中包含了subplot和axes兩個實體, 剛剛添加的

可以看到如果添加的子圖位置重疊的可能存在的情況,而輸出結果為:
AxesSubplot(0.125,0.53;0.775x0.35)
[<AxesSubplot:>, <Axes:>, <Axes:>]
figure.axes的串列中當前有三個元素,代表三個子圖,
而我們可以通過figure.delaxes()來洗掉其中的圖表,或者可以通過迭代訪問串列中的元素獲取子圖表,再在其上做修改:
fig = plt.figure()
ax1 = fig.add_subplot(211) # 作一幅2*1的圖,選擇第1個子圖
ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3]) # 再添加一個子圖位置引數,四個數分別代表了(left,bottom,width,height)
ax3 = fig.add_axes([0.2,0.1,0.3,0.4])
print(ax1)
print(fig.axes) # fig.axes 中包含了subplot和axes兩個實體, 剛剛添加的
for ax in fig.axes:
ax.grid(True)

Axes容器
Axes是matplotlib的核心,大量的用于繪圖的Artist存放在它內部,并且它有許多輔助方法來創建和添加Artist給它自己,而且它也有許多賦值方法來訪問和修改這些Artist,
和figure類似,axes包含一個patch屬性,這個可以認為就是它的繪圖區域:
fig = plt.figure()
ax = fig.add_subplot(111)
rect = ax.patch # 獲取實體
rect.set_facecolor("blue")

Axes有許多方法用于繪圖,如.plot()、.text()、.hist()、.imshow()等方法用于創建大多數常見的primitive(如Line2D,Rectangle,Text,Image等等),
可以在任意區域創建Axes,通過Figure.add_axes([left,bottom,width,height])來創建一個任意區域的Axes,其中left,bottom,width,height都是[0—1]之間的浮點數,他們代表了相對于Figure的坐標,
而我們往axes里面添加圖表是通過add_line和add_patch來進行添加,
另外Axes還包含兩個最重要的Artist container:
ax.xaxis:XAxis物件的實體,用于處理x軸tick以及label的繪制ax.yaxis:YAxis物件的實體,用于處理y軸tick以及label的繪制
Axis容器
該容器用來處理跟坐標軸相關的屬性,它包括坐標軸上的刻度線、刻度label、坐標網格、坐標軸標題等,而且可以獨立對上下左右四個坐標軸進行處理,
可以通過下面的方法獲取坐標軸的各個屬性實體:
fig, ax = plt.subplots()
x = range(0,5)
y = [2,5,7,8,10]
plt.plot(x, y, '-')
axis = ax.xaxis # axis為X軸物件
axis.get_ticklocs() # 獲取刻度線位置
axis.get_ticklabels() # 獲取刻度label串列(一個Text實體的串列)
axis.get_ticklines() # 獲取刻度線串列(一個Line2D實體的串列)
axis.get_data_interval()# 獲取軸刻度間隔
axis.get_view_interval()# 獲取軸視角(位置)的間隔
也可以對獲取的屬性進行修改,例如:
fig = plt.figure() # 創建一個新圖表
rect = fig.patch # 矩形實體并將其設為黃色
rect.set_facecolor('lightgoldenrodyellow')
ax1 = fig.add_axes([0.1, 0.3, 0.4, 0.4]) # 創一個axes物件,從(0.1,0.3)的位置開始,寬和高都為0.4,
rect = ax1.patch # ax1的矩形設為灰色
rect.set_facecolor('lightslategray')
for label in ax1.xaxis.get_ticklabels():
# 呼叫x軸刻度標簽實體,是一個text實體
label.set_color('blue') # 顏色
label.set_rotation(45) # 旋轉角度
label.set_fontsize(14) # 字體大小
for line in ax1.yaxis.get_ticklines():
# 呼叫y軸刻度線條實體, 是一個Line2D實體
line.set_markeredgecolor('green') # 顏色
line.set_markersize(25) # marker大小
line.set_markeredgewidth(2)# marker粗細

Tick容器
它是axis下方的一個容器物件,包含了tick、grid、line實體以及對應的label,我們可以訪問它的屬性來獲取這些實體:
Tick.tick1line:Line2D實體Tick.tick2line:Line2D實體Tick.gridline:Line2D實體Tick.label1:Text實體Tick.label2:Text實體
y軸分為左右兩個,因此tick1對應左側的軸;tick2對應右側的軸,
x軸分為上下兩個,因此tick1對應下側的軸;tick2對應上側的軸,
例如我們做如下修改:
fig, ax = plt.subplots()
ax.plot(100*np.random.rand(20))
ax.yaxis.set_tick_params(which='major', labelcolor='blue',
labelleft=False, labelright=True);

將主軸設在右邊且修改其顏色,
思考題
- primitives 和 container的區別和聯系是什么,分別用于控制可視化圖表中的哪些要
素- 【答】:我認為container是一個容器,而primitives 是基本元素,可以理解為container是包容primitives的,例如figure,axes,axis等作為一個容器,它們可以包含很多primitives 的基礎元素在其上面進行展示
- 使用提供的drug資料集,對第一列yyyy和第二列state分組求和,畫出下面折線圖,PA加粗標黃,其他為灰色,
import pandas as pd
df = pd.read_csv("Drugs.csv")
df.head(5)
new_df = df.groupby(["YYYY","State"]).sum()
new_df

data = https://www.cnblogs.com/FavoriteStar/archive/2022/11/26/new_df.reset_index().pivot(index='YYYY', columns='State', values='DrugReports')
data

data = https://www.cnblogs.com/FavoriteStar/archive/2022/11/26/data.reset_index()
data

因此就可以開始繪圖了:
fig,ax = plt.subplots(figsize = (12,12))
ax.grid(True, color='white')
rect = ax.patch
rect.set_facecolor('#efefef')
ax.plot(data["YYYY"], data["KY"],color='#afafaf')
ax.plot(data["YYYY"], data["OH"],color='#afafaf')
ax.plot(data["YYYY"], data["PA"],color='yellow',linewidth='8')
ax.plot(data["YYYY"], data["VA"],color='#afafaf')
ax.plot(data["YYYY"], data["WV"],color='#afafaf')
ax.set_title('Evolution of PA vs other states', color='yellow', loc='left')
ax.set_xlabel('Year')
ax.set_ylabel('DrugReports')

- 分別用一組長方形柱和填充面積的方式模仿畫出下圖,函式 y = -1 * (x - 2) * (x - 8) +10 在區間[2,9]的積分面積
import numpy as np
x = np.linspace(0,10)
y = -1 * (x - 2) * (x - 8) + 10
fig,ax = plt.subplots(2,1,figsize = (8,12))
x_bar = np.linspace(2,9)
y_bar = -1 * (x_bar - 2) * (x_bar - 8) + 10
y_bar_button = y_bar * 0
ax[0].plot(x,y,color="red")
ax[1].plot(x,y,color="red")
ax[0].bar(x_bar, y_bar,width=0.1, color='lightgray')
ax[1].bar(x_bar, y_bar, width = 0.1, color='lightgray')
ax[0].set_ylim((0,20))
ax[1].set_ylim((0,20))
ax[1].fill_between(x_bar, y_bar, y_bar_button, color="lightgray")

布局格式定方圓
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 #用來正常顯示負號
子圖
使用plt.subplots()繪制均勻狀態下的子圖
該函式的回傳分別是畫布和子圖構成的串列,傳入的引數為行、列、第幾個子圖,figsize用來指定畫布的大小,sharex和sharey用來表示是否共享橫軸和縱軸刻度,tight_layout用來調整子圖的相對大小使字符不重疊:
fig, axs = plt.subplots(2,5, figsize = (10,4), sharex = True, sharey = True)
fig.suptitle("樣例1",size = 20)
for i in range(2):
for j in range(5):
axs[i][j].scatter(np.random.randn(10), np.random.randn(10))
axs[i][j].set_title('第%d行,第%d列'%(i+1,j+1))
axs[i][j].set_xlim(-5,5)
axs[i][j].set_ylim(-5,5)
if i==1: axs[i][j].set_xlabel('橫坐標')
if j==0: axs[i][j].set_ylabel('縱坐標')
fig.tight_layout()

前面是利用subplots(注意加了s)顯式的創建多個物件,然后一一進行畫圖;我們還可以通過plt和subplot(注意沒加s),每次在指定位置創建子圖,創建后當前的繪制都會指向該子圖:
plt.figure()
# 子圖1
plt.subplot(2,2,1)
plt.plot([1,2], 'r')
# 子圖2
plt.subplot(2,2,2)
plt.plot([1,2], 'b')
#子圖3
plt.subplot(224) # 當三位數都小于10時,可以省略中間的逗號,這行命令等價于plt.subplot(2,2,4)
plt.plot([1,2], 'g');

除了常規的直角坐標系,還可以用projection方法創建極坐標系下的圖表:
N = 300
r = 2 * np.random.rand(N)
theta = 2 * np.pi * np.random.rand(N)
area = 50 * r**2
colors = theta
plt.subplot(projection='polar')
plt.scatter(theta, r, c=colors, s=area, cmap='hsv', alpha=0.75);

練一練
請思考如何用極坐標系畫出類似的玫瑰圖
fig = plt.figure(figsize = (8,12))
ax = plt.subplot(projection = "polar")
x = np.arange(100,1000, 20) # 間隔為20
y = np.linspace(0,np.pi*2, len(x))
ax.set_theta_direction(-1) # 設定極坐標的方向為順時針,1為逆時針
ax.set_theta_zero_location('N') # 設定開始畫的方位,有8個方位
ax.bar(y, x, width = 0.15,color=np.random.random((len(r), 3)))
plt.tight_layout()
主要就是set_theta_direction和set_theta_zero_location兩個函式調整影像,

使用GridSpec繪制非均勻子圖
所謂非均勻包含兩層含義,第一是指圖的比例大小不同但沒有跨行或跨列,第二是指圖為跨列或跨行狀態
利用 add_gridspec 可以指定相對寬度比例 width_ratios 和相對高度比例引數 height_ratios
fig = plt.figure(figsize=(10, 4))
spec = fig.add_gridspec(nrows=2, ncols=5, width_ratios=[1,2,3,4,5], height_ratios=[1,3])
fig.suptitle('樣例2', size=20)
for i in range(2):
for j in range(5):
ax = fig.add_subplot(spec[i, j]) # 注意此處的呼叫方式
ax.scatter(np.random.randn(10), np.random.randn(10))
ax.set_title('第%d行,第%d列'%(i+1,j+1))
if i==1: ax.set_xlabel('橫坐標')
if j==0: ax.set_ylabel('縱坐標')
fig.tight_layout()

上述創建子圖時用到了spec[i,j]的方法,說明它是一個可索引的串列,那么同樣也可以對其采用切片:
fig = plt.figure(figsize=(10, 4))
spec = fig.add_gridspec(nrows=2, ncols=6, width_ratios=[2,2.5,3,1,1.5,2], height_ratios=[1,2])
fig.suptitle('樣例3', size=20)
# sub1
ax = fig.add_subplot(spec[0, :3]) # 高度取第一個,寬度前三個都要了,就是1,7.5
ax.scatter(np.random.randn(10), np.random.randn(10))
# sub2
ax = fig.add_subplot(spec[0, 3:5]) # 1,1+1.5
ax.scatter(np.random.randn(10), np.random.randn(10))
# sub3
ax = fig.add_subplot(spec[:, 5])
ax.scatter(np.random.randn(10), np.random.randn(10))
# sub4
ax = fig.add_subplot(spec[1, 0])
ax.scatter(np.random.randn(10), np.random.randn(10))
# sub5
ax = fig.add_subplot(spec[1, 1:5])
ax.scatter(np.random.randn(10), np.random.randn(10))
fig.tight_layout()

子圖上的方法
補充一些子圖上的常用方法,
常用來畫直線的方法為axhline, axvline, axline (水平、垂直、任意方向)
fig, ax = plt.subplots(figsize=(4,3))
ax.axhline(0.5,0.1,0.8, color = 'red')
# 第一個引數為水平y等于多少,第二個為xmin,第三個為xmax,都是浮點數代表坐標軸占百分比
ax.axvline(0.5,0.2,0.8, color = "blue")
ax.axline([0.3,0.3],[0.7,0.7], color = "green");

利用grid可以添加灰色網格:
fig, ax = plt.subplots(figsize=(4,3))
ax.grid(True)

使用set_xscale或者set_yscale可以設定坐標軸的刻度:
fig, axs = plt.subplots(1, 2, figsize=(10, 4))
for j in range(2):
axs[j].plot(list('abcd'), [10**i for i in range(4)])
if j==0:
axs[j].set_yscale('log')
else:
pass
fig.tight_layout()

思考題
- 墨爾本1981年至1990年的每月溫度情況
data = https://www.cnblogs.com/FavoriteStar/archive/2022/11/26/pd.read_csv("layout_ex1.csv")
data["Time"] = pd.to_datetime(data["Time"])
data["year_num"] = data["Time"].apply(lambda x: x.year)
fig, ax = plt.subplots(2, 5, figsize = (20,4))
fig.suptitle('墨爾本1981年至1990年月溫度曲線',size=20,y=1.1)
for i in range(2):
for j in range(5):
tem = data[data["year_num"] == j+1981+i*5]["Temperature"]
x = np.arange(0,12)
ax[i][j].plot(x,tem,marker = "o",color='b')
ax[i][j].set_title(str(j+1981 + i*5 ) + "年")
if( j == 0):
ax[i][j].set_ylabel("氣溫")
plt.tight_layout()

- 用
np.random.randn(2, 150)生成一組二維資料,使用兩種非均勻子圖的分割方法,做出該資料對應的散點圖和邊際分布圖
data = https://www.cnblogs.com/FavoriteStar/archive/2022/11/26/np.random.randn(2,150)
fig = plt.figure(figsize = (12,12))
spec = fig.add_gridspec(nrows = 2, ncols = 2,width_ratios = [3,1],height_ratios=[1,3])
ax = fig.add_subplot(spec[0,0])
ax.hist(data[0,:],color ="blue",width = 0.4)
ax.axis("off")
ax2 = fig.add_subplot(spec[1,1])
ax2.hist(data[1,:], orientation='horizontal',color = "blue",rwidth = 0.8)
# 第二個引數設定為在y上面
ax2.axis("off")
ax3 = fig.add_subplot(spec[1,0])
ax3.scatter(data[0,:],data[1,:],color = "blue")
ax3.grid(True)
ax3.set_ylabel("my_data_y")
ax3.set_xlabel("my_data_x")
plt.tight_layout()

文字圖例盡眉目
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.dates as mdates
import datetime
Figure和Axes上的文本
文本API示例
下面這些命令是通過pyplot API和ooAPI分別創建文本的方式:

fig = plt.figure()
ax = fig.add_subplot()
# 設定x和y軸標簽
ax.set_xlabel('xlabel')
ax.set_ylabel('ylabel')
# 設定x和y軸顯示范圍均為0到10
ax.axis([0, 10, 0, 10])
ax.text(3, 8, 'boxed italics text in data coords', style='italic',
bbox={'facecolor': 'red', 'alpha': 0.5, 'pad': 10})
# 在畫布上添加文本,一般在子圖上添加文本是更常見的操作,這種方法很少用
fig.text(0.4,0.8,'This is text for figure')
ax.plot([2], [1], 'o')
# 添加注解
ax.annotate('annotate', xy=(2, 1), xytext=(3, 4),
arrowprops=dict(facecolor='black', shrink=0.05));

text-子圖上的文本
其呼叫方法為axes.text(),那么其引數為:
- x,y:文本出現的位置
- s:文本的內容
- fontdict:可選引數,用來調整文本的屬性
重點解釋下fontdict和**kwargs引數,這兩種方式都可以用于調整呈現的文本樣式,最終效果是一樣的,不僅text方法,其他文本方法如set_xlabel,set_title等同樣適用這兩種方式修改樣式,通過一個例子演示這兩種方法是如何使用的,
fig = plt.figure(figsize = (10,3))
axes = fig.subplots(1,2)
axes[0].text(0.3,0.8, "modift by **kwargs", style="italic",
bbox = {"facecolor":"red", "alpha":0.5, "pad": 10})
font = {"bbox": {"facecolor":"red", "alpha":0.5, "pad": 10},
"style":"italic"}
axes[1].text(0.3,0.8, "modify by fontdict", fontdict = font)

那么這些樣式常用的引數如下:


xlabel和ylabel
其呼叫方法為axes.set_xlabel和axes.set_ylabel
其引數為:
- xlabel:標簽內容
- fontdict和之前一樣
- **kwargs也和之前一樣
- labelpad:標簽和坐標軸之間的距離
- loc:標簽位置,可選為"left","center","right"
在**kwargs中有另外的引數可以調整標簽的位置等資訊,下面來觀察他們的區別:
fig = plt.figure(figsize=(10,3))
axes = fig.subplots(1,2)
axes[0].set_xlabel('xlabel',labelpad=20,loc='left')
# loc引數僅能提供粗略的位置調整,如果想要更精確的設定標簽的位置,可以使用position引數+horizontalalignment引數來定位
# position由一個元組程序,第一個元素0.2表示x軸標簽在x軸的位置,第二個元素對于xlabel其實是無意義的,隨便填一個數都可以
# horizontalalignment='left'表示左對齊,這樣設定后x軸標簽就能精確定位在x=0.2的位置處
axes[1].set_xlabel('xlabel', position=(0.2, _), horizontalalignment='left');

title和suptitle-子圖和畫布的標題
title呼叫方法為axes.set_title(),其引數為:
- label:標簽內容
- fontdict,loc,**kwargs和之前一樣
- pad:標題偏離圖表頂部的位置
- y:title所在子圖垂向的位置,默認在子圖的頂部
suptitle的呼叫為figure.suptitle(),
下面查看pad和y的影響:
fig = plt.figure(figsize=(10,3))
fig.suptitle('This is figure title',y=1.2) # 通過引數y設定高度
axes = fig.subplots(1,2)
axes[0].set_title('This is title,pad = 15',pad=15)
axes[1].set_title('This is title,pad = 6',pad=6);
fig = plt.figure(figsize=(10,3))
fig.suptitle('This is figure title2',y=1)
axes = fig.subplots(1,2)
axes[0].set_title('This is title,y = 1',y = 1)
axes[1].set_title('This is title,y = 1.2',y = 1.2);


可以看到兩者其實就是控制標題與圖的距離而已,
annotate-子圖的注解
呼叫方式為axes.annotate(),其引數為:
- text:注解的內容
- xy:注解箭頭指向的位置
- xytext:注解文字的坐標
- xycoords:用來定義xy引數的坐標系
- textcoords:用來定義xytext引數的坐標系
- arrowprops:用來定義指向箭頭的樣式
其引數特別多樣化,這里只是舉個例子:
fig = plt.figure()
ax = fig.add_subplot()
ax.annotate("annotate1",
xy=(0.2, 0.2), xycoords='data',
xytext=(0.8, 0.8), textcoords='data',
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=0.2")
);

字體的屬性設定
字體設定一般有全域字體設定和自定義區域字體設定兩種方法,
為了方便在圖中加入合適的字體,可以嘗試了解中文字體的英文名稱,此鏈接中就有常用的中文字體的英文名
#該block講述如何在matplotlib里面,修改字體默認屬性,完成全域字體的更改,
plt.rcParams['font.sans-serif'] = ['SimSun'] # 指定默認字體為新宋體,
plt.rcParams['axes.unicode_minus'] = False # 解決保存影像時 負號'-' 顯示為方塊和報錯的問題,
#區域字體的修改方法1
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
plt.plot(x, label='小示例圖示簽')
# 直接用字體的名字
plt.xlabel('x 軸名稱引數', fontproperties='Microsoft YaHei', fontsize=16) # 設定x軸名稱,采用微軟雅黑字體
plt.ylabel('y 軸名稱引數', fontproperties='Microsoft YaHei', fontsize=14) # 設定Y軸名稱
plt.title('坐標系的標題', fontproperties='Microsoft YaHei', fontsize=20) # 設定坐標系標題的字體
plt.legend(loc='lower right', prop={"family": 'Microsoft YaHei'}, fontsize=10) ; # 小示例圖的字體設定

tick上的文本
設定tick(刻度)和ticklabel(刻度標簽)也是可視化中經常需要操作的步驟,matplotlib既提供了自動生成刻度和刻度標簽的模式(默認狀態),同時也提供了許多靈活設定的方式,
簡單模式
直接使用axis.set_ticks設定標簽位置,使用axis.set_ticklabels設定標簽格式:
x1 = np.linspace(0.0, 5.0, 100)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
fig, axs = plt.subplots(2, 1, figsize=(5, 3), tight_layout=True)
axs[0].plot(x1, y1)
axs[1].plot(x1, y1)
axs[1].xaxis.set_ticks(np.arange(0., 10.1, 2.));

可以自動設定相對來說會好一點(上圖)
fig, axs = plt.subplots(2, 1, figsize=(5, 3), tight_layout=True)
axs[0].plot(x1, y1)
axs[1].plot(x1, y1)
ticks = np.arange(0., 8.1, 2.)
tickla = [f'{tick:1.2f}' for tick in ticks]
axs[1].xaxis.set_ticks(ticks)
axs[1].xaxis.set_ticklabels(tickla);

我們通常設定tick都是要與數值的范圍匹配, 然后再設定ticklabel為我們想要的型別,如下:
fig, axs = plt.subplots(2, 1, figsize=(6, 4), tight_layout=True)
x1 = np.linspace(0.0, 6.0, 100)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
axs[0].plot(x1, y1)
axs[0].set_xticks([0,1,2,3,4,5,6])
axs[1].plot(x1, y1)
axs[1].set_xticks([0,1,2,3,4,5,6])#要將x軸的刻度放在資料范圍中的哪些位置
axs[1].set_xticklabels(['zero','one', 'two', 'three', 'four', 'five','six'],#設定刻度對應的標簽
rotation=30, fontsize='small')#rotation選項設定x刻度標簽傾斜30度,
axs[1].xaxis.set_ticks_position('top')
#set_ticks_position()方法是用來設定刻度所在的位置,常用的引數有bottom、top、both、none
print(axs[1].xaxis.get_ticklines());

上方的例子就是位置在bottom,下方就是在top,both就是上下都有,none就是都沒有,
Tick Lacators and Formatters
除了上述的簡單模式以外,還可以通過Axis.set_major_locator和Axis.set_minor_locator方法用來設定標簽的位置,Axis.set_major_formatter和Axis.set_minor_formatter方法用來設定標簽的格式,這種方式的好處是不用顯式地列舉出刻度值串列,
set_major_formatter和set_minor_formatter這兩個formatter格式命令可以接收字串格式(matplotlib.ticker.StrMethodFormatter)或函式引數(matplotlib.ticker.FuncFormatter)來設定刻度值的格式 ,
這部分的內容比較推薦用到的時候再去查,
Tick Formatters
接受字串:
fig, axs = plt.subplots(2, 2, figsize=(12, 5), tight_layout=True)
for n, ax in enumerate(axs.flat):
ax.plot(x1*10., y1)
formatter = matplotlib.ticker.FormatStrFormatter('%1.1f')
axs[0, 1].xaxis.set_major_formatter(formatter)
formatter = matplotlib.ticker.FormatStrFormatter('-%1.1f')
axs[1, 0].xaxis.set_major_formatter(formatter)
formatter = matplotlib.ticker.FormatStrFormatter('%1.5f')
axs[1, 1].xaxis.set_major_formatter(formatter);

接受函式:
def formatoddticks(x, pos):
if x % 2:
return f'{x:1.2f}'
else:
return ''
fig, ax = plt.subplots(figsize=(5, 3), tight_layout=True)
ax.plot(x1, y1)
ax.xaxis.set_major_formatter(formatoddticks);

Tick Locators
這個實作更復雜的操作:
fig, axs = plt.subplots(2, 2, figsize=(8, 5), tight_layout=True)
for n, ax in enumerate(axs.flat):
ax.plot(x1*10., y1)
locator = matplotlib.ticker.AutoLocator()
axs[0, 0].xaxis.set_major_locator(locator)
locator = matplotlib.ticker.MaxNLocator(nbins=3)
axs[0, 1].xaxis.set_major_locator(locator)
locator = matplotlib.ticker.MultipleLocator(5)
axs[1, 0].xaxis.set_major_locator(locator)
locator = matplotlib.ticker.FixedLocator([0,7,14,21,28])
axs[1, 1].xaxis.set_major_locator(locator);

# 特殊的日期型locator和formatter
locator = mdates.DayLocator(bymonthday=[1,15,25])
formatter = mdates.DateFormatter('%b %d')
fig, ax = plt.subplots(figsize=(5, 3), tight_layout=True)
ax.xaxis.set_major_locator(locator)
ax.xaxis.set_major_formatter(formatter)
base = datetime.datetime(2017, 1, 1, 0, 0, 1)
time = [base + datetime.timedelta(days=x) for x in range(len(x1))]
ax.plot(time, y1)
ax.tick_params(axis='x', rotation=70);

legend圖例
在學習legend之前需要先學習幾個術語:
- legend entry(圖例條目):每個圖例都有一個或者多個條目組成,一個條目包含一個key和對應的label,例如圖中三條曲線需要標注,那么就是3個條目
- legend key(圖例鍵):每個legend label左邊的標記,指明是哪條曲線
- legend label(圖例標簽):描述文本
- legend handle(圖例句柄):用于在圖例中生成適當圖例條目的原始物件
以下圖為例,右側的方框中的共有兩個legend entry;兩個legend key,分別是一個藍色和一個黃色的legend key;兩個legend label,一個名為‘Line up’和一個名為‘Line Down’的legend label

圖例的繪制同樣有OO模式和pyplot模式兩種方式,寫法都是一樣的,使用legend()即可呼叫,
fig, ax = plt.subplots()
line_up, = ax.plot([1, 2, 3], label='Line 2')
line_down, = ax.plot([3, 2, 1], label='Line 1')
ax.legend(handles = [line_up, line_down], labels = ['Line Up', 'Line Down']);

fig, ax = plt.subplots()
line_up, = ax.plot([1, 2, 3], label='Line 2')
line_down, = ax.plot([3, 2, 1], label='Line 1')
ax.legend()

而設定圖例的位置,可以通過設定loc引數的值來設定,其有10個位置可以選擇,每個都有字串的形式和對應的數字形式:
| Location String | Location Code |
|---|---|
| best | 0 |
| upper right | 1 |
| upper left | 2 |
| lower left | 3 |
| lower right | 4 |
| right | 5 |
| center left | 6 |
| center right | 7 |
| lower center | 8 |
| upper center | 9 |
| center | 10 |
fig,axes = plt.subplots(2,5,figsize=(15,5))
for i in range(2):
for j in range(5):
axes[i][j].plot([0.5],[0.5])
axes[i][j].legend(labels='a',loc=i*5+j) # 觀察loc引數傳入不同值時圖例的位置
fig.tight_layout()

還可以設定圖例的邊框和背景:
fig = plt.figure(figsize=(10,3))
axes = fig.subplots(1,3)
for i, ax in enumerate(axes):
ax.plot([1,2,3],label=f'ax {i}')
axes[0].legend(frameon=False) #去掉圖例邊框
axes[1].legend(edgecolor='blue') #設定圖例邊框顏色
axes[2].legend(facecolor='gray'); #設定圖例背景顏色,若無邊框,引數無效

也可以為圖例加上標題:
fig,ax =plt.subplots()
ax.plot([1,2,3],label='label')
ax.legend(title='legend title');

思考題
嘗試使用兩種方式模仿畫出下面的圖表(重點是柱狀圖上的標簽),本文學習的text方法和matplotlib自帶的柱狀圖示簽方法bar_label
第一種:
label = ["Jim","Slim","Harry","Dick","Tom"]
y = [4,7,6,8,10]
error = np.random.rand(len(y)).round(2) #誤差
fig,ax = plt.subplots()
ax.set_title("How fast do you want to go today?")
ax.set_xlim(0,15)
for i in range(0, len(y)):
ax.text(y[i] + error[i]+1, label[i], '±' + str(error[i]), fontsize=10,horizontalalignment='center',color='blue')
ax.set_xlabel('performance')
ax.barh(label, y, color = 'blue',xerr = error)
# barh有一個引數為xerr就是來畫誤差線的
label = ["Jim","Slim","Harry","Dick","Tom"]
y = [4,7,6,8,10]
error = np.random.rand(len(y)).round(2) #誤差
fig,ax = plt.subplots()
ax.set_title("How fast do you want to go today?")
ax.set_xlim(0,15)
ax.set_xlabel('performance')
b = ax.barh(label, y, color = 'blue',xerr = error)
plt.bar_label(b, ["±"+str(i) for i in error])

樣式色彩秀芳華
第五回詳細介紹matplotlib中樣式和顏色的使用
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
matplotlib的繪圖樣式(style)
設定樣式最簡單就是在繪制每一個元素時在引數中設定對應的樣式,不過也可以用方法來批量修改全域的樣式,
matplotlib預先定義樣式
只需要在python腳步最開始時輸入想使用的style的名稱就可以呼叫,那么我們可以查看有哪些方式方便使用:
print(plt.style.available)
['Solarize_Light2', '_classic_test_patch', '_mpl-gallery', '_mpl-gallery-nogrid', 'bmh', 'classic', 'dark_background', 'fast', 'fivethirtyeight', 'ggplot', 'grayscale', 'seaborn', 'seaborn-bright', 'seaborn-colorblind', 'seaborn-dark', 'seaborn-dark-palette', 'seaborn-darkgrid', 'seaborn-deep', 'seaborn-muted', 'seaborn-notebook', 'seaborn-paper', 'seaborn-pastel', 'seaborn-poster', 'seaborn-talk', 'seaborn-ticks', 'seaborn-white', 'seaborn-whitegrid', 'tableau-colorblind10']
那么使用方法例如:
plt.style.use('ggplot')
plt.plot([1,2,3,4],[2,3,4,5]);

用戶自定義stylesheet
在任意路徑下創建一個后綴名為mplstyle的樣式清單,編輯檔案添加以下樣式內容:
axes.titlesize : 24
axes.labelsize : 20
lines.linewidth : 3
lines.markersize : 10
xtick.labelsize : 16
ytick.labelsize : 16
參考自定義stylesheet后觀察圖表變化:
plt.style.use('style1.mplstyle')
plt.plot([1,2,3,4],[2,3,4,5]);

值得特別注意的是,matplotlib支持混合樣式的參考,只需在參考時輸入一個樣式串列,若是幾個樣式中涉及到同一個引數,右邊的樣式表會覆寫左邊的值:
plt.style.use(['dark_background', 'style1.mplstyle'])
plt.plot([1,2,3,4],[2,3,4,5]);

設定rcparams
還可以通過修改默認rc設定的方式改變樣式,所有rc設定都保存在一個叫做 matplotlib.rcParams的變數中,修改過后再繪圖,可以看到繪圖樣式發生了變化,
plt.style.use('default') # 恢復到默認樣式
mpl.rcParams['lines.linewidth'] = 2
mpl.rcParams['lines.linestyle'] = '--'
plt.plot([1,2,3,4],[2,3,4,5]);

另外matplotlib也還提供了一種更便捷的修改樣式方式,可以一次性修改多個樣式,
mpl.rc('lines', linewidth=4, linestyle='-.')
matplotlib的色彩設定color
在matplotlib中,設定顏色有以下幾種方式
RGB或者RGBA
plt.plot([1,2,3],[4,5,6],color=(0.1, 0.2, 0.5))
plt.plot([4,5,6],[1,2,3],color=(0.1, 0.2, 0.5, 0.5));
顏色用[0,1]之間的浮點數表示,四個分量按順序分別為(red, green, blue, alpha),其中alpha透明度可省略,

HEX RGB或者RGBA
# 用十六進制顏色碼表示,同樣最后兩位表示透明度,可省略
plt.plot([1,2,3],[4,5,6],color='#0f0f0f')
plt.plot([4,5,6],[1,2,3],color='#0f0f0f80');

灰度色階
# 當只有一個位于[0,1]的值時,表示灰度色階
plt.plot([1,2,3],[4,5,6],color='0.5');

單字符基本顏色
八個基本顏色可以用單個字符來表示,分別是'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w',對應的是blue, green, red, cyan, magenta, yellow, black, and white的英文縮寫,設定color='m'即可,
顏色名稱
matplotlib提供了顏色對照表,可供查詢顏色對應的名稱

用colormap設定一組顏色
具體可以閱讀這篇文章,
x = np.random.randn(50)
y = np.random.randn(50)
plt.scatter(x,y,c=x,cmap='RdYlBu');

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/538448.html
標籤:其他
上一篇:定義(創建)、呼叫函式及回傳值
下一篇:python中的高階函式
