蛛網(wǎng)圖祷肯,最早知道是在玩FIFA游戲的時候沉填,球員的能力用蛛網(wǎng)圖來表示與比較,那時覺得非常新鮮佑笋。后來翼闹,在實際的工作中,其實很少用到:一方面蒋纬,直接提供蛛網(wǎng)圖的工具少猎荠;另一方面,過往的經(jīng)歷中多維度比較用到的地方也比較少蜀备。
在學習Python
的過程中关摇,重新燃起對雷達圖的興趣,但在python重要的圖庫Matplotlib
與Seaborn
里都沒有直接實現(xiàn)雷達圖的函數(shù)碾阁。因此输虱,雖然感興趣,也沒有去觸及脂凶,直到一天在Udacity數(shù)據(jù)分析進階VIP班的一個學生的Tableau
作業(yè)里看到他使用了雷達圖(Tableau
圖庫里也沒有直接提供雷達圖)宪睹,使我饒有興趣地去思考雷達圖到底是什么愁茁?
最初的想法
最初的想法,在直角坐標系通過畫圓的方式尋找多邊形在圓軸上點的坐標亭病,只要知道半徑長度鹅很,以及原點與圓周上的點的連線與X軸的夾角
便能通過三角函數(shù)求出x與y 的坐標。
半徑可看作是一個向量罪帖。向量是一個方向道宅,向量中的每一個元素代表著維度,維度的值是維度方向的距離胸蛛。直角坐標系由x軸,y軸及原點組成的二維空間樱报,x是一個維度葬项,x的值在x軸方向上與原點之間的距離,y是另一個維度迹蛤,同樣的民珍,y的值則是在y軸方向上與原點的距離,x與y平行移動的交匯點為坐標盗飒,坐標點與原點的連線是x軸與y軸上的兩個兩個向量的相加所形成的新向量嚷量,暫命名為A向量,A向量的長度為r逆趣,圍繞著原點旋轉(zhuǎn)便能形成一個以原點為圓心蝶溶,r為半徑的圓这敬,根據(jù)A向量與x軸行程的角度
總能得出A向量在x軸上的長度為
储狭,在y軸上的長度為
,由此祖秒,可以得出該在圓上的任意一個點的坐標(x,y)痕囱。
假設(shè)雷達圖由6個維度構(gòu)成田轧,那么最初躺在x軸上的向量相當于旋轉(zhuǎn)了6次,每次旋轉(zhuǎn)60o在圓周上留下一個點鞍恢,點與點之間的連線構(gòu)成了雷達圖傻粘。那么,使用折線圖便能實現(xiàn)雷達圖帮掉。
突然醒悟
仔細翻讀Matplotlib
的范例弦悉,看到了polar坐標系,突然想明白:為什么不直接旋轉(zhuǎn)X坐標軸呢蟆炊?將直角坐標系中的X軸首尾相連繞成了一個圓警绩,將x軸的線性的長度轉(zhuǎn)換成了弧長,Y軸不變盅称,在此之下的折線圖就是雷達圖肩祥,僅此而已后室。想明白了,生成雷達圖便成了輕而易舉之事混狠。接下來岸霹,將詳細解釋雷達圖編寫的過程。
用Python繪制雷達圖
pyechart
用python語言制作了一系列流行的圖将饺,其中包括雷達圖贡避。我將其雷達圖的截圖放在了本文的最頂端。然而pyechart
只是用Python
語言生成的Java
予弧,應用在html
里刮吧。當然,在實際應用中掖蛤,用網(wǎng)頁顯示與觀眾的互動性更強杀捻,如果用網(wǎng)頁作為儀表盤,顯然使用pyechart
就好了蚓庭。但在這里致讥,我期望去了解雷達圖的原理,因此借用pyechart-雷達圖 示例中的數(shù)據(jù)器赞,用Matplotlib
來重現(xiàn)垢袱。
這是使用Matplotlib
畫出的結(jié)果,除了不會動態(tài)顯示數(shù)據(jù)以外港柜,長相與pyechart
的雷達圖基本保持了一致请契,過程并不復雜。接下來夏醉,將分步驟展現(xiàn)實現(xiàn)的過程與代碼姚糊。
Python分步驟實現(xiàn)雷達圖
1. 范例數(shù)據(jù)
schema = [
("銷售", 6000),
("管理", 16000),
("信息技術(shù)", 30000),
("客戶服務", 38000),
("研發(fā)", 52000),
("市場", 25000)
]
v1 = [[4300, 10000, 28000, 35000, 50000, 19000]]
v2 = [[5000, 14000, 28000, 31000, 42000, 21000]]
schema 中包含標簽與6個維度分別的總分,v1授舟、v2可能是2個部門分別按6個維度打分的結(jié)果救恨。整理數(shù)據(jù),首先抽出labels
释树, 然后對v1肠槽、v2 進行歸一(標準化,normalize)奢啥,將分值轉(zhuǎn)化為比率秸仙,或百分制。
# 拆分schema到標簽labs與總分full_marks
labs = []
full_marks = []
for value in schema:
labs.append(value[0])
full_marks.append(value[1])
# v1,v2進行歸一處里
Y = np.vstack((v1,v2))
Y = Y/np.array(full_marks)
print('labs = {}'.format(labs))
print('Y = {}'.format(Y))
--------------------------------------------------------------------------
Output:
labs = ['銷售', '管理', '信息技術(shù)', '客戶服務', '研發(fā)', '市場']
Y = [[ 0.71666667 0.625 0.93333333 0.92105263 0.96153846 0.76 ]
[ 0.83333333 0.875 0.93333333 0.81578947 0.80769231 0.84 ]]
2. X軸變形
x軸上所呈現(xiàn)的應該是labs桩盲,用labs的index來作為x軸上的坐標寂纪,由于x軸將由一根直線首尾相接成圓,原先x軸上的點離原點的距離將相應地換算為弧長
,每一維度間所間隔的角度
由維度
決定捞蛋,將圓周長切分成
個等份的弧度孝冒。弧長的計算公式為:
# 獲取 r 與 theta
N = len(labs)
r = np.arange(N)
theta = np.linspace(0, 360, N, endpoint=False)
# 將角度轉(zhuǎn)化為單位弧度
X_ticks = np.radians(theta) # x軸標簽所在的位置
print(' N = {:d}, \n r = {}, \n theta = {}, \n X_ticks = {}'.format(N, r, theta, X_ticks))
-------------------------------------------------------------------
Output:
N = 6,
r = [0 1 2 3 4 5],
theta = [ 0. 60. 120. 180. 240. 300.],
X_ticks = [ 0. 1.0472 2.0944 3.1416 4.1888 5.236 ]
3. 制圖1
簡單地轉(zhuǎn)換一下X軸的數(shù)值拟杉,然后設(shè)置坐標系為“polar”庄涡,雷達圖應該就能被制作出來了。
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.plot(X_ticks, Y[0], marker='o')
ax.plot(X_ticks, Y[1], marker='o')
ax.set_xticks(X_ticks)
plt.show()
首次嘗試的雷達圖有一些問題:
- 聯(lián)線缺了一個口搬设,需要將數(shù)列的第一個數(shù)字增補到最后一個穴店,使得首位相連;
- 圖像看起來有些歪拿穴,正中點在水平線上泣洞,尤其是單數(shù)維度時。因此需要旋轉(zhuǎn)圖像默色,使得正中點在垂直線上球凰。
4. 調(diào)整
# 獲取 r 與 theta
N = len(labs)
r = np.arange(N)
theta = np.linspace(0, 360, N, endpoint=False)
# 調(diào)整角度使得正中在垂直線上
adj_angle = theta[-1] + 90 - 360
theta += adj_angle
# 將角度轉(zhuǎn)化為單位弧度
X_ticks = np.radians(theta) # x軸標簽所在的位置
# 首尾相連
X = np.append(X_ticks,X_ticks[0])
Y = np.hstack((Y, Y[:,0].reshape(2,1)))
print('theta = {}, \nX = {}, \nY={}'.format(theta, X.round(4), Y.round(2)))
-------------------------------------------------------------------------
Output:
theta = [ 30. 90. 150. 210. 270. 330.],
X = [ 0.5236 1.5708 2.618 3.6652 4.7124 5.7596 0.5236],
Y=[[ 0.72 0.62 0.93 0.92 0.96 0.76 0.72]
[ 0.83 0.88 0.93 0.82 0.81 0.84 0.83]]
所有的角度增加了30o,X與Y軸的首尾的數(shù)字都已相同该窗,雷達圖應該就緒。
執(zhí)行與首次制圖時相同的代碼蚤霞,只是把X_ticks與Y替換成調(diào)整后的X與Y酗失,雷達圖的內(nèi)里已經(jīng)全部出來了,與pyechart
雷達圖的圖形一致昧绣,現(xiàn)在還差一些形狀與數(shù)據(jù)標簽规肴。
5. 背景制作
fig, ax = plt.subplots(figsize=(5, 5),
subplot_kw=dict(projection='polar'))
# 畫圖
ax.plot(X, Y[0], marker='o')
ax.plot(X, Y[1], marker='o')
ax.set_xticks(X)
# 設(shè)置背景坐標系
ax.set_xticklabels(labs, fontproperties = labFont, fontsize = 'large') # 設(shè)置標簽
ax.set_yticklabels([])
ax.spines['polar'].set_visible(False) # 將軸隱藏
ax.grid(axis='y') # 只有y軸設(shè)置grid
# 設(shè)置X軸的grid
n_grids = np.linspace(0,1, 6, endpoint=True) # grid的網(wǎng)格數(shù)
grids = [[i] * (len(X)) for i in n_grids] #grids的半徑
for i, grid in enumerate(grids[:-1]): # 給grid 填充間隔色
ax.plot(X, grid, color='grey', linewidth=0.5)
if (i>0) & (i % 2 == 0):
ax.fill_between(X, grids[i], grids[i-1], color='grey', alpha=0.1)
plt.show()
基本上成功了,就差一個軸 (sprine) 的設(shè)置夜畴,非常抱歉沒有弄懂Matplotlib
如何去設(shè)置軸的形狀拖刃,不過好在他的范例中給出了雷達圖的函數(shù)代碼,運行這段函數(shù)贪绘,向figure中的projection注冊了這段代碼后兑牡,將上文的代碼中的'polar' 改為'radar'便大功告成了。重寫的完整代碼如下:
# 調(diào)用Radar圖函數(shù)
N = len(labs)
theta = radar_factory(N, frame='polygon')
fig, ax = plt.subplots(figsize=(5, 5),
subplot_kw=dict(projection='radar'))
# 畫圖
ax.plot(X, Y[0], marker='o')
ax.plot(X, Y[1], marker='o')
ax.set_xticks(X)
# 設(shè)置背景坐標系
ax.set_xticklabels(labs, fontproperties = labFont, fontsize = 'large') # 設(shè)置標簽
ax.set_yticklabels([])
ax.spines['polar'].set_visible(False) # 將軸隱藏
ax.grid(axis='y') # 只有y軸設(shè)置grid
# 設(shè)置X軸的grid
n_grids = np.linspace(0,1, 6, endpoint=True) # grid的網(wǎng)格數(shù)
grids = [[i] * (len(X)) for i in n_grids] #grids的半徑
for i, grid in enumerate(grids[:-1]): # 給grid 填充間隔色
ax.plot(X, grid, color='grey', linewidth=0.5)
if (i>0) & (i % 2 == 0):
ax.fill_between(X, grids[i], grids[i-1], color='grey', alpha=0.1)
plt.show()
結(jié)果已在前述展示過了税灌,不再重復均函。
參考:
Radar chart (aka spider or star chart) https://matplotlib.org/gallery/specialty_plots/radar_chart.html?highlight=radar
pyechart-雷達圖 http://pyecharts.org/#/zh-cn/charts_base?id=radar(雷達圖)