Python 基礎資料視覺化—Matplotlib

對資料進行視覺化,讓你更加了解它

Yu-Hsuan Chou
20 min readNov 20, 2019

為什麼要做資料視覺化?

有效的視覺化可以幫助用戶分析和推理資料和證據。它使複雜的資料更容易理解、理解和使用。—— Wikipedia

現在的資訊傳達大多講求淺顯易懂以及良好的圖像化,而資料視覺化的目的就在於讓現在這些大量的數據資料能夠更好地被梳理,得以表現出資料的規律、趨勢、分布等等現象,然後再據此做更好的討論以及策略研擬。

如何選擇適當的視覺效果?

我們經常看到各式各樣的視覺化效果,像是基本的長條圖、折線圖、圓餅圖等等,可以依據需求來選擇。舉例來說,如果需要做趨勢的比較,大多會選擇折線圖、如果要做分布的呈現,則是可能會選擇密度圖或是直方圖。

下方即是一張由 Qlik 公司所繪製的表格,教大家如何選擇適合的圖表或是視覺化效果。(他們家的產品是視覺化軟體,但我自己沒有使用過也不知道好不好用,不知道是不是跟 PowerBI 有點像就是了)

視覺化BI軟體公司Qlik所繪製的選擇方式

在這些效果的選擇上面,可能在等等的效果簡介當中會更加清楚什麼時機使用較好。

如何繪製這些視覺效果?

接下來將會介紹六種視覺效果:折線圖、散布圖、長條圖、直方圖、盒狀圖以及圓餅圖,以及如何利用 Python 的 Matplotlob 套件當中的 pyplot 來進行繪製,同時為了處理資料也會使用到 Pandas,故 import 模組的寫法如下:

import matplotlib.pyplot as plt
import pandas as pd

以下將 pyplot 簡寫為 plt

這一次用來繪製圖型的資料是 NBA 球員歷年的資料,包含球員個人的歷年數據(球隊、總得分數、出場數、進球率、抄截、失誤、籃板數量等等)、球隊的數據、以及球員在聯盟呈報的身高體重(可能是體測的數據),這些資料來自於 NBA 的 api ,讓我不用自己寫爬蟲去爬資料,2019 年 7 月的時候就可以抓到2018–19賽季的資料了,可見官方的資料也是更新的非常迅速。

順帶一提,如果沒有記錯的話,我使用的是 nba-api 這個 Python 的 module,最近有了新的改版,總之是十分方便,感恩讚嘆。

折線圖 Line Plot

首先第一個是 Line Plot 折線圖,通常用於呈現單一資料的時間趨勢,或是單一資料的不同群體的時間趨勢,故經常使用在時間序列類型的資料上。

舉例來說,如果我們今天拿的是球員 LeBron James 的歷年數據資料,資料長相如圖,是一個叫做 LeBron 的 dataframe:

當我們想要繪製他歷年得分的折線圖,可能會寫下如下的程式碼:

plt.plot(LeBron.SEASON_ID, LeBron.PTS, color='b')
plt.xlabel('SEASON') # 設定x軸標題
plt.xticks(LeBron.SEASON_ID, rotation='vertical') # 設定x軸label以及垂直顯示
plt.title('LeBron James') # 設定圖表標題
plt.show()

可以得到如下的折線圖:

我們利用 plt.plot() 這個方法來繪製折線圖,從 matplotlib ≥ 2.2 以後,就可以將字串資料(這裡的 SEASON.ID )放進繪圖當中。 plt.plot() 的用法如下:

plt.plot(x, y, data, marker, color, linestyle...)

xy 分別代表兩軸的資料; data 則是資料的來源,可以略過不寫;而 markercolor 則是分別代表折線圖當中點的格式以及整個折線的顏色, linestyle 即是線段的風格,可以選擇虛線或是點虛線等等。

另外值得一提的是,還有一個參數叫做 fmt ,是將剛才的 markercolor 以及 linestyle 寫成一個 fmt 。舉例來說,如果想要將折線設定成藍色,折點設成圓形,線段設成虛線,只需要寫 fmt='bo--' ,不另外設定三者的內容即可得到如下的結果:

其他更多 plt.plot() 參數的用法請詳見 document。除此之外,如果想要在同一張圖表上繪製兩筆不同的資料線,只需要重複 plt.plot() 的流程即可疊加圖層,我們利用 Dwayne Wade 的數據資料來進行呈現:

plt.plot(LeBron.SEASON_ID, LeBron.PTS, 'b', label='LeBron')
plt.plot(Wade.SEASON_ID, Wade.PTS, 'r', label='Dwayne')
plt.xlabel('SEASON')
plt.xticks(Wade.SEASON_ID,rotation='vertical') #因兩人賽季數相同,故任選
plt.title('LeBron James and Dwyane Wade')
plt.legend(loc = 'lower left')
plt.show()

同時我們利用 plt.legend() 來設定圖例, loc 是位置參數,可以設定圖例方塊的位置。而在 plt.plot() 當中設定 label 是為了可以製作圖例,讓 plt.legend() 知道不同的線應該標註什麼。圖形如下:

如果想要將上面的折線圖拆成兩張圖,可以嘗試 plt.subplot() 來設定。舉例如下:

plt.subplot(2, 1 ,1)
plt.plot(LeBron.SEASON_ID, LeBron.PTS, 'b')
plt.xlabel('SEASON')
plt.xticks(LeBron.SEASON_ID, rotation='vertical')
plt.title('LeBron James')
plt.subplot(2, 1 ,2)
plt.plot(Wade.SEASON_ID, Wade.PTS, 'r')
plt.xlabel('SEASON')
plt.xticks(Wade.SEASON_ID, rotation='vertical')
plt.title('Dwyane Wade')
plt.tight_layout() #隔開兩個圖
plt.show()

散布圖 Scatter Plot

接下來是散布圖 Scatter Plot,通常用於描繪兩筆資料之間的相關程度,在進行機器學習或是資料科學的運用時,這樣的特性也使其多被用在 feature engineering 時的參考。

舉例來說,今天我們想要繪製 NBA 聯盟當中球員身高與體重的關係圖,我們的資料長相如下,是一個叫做 position_data 的 dataframe。

如果我們只想知道中鋒(Center)的身高體重分布,此時我們可能會寫下如下的程式碼,然後得到如圖的結果:

plt.scatter("HEIGHT", "WEIGHT", data=position_data[position_data['POSITION']=='Center'], alpha = 0.2)
plt.xlabel('Height(")')
plt.ylabel('Weight(lb)')
plt.show()

使用的方法是 plt.scatter() ,用法如下:

plt.scatter(x, y, [s, c, marker, alpha, label])

xy 分別是 x 軸和 y 軸的資料, s 指的是資料點的大小 scale, cmarkeralpha 分別代表顏色、資料點的點格式以及透明度。而 label 則是製作圖例時的標籤。

因此,如果我們想要一次將所有球員的身高體重關係圖描繪出來,可能的寫法如下:

df1 = position_data[position_data['POSITION']=='Center']
df2 = position_data[position_data['POSITION']=='Forward']
df3 = position_data[position_data['POSITION']=='Guard']
plt.scatter("HEIGHT", "WEIGHT", data=df1, alpha = 0.2)
plt.scatter("HEIGHT", "WEIGHT", data=df2, alpha = 0.2)
plt.scatter("HEIGHT", "WEIGHT", data=df3, alpha = 0.2)
plt.show()

得到的結果如圖:

但如上圖的結果多少有些難懂,這時候我們該怎麼區分這些不同顏色的點呢?

第一個方法當然是圖利,圖例的用法我們已經在上一節折線圖時說明過了,再加上更改 marker 、加上 title 可以得到的長相大約如下:

但在這邊要教給大家的是 plt.annotate() 的用法。什麼是 annotate 呢?就是標註的意思。在 matplotlib 當中,提供了方便我們在圖上進行文字、箭頭標記的方法,就是 plt.annotate 。基本的用法如下:

plt.annotate(s, xy, xytext, arrowprops)

s 代表的是這個標註的文字, xy 代表的是這個標註的座標位置(單位和我們的資料相同,十分方便), xytext 指的是這個標註文字的座標, arrowprops 則是如果我們要新增箭頭標記時,箭頭標記的屬性(像是顏色之類的)。

舉個例子吧!假如我們寫了如下的程式碼:

df1 = position_data[position_data['POSITION']=='Center']
df2 = position_data[position_data['POSITION']=='Forward']
df3 = position_data[position_data['POSITION']=='Guard']
plt.scatter("HEIGHT", "WEIGHT", data=df1, marker='x', alpha = 0.2)
plt.scatter("HEIGHT", "WEIGHT", data=df2, marker='*', alpha = 0.2)
plt.scatter("HEIGHT", "WEIGHT", data=df3, alpha = 0.2)
plt.annotate('Guard', xy=(73, 200), xytext=(73, 225), arrowprops = {'color':'green'})
plt.annotate('Center', xy=(85, 275), xytext=(85, 300), arrowprops = {'color':'blue'})
plt.annotate('Forward', xy=(80, 255), xytext=(80, 280), arrowprops = {'color':'orange'})
plt.title('NBA position')
plt.xlabel('Height(")')
plt.ylabel('Weight(lb)')
plt.show()

