用 Python 制作子弹图也这么简单,爱了~
眾所周知,Python 的應(yīng)用是非常廣泛的,今天我們就通過(guò) matplotlib 庫(kù)學(xué)習(xí)下如何制作精美的子彈圖
1什么是子彈圖
一個(gè)子彈圖約定俗成的定義
子彈圖使用長(zhǎng)度/高度、位置和顏色對(duì)數(shù)據(jù)進(jìn)行編碼,以顯示與目標(biāo)和性能帶相比的實(shí)際情況
我們先來(lái)看下子彈圖大概長(zhǎng)什么樣子
子彈圖具有單一的主要度量(例如,當(dāng)前年初至今的收入),將該度量與一個(gè)或多個(gè)其他度量進(jìn)行比較以豐富其含義(例如,與目標(biāo)相比),并將其顯示在性能的定性范圍的背景,例如差、滿意和好。定性范圍顯示為單一色調(diào)的不同強(qiáng)度,使色盲者可以辨別它們,并將儀表板上的顏色使用限制在最低限度
好了,差不多這就是子彈圖的應(yīng)用場(chǎng)景和繪制標(biāo)準(zhǔn)了,下面我們就開(kāi)始制作吧
2構(gòu)建圖表
思路大致是,可以使用堆疊條形圖來(lái)表示各種范圍,并使用另一個(gè)較小的條形圖來(lái)表示值,最后,用一條垂直線標(biāo)記目標(biāo)
可以看出,我們需要多個(gè)組件圖層,使用 matplotlib 來(lái)實(shí)現(xiàn)會(huì)比較方便
import?matplotlib.pyplot?as?plt import?seaborn?as?sns from?matplotlib.ticker?import?FuncFormatter%matplotlib?inline這里我們還導(dǎo)入了 Seaborn,是因?yàn)?Seaborn 有一些非常有用的工具來(lái)管理調(diào)色板,利用這種功能比嘗試以其他方式復(fù)制它更容易
我們需要生成調(diào)色板的主要原因是我們很可能希望為各種定性范圍生成視覺(jué)上吸引人的配色方案,直接使用 seaborn 來(lái)完成會(huì)方便很多
在下面的例子中,我們可以使用 palplot 便利函數(shù)來(lái)顯示 5 種綠色色調(diào)的調(diào)色板
sns.palplot(sns.light_palette("green",?5))sns.palplot(sns.light_palette("purple",8,?reverse=True))以相反的順序制作 8 種不同深淺的紫色
我們現(xiàn)在知道了如何設(shè)置調(diào)色板,接下來(lái)讓我們使用 Matplotlib 根據(jù)上面列出的原則創(chuàng)建一個(gè)簡(jiǎn)單的子彈圖
首先,定義我們想要繪制的值
limits?=?[80,?100,?150] data_to_plot?=?("Example?1",?105,?120)這個(gè)將創(chuàng)建 3 個(gè)范圍:0-80、81-100、101-150 和一個(gè)值為 105 和目標(biāo)線為 120 的“示例”線 接下來(lái),構(gòu)建一個(gè)藍(lán)色調(diào)色板:
palette?=?sns.color_palette("Blues_r",?len(limits))接下來(lái)是構(gòu)建范圍的堆積條形圖:
fig,?ax?=?plt.subplots() ax.set_aspect('equal') ax.set_yticks([1]) ax.set_yticklabels([data_to_plot[0]])prev_limit?=?0 for?idx,?lim?in?enumerate(limits):ax.barh([1],?lim-prev_limit,?left=prev_limit,?height=15,?color=palette[idx])prev_limit?=?lim然后我們可以添加一個(gè)較小的條形圖來(lái)表示 105 的值:
ax.barh([1],?data_to_plot[1],?color='black',?height=5)已經(jīng)初見(jiàn)雛形了
最后一步是使用 axvline 添加目標(biāo)標(biāo)記:
ax.axvline(data_to_plot[2],?color="gray",?ymin=0.10,?ymax=0.9)上面我就完成了子彈圖的簡(jiǎn)單制作,但是我們所有的測(cè)試數(shù)值都是寫(xiě)死的,下面我們編寫(xiě)一個(gè)可以填寫(xiě)任意數(shù)值的代碼
3最終代碼
def?bulletgraph(data=None,?limits=None,?labels=None,?axis_label=None,?title=None,size=(5,?3),?palette=None,?formatter=None,?target_color="gray",bar_color="black",?label_color="gray"):#?Determine?the?max?value?for?adjusting?the?bar?height#?Dividing?by?10?seems?to?work?pretty?wellh?=?limits[-1]?/?10#?Use?the?green?palette?as?a?sensible?defaultif?palette?is?None:palette?=?sns.light_palette("green",?len(limits),?reverse=False)#?Must?be?able?to?handle?one?or?many?data?sets?via?multiple?subplotsif?len(data)?==?1:fig,?ax?=?plt.subplots(figsize=size,?sharex=True)else:fig,?axarr?=?plt.subplots(len(data),?figsize=size,?sharex=True)#?Add?each?bullet?graph?bar?to?a?subplotfor?idx,?item?in?enumerate(data):#?Get?the?axis?from?the?array?of?axes?returned?when?the?plot?is?createdif?len(data)?>?1:ax?=?axarr[idx]#?Formatting?to?get?rid?of?extra?marking?clutterax.set_aspect('equal')ax.set_yticklabels([item[0]])ax.set_yticks([1])ax.spines['bottom'].set_visible(False)ax.spines['top'].set_visible(False)ax.spines['right'].set_visible(False)ax.spines['left'].set_visible(False)prev_limit?=?0for?idx2,?lim?in?enumerate(limits):#?Draw?the?barax.barh([1],?lim?-?prev_limit,?left=prev_limit,?height=h,color=palette[idx2])prev_limit?=?limrects?=?ax.patches#?The?last?item?in?the?list?is?the?value?we're?measuring#?Draw?the?value?we're?measuringax.barh([1],?item[1],?height=(h?/?3),?color=bar_color)#?Need?the?ymin?and?max?in?order?to?make?sure?the?target?marker#?fitsymin,?ymax?=?ax.get_ylim()ax.vlines(item[2],?ymin?*?.9,?ymax?*?.9,?linewidth=1.5,?color=target_color)#?Now?make?some?labelsif?labels?is?not?None:for?rect,?label?in?zip(rects,?labels):height?=?rect.get_height()ax.text(rect.get_x()?+?rect.get_width()?/?2,-height?*?.4,label,ha='center',va='bottom',color=label_color)if?formatter:ax.xaxis.set_major_formatter(formatter)if?axis_label:ax.set_xlabel(axis_label)if?title:fig.suptitle(title,?fontsize=14)fig.subplots_adjust(hspace=0)代碼雖然看起來(lái)有點(diǎn)長(zhǎng),但是其實(shí)都是上面步驟的疊加,都比較簡(jiǎn)單,就不再重復(fù)說(shuō)明了
我們直接調(diào)用一下看看效果
data_to_plot2?=?[("張三",?105,?120),("李四",?99,?110),("王五",?109,?125),("趙六",?135,?123),("錢(qián)七",?45,?105)]bulletgraph(data_to_plot2,?limits=[20,?60,?100,?160],labels=["Poor",?"OK",?"Good",?"Excellent"],?size=(8,5),axis_label="Performance?Measure",?label_color="black",bar_color="#252525",?target_color='#f7f7f7',title="銷售代表表現(xiàn)")我們還可以進(jìn)行一些優(yōu)化,格式化 x 軸以便更一致地顯示信息
在下面這個(gè)例子中,我們可以衡量一家假設(shè)公司的營(yíng)銷預(yù)算績(jī)效
def?money(x,?pos):'The?two?args?are?the?value?and?tick?position'return?"${:,.0f}".format(x)money_fmt?=?FuncFormatter(money) data_to_plot3?=?[("HR",?50000,?60000),("Marketing",?75000,?65000),("Sales",?125000,?80000),("R&D",?195000,?115000)] palette?=?sns.light_palette("grey",?3,?reverse=False) bulletgraph(data_to_plot3,?limits=[50000,?125000,?200000],labels=["Below",?"On?Target",?"Above"],?size=(10,5),axis_label="Annual?Budget",?label_color="black",bar_color="#252525",?target_color='#f7f7f7',?palette=palette,title="營(yíng)銷渠道預(yù)算績(jī)效",formatter=money_fmt)看起來(lái)效果都不錯(cuò)哦,怎么樣,一起跟著做起來(lái)吧!
END
推薦閱讀牛逼!Python常用數(shù)據(jù)類型的基本操作(長(zhǎng)文系列第①篇) 牛逼!Python的判斷、循環(huán)和各種表達(dá)式(長(zhǎng)文系列第②篇)牛逼!Python函數(shù)和文件操作(長(zhǎng)文系列第③篇)牛逼!Python錯(cuò)誤、異常和模塊(長(zhǎng)文系列第④篇)總結(jié)
以上是生活随笔為你收集整理的用 Python 制作子弹图也这么简单,爱了~的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python这些操作,逆天且实用!
- 下一篇: 算法导论学习笔记 第2章 算法基础