數據可視化之matplotlib繪圖篇

引言

首先來看幾個簡單的圖表, 下面4段不一樣的matplotlib繪圖代碼最終的結果是同樣的,繪製的圖形以下圖所示。html

a = np.linspace(-5, 5, 100)
b = np.sin(a)
c = np.cos(a)

# -------------------------------------- #
# ---------------First way-------------- #
fig, ax = plt.subplots()
ax.set_title('Sin-Cos')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.plot(a, b, c='r')
ax.plot(a, c, c='g')
ax.legend(['a', 'b'])
fig.show()


# -------------------------------------- #
# ---------------Second way-------------- #
fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('Sin-Cos')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.plot(a, b, c='r')
ax.plot(a, c, c='g')
ax.legend(['a', 'b'])
fig.show()


# -------------------------------------- #
# ---------------Third way-------------- #
plt.figure()
plt.title('Sin-Cos')
plt.xlabel('X')
plt.ylabel('Y')
plt.plot(a, b, c='r')
plt.plot(a, c, c='g')
plt.legend(['a', 'b'])
plt.show()


# -------------------------------------- #
# ---------------Fourth way------------- #
plt.subplot(111)
plt.title('Sin-Cos')
plt.xlabel('X')
plt.ylabel('Y')
plt.plot(a, b, c='r')
plt.plot(a, c, c='g')
plt.legend(['a', 'b'])
plt.show()

9TKbyd6TwJwAAAABJRU5ErkJggg__.png

在以上的4種實現方式中,可能大多數都會使用後面兩種,即直接使用plt.plot()進行繪製,我剛開始也同樣,它看起來簡單,用起來也很方便。但當你逐漸習慣使用默認的方式來進行繪圖時,慢慢會出現各類的問題,尤爲是當你須要對圖表使用不少自定義的設置的時候。默認接口隱藏了繪圖過程當中的不少細節,由於不瞭解其內部細節,因此在繪圖過程當中,對一些圖表的設置方法全靠記憶。api

Matplotlib中的部件

好了,說了這麼多,咱們先來了解matplotlib繪圖過程當中幾個主要的名稱,以下圖所示:
fig_map.jpgdom

  • Figure能夠理解爲畫板,使用fig = plt.figure()會建立一個畫板。
  • Axes能夠理解爲畫板上的各類圖形,一個圖形就是一個axes,利用axes能夠對圖形的各個部分進行設置。好比使用fig.add_subplot()會在fig上建立一個axes。
  • Axis表示一個Axes的座標軸,好比x軸,y軸以及z軸等。

接着,介紹圖形(Axes)中的主要部件,瞭解了每一個部件在圖中的位置以及對應的功能以後才能按照本身的方式來對圖表進行設置。函數

1545653427235_pd.png

以上圖中的全部部件,均可以經過axes來實現精確的控制,好比須要設置x軸主刻度標籤, 便可使用axes.xaxis.set_ticklabels(['', '']),這種面向對象的控制方式,很容易理解和實現。字體

實戰

基於上面的介紹,咱們來繪製一些圖形。spa

時間序列圖

直接上代碼:3d

# 導入FontProperties類,該類用於定義字體,包括字體,大小等
from matplotlib.font_manager import FontProperties

# 定義標籤字體,字體爲Time New Roman
# 也能夠經過指定fname(字體文件的路徑)來定義字體
label_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')

# 定義標題字體
title_font = FontProperties(family='Times New Roman', size=20, weight='bold', style='normal')

# 定義座標軸刻度字體
ticks_font = FontProperties(family='Times New Roman', size=12, weight='bold', style='normal')

# 定義legend字體
legend_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')

year = np.arange(1985, 2017, 1)
ndvi = np.random.rand(len(year))

# 建立fig和axes
fig, ax = plt.subplots(figsize=(10, 5))


# 取消x方向的格網
# axis.xaxis.grid(False)
# 取消y方向的格網
# axis.yaxis.grid(False)

# 取消全部格網
ax.grid(False)


# 定義標題,字體和顏色
# axis.yaxis.label.set_fontproperties(font)
# axis.xaxis.label.set_fontproperties(font)

ax.set_xlabel('Year', fontproperties=label_font, color='black')
ax.set_ylabel('NDVI', fontproperties=label_font, color='black')


# 定義座標軸顯示範圍
ax.set_xlim(1985, 2017)
ax.set_ylim(0, 1)

# 取消圖表的上邊界和右邊界
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# 將圖表的左邊界和下邊界的顏色設爲黑色(默認爲黑色)
# 顏色能夠參考:
# https://matplotlib.org/3.1.3/gallery/color/color_demo.html#sphx-glr-gallery-color-color-demo-py
ax.spines['left'].set_color('black')
ax.spines['bottom'].set_color('black')

# 將圖表的主刻度字體改成指定字體,並設定刻度不旋轉,遍歷全部主刻度
for tick in ax.xaxis.get_majorticklabels():
    tick.set_fontproperties(ticks_font)
    tick.set_rotation(0)
    
for tick in ax.yaxis.get_majorticklabels():
    tick.set_fontproperties(ticks_font)
    tick.set_rotation(0)
    
# 設置圖表的主刻度線的位置,x軸在下邊,y軸在左邊
ax.yaxis.set_ticks_position('left')
ax.xaxis.set_ticks_position('bottom')

# 設置顯示的x主刻度
ax.set_xticks(np.arange(1985, 2017, 3))

# 繪製時間序列
ax.plot(year, ndvi)

# 設置第一條line(這裏只有一條)的屬性
ax.lines[0].set_linewidth(2)
ax.lines[0].set_linestyle('-')
ax.lines[0].set_color('black')
ax.lines[0].set_marker('*')
ax.lines[0].set_markeredgecolor('red')

# 設置圖例
ax.legend(['TS1', ], prop=legend_font, facecolor='white')

#設置標題
ax.set_title('Time Series', fontproperties=title_font, color='black')

plt.show()

繪製結果以下所示:
FCt5NUOKUdkAAAAASUVORK5CYII_.pngcode

繪製圖表的代碼雖然比較長,但結構比較清楚,結合前面的部件介紹,瞭解每一步的含義,相信很容易就能看懂。orm

帶直方圖的散點圖

代碼以下:htm

# 定義座標軸刻度字體
ticks_font = FontProperties(family='Times New Roman', size=14, weight='normal', style='normal')

# 定義畫板大小
fig_width = 5

# 
space = fig_width / 100


left, bottom, width, height = fig_width / 10, fig_width / 10, fig_width / 2, fig_width / 2
scatter_region = [left, bottom, width, height]
topbar_region = [left, bottom +  height + space, width, height/2]
rightbar_region = [left + width + space, bottom, width/2, height]

# 定義畫板
plt.figure(figsize=(fig_width, fig_width))

# 定義Axes用於繪製散點圖
# plt.axes([left, bottom, width, height])
ax_scatter = plt.axes(scatter_region)

# 定義Axes用於繪製x的直方圖
ax_topbar = plt.axes(topbar_region)

# 定義Axes用於繪製y的直方圖
ax_rightbar = plt.axes(rightbar_region)

# ax_rightbar.invert_xaxis()
# ax_topbar.xaxis.set_ticks([])
# ax_rightbar.xaxis.set_ticks_position('bottom')
# ax_rightbar.yaxis.set_ticks([])

# 設置座標軸屬性, 這裏使用set_tick_params(),能夠同時設置座標軸的多個屬性,詳情能夠參考:
# https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.axes.Axes.tick_params.html#matplotlib.axes.Axes.tick_params
ax_rightbar.yaxis.set_tick_params(bottom=False, labelbottom=False)
ax_rightbar.xaxis.set_tick_params(direction='out', color='black', left=True, labelleft=True)
ax_topbar.yaxis.set_tick_params(direction='out', color='black', left=True, labelleft=True)
ax_topbar.xaxis.set_tick_params(bottom=False, labelbottom=False)


