Python之Matplotlib數據可視化(五):文字與註釋

可視化對於大家來說確實是有關的,因爲確實是直觀的,每一組大數據如果可以用可視化進行展示的話可以讓大家豁然開朗。但在另外一些場景中,輔之以少量的文字提示(textual cue)和標籤是必不可少的。雖然最基本的註釋(annotation)類型可能只是座標軸標題與圖標題,但註釋可遠遠不止這些。讓我們可視化一些數據,看看如何通過添加註釋來更恰當地表達信息。

首先導入畫圖需要用到的一些函數:

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

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

數據可以在 https://github.com/jakevdp/data-CDCbirths 下載,數據類型如下:
在這裏插入圖片描述

用清洗方法處理數據,然後畫出結果。

日均出生人數統計圖

births = pd.read_csv('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);
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('seaborn-whitegrid')
import numpy as np
import pandas as pd
births = pd.read_csv('C:\\Users\\Y\\Desktop\\data-CDCbirths-master\\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.show()

在這裏插入圖片描述

爲日均出生人數統計圖添加註釋

在用這樣的圖表達觀點時,如果可以在圖中增加一些註釋,就更能吸引讀者的注意了。可以通過 plt.text / ax.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)
# 設置座標軸標題
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'));
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('seaborn-whitegrid')
import numpy as np
import pandas as pd
births = pd.read_csv('C:\\Users\\Y\\Desktop\\data-CDCbirths-master\\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)
# 在圖上增加文字標籤
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)
# 設置座標軸標題
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'));
plt.show()

在這裏插入圖片描述
ax.text 方法需要一個 x 軸座標、一個 y 軸座標、一個字符串和一些可選參數,比如文字的顏色、字號、風格、對齊方式以及其他文字屬性。這裏用了 ha='right'ha='center'ha 是水平對齊方式(horizonal alignment)的縮寫。關於配置參數的更多信息,請參考plt.text()mpl.text.Text() 的程序文檔。

2 座標變換與文字位置

前面的示例將文字放在了目標數據的位置上。但有時候可能需要將文字放在與數據無關的位置上,比如座標軸或者圖形中。在 Matplotlib 中,我們通過調整座標變換(transform)來實現。

任何圖形顯示框架都需要一些變換座標系的機制。例如,當一個位於 (x, y) = (1, 1) 位置的點需要以某種方式顯示在圖上特定的位置時,就需要用屏幕的像素來表示。用數學方法處理這種座標系變換很簡單,Matplotlib 有一組非常棒的工具可以實現類似功能(這些工具位於 matplotlib.transforms 子模塊中)。

雖然一般用戶並不需要關心這些變換的細節,但是瞭解這些知識對在圖上放置文字大有幫助。一共有三種解決這類問題的預定義變換方式。

ax.transData	以數據爲基準的座標變換。
ax.transAxes	以座標軸爲基準的座標變換(以座標軸維度爲單位)。
fig.transFigure	以圖形爲基準的座標變換(以圖形維度爲單位)。

默認情況下,上面的文字在各自的座標系中都是左對齊的。這三個字符串開頭的 . 字符基本就是對應的座標位置。
transData 座標用 x 軸與 y 軸的標籤作爲數據座標。
transAxes 座標以座標軸(圖中白色矩形)左下角的位置爲原點,按座標軸尺寸的比例呈現座標。
transFigure 座標與之類似,不過是以圖形(圖中灰色矩形)左下角的位置爲原點,按圖形尺寸的比例呈現座標。

對比 Matplotlib 的三種座標系(1)

下面舉一個例子,用三種變換方式將文字畫在不同的位置:

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);
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('seaborn-whitegrid')
import numpy as np
import pandas as pd
births = pd.read_csv('C:\\Users\\Y\\Desktop\\data-CDCbirths-master\\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(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);
plt.show()

在這裏插入圖片描述

對比 Matplotlib 的三種座標系(2)

需要注意的是,假如你改變了座標軸上下限,那麼只有 transData 座標會受影響,其他座標系都不變

ax.set_xlim(0, 2)
ax.set_ylim(-6, 6)
fig
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('seaborn-whitegrid')
import numpy as np
import pandas as pd
births = pd.read_csv('C:\\Users\\Y\\Desktop\\data-CDCbirths-master\\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(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);
ax.set_xlim(0, 2)
ax.set_ylim(-6, 6)
fig
plt.show()

在這裏插入圖片描述
如果你改變了座標軸上下限,那麼就可以更清晰地看到剛剛所說的變化。

3 箭頭與註釋

除了刻度線和文字,簡單的箭頭也是一種有用的註釋標籤。

在 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"));
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('seaborn-whitegrid')
import numpy as np
import pandas as pd
births = pd.read_csv('C:\\Users\\Y\\Desktop\\data-CDCbirths-master\\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()
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"));

plt.show()

在這裏插入圖片描述
箭頭的風格是通過 arrowprops 字典控制的,裏面有許多可用的選項。由於這些選項在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')
# 設置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.set_ylim(3600, 5400);
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('seaborn-whitegrid')
import numpy as np
import pandas as pd
births = pd.read_csv('C:\\Users\\Y\\Desktop\\data-CDCbirths-master\\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)
# 在圖上增加箭頭標籤
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')
# 設置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.set_ylim(3600, 5400);

plt.show()

在這裏插入圖片描述
你可能已經注意到了,箭頭和文本框的配置功能非常細緻,這樣你就可以創建自己想要的箭頭風格了。不過,功能太過細緻往往也就意味着操作起來比較複雜,如果真要做一個產品級的圖形,可能得耗費大量的時間。最後我想說一句,前面適用的混合風格並不是數據可視化的最佳實踐,僅僅是爲演示一些功能而已。

備註

各位老鐵覺得可以的話來個“點贊”、“關注”、“評論”
各位老鐵覺得可以的話來個“點贊”、“關注”、“評論”
各位老鐵覺得可以的話來個“點贊”、“關注”、“評論”

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章