matplotlib詳細教學

2022-11-27 06:07:31

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有兩種型別:primitivescontainers

  • 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 = new_df.reset_index().pivot(index='YYYY', columns='State', values='DrugReports')
data

data = 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 = 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 = 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_locatorAxis.set_minor_locator方法用來設定標籤的位置,Axis.set_major_formatterAxis.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');