我們透過 plt.annotate() 來設定文字以及箭頭的位置和屬性,可以得到如下的圖形:

如果我們沒有另外設定箭頭的屬性 arrowprop 和文字坐標 xytext ,我們的文字內容就會隨著 xy 座標的位置而定。

直方圖 Histogram

再來是直方圖,也就是 histogram。直方圖到底是用來做什麼的呢?一般來說,直方圖多被用來表達一組連續數值資料的分佈。

要繪製直方圖也十分簡單,使用的是 plt.hist() ,官方文件的用法如下:

plt.hist(x, bins=None, range=None, density=None, cumulative=False, histtype='bar', align='mid', orientation='vertical', rwidth=None, color=None, label=None, stacked=False)

bin 指的是每一個直方的區間, range 是範圍,等等參數都可以從字面上解讀其意思。稍微講解幾個比較值得一提的參數,首先 density 指的是這張圖將會以密度為顯示, cumulative 指的則是會以累積函數的方式呈現此張直方圖, stacked 則是會讓兩個不同的直方圖堆疊起來。

先講個簡單的例子吧!假如我們想要查看聯盟當中所有球員的身高分佈,可能會寫下如下的程式碼:

plt.hist(position_data.HEIGHT, density=False, color = 'lightblue', cumulative = False, label = "Height")
plt.legend()
plt.xlabel('Height(")')
plt.show()

然後得到如下的圖形:

那麼如果我們想要將中鋒 Center、前鋒 Forward、後衛 Guard 三種不同的位置區分開來,就可以使用 stacked ,程式碼如下:

df1 = position_data[position_data['POSITION']=='Center']
df2 = position_data[position_data['POSITION']=='Forward']
df3 = position_data[position_data['POSITION']=='Guard']
plt.hist([df1.HEIGHT, df2.HEIGHT, df3.HEIGHT], label = ['Center', 'Forward', 'Guard'], stacked=True)
plt.legend()
plt.show()

如此一來是不是更能區分三種不同位置球員的身高分佈,又能夠縱觀整個聯盟的身高分佈呢!

長條圖 Bar Chart

接著要跟大家分享的是長條圖,也是大家十分常見的視覺效果。通常是用來表達一組數量資料,比較類別之間的差異。再提醒大家一次長條圖和直方圖的不同,直方圖通常用在連續的數值資料,用來表達資料的分佈情況,而長條圖則是表達類別的差異,因此交換順序也不影響這個視覺效果。

那要怎麼繪製一張長條圖呢?舉個簡單的例子,因為我們今天擁有聯盟裡所有球員的位置資料,那我們如果想要知道每個位置的球員數量分布,就可以使用長條圖來表達,寫法如下:

plt.bar(position_data.POSITION.unique(),
position_data.POSITION.value_counts(),
width=0.5,
bottom=None,
align='center',
color=['lightsteelblue',
'cornflowerblue',
'royalblue',
'midnightblue',
'navy',
'darkblue',
'mediumblue'])
plt.xticks(rotation='vertical')
plt.show()

中間的 color 用來設定每一條長條的顏色,可供選擇的顏色表等等將會提供給大家。除此之外,在這邊用到的 pandas.Series.unique()pandas.Series.value_counts()pandas 當中的方法,分別是用來計算一個欄位曾經出現過哪些數值,以及去數這些數值所出現過的次數,在這個例子當中作為 x 軸以及 y 軸的資料使用,可以得到如下的視覺效果:

由圖可見,專精一個位置的球員數量較多,尤其又以前鋒和中鋒的數量最多。

plt.bar() 的詳細用法則如下:

plt.bar(x, height, width=0.8, bottom=None, align='center', data=None, color, orientation, tick_label)

x 指的是 x 軸的資料,在這裡我們設定成球員的位置; height 則是長條的高度,設定為一個位置的球員數量,其他參數大多是用來調整對齊位置、顏色、方向等,用來更好的表達此一資料的方式。

