圓餅圖太多項目很雜亂怎麼辦?
用Matplotlib實作帶堆疊長條圖的圓餅圖
-
Python
-
94
- 發佈於 2025-01-17
- 更新於 2025-04-16
8
<h1> </h1><p>我們在<a href="https://factsviz.tw/article/pie-chart-design-trap">「圓餅圖的陷阱和替代圖表:教你如何正確製作圓餅圖」</a>中提到,若項目個數過多,容易導致許多低占比的項目擠在一起,降低辨識度削弱呈現效果。因此我們可以將低占比項目組合成「其他項」,並用堆疊長條圖另外呈現,這麼做可以在提供所有資訊之餘,不模糊焦點。</p><h4>圓餅圖</h4><p>我們先製作一個簡單的圓餅圖。</p><p><img src="/imagefiles/python-matplotlib-piechart-stackbarchart-1.png"></p><pre><code class="language-python">import numpy as np
import matplotlib.pyplot as plt
data=[1,2,3]
fig, ax=plt.subplots(figsize=(8,5)) #建立畫布figure和axes
#有三種設定顏色的方法
#https://matplotlib.org/stable/gallery/color/named_colors.html#css-colors
#colors=['lightsteelblue','cornflowerblue','royalblue']
#colors=[[0.1, 0.2, 0.5, 0.4],[0.1, 0.2, 0.5, 0.6],[0.1, 0.2, 0.5, 0.8]]
colors = plt.get_cmap('Blues')(np.linspace(0.2, 0.7, len(data)))
#pctdistance:百分比標籤在切片外的話需>1
#labeldistance:Label在切片內的話需<1
ax.pie(data, labels=data,
autopct="%1.2f%%",
colors=colors,
pctdistance=1.3, labeldistance=.5,
startangle=90, #從x軸往逆時針90度
explode=(0.05,0.05,0.05)) #各切片往外移動的距離
plt.show()
</code></pre><p> </p><h4>帶堆疊長條圖的圓餅圖</h4><p>堆疊長條圖不僅可以用來呈現「其他項」中各個低占比項目,也可以用在圓餅圖切片延伸的項目上。在範例中,我們用圓餅圖呈現大家對三個選項的同意票數比例,並用堆疊長條圖顯示其中一個選項的同意票年齡分佈。</p><p><img src="/imagefiles/python-matplotlib-piechart-stackbarchart-2.png"></p><pre><code class="language-python">import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch
fig, (ax1, ax2)=plt.subplots(1,2, figsize=(8,5)) #建立兩個子圖
fig.subplots_adjust(wspace=0) #調整圖之間的距離(將內間距設為0,拉近距離)
data=[50,30,100] #把要製成堆積長條圖的項目放在第一個 #切片會從第一個資料開始逆時針排列
angle=-180*(data[0]/sum(data)) #讓要製成堆疊長條圖的該切片置於中間
pie_colors=['cornflowerblue','lightsteelblue','royalblue']
#ax.pie會回傳patched, label text, value text,這裡只需第一個
#(不需要的返回值以底線代替,此處有多個值不被需要,在底線前方加上星號)
#startangle為第一個切片開始的角度(以x軸為0逆時針加)
#explode為各切片往外移動的距離
wedges,*_=ax1.pie(data, labels=data,autopct="%1.2f%%",
colors=pie_colors,
startangle=angle, explode=[0.1,0,0])
#堆疊長條圖
age_data=[13,2,20,15]
age_ratio=[x/sum(age_data) for x in age_data]
age_label=['under 21', '21-40', '41-60', 'over 61']
bottom = 1 #一開始從畫布最上面開始往下畫(第一個長條的Y座標為(0,1))
width = .2 #長條的寬度
#enumerate:(index, vale);zip打包成tuple、*zip解壓;reversed反轉順序,讓堆疊由下往上
for i, (height, label) in enumerate(reversed([*zip(age_ratio, age_label)])):
#x座標皆為0;每個長條長度(height)為占比、寬度為width;y座標從第一個長條的1(最上方)-該長條長度開始,
#一直往下減去各長條的占比(長度);label為各長條的標籤;alpha為顏色不透明度,index越高的長條越不透明
bottom -= height
bc = ax2.bar(0, height, width, bottom=bottom, color=pie_colors[0], label=label,
alpha=0.1 + 0.25 * i)
ax2.bar_label(bc, labels=[f"{height:.0%}"], label_type='center')
#以增加註釋的方式加上文字標籤,置於每個長條的中間;xy為註釋箭頭所指的點,
#此例無箭頭因此無顯示;xytext為文字的座標位置
ax2.annotate(label, xy=(0.2,1) ,xytext=(0.2, bottom+(height/2)))
ax2.set_title('age of approvers') #設定標題
ax2.axis('off') #移除軸線
ax2.set_xlim(-2.5*width, 2.5*width) #設定x軸範圍,連動bar寬度,讓圖變窄
#連接線
#theta1:該切片右邊角的點(起點);theta2: 左邊角的點(終點)
theta1, theta2=wedges[0].theta1, wedges[0].theta2
#center: 該切片中心點(因有explode所以跟其他切片中心點(0,0)不同);r:半徑(1)
center, r=wedges[0].center, wedges[0].r
bar_height=sum(age_ratio) #長條圖的總長度
#上面的連接線
#2pi弧度=360角度,pi弧度=180度;theta2約為50(x軸為0逆時針50度)
#np.pi/180=1角度為多少弧度;np.pi/180*theta2=該切片以圓心畫一個直角三角形,對著圓弧的弧度
#用r*np.cos計算出直角三角形的最短邊長度(在x軸上)
x = r * np.cos(np.pi / 180 * theta2) + center[0]
y = r * np.sin(np.pi / 180 * theta2) + center[1] #計算直角三角形中平行y軸的邊的長度
#xyA為長條圖左上角;xyB為圓餅圖該切片最上面的點;ax.transData表示資料座標系統
con = ConnectionPatch(xyA=(-width / 2, bar_height), coordsA=ax2.transData,
xyB=(x, y), coordsB=ax1.transData)
con.set_color(pie_colors[0]) #連接線顏色;或使用rgba,例如(0,0,1,0.5)
con.set_linewidth(1) #連接線寬度
ax2.add_artist(con) #加上連接線
#下面的連接線
x = r * np.cos(np.pi / 180 * theta1) + center[0]
y = r * np.sin(np.pi / 180 * theta1) + center[1]
#xyA為長條圖左下角,xyB為圓餅圖該切片最下面的點
con = ConnectionPatch(xyA=(-width / 2, 0), coordsA=ax2.transData,
xyB=(x, y), coordsB=ax1.transData)
con.set_color(pie_colors[0]) #連接線顏色;或使用rgba,例如(0,0,1,0.5)
con.set_linewidth(1) #連接線寬度
ax2.add_artist(con) #加上連接線
plt.show()
</code></pre><p> </p><h4><strong>參考資料</strong></h4><p><a href="https://matplotlib.org/stable/gallery/pie_and_polar_charts/pie_features.html">圓餅圖官方文件</a></p><p><a href="https://matplotlib.org/stable/gallery/pie_and_polar_charts/bar_of_pie.html">圓餅圖+堆疊長條圖官方文件</a> </p>