# ax_scatter.xaxis.set_tick_params(direction='out', color='black', labelsize=12)
# ax_scatter.yaxis.set_tick_params(direction='out', color='black', labelsize=12)
# 以上兩行代碼等同於下面一句代碼
ax_scatter.tick_params(direction='out', color='black')

# 設置座標軸字體
for ax in [ax_topbar, ax_scatter, ax_rightbar]:
    for tick in ax.yaxis.get_majorticklabels():
        tick.set_fontproperties(ticks_font)
    for tick in ax.xaxis.get_majorticklabels():
        tick.set_fontproperties(ticks_font)

# s參數用於設置散點大小,c用於設置cmap
ax_scatter.scatter(x, y, s=np.abs(x / y) * 10, c=np.abs(x * y) * 0.02, alpha=0.6)

ax_topbar.hist(x, bins=np.linspace(np.min(x), np.max(x), 16), color='#2C5784')
ax_rightbar.hist(y, bins=np.linspace(np.min(y), np.max(y), 16), orientation='horizontal', color='#20866C')
plt.show()

繪製結果以下圖所示:
wGXmRwx0SBPmQAAAABJRU5ErkJggg__.png

帶標註的函數圖像

# 生成數據
x = np.linspace(-4, 4, 1000)
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.exp(x)

from matplotlib.font_manager import FontProperties
label_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')
title_font = FontProperties(family='Times New Roman', size=20, weight='bold', style='normal')
ticks_font = FontProperties(family='Times New Roman', size=12, weight='bold', style='normal')
legend_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')
fig, ax = plt.subplots(figsize=(8, 10), dpi=100, ncols=1, nrows=2)

# 繪製函數圖像
ax[0].plot(x, y1, color='red')
ax[0].plot(x, y2, color='orange')
ax[1].plot(x, y3, color='blue')
# ax1和ax共享x軸,當須要在同一個圖表中顯示比例不一樣的圖形時使用,好比exp(x)和sin(x)
# ax1 = ax.twinx()


# --------------------------------------- Fig 1 --------------------------------------- #
# 設置標題,座標軸標籤
ax[0].set_title(r'$y=sin(x)$', fontproperties=title_font, color='black', pad=10)
# ax[0].set_xlabel(r'$x$', fontproperties=label_font, color='black', ha='right')
# ax[0].set_ylabel(r'$y$', fontproperties=label_font, color='black')
# ax[0].xaxis.set_label_coords(5, 0)
# ax[0].xaxis.set_label_coords(0, 1.5)

# 不顯示右座標軸和上座標軸
ax[0].spines['top'].set_visible(False)
ax[0].spines['right'].set_visible(False)

# 移動左座標軸和底部座標軸到圖形中間位置
ax[0].spines['left'].set_position('center')
ax[0].spines['bottom'].set_position('center')


# 設置x軸刻度
# ax[0].xaxis.set_ticks_position('left')
# 設置x軸刻度座標
ax[0].set_xticks([-np.pi, -np.pi/2, 0, np.pi/2, np.pi])
# 設置x軸刻度標籤
ax[0].set_xticklabels([r'$-\pi$', r'$-\frac{\pi}{2}$', r'$0$', r'$\frac{\pi}{2}$', r'$\pi$'])

# 設置y軸刻度
ax[0].set_yticks([-1, 0, 1])

# 設置y軸刻度的位置
ax[0].yaxis.set_ticks_position('left')

# ax[0].lines[1].set_linestyle('--')

# annotate函數的詳細參數解釋能夠參考:
# https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.axes.Axes.annotate.html
"""
annotate(text, xy, xytext, xycoords, textcoords, arrowprops ,*args, **kwargs)
    text(string): 須要標註的文本
    xy(tuple -> (float, float)): 標註的點/位置
    xytext(tuple -> (float, float)): 放置標註文本的位置
    textcoords(string): 給定xytext的座標系統,可選('offset points', 'offset pixel')
    xycoords(string): 給定xy的座標系,默認'data',表示使用待標註對象的座標系統
    fontproperties(FontProperties): 須要標註的文本
    arrowprops(dict): 設置標註文本和標註點之間的鏈接方式。arrowstyle="->" 表示使用箭頭鏈接,
    connectionstyle="arc3,rad=.6" 表示鏈接類型爲圓弧, 半徑爲0.6
"""