但在長條圖的表達當中,有時我們會想要知道有哪些類別的數量有超過一定的標準。舉剛剛的例子來說,我們可能會想知道打哪些位置的球員數量會超過 100 個人,那這時候可能就會想要利用畫一條標示 100 人橫線的方式來標示位置,對吧?

這時候要使用的就是 plt.axhline() 。(如果要畫垂直的線段,請使用 plt.avhline() )用法如下:

plt.bar(position_data.POSITION.unique(),
position_data.POSITION.value_counts(),
width=0.5,
bottom=None,
align='center',
color=['lightsteelblue',
'cornflowerblue',
'royalblue',
'midnightblue',
'navy',
'darkblue',
'mediumblue'],
# orientation='horizontal'
)
plt.xticks(rotation='vertical')
plt.axhline(y=100, c="r", ls="--", lw=2)
plt.show()

得到的結果如圖:

官方給定的用法如下, y 代表的是要標線的 y 軸位置,其他參數則是設定線段的樣式等等:

plt.axhline(y, c, ls, lw)

如果將這個方法用在折線圖,還可以使用 plt.fill() 或是 plt.fill_between() 來完成如下的塗色效果。

盒狀圖 Box Plot

最後一個要介紹的是盒狀圖,也是十分常見的一種視覺效果。通常用來表達一組數據資料裡面,不同類別資料之間的分布差異。

在介紹怎麼繪製以前,先來理解盒狀圖的長相意義,可以看這個簡介:

接著,我們延續剛才的幾個例子,來討論各個球員位置的體重分布是不是有所不同。

於是我們利用 plt.boxplot() 來繪製這一張盒狀圖,簡單起見,我們先寫下如下的程式碼:

plt.boxplot([position_data[position_data['POSITION']=='Center'].WEIGHT, position_data[position_data['POSITION']=='Forward'].WEIGHT, position_data[position_data['POSITION']=='Guard'].WEIGHT], 
labels = ['Center', 'Forward', 'Guard'])
plt.ylabel('Weight(lb)')
plt.show()

這時候我們就可以得到三個盒狀圖,可以比較他們之間的體重分布差異了。

此時可能會產生一個疑惑,既然盒狀圖的介紹說,上下的橫線代表的是最大以及最小值,那麼再往外延伸的這些圓點又是什麼呢?在 matplotlib 當中叫做 fliers,其實就是超過預設範圍(大於第三四分位數+1.5四分位距,或是小於第一四分位數−1.5四分位距)的那些資料點,也可以稱作為 outlier。

那麼這個 plt.boxplot() 具體來說要怎麼用呢?官方文件是這麼寫的:

plot.boxplot(x, notch=None, sym=None, vert=None, whis=None, positions=None, widths=None, usermedians=None, conf_intervals=None, labels=None, meanline=None, [other properties of box, fliers, ..etc])

x 指的就是要畫成盒狀圖的資料,在這裡我們給的是一個由三個 pandas.Series 所組成的 list,如此一來就可以在一張圖中繪製三個盒狀圖; notch 指的是是否要繪製成 notched box plot,是一種可以表達信賴區間以及中位數的盒狀圖; sym 指的是 fliers、也可以叫做 outliers 要使用的 marker 樣式。其他還有很多可以調整的參數,通常會依據我們的使用情況來調整這些內容。

可以設定哪些視覺效果的樣式?

可能看來看去會覺得,會特別想要去調整的果然還是這些視覺效果的樣式,在這裡就提供一些 matplotlib 內建可供選擇的樣式給大家參考。

資料點格式 marker

像是折線圖的折點、散布圖資料點、或是盒狀圖的離群值,都可以去調整這些 marker 的樣式。

提供的顏色選項 color

matplotlib 提供的顏色選擇其實很多,包含了基本顏色:

CSS 支援的顏色:

視覺化軟體 Tableau 的顏色:

還有 XKCD 的顏色可供選擇,都只需要在設定顏色時,以字串的形式給定顏色參數即可:舉 CSS 色票的例子,只要寫color='steelblue' 即可。

如果不想要一個一個設定,也可以透過設定 cmap (colormap) 的方式來設定顏色組合,可以參考官方文件:

可供選擇的模板樣式style

最後不得不提的,就是 matplotlib 內建的模板 style 了。有些人可能學習過 R,知道 R 最常見的視覺化套件 ggplot 的風格,或是有些人可能知道 python 的另一個視覺化套件 seaborn 畫出來的圖特別精美,都可以在 matplotlib 當中進行設定,讓我們畫出來的圖自帶這些樣式。

Reference

--

--