有趣的圖形:用Python繪製帶餅圖的散點圖兼論marker的隱藏功能
01 帶餅圖的散點圖
有這樣一個例子:假設有五個人,每個人的月均收入水平為a=[1,3,2,4,3],消費水平b=[2,1,3,3,5],數據單位均有千元。同時五個人消費水平中,按照每月衣食住行的消費比例為:
s1=[0.1,0.2,0.3,0.4]
s2=[0.35,0.35,0.2,0.1]
s3=[0.2,0.25,0.25,0.3]
s4=[0.5,0.1,0.15,0.25]
s5=[0.0,0.25,0.4,0.35]
依據上述數據(數據純屬虛構),我可以將這些數據畫在一個圖形里,如圖:
(其中,藍色:衣;黃色:食;紅色:住;綠色:行)
這個圖形就是帶餅圖的散點圖,從圖中不僅可以看出五個人的消費與收入的關係趨勢,也可以看出每個人的衣食住行消費比例不同。
是不是很有趣的一個圖形?它是怎麼做出來的?
02 matplotlib中marker參數的一個隱藏功能
上圖是用python中matplotlib包繪製的,而繪製成帶餅圖的散點圖則是用了裡邊關鍵的marker參數,所以在介紹如何繪製此圖之前,先說說marker參數的一個隱藏功能。
一般的我們繪製散點圖基本的命令為:
import matplotlib.pyplot as pltnplt.scatter(x, y, s=20, c=None, marker=o)n
其中,s是點的大小,c是顏色,marker就是指定點標記的形狀,在這裡用的就是小圓點o;我們還可以用「*」、「x」、「Δ」等等,甚至還有數字、字母所代表的形狀。
事實上,不僅僅如此,在marker的help文檔中還指出了一個我們不經常用的標記方式,那就是元組——(numsides,
style, angle),numsides是邊的個數,angle是旋轉角度,style只有0,1,2,3四個值,舉個例子。我設置marker=(9,0, 30),就出來個九邊形的散點圖,如下:
這種定義方式大大拓展了散點形狀的自定義方式,本文所做的餅圖也源於此。
03 單個帶餅圖的散點圖繪製過程
但是,完全繪製成上述那個圖形也並非那麼容易,下面我們從一個帶餅圖的散點繪製講起。
比如上面的a=1,b=2那個點的消費比例為:s1=[0.1,0.2,0.3,0.4],代碼如下:
x = [0] +np.cos(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist()#[0]表示x的初始值ny = [0] +np.sin(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist() #用tolist()形成數列nxy1 = list(zip(x,y))nnx = [0] +np.cos(np.linspace(2 * np.pi * 0.1, 2 * np.pi * 0.3, 5)).tolist()ny = [0] +np.sin(np.linspace(2 * np.pi * 0.1, 2 * np.pi * 0.3, 5)).tolist()nxy2 = list(zip(x,y))nnx = [0] +np.cos(np.linspace(2 * np.pi * 0.3, 2 * np.pi* 0.6, 5)).tolist()ny = [0] +np.sin(np.linspace(2 * np.pi * 0.3, 2 * np.pi* 0.6, 5)).tolist()nxy3 = list(zip(x,y))nnx = [0] +np.cos(np.linspace(2 * np.pi * 0.6, 2 * np.pi*1, 5)).tolist()ny = [0] +np.sin(np.linspace(2 * np.pi * 0.6, 2 * np.pi*1, 5)).tolist()nxy4 = list(zip(x,y))nfig, ax =plt.subplots()nax.scatter(a[0],b[0], marker=(xy1),s=500,facecolor=blue)nax.scatter(a[0],b[0], marker=(xy2),s=500,facecolor=y)nax.scatter(a[0],b[0], marker=(xy3),s=500,facecolor=red)nax.scatter(a[0],b[0], marker=(xy4),s=500,facecolor=green) #為了餅圖看得清,散點的size要大一些nnplt.show()n
得到結果就是:
首先講講x、y變數的生成,其原理是先根據每個佔比數值所形成的角度(乘以2π,如2 * np.pi * 0.1),然後再用np.linspace函數五等分形成6個角度值,每個值賦予cos、sin函數,這是因為cos2θ+sin2θ=1,所有經過cos、sin函數的值會自動形成一個圓形。
值得注意的是,因為四個佔比要圍成一個圓形,所以除了第一個佔比外,後邊的都要用累計佔比,如第二個0.2的佔比np.linspace(2 * np.pi * 0.1, 2 * np.pi
* 0.3, 5),第三個0.3的就是0.3-0.6之間,第四個是0.6-1之間。如果還沒明白那就單獨把一個x、y生成的變數,單獨作圖可以看一下:
x = [0] +np.cos(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist()ny = [0] +np.sin(np.linspace(0, 2 * np.pi * 0.1, 5)).tolist()nplt.plot(x,y,c=b)nplt.show()n
可以看出,第一個佔比x、y的軌跡就是一個弧形,然後再用初始值(0,0)牽引著,這樣再對這個軌跡進行填充時,就會形成一個扇形,如下:
plt.scatter(a[0],b[0], marker=(xy1),ns=500,facecolor=blue)nplt.show()n
這樣一個x、y所形成的marker=(xy1)標記,再用facecolor=blue填充就會形成一個扇形散點標記。最後用fig, ax =
plt.subplots()把xy2、xy3、xy4所有子圖都放上去,就形成一個圓形,然後用不同顏色填充,就形成一個餅圖,每個餅圖的角度大小由其佔比比例決定。04 所有點的餅圖-散點圖
上邊是一個點的餅圖-散點圖,若是將a=[1,3,2,4,3],b=[2,1,3,3,5],以及b的所有個體衣食住行的消費比例全放進去,那就需要用到while、for循環條件,代碼如下:
#5個消費水平下衣食住行的佔比ns1=[0.1,0.2,0.3,0.4]nns2=[0.35,0.35,0.2,0.1]ns3=[0.2,0.25,0.25,0.3]ns4=[0.5,0.1,0.15,0.25]ns5=[0.0,0.25,0.4,0.35]n#計算累計佔比nss1=[s1[0],sum(s1[0:2]),sum(s1[0:3]),sum(s1[0:4])]nss2=[s2[0],sum(s2[0:2]),sum(s2[0:3]),sum(s2[0:4])]nss3=[s3[0],sum(s3[0:2]),sum(s3[0:3]),sum(s3[0:4])]nss4=[s4[0],sum(s4[0:2]),sum(s4[0:3]),sum(s4[0:4])]nss5=[s5[0],sum(s5[0:2]),sum(s5[0:3]),sum(s5[0:4])]nns=[ss1,ss2,ss3,ss4,ss5]na=[1,3,2,4,3] #收入水平(千元)nb=[2,1,3,3,5] #消費水平(千元)nfig, ax =nplt.subplots(figsize=(10,6))ni=0nwhile i<len(b):n x = [0] + np.cos(np.linspace(0, 2 * np.pi *n s[i][0], 15)).tolist()n y =[0] + np.sin(np.linspace(0, 2 * np.pi * n s[i][0], 15)).tolist()n xy1 = list(zip(x, y))nn x = [0] + np.cos(np.linspace(2 * np.pi *n s[i][0], 2 * np.pi * s[i][1], 15)).tolist()n y = [0] + np.sin(np.linspace(2 * np.pi *n s[i][0], 2 * np.pi * s[i][1], 15)).tolist()n xy2 = list(zip(x, y))nn x = [0] + np.cos(np.linspace(2 * np.pi *s[i][1], n 2 * np.pi*s[i][2], 15)).tolist()n y = [0] + np.sin(np.linspace(2 * np.pi *s[i][1], n 2 * np.pi*s[i][2], 15)).tolist()n xy3 = list(zip(x, y))nn x = [0] + np.cos(np.linspace(2 * np.pi *s[i][2], n 2 * np.pi*1,15)).tolist()n y = [0] + np.sin(np.linspace(2 * np.pi *s[i][2], n 2 * np.pi*1, 15)).tolist()n xy4 = list(zip(x, y))nnxy=[xy1,xy2,xy3,xy4]nc=[b,y,r,g]nfor j in range(4):n ax.scatter(a[i], b[i], marker=(xy[j]),n s=800,facecolor=c[j])nni=i+1nnplt.show()n
最終得到本文前邊那個圖形。
另外還需說明一下,這個圖形只適用於小樣本數據,也就是圖形三點的個數不能太多,每個點中比例數量也不能太多,否則影響展示效果。
寫作不易,特別是技術類的寫作,請大家多多支持,關注、點贊、轉發等等,也歡迎大家關注知乎爬蟲與數據分析專欄:https://zhuanlan.zhihu.com/zjying2000。
推薦閱讀: