1. 程式人生 > 實用技巧 >Matplotlib基礎--文字和標註

Matplotlib基礎--文字和標註

建立一個優秀的視覺化圖表的關鍵在於引導讀者,讓他們能理解圖表所講述的故事。在一些情況下,這個故事可以通過純影象的方式表達,不需要額外新增文字,但是在另外一些情況中,圖表需要文字的提示和標籤才能將故事講好。也許標註最基本的型別就是圖表的標籤和標題,但是其中的選項引數卻有很多。讓我們在本節中使用一些資料來建立視覺化圖表並標註這些圖表來表達這些有趣的資訊。首先還是需要將要用到的模組和包匯入Pycharm:

import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('seaborn-whitegrid')
import numpy 
as np import pandas as pd

例子:節假日對美國出生率的影響

本例中的資料可以在 https://raw.githubusercontent.com/jakevdp/data-CDCbirths/master/births.csv 下載。

我們先按照前面的方式進行同樣的資料清洗程式,然後以圖表展示這個結果:

births = pd.read_csv(r'D:\python\Github學習材料\Python資料科學手冊\data\births.csv')

quartiles = np.percentile(births['births'], [25, 50, 75])
mu, sig 
= quartiles[1], 0.74 * (quartiles[2] - quartiles[0]) births = births.query('(births > @mu - 5 * @sig) & (births < @mu + 5 * @sig)') births['day'] = births['day'].astype(int) births.index = pd.to_datetime(10000 * births.year + 100 * births.month + births.day, format
='%Y%m%d') births_by_date = births.pivot_table('births', [births.index.month, births.index.day]) births_by_date.index = [pd.datetime(2012, month, day) for (month, day) in births_by_date.index]
fig, ax = plt.subplots(figsize=(12, 4))
births_by_date.plot(ax=ax);

當我們繪製了這樣的圖表來表達資料時,如果我們能對一些圖表的特性作出標註來吸引讀者的注意力通常是非常有幫助的。這可以通過呼叫plt.textax.text函式來實現,它們可以在某個特定的 x,y 軸位置輸出一段文字:

fig, ax = plt.subplots(figsize=(12, 4))
births_by_date.plot(ax=ax)

# 在折線的特殊位置標註文字
style = dict(size=10, color='gray')

ax.text('2012-1-1', 3950, "New Year's Day", **style)
ax.text('2012-7-4', 4250, "Independence Day", ha='center', **style)
ax.text('2012-9-4', 4850, "Labor Day", ha='center', **style)
ax.text('2012-10-31', 4600, "Halloween", ha='right', **style)
ax.text('2012-11-25', 4450, "Thanksgiving", ha='center', **style)
ax.text('2012-12-25', 3850, "Christmas ", ha='right', **style)

# 設定標題和y軸標籤
ax.set(title='USA births by day of year (1969-1988)',
       ylabel='average daily births')

# 設定x軸標籤月份居中
ax.xaxis.set_major_locator(mpl.dates.MonthLocator())
ax.xaxis.set_minor_locator(mpl.dates.MonthLocator(bymonthday=15))
ax.xaxis.set_major_formatter(plt.NullFormatter())
ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%h'));

ax.text方法接收 x 位置、y 位置、一個字串和額外可選的關鍵字引數可以用來設定顏色、大小、樣式、對齊等文字格式。上面我們使用了ha='right'ha='center',這裡的ha是*hirizonal alignment(水平對齊)*的縮寫。要查閱更多的可用引數,請檢視plt.text()mpl.text.Text()的文件字串內容。

轉換和文字位置

在剛才的例子中,我們將文字標註根據資料位置進行了定位。有些時候我們需要將文字標註獨立於資料位置而根據圖表位置進行定位。Matplotlib 通過轉換完成這項工作。

任何的圖形顯示框架都需要在座標系統之間進行轉換的機制。例如,一個數據點位於被轉換為圖表中的某個位置,進而轉換為螢幕上顯示的畫素。這樣的座標轉換在數學上都相對來說比較直接,,而且 Matplotlib 提供了一系列的工具實現了轉換(這些工具可以在matplotlib.transforms模組中找到)。

一般來說,使用者很少需要關注這些轉換的細節,但是當考慮將文字在圖表上展示時,這些知識卻比較有用。在這種情況中,下面三種定義好的轉換是比較有用的:

  • ax.transData:與資料座標相關的轉換
  • ax.tranAxes:與 Axes 尺寸相關的轉換(單位是 axes 的寬和高)
  • ax.tranFigure:與 figure 尺寸相關的轉換(單位是 figure 的寬和高)

下面我們來看看使用這些轉換將文字寫在圖表中不同位置的例子:

fig, ax = plt.subplots(facecolor='lightgray')
ax.axis([0, 10, 0, 10])

# transform=ax.transData是預設的,這裡寫出來是為了明確對比
ax.text(1, 5, ". Data: (1, 5)", transform=ax.transData)
ax.text(0.5, 0.1, ". Axes: (0.5, 0.1)", transform=ax.transAxes)
ax.text(0.2, 0.2, ". Figure: (0.2, 0.2)", transform=fig.transFigure);

注意預設情況下,文字是在指定座標位置靠左對齊的:這裡每個字串開始的"."的位置就是每種轉換的座標位置

transData座標給定的是通常使用的 x 和 y 軸座標位置。transAxes座標給定的是從 axes 左下角開始算起(白色區域)的座標位置,使用的是寬度和長度的佔比。transFigure座標類似,給定的是從 figure 左下角開始算起(灰色區域)的座標位置,使用的也是寬度和長度的佔比。

因此如果我們改變了軸的最大長度,只有transData座標會收到影響,其他兩個還是保持在相同位置:

ax.set_xlim(0, 2)
ax.set_ylim(-6, 6)
fig

這個變化可以通過動態改變軸的最大長度看的更加清楚:如果你在 notebook 執行這段程式碼,你可以將%matplotlib inline改為%matplotlib notebook,然後使用圖表的選單來互動式的改變圖表。

箭頭和標註

除了刻度標籤和文字標籤,另一種常用的標註是箭頭

在 Matplotlib 中繪製箭頭通常比你想象的難得多。雖然有plt.arrow()函式,作者不建議使用它:這個函式繪製的箭頭是一個 SVG 物件,因此在圖表使用不同的比例的情況會產生問題,結果通常不能讓使用者滿意。因此,作者建議使用plt.annotate()函式。這個函式會繪製一些文字以及一個箭頭,並且箭頭可以非常靈活的進行配置。

下面我們提供一些引數來使用annotate函式:

fig, ax = plt.subplots()

x = np.linspace(0, 20, 1000)
ax.plot(x, np.cos(x))
ax.axis('equal')

ax.annotate('local maximum', xy=(6.28, 1), xytext=(10, 4),
            arrowprops=dict(facecolor='black', shrink=0.05))

ax.annotate('local minimum', xy=(5 * np.pi, -1), xytext=(2, -6),
            arrowprops=dict(arrowstyle="->",
                            connectionstyle="angle3,angleA=0,angleB=-90"));

箭頭的樣式是使用箭頭屬性字典值進行控制的,裡面有很多可用的引數。這些引數在 Matplotlib 的線上文件中已經有了很詳細的說明,因此在這裡就不將這部分內容重複介紹一遍了。我們在前面出生率圖上再使用一些引數進行更多的說明:

fig, ax = plt.subplots(figsize=(12, 4))
births_by_date.plot(ax=ax)

# 為圖表新增標註
ax.annotate("New Year's Day", xy=('2012-1-1', 4100),  xycoords='data',
            xytext=(50, -30), textcoords='offset points',
            arrowprops=dict(arrowstyle="->",
                            connectionstyle="arc3,rad=-0.2"))

ax.annotate("Independence Day", xy=('2012-7-4', 4250),  xycoords='data',
            bbox=dict(boxstyle="round", fc="none", ec="gray"),
            xytext=(10, -40), textcoords='offset points', ha='center',
            arrowprops=dict(arrowstyle="->"))

ax.annotate('Labor Day', xy=('2012-9-4', 4850), xycoords='data', ha='center',
            xytext=(0, -20), textcoords='offset points')
ax.annotate('', xy=('2012-9-1', 4850), xytext=('2012-9-7', 4850),
            xycoords='data', textcoords='data',
            arrowprops={'arrowstyle': '|-|,widthA=0.2,widthB=0.2', })

ax.annotate('Halloween', xy=('2012-10-31', 4600),  xycoords='data',
            xytext=(-80, -40), textcoords='offset points',
            arrowprops=dict(arrowstyle="fancy",
                            fc="0.6", ec="none",
                            connectionstyle="angle3,angleA=0,angleB=-90"))

ax.annotate('Thanksgiving', xy=('2012-11-25', 4500),  xycoords='data',
            xytext=(-120, -60), textcoords='offset points',
            bbox=dict(boxstyle="round4,pad=.5", fc="0.9"),
            arrowprops=dict(arrowstyle="->",
                            connectionstyle="angle,angleA=0,angleB=80,rad=20"))


ax.annotate('Christmas', xy=('2012-12-25', 3850),  xycoords='data',
             xytext=(-30, 0), textcoords='offset points',
             size=13, ha='right', va="center",
             bbox=dict(boxstyle="round", alpha=0.1),
             arrowprops=dict(arrowstyle="wedge,tail_width=0.5", alpha=0.1));

# 設定圖表標題和座標軸標記
ax.set(title='USA births by day of year (1969-1988)',
       ylabel='average daily births')

# 設定月份座標居中顯示
ax.xaxis.set_major_locator(mpl.dates.MonthLocator())
ax.xaxis.set_minor_locator(mpl.dates.MonthLocator(bymonthday=15))
ax.xaxis.set_major_formatter(plt.NullFormatter())
ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%h'));

ax.set_ylim(3600, 5400);

上圖中箭頭和文字框都非常詳盡了:可以看出你幾乎可以使用plt.annotate建立任何你想要的箭頭樣式。不幸的是,這意味著這種特性都需要手工進行調整,因此如果需要獲得印刷質量的影象,這將是一個非常耗費時間的工作。最後,必須指出,上述這種多種樣式混合的方式來展現資料肯定不是最佳實踐,這裡只是為了儘可能多的介紹可用的引數。

更多關於 Matplotlib 的箭頭和標註樣式的討論和例子可以訪問 Matplotlib gallery。