ax[0].annotate(r'$Sin(\frac{2\pi}{3}) = \frac{\sqrt{3}}{2}$', xy=(np.pi * 2 / 3, np.sqrt(3)/2), xycoords='data', xytext=(10, 30), \
               textcoords='offset points', arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.6"), fontproperties=ticks_font)

# 這裏座標軸標籤是使用註記來設置的
ax[0].annotate(r'$X$', xy=(4.4, 0), xycoords='data', color='black', fontproperties=label_font)

ax[0].annotate(r'$Cos(\frac{2\pi}{3}) = -\frac{1}{2}$', xy=(np.pi * 2 / 3, -1/2), xycoords='data', xytext=(-100, -30), \
               textcoords='offset points', arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"), fontproperties=ticks_font)

ax[0].annotate(r'$Y$', xy=(-0.3, 1.05), xycoords='data', color='black', fontproperties=label_font)

# 標記特定位置
ax[0].scatter([2*np.pi/3, ], [-1/2,], color='orange', s=40)
ax[0].plot([2*np.pi/3, 2*np.pi/3], [0, -1/2], linestyle='--', color='orange')
ax[0].scatter([2*np.pi/3, ], [np.sqrt(3)/2,], color='red', s=40)
ax[0].plot([2*np.pi/3, 2*np.pi/3], [0, np.sqrt(3)/2], linestyle='--', color='red')

# 設置圖例
ax[0].legend([r'$Sin(x)$', r'$Cos(x)$',], loc='best', prop=legend_font, shadow=False, facecolor='white')

# --------------------------------------- Fig 2 --------------------------------------- #
ax[1].spines['top'].set_visible(False)
ax[1].spines['right'].set_visible(False)

# 統一設置刻度標籤字體
for sub_ax in ax:
    for tick in sub_ax.xaxis.get_majorticklabels():
        tick.set_fontproperties(ticks_font)
        
    for tick in sub_ax.yaxis.get_majorticklabels():
        tick.set_fontproperties(ticks_font)
        
ax[1].annotate(r'$X$', xy=(4.4, -0.7), xycoords='data', color='black', fontproperties=label_font)
ax[1].annotate(r'$Y$', xy=(-4.4, 56), xycoords='data', color='black', fontproperties=label_font)

# 填充圖像和座標軸之間的圖形
# 若是要在x方向上的曲線之間填充,可使用fill_betweenx
ax[1].fill_between(x, y3, 0, color='green')


# 使用tighe_layout能夠自動調整子圖參數,以使子圖適合圖形區域。
fig.tight_layout()
plt.show()

P6rdqrnAMX2fqKZtDHwHODYi7gSmAV8qf8EqdVmsPOaWJEmSJHWdF29LkiRJ6jGLhSRJkqQes1hIkiRJ6jGLhSRJkqQes1hIkiRJ.png

熱力圖(Heatmap)

# Heatmap

from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
from matplotlib.font_manager import FontProperties

label_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')
title_font = FontProperties(family='Times New Roman', size=20, weight='bold', style='normal')
ticks_font = FontProperties(family='Times New Roman', size=12, weight='bold', style='normal')
legend_font = FontProperties(family='Times New Roman', size=18, weight='bold', style='normal')

vegetables = ["cucumber", "tomato", "lettuce", "asparagus",
              "potato", "wheat", "barley"]
farmers = ["Farmer Joe", "Upland Bros.", "Smith Gardening",
           "Agrifun", "Organiculture", "BioGoods Ltd.", "Cornylee Corp."]

harvest = np.array([[0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0],
                    [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0],
                    [1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0],
                    [0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0],
                    [0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0],
                    [1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1],
                    [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3]])

fig_size=8
fig, ax = plt.subplots(figsize=(fig_size, fig_size), dpi=100)

# 使用matplotlib3.1.1繪製,會出現heatmap顯示不全的問題
# 這裏使用的是matplotlib3.1.0
im = ax.imshow(harvest, cmap=plt.cm.GnBu_r)

ax.set_xticks(np.arange(len(farmers)))
ax.set_yticks(np.arange(len(vegetables)))

# ha -> horizontal align
# va -> vertical align
ax.set_xticklabels(farmers, ha='right', va='bottom', rotation_mode="anchor")
ax.set_yticklabels(vegetables)

for tick in ax.xaxis.get_majorticklabels():
    tick.set_rotation(45)
    tick.set_fontproperties(ticks_font)


for tick in ax.yaxis.get_majorticklabels():
    tick.set_fontproperties(ticks_font)
    

for i in range(len(vegetables)):
    for j in range(len(farmers)):
        text = ax.text(j, i, harvest[i, j],
                       ha="center", va="center", color="w", fontproperties=ticks_font)
   

# 參數的設置我暫時也不太理解,能夠參考:
# https://stackoverflow.com/questions/18195758/set-matplotlib-colorbar-size-to-match-graph

# 建立顏色條,colorbar能夠理解爲子圖,即sub fig
cbar = fig.colorbar(im, orientation='vertical', fraction=0.046, pad=0.04)

# colorbar.ax能夠看作是一個fig中的axes,具備axes的全部屬性和方法

# 設置顏色條的刻度標籤
cbar.ax.set_yticklabels(np.arange(0, 7))

# 設置顏色條的刻度標籤字體

ticklabs = cbar.ax.get_yticklabels()
cbar.ax.set_yticklabels(ticklabs, fontproperties=ticks_font)

# 設置顏色條座標軸字體
# https://stackoverflow.com/questions/15908371/matplotlib-colorbars-and-its-text-labels
cbar.ax.set_ylabel('Color Bar', fontproperties=title_font, rotation=270, labelpad=16)

# 設置標題字體
ax.set_title("Harvest of local farmers (in tons/year)", fontproperties=title_font, pad=10)
fig.tight_layout()
plt.show()

繪製結果以下所示:
B8D3WUq5uaarAAAAAElFTkSuQmCC.png

總結

以上的幾種圖形基本上按照了如下步驟進行繪製:

  1. 定義字體(好比圖表標題、座標軸標題、刻度數字以及圖例等)
  2. 建立axes(plt.axes()、plt.subplots()或者fig.add_subplot() )
  3. 設置fig的屬性(好比背景顏色等)
  4. 使用axes設置軸(Spine)的屬性(包括顯示範圍、顏色、字體以及刻度等)
  5. 使用axes繪製圖形
  6. 使用axes設置線的屬性(好比線型、顏色以及粗細等等)
  7. 使用axes設置標題以及圖例

本文主要介紹了matplotlib繪圖對象中fig和axes的差別,能夠這樣理解,fig是畫板,而axes是畫板上的圖形,一個fig中能夠包含多個axes,而axes纔是控制繪圖的關鍵。
其次經過幾種經常使用圖形的繪製,介紹了matplotlib繪圖過程當中大部分的經常使用操做。

以上全部圖形的繪製均使用了同一種方式,即利用axes來進行繪圖,主要有兩個緣由,其一,這樣寫的代碼可讀性比較好,比較容易修改(至少我的認爲是這樣的)。其二,這種方式能夠實現對圖表的任意部件進行自定義的修改。

我雖然都使用了axes來繪圖,而且也推薦使用這種方式來進行繪圖。但並不意味着你也要使用這種方式,若是隻是繪製一些示意圖,對圖形並無太多控制,其實plt.plot()可能更方便,這也是matplotlib默認的方式。

Reference

https://matplotlib.org/tutori...

https://matplotlib.org/1.5.1/...

https://zhuanlan.zhihu.com/p/...

https://matplotlib.org/3.1.3/...

https://matplotlib.org/3.1.1/...

https://stackoverflow.com/que...

相關文章
相關標籤/搜索