作者:蟄蟲適航
來源:蟄蟲適航
簡介Matplotlib可以說是Python最聲名遠揚的可視化庫了,也是Python數據分析庫的「三駕馬車」之一。Matplotlib是基礎而非常強大的可視化庫,Seaborn等好用的可視化庫是在前者的基礎上進行的封裝。Matplotlib擅長快速出簡單的圖、有豐富的接口進行精細化繪圖、和Numpy結合做科學可視化及三維圖配合默契、三維圖。但也有些缺點,如不容易基於實用目的繪製有一定難度的圖表(如小提琴圖等)、標籤等元素需指定坐標而不能自適應優化顯示、難以實現交互。
官網[1]說:
Matplotlib tries to make easy things easy and hard things possible.
越用越認可這句話,Matplotlib非常強大,Hard things是possible但並非easy and fast。
可視化基礎框架對於一個數據表df(通過pandas讀入為DataFrame)來說,用Matplotlib對其進行可視化的基礎框架為:
fig, ax = plt.subplots()ax.plot(df['x'],df['y'])通過上面幾行代碼就可以畫出df表周一到周五y指標的變化折線。
折線圖繪製示例
Matplotlib其實為作圖提供了兩套可視化接口,分別為:
•plt.plot()系列•fig, ax = plt.subplots(); ax.plot() 系列
根據官網教程,分別對應MATLAB的陳述式語法和面向對象寫法,具體可參考tutorials:lifecycle.html[2] ,
個人理解,plt.plot()適合用於快速出圖,讀入一個數據表後想快速知道數據分布、指標關係等,通過plt.plot()系列語句直接出圖,而ax.plot()更方便用來精細繪圖,接口對各種圖表元素的編輯很友好。
在Matplotlib官網搜索,通常能看到兩套接口,如搜繪製餅圖的關鍵詞pie,結果中的axes.Axes.pie對應ax.pie()的用法,pyplot.pie對應plt.pie()的函數接口。
兩套接口
ax.×××()的寫法看起來要寫的語句多些,但這種面向對象(object-oriented)的寫法通過fig, ax = plt.subplots()建立畫布(figure)和定義軸域(axes),能更明確在哪作畫和映射規則,給用戶更大的自由度和更精細的調參能力。Axes包含了一套坐標軸(axis),確定了x/y坐標軸之後,數值再確定對應坐標,也就唯一確定了所在位置(這是二維情況下,更高維度就會對應著更多的axis),散點圖是去確定點在軸域下的位置,柱狀圖是確定每個柱柱所在的位置,因此一套Axes就確定了唯一的獨立的圖,一個畫布可以有多套Axes。更具體的辨析可讀姚太多啊的一篇文章[3],簡單說就是ax.×××()更方便調細節,初學者儘量避免用plt.×××系列來畫圖。
基礎圖表繪製數據可視化從目的來說,是為了更直觀展示數據或數據之間的對比、分布或關聯關係。散點圖、折線圖、柱狀圖、條形圖、餅圖、直方圖是非常常用而基礎的可視化圖。個人認為通過畫這幾種基礎圖並調細節是很好的學可視化實踐。
將數據映射為可視圖表
為了整體的美觀和一致性,本文都用了一套自定義配色,通過mpl.rcParams["axes.prop_cycle"] = mpl.cycler('color', ['1EAFAE', 'A3FFFF', '69FFFF']) 語句實現簡單改配色,具體關於mpl.rcParams後面再展開。
畫散點圖可以用兩種主要的方法,scatter(x,y)和plot(x,y,'o') 。通過ax.scatter(x,y)繪製以x為橫坐標,y為縱坐標的散點圖,scatter的重要參數如下:
•x,y:對應著x軸和y軸的數據,散點畫在坐標軸裡的[xi,yi]處。•s,c,alpha: 對應散點大小(size)、顏色(color)、透明度,都可以傳一個和點數量相同長度的數組,如s=df['z']可以做氣泡圖,一般氣泡圖為了防止遮蓋問題,通常設置一定的透明度,alpha的範圍為0到1。c='#BA5C25'設置點顏色,c賦值為一個數組可以做出每個點一個顏色的效果。•marker:設置點的形狀;•cmap:顏色映射;•norm:當顏色c為一組浮點數時,把值標準化到[0,1]做顏色映射,vmin和vamx參數是結合 norm 來用的;
散點圖參數示例
ax.plot(x,y,'o')也可以畫散點圖,ax.plot()核心是繪製坐標系下的點和點之間的連線的,當突出點的大小而省略線時,就是散點圖了,同樣突出線就變成了折線圖。通過fmt(也就是format_string)參數來控制這些,包括點的形狀、顏色、線的風格顏色等。折線圖基礎繪製效果可回看上一部分可視化基礎框架。
plot()的常用參數如下:
•x,y: x軸和y軸的數據,當plot()只有一個輸入列表或數組時,參數被當做y軸,也就是value,x軸以索引自動生成,也就是ax.plot(y)相當於ax.plot(range(len(y)),y);•fmt: 控制x,y繪製的折線的點形狀、顏色、線的風格、顏色,fmt參數可分類為三種:顏色字符、風格字符和標記字符[4];•其他的lines.Line2D支持的屬性, 如color控制線顏色,marker控制點形狀,linestyle控制線風格類型及linewidth控制線寬等,如果既設置了fmt又指定了color呢?可以實踐一下,線的顏色會根據color屬性最終顯示。
常用fmt字符意義整理
plot()除了plot(x,y,[fmt])這種寫法之外,還可以傳多套x,y以繪製多條折線,寫法是plot(x,y,[fmt],x2,y2,[fmt2],…)。
另外plot()還支持plot('col1','col2',data=df)這種寫法,這是對二維表格數據更友好的接口。本文講到的其他圖形如bar、barh等基本也都是支持ax.×××(df['x'],df['y']) 和ax.×××(x,y,data)寫法的。
plot() 3種寫法及結果圖
通過ax.bar(x,height)繪製柱狀圖,條形圖的繪製用ax.barh(y,width),因bar和barh的用法很類似,參數之間有對應關係,這裡結合著看。
柱狀圖繪製及參數理解
•x,height: x軸的值和各柱的高,相當於折線圖的x,y;•width: 柱的寬度,默認是0.8,也可以傳入一個數組,畫不等寬的柱狀圖;•bottom: 每個柱底部開始位置,默認是0,改bottom可以畫堆積柱狀圖、瀑布圖等;•align: 柱狀的x是在柱底部中心還是邊緣,{'center', 'edge'},默認是center;•data: 可以傳入一個DataFrame,用法和前面說到的ax.plot('col1','col2',data=df)一致;•其他像color(柱顏色)、edgecolor(柱邊框色)、linewidth(邊框線寬)等圖元屬性用法都一致,linewidth也是可以簡寫為lw的,顏色可以傳一個數組,可以畫出五彩斑斕的柱,也可藉由這個參數美化瀑布圖;•條形圖barh的參數有barh(y,width,height,left,align),y是Y軸的值,每個柱的位置,因此barh的y對應bar的x,barh的width對應bar的height,barh的height對應bar的width。每個柱開始的位置是left,對應bar的bottom。align、data、color等一致。
注意的是柱狀圖繪製語句ax.bar(x,height)的返回值是一個容器(BarContainer),包含了所有畫出來的柱。通過這個返回值可以對柱進行一些個性化的處理,另外的應用就是根據返回柱的屬性給每個柱標上文本標籤。
fig,ax= plt.subplots()rects=ax.bar(df['x'],df['y'])ax.set_ylim(0,100)for rect in rects: height = rect.get_height() ax.annotate('{}'.format(height), xy=(rect.get_x() + rect.get_width() / 2, height), xytext=(0,1), textcoords="offset points", ha='center', va='bottom')通過給x以一定的偏移量,繪製簇狀柱形圖。代碼如下:
x = list(range(1,len(df)+1))
width = 0.2 x1=[i-width for i in x]x2=[i+width for i in x]fig, ax = plt.subplots(figsize=(6,5))rects1 = ax.bar(x1,df['y'], width*2, label='Men',color='#1EAFAE')rects2 = ax.bar(x2,df['z'], width*2, label='Women',color='#69FFFF')
ax.set_xticks(x)ax.set_xticklabels(df['x'])ax.legend()
簇狀柱形圖
通過給bottom參數傳一個數組,可以畫堆疊柱狀圖:堆疊柱除了等值堆疊之外,還可以等比堆疊,思路就是將每個x對應的柱都做一下數值變換,把柱的高度約束在[0,1],且堆疊之和為1,height=h[i]/sum(h)。
#堆疊柱狀圖fig,ax= plt.subplots()ax.bar(df['x'],df['y'],label='Men')ax.bar(df['x'],df['z'],bottom=df['y'],label='Women')ax.legend()
#等比例堆疊柱fig,ax= plt.subplots()df['y1']=df.apply(lambda x:(x['y'])*100/(x['y']+x['z']),axis=1)df['z1']=df.apply(lambda x:(x['z'])*100/(x['y']+x['z']),axis=1)ax.bar(df['x'],df['y1'])ax.bar(df['x'],df['z1'],bottom=df['y1'])ax.set_ylim(0,100) #標籤設置等代碼省略堆疊柱狀圖繪製
調節width參數使得柱和柱之間的寬度為0,並對數據進行統計在畫圖,可以用ax.bar()繪製直方圖,但也不需要這麼複雜,Matplotlib提供了繪製直方圖的接口ax.hist(x,bins,normed),可以直接對某列數據繪製直方圖。x是需要統計分布的數據列,bins控制分箱的個數,默認是10。
箱線圖在數據分析中挺常用的,箱線圖對於數據分布有很好的展示作用,Matplotlib提供了boxplot(x)用於繪製箱線圖。
fig, ax= plt.subplots()ax.boxplot(df['y']) #箱線圖用同一列數據繪製的直方圖與箱線圖
餅圖是可視化中基礎而重要的圖形,是各種數據報告的常客,Matplotlib繪製餅圖時因為xy軸默認比例尺不同,為了得到不扁的餅,需設置xy軸1像素對應的值相等。
fig,ax=plt.subplots(subplot_kw=dict(aspect="equal")) ax.pie(df['y'])
fig, ax = plt.subplots(subplot_kw=dict(aspect="equal")) wps=dict(width=0.3, edgecolor='w')ax.pie(df['y'], radius=1,startangle=90,counterclock=False,wedgeprops=wps)ax.pie(df['z'], radius=1-0.3,startangle=90,counterclock=False,wedgeprops=wps)餅圖與圓環圖
圖表元素調校一張可視化圖上除了主要的點、線、面之外,文本標籤、坐標軸標籤等也是很重要的可視媒介,特別是對於信息圖表而言。下面這張圖[5]基本囊括了用到的圖形元素:
Figure圖元的直觀展示
加文本可以通過 ax.text(x,y, "Text") 。添加標題通常寫ax.set_title(),另外也可以用ax.title.set_text('title')或ax.set(title='ttl')設置標題, 整理如下:
常用圖表標籤添加語句
•ax.text(x,y, "Text"): 在坐標[x,y]處添加文本Text,文本支持latex公式,如ax.text(2,6, r'$E=mc^2$', fontsize=15);•ax.set_title(): 添加標題;•ax.set_ylim(0,4) : 設置y軸值的範圍(類似於函數的值域),例如對於y=[],直接ax.plot(y)畫出來的折線圖y軸範圍是 ,通過ax.set_ylim(0,4) 可以顯示0~100範圍的效果。同理通過ax.set_ylim(0,4)設置x軸範圍(定義域);•ax.set_ylabel("Y axis label"): 給y軸加上坐標軸標題;•ax.tick_params(which='major', width=1.0): 細調坐標軸刻度;•ax.legend() : 設置圖例 ;
圖形元素設置除了文本類型之外,也可以往裡加形狀 。
•加線: import matplotlib.lines as lines;ax.add_artist(lines.Line2D([15,15], [0, 10],color='#1EAFAE')),其效果可參考矩陣圖繪製效果;•加帶箭頭的線: ax.arrow(0, 0, 0.5, 0.5, head_width=0.05, head_length=0.1, fc='k', ec='k');•加一個垂直的平均線: ax.axvline(x, ls='--', color='r'),那水平平均線呢?axhline(y=0, xmin=0, xmax=1, **kwargs);•加垂直或水平的強調矩形: ax.axhspan(ymin, ymax, xmin=0, xmax=1, **kwargs) 和 ax.axvspan(xmin, xmax, ymin=0, ymax=1, **kwargs);•加矩形: patches.append(mpatches.Rectangle([0.5, 0.5], 0.5,0.8)),可用來畫甘特圖;•加圓形(及橢圓): patches.append( mpatches.Ellipse((x,y), width, height));•加帶箭頭的形狀: ax.annotate('箭頭文本', xy=(4,5), xytext=(3,2),color, arrowprops=dict( arrowstyle='->', connectionstyle="arc3")),加圖標型的箭頭:patches.append( mpatches.Arrow(x,y, dx,dy,width));•加圖片: mpimg.imread(ipath); ax.axis('off'); ax.imshow(img);
給散點圖加標籤並加分隔線來繪製矩陣圖,以實踐一下以上方法:import matplotlib.lines as linesfig, ax= plt.subplots()ax.plot(df['z'],df['y'],'o')ax.add_artist(lines.Line2D([70,70], [30,100],color='#000000',lw=3)) #是[x1,x2],[y1,y2] 不是[x1,y1],[x2,y2]ax.add_artist(lines.Line2D([30,100], [65,65],color='#000000',lw=3))ax.set_xlim(30,100)ax.set_ylim(30,100)ax.set_xlabel("z")ax.set_ylabel("y")矩陣圖繪製示例
繪製瀑布圖綜合運用ax.bar()的參數和文本標籤,並封裝為一個函數,以後使用只需要調用就好:
x=[17,-3,7,6]
def waterfall_chart(x): j=0 k=0 x0=[] for i in x: if i<0: x0.append(j+i) else: x0.append(j) j+=i x1=[abs(i) for i in x] c1=['#1EAFAE' if i>0 else '#69FFFF' for i in x] c1.append('#BA5C25') x0[0]=0 x0.append(0) x1.append(j) x.append(j) w=list(range(1,len(x)+1)) fig,ax= plt.subplots(figsize=(6,5)) ax.bar(w,x0,alpha=0) rects=ax.bar(w,x1,bottom=x0,color=c1) ax.set_ylim(0, 30) i=0 for rect in rects: height = rect.get_height()+x0[i] ax.annotate('{}'.format(x[i]), xy=(rect.get_x() + rect.get_width() / 2, height), xytext=(0,1), textcoords="offset points", ha='center', va='bottom') i+=1 ax.set_xticklabels(['','Q1','Q2','Q3','Q4','Ys']) return axwaterfall_chart(x)瀑布圖繪製效果
組合圖為了更好地展現數據間的聯繫或變化,我們有時會需要將多種圖表類型用在同一張可視化圖裡,一種是共用坐標軸的組合圖,例如面積圖+柱狀圖的組合、散點+折線圖就是很基礎的組合圖。另一種是雙坐標軸,很常見的圖是左邊的y軸是月活,畫柱狀圖,右邊的y軸是增長率,畫折線圖。
共用坐標軸組合圖兩例子
棒棒糖圖(Lollipop)是將條形圖的柱變得很細並突出末端的一類圖,形似棒棒糖,特別適合於展示分類標籤很多的數據。可以通過將柱狀圖和散點圖結合的方法繪製,Matplotlib庫繪製起來並不複雜,代碼如下。但對於一些散點圖的y軸不支持分類標籤的庫來說,要畫棒棒糖圖還是挺複雜的。
y = [5, 4, 11, 10, 15, 11, 13, 8,13,15,13,19]x=['c'+str(i) for i in range(len(y))]
fig, ax= plt.subplots(figsize=(6,6)) ax.barh(x,y,height=0.08,zorder=1) #圖層順序的解決方案ax.scatter(y,x,zorder=2,color='#ba5c25')有時為了對比兩類數據,除了用簇狀柱形圖或簇狀條形圖外,也可以試試啞鈴圖,理解了上面畫棒棒糖圖的方法之後,要組合出啞鈴圖並不難,對數據進行一定運算後用barh加兩個scatter就可以畫出來。
棒棒糖圖與啞鈴圖
帕累託圖是雙坐標軸的可視化典例。帕累託圖特別適合展示符合長尾效應的數據。Matplotlib給我們提供了ax.twinx()用於生成共用x軸的另一個Axes,效果就是左邊的y軸比例尺和右邊比例尺不一定一樣,能更好地將兩類圖進行效果組合。
y=[23,162,51,119,12,3,8] x=[str(i) for i in range(2,len(y)+2)]y=sorted(y,reverse=True)ysum=sum(y)y2=[]cc=0for i in y: cc+=i y2.append(cc/ysum*100)fig = plt.figure()ax1 = fig.add_subplot(111)rects=ax1.bar(x,y,color='#1EAFAE')ax1.set_ylabel('Month IC')ax1.set_ylim(0, 180)ax2 = ax1.twinx() ax2.set_ylim(0, 100)
ax2.plot(x, y2,'o',color='#FFA069',linewidth=2,ls='-')ax2.set_ylabel('%')
for rect in rects: height = rect.get_height() ax1.annotate('{}'.format(height), xy=(rect.get_x() + rect.get_width() / 2, height), xytext=(0,1), textcoords="offset points", ha='center', va='bottom')ax1.set_title("Pareto in Matplotlib")帕累託圖繪製效果
子圖除了組合圖外,有時候我們也需要將多個圖並排以展現某種數據關係。前面說過一個畫布下可以有多套Axes,正常情況下我們只需要一套Axes用來畫圖,但是也經常需要在一個畫布中畫多張圖,形成分面或子母圖的效果,前面我們基本都是寫fig, ax= plt.subplots(),實際上subplots()可以設置nrows、ncols參數生成多套Axes。
plt.subplots()的常用寫法有:
•plt.subplot(3,2,4):在全局繪圖區域中建立3行、2列的分區繪圖區域,並定位到第4個子圖區域,返回一個axes;•plt.subplot(324): 效果和上面subplot(3,2,4)的寫法一致;•plt.subplots(): 默認1行1列,生成的axes就是一個;•plt.subplots(3,2) : 沒有指定繪圖編號,返回值包括一個figure和多個axes,和ax[0, 0].×××(x, y)搭配著用;•fig = plt.figure(); ax= fig.add_subplot(221):先建立一個畫布,在畫布上添加2x2個子圖,並定位到順序第一個子圖;
生成多個繪圖區域
圖中代碼在全局繪圖區域中建立n行、m列的分區繪圖區域,並定位到其中一個子圖區域。之後ax的用法和前面一致,不贅述。
生成的圖片在shell環境中彈出的界面有保存圖片的按鍵,在jupyter環境中可以點擊圖片然後右鍵保存。而如果要通過代碼保存圖片到本地,一般通過plt.savefig(fname,dpi=300)保存圖片,參數有文件保存路徑(fname)、圖片每英寸像素(dpi)、邊緣顏色(edgecolor)等。在shell環境中一般通過plt.show()展示圖片,而jupyter notebook中通常寫%matplotlib inline將圖片直接在Out[]裡輸出展示。
inline vs show
Matplotlib的rcParams接口可以設置很多個性化內容,包括剛提到的savefig的edgecolor默認值,可以寫mpl.rcParams["savefig.edgecolor"]='blue'改變原來的默認值white。直方圖的默認分箱數可以通過rcParams["hist.bins"]=5改變。
而為了在Matplotlib中支持中文,各教程的解決方案基本都有mpl.rcParams['font.family']='SimHei'這句,就是將Matplotlib的字體替換為微軟雅黑。前面基礎圖表繪製部分通過更新mpl.rcParams["axes.prop_cycle"]改變了繪圖的主題色,Matplotlib本身是提供了備選的繪圖渲染的各種主題,可以通過style.use('ggplot')調用ggplot主題(想換回默認主題用style.use('default'))。就像給輸入法換皮膚一樣,rcParams接口給了我們更多的自由度和個性化。
三維及科學可視化三維可視化和科學可視化是Matplotlib特別擅長的領域,人類作為三維生物,對三維的圖像有一定的偏好,扁平化和三維各有優勢,各有不同的應用場合,能畫好二維可視化圖也該會畫三維的圖表,且一些場景用好三維有奇效。Matplotlib的三維可視化封裝在mpl_toolkits工具套件的mplot3d裡,mplot3d下的API主要包括Axes3D(三維坐標軸區域)、art3d.xx3D(三維圖元)和proj3d(三維坐標變換)。
from mpl_toolkits.mplot3d import Axes3D fig = plt.figure()ax = fig.add_subplot(111, projection='3d')ax.bar(df['x'],df['y'], zs=0, zdir='y', alpha=0.8)ax.bar(df['x'],df['z'], zs=1, zdir='y', alpha=0.8)ax.bar(df['x'],df['y'], zs=2, zdir='y', alpha=0.8)ax.set_xlabel('X')ax.set_ylabel('Y')ax.set_zlabel('Z')
ax.set_yticks([0,1,2])繪製三維下的柱圖
三維可視化和科學可視化聯繫很緊密,科研作圖中應用廣泛,各種漂亮的參數曲面在官網示例裡有很多,這裡略過、當然二維下也能畫出很優美的函數圖像,結合numpy生成[0,2]之間的正弦函數曲線僅需4行代碼:
t = np.arange(0.0, 2.0, 0.01)s = 1 + np.sin(2 * np.pi * t)fig, ax = plt.subplots()ax.plot(t, s)科學可視化之正弦函數圖像
繪製指數函數、分形的雪花曲線也是類似的過程,在官網案例集有類似的例子,具體這裡不展開。
總結下本文從Matplotlib的可視化基礎框架一步步畫散點、折線、柱狀、箱線等圖,通過理解參數拓展畫了瀑布圖、矩陣圖、棒棒糖圖等,並且微調坐標軸文本、標題等圖形元素,讓可視化更完備,通過雙y軸繪製帕累託圖等組合圖,也繪製了包含多張子圖的圖和三維圖。
通過以上實踐可以看到的Matplotlib可視化語法的特點是繪圖對象和標籤標題等元素有一定獨立性,且有不同層級的接口可以用來微調元素,例如設置標題就有多種寫法 ,Matplotlib不同於ggplot2的管道寫法、也不同於Altair等庫將數據傳到chart對象再調用mark_bar()等確定繪製什麼圖。可視化是要好看,也不能忘了所展現的數據與數據間的關係是重點。
本文思維導圖
最後在極坐標下繪製一個心形線結束本文。
點擊 閱讀原文 可直達文中繪圖代碼的jupyter notebook文檔。有任何建議歡迎留言交流。
import numpy as nptheta= np.arange(0, 2*np.pi, 0.05)r=5*(1-np.sin(theta)) ax = plt.subplot(111, projection='polar')ax.plot(theta, r,lw=3)ax.grid(True)ax.text(1.56,6,'r=a(1-sinθ)',ha='center', fontsize=18,color='#1EAFAE')「極坐標繪心形線」
References[1] 官網,豐富而全面的參考: https://matplotlib.org/
[2] 兩種寫法及繪圖生命周期:https://matplotlib.org/tutorials/introductory/lifecycle.html#sphx-glr-tutorials-introductory-lifecycle-py
[3] matplotlib:先搞明白plt. /ax./ fig再畫:https://zhuanlan.zhihu.com/p/93423829
[4] 嵩天老師MOOC課程:https://www.icourse163.org/course/0809BIT021B-1001870002
[5] Figure上的圖元:https://matplotlib.org/tutorials/introductory/usage.html
數據森麟公眾號的交流群已經建立,許多小夥伴已經加入其中,感謝大家的支持。大家可以在群裡交流關於數據分析&數據挖掘的相關內容,還沒有加入的小夥伴可以掃描下方管理員二維碼,進群前一定要關注公眾號奧,關注後讓管理員幫忙拉進群,期待大家的加入。
管理員二維碼: