5 种方法教你用Python玩转histogram直方图
直方圖是一個(gè)可以快速展示數(shù)據(jù)概率分布的工具,直觀易于理解,并深受數(shù)據(jù)愛好者的喜愛。大家平時(shí)可能見到最多就是?matplotlib,seaborn?等高級封裝的庫包,類似以下這樣的繪圖。
本篇博主將要總結(jié)一下使用Python繪制直方圖的所有方法,大致可分為三大類(詳細(xì)劃分是五類,參照文末總結(jié)):
純Python實(shí)現(xiàn)直方圖,不使用任何第三方庫
使用Numpy來創(chuàng)建直方圖總結(jié)數(shù)據(jù)
使用matplotlib,pandas,seaborn繪制直方圖
下面,我們來逐一介紹每種方法的來龍去脈。
純Python實(shí)現(xiàn)histogram
當(dāng)準(zhǔn)備用純Python來繪制直方圖的時(shí)候,最簡單的想法就是將每個(gè)值出現(xiàn)的次數(shù)以報(bào)告形式展示。這種情況下,使用?字典?來完成這個(gè)任務(wù)是非常合適的,我們看看下面代碼是如何實(shí)現(xiàn)的。
>>>?a?=?(0,?1,?1,?1,?2,?3,?7,?7,?23)>>>?def?count_elements(seq)?->?dict: ...?????"""Tally?elements?from?`seq`.""" ...?????hist?=?{} ...?????for?i?in?seq: ...?????????hist[i]?=?hist.get(i,?0)?+?1 ...?????return?hist>>>?counted?=?count_elements(a) >>>?counted {0:?1,?1:?3,?2:?1,?3:?1,?7:?2,?23:?1}我們看到,count_elements() 返回了一個(gè)字典,字典里出現(xiàn)的鍵為目標(biāo)列表里面的所有唯一數(shù)值,而值為所有數(shù)值出現(xiàn)的頻率次數(shù)。hist[i] = hist.get(i, 0) + 1 實(shí)現(xiàn)了每個(gè)數(shù)值次數(shù)的累積,每次加一。
實(shí)際上,這個(gè)功能可以用一個(gè)Python的標(biāo)準(zhǔn)庫 collection.Counter 類來完成,它兼容Pyhont 字典并覆蓋了字典的 .update() 方法。
可以看到這個(gè)方法和前面我們自己實(shí)現(xiàn)的方法結(jié)果是一樣的,我們也可以通過?collection.Counter 來檢驗(yàn)兩種方法得到的結(jié)果是否相等。
>>>?recounted.items()?==?counted.items() True我們利用上面的函數(shù)重新再造一個(gè)輪子?ASCII_histogram,并最終通過Python的輸出格式format來實(shí)現(xiàn)直方圖的展示,代碼如下:
這個(gè)函數(shù)按照數(shù)值大小順序進(jìn)行繪圖,數(shù)值出現(xiàn)次數(shù)用 (+) 符號表示。在字典上調(diào)用 sorted() 將會返回一個(gè)按鍵順序排列的列表,然后就可以獲取相應(yīng)的次數(shù) counted[k] 。
>>>?import?random >>>?random.seed(1)>>>?vals?=?[1,?3,?4,?6,?8,?9,?10] >>>?#?`vals`?里面的數(shù)字將會出現(xiàn)5到15次 >>>?freq?=?(random.randint(5,?15)?for?_?in?vals)>>>?data?=?[] >>>?for?f,?v?in?zip(freq,?vals): ...?????data.extend([v]?*?f)>>>?ascii_histogram(data)1?+++++++3?++++++++++++++4?++++++6?+++++++++8?++++++9?++++++++++++10?++++++++++++這個(gè)代碼中,vals內(nèi)的數(shù)值是不重復(fù)的,并且每個(gè)數(shù)值出現(xiàn)的頻數(shù)是由我們自己定義的,在5和15之間隨機(jī)選擇。然后運(yùn)用我們上面封裝的函數(shù),就得到了純Python版本的直方圖展示。
總結(jié):純python實(shí)現(xiàn)頻數(shù)表(非標(biāo)準(zhǔn)直方圖),可直接使用collection.Counter方法實(shí)現(xiàn)。
使用Numpy實(shí)現(xiàn)histogram
以上是使用純Python來完成的簡單直方圖,但是從數(shù)學(xué)意義上來看,直方圖是分箱到頻數(shù)的一種映射,它可以用來估計(jì)變量的概率密度函數(shù)的。而上面純Python實(shí)現(xiàn)版本只是單純的頻數(shù)統(tǒng)計(jì),不是真正意義上的直方圖。
因此,我們從上面實(shí)現(xiàn)的簡單直方圖繼續(xù)往下進(jìn)行升級。一個(gè)真正的直方圖首先應(yīng)該是將變量分區(qū)域(箱)的,也就是分成不同的區(qū)間范圍,然后對每個(gè)區(qū)間內(nèi)的觀測值數(shù)量進(jìn)行計(jì)數(shù)。恰巧,Numpy的直方圖方法就可以做到這點(diǎn),不僅僅如此,它也是后面將要提到的matplotlib和pandas使用的基礎(chǔ)。
舉個(gè)例子,來看一組從拉普拉斯分布上提取出來的浮點(diǎn)型樣本數(shù)據(jù)。這個(gè)分布比標(biāo)準(zhǔn)正態(tài)分布擁有更寬的尾部,并有兩個(gè)描述參數(shù)(location和scale):
>>>?import?numpy?as?np>>>?np.random.seed(444) >>>?np.set_printoptions(precision=3)>>>?d?=?np.random.laplace(loc=15,?scale=3,?size=500) >>>?d[:5] array([18.406,?18.087,?16.004,?16.221,??7.358])由于這是一個(gè)連續(xù)型的分布,對于每個(gè)單獨(dú)的浮點(diǎn)值(即所有的無數(shù)個(gè)小數(shù)位置)并不能做很好的標(biāo)簽(因?yàn)辄c(diǎn)實(shí)在太多了)。但是,你可以將數(shù)據(jù)做 分箱 處理,然后統(tǒng)計(jì)每個(gè)箱內(nèi)觀察值的數(shù)量,這就是真正的直方圖所要做的工作。
下面我們看看是如何用Numpy來實(shí)現(xiàn)直方圖頻數(shù)統(tǒng)計(jì)的。
>>>?hist,?bin_edges?=?np.histogram(d)>>>?hist array([?1,??0,??3,??4,??4,?10,?13,??9,??2,??4])>>>?bin_edges array([?3.217,??5.199,??7.181,??9.163,?11.145,?13.127,?15.109,?17.091,19.073,?21.055,?23.037])這個(gè)結(jié)果可能不是很直觀。來說一下,np.histogram() 默認(rèn)地使用10個(gè)相同大小的區(qū)間(箱),然后返回一個(gè)元組(頻數(shù),分箱的邊界),如上所示。要注意的是:這個(gè)邊界的數(shù)量是要比分箱數(shù)多一個(gè)的,可以簡單通過下面代碼證實(shí)。
>>>?hist.size,?bin_edges.size (10,?11)那問題來了,Numpy到底是如何進(jìn)行分箱的呢?只是通過簡單的?np.histogram()?就可以完成了,但具體是如何實(shí)現(xiàn)的我們?nèi)匀蝗徊恢O旅孀屛覀儊韺?np.histogram() 的內(nèi)部進(jìn)行解剖,看看到底是如何實(shí)現(xiàn)的(以最前面提到的a列表為例)。
>>>?#?取a的最小值和最大值 >>>?first_edge,?last_edge?=?a.min(),?a.max()>>>?n_equal_bins?=?10??#?NumPy得默認(rèn)設(shè)置,10個(gè)分箱 >>>?bin_edges?=?np.linspace(start=first_edge,?stop=last_edge, ...?????????????????????????num=n_equal_bins?+?1,?endpoint=True) ... >>>?bin_edges array([?0.?,??2.3,??4.6,??6.9,??9.2,?11.5,?13.8,?16.1,?18.4,?20.7,?23.?])解釋一下:首先獲取a列表的最小值和最大值,然后設(shè)置默認(rèn)的分箱數(shù)量,最后使用Numpy的 linspace 方法進(jìn)行數(shù)據(jù)段分割。分箱區(qū)間的結(jié)果也正好與實(shí)際吻合,0到23均等分為10份,23/10,那么每份寬度為2.3。
除了np.histogram之外,還存在其它兩種可以達(dá)到同樣功能的方法:np.bincount() 和 np.searchsorted(),下面看看代碼以及比較結(jié)果。
>>>?bcounts?=?np.bincount(a) >>>?hist,?_?=?np.histogram(a,?range=(0,?a.max()),?bins=a.max()?+?1)>>>?np.array_equal(hist,?bcounts) True>>>?#?Reproducing?`collections.Counter` >>>?dict(zip(np.unique(a),?bcounts[bcounts.nonzero()])) {0:?1,?1:?3,?2:?1,?3:?1,?7:?2,?23:?1}總結(jié):通過Numpy實(shí)現(xiàn)直方圖,可直接使用np.histogram()或者np.bincount()。
使用Matplotlib和Pandas可視化Histogram
從上面的學(xué)習(xí),我們看到了如何使用Python的基礎(chǔ)工具搭建一個(gè)直方圖,下面我們來看看如何使用更為強(qiáng)大的Python庫包來完成直方圖。Matplotlib基于Numpy的histogram進(jìn)行了多樣化的封裝并提供了更加完善的可視化功能。
import?matplotlib.pyplot?as?plt#??matplotlib.axes.Axes.hist()?方法的接口 n,?bins,?patches?=?plt.hist(x=d,?bins='auto',?color='#0504aa',alpha=0.7,?rwidth=0.85) plt.grid(axis='y',?alpha=0.75) plt.xlabel('Value') plt.ylabel('Frequency') plt.title('My?Very?Own?Histogram') plt.text(23,?45,?r'$\mu=15,?b=3$') maxfreq?=?n.max() #?設(shè)置y軸的上限 plt.ylim(ymax=np.ceil(maxfreq?/?10)?*?10?if?maxfreq?%?10?else?maxfreq?+?10)之前我們的做法是,在x軸上定義了分箱邊界,y軸是相對應(yīng)的頻數(shù),不難發(fā)現(xiàn)我們都是手動定義了分箱的數(shù)目。但是在以上的高級方法中,我們可以通過設(shè)置?bins='auto'?自動在寫好的兩個(gè)算法中擇優(yōu)選擇并最終算出最適合的分箱數(shù)。這里,算法的目的就是選擇出一個(gè)合適的區(qū)間(箱)寬度,并生成一個(gè)最能代表數(shù)據(jù)的直方圖來。
如果使用Python的科學(xué)計(jì)算工具實(shí)現(xiàn),那么可以使用Pandas的 Series.histogram() ,并通過 matplotlib.pyplot.hist() 來繪制輸入Series的直方圖,如下代碼所示。
import?pandas?as?pdsize,?scale?=?1000,?10 commutes?=?pd.Series(np.random.gamma(scale,?size=size)?**?1.5)commutes.plot.hist(grid=True,?bins=20,?rwidth=0.9,color='#607c8e') plt.title('Commute?Times?for?1,000?Commuters') plt.xlabel('Counts') plt.ylabel('Commute?Time') plt.grid(axis='y',?alpha=0.75)pandas.DataFrame.histogram() 的用法與Series是一樣的,但生成的是對DataFrame數(shù)據(jù)中的每一列的直方圖。
總結(jié):通過pandas實(shí)現(xiàn)直方圖,可使用Seris.plot.hist(),DataFrame.plot.hist(),matplotlib實(shí)現(xiàn)直方圖可以用matplotlib.pyplot.hist()。
繪制核密度估計(jì)(KDE)
KDE(Kernel density estimation)是核密度估計(jì)的意思,它用來估計(jì)隨機(jī)變量的概率密度函數(shù),可以將數(shù)據(jù)變得更平緩。
使用Pandas庫的話,你可以使用 plot.kde() 創(chuàng)建一個(gè)核密度的繪圖,plot.kde() 對于 Series和DataFrame數(shù)據(jù)結(jié)構(gòu)都適用。但是首先,我們先生成兩個(gè)不同的數(shù)據(jù)樣本作為比較(兩個(gè)正太分布的樣本):
>>>?#?兩個(gè)正太分布的樣本 >>>?means?=?10,?20 >>>?stdevs?=?4,?2 >>>?dist?=?pd.DataFrame( ...?????np.random.normal(loc=means,?scale=stdevs,?size=(1000,?2)), ...?????columns=['a',?'b']) >>>?dist.agg(['min',?'max',?'mean',?'std']).round(decimals=2)a??????b min???-1.57??12.46 max???25.32??26.44 mean??10.12??19.94 std????3.94???1.94以上看到,我們生成了兩組正態(tài)分布樣本,并且通過一些描述性統(tǒng)計(jì)參數(shù)對兩組數(shù)據(jù)進(jìn)行了簡單的對比。現(xiàn)在,我們可以在同一個(gè)Matplotlib軸上繪制每個(gè)直方圖以及對應(yīng)的kde,使用pandas的plot.kde()的好處就是:它會自動的將所有列的直方圖和kde都顯示出來,用起來非常方便,具體代碼如下:
fig,?ax?=?plt.subplots() dist.plot.kde(ax=ax,?legend=False,?title='Histogram:?A?vs.?B') dist.plot.hist(density=True,?ax=ax) ax.set_ylabel('Probability') ax.grid(axis='y') ax.set_facecolor('#d8dcd6')總結(jié):通過pandas實(shí)現(xiàn)kde圖,可使用Seris.plot.kde(),DataFrame.plot.kde()。
使用Seaborn的完美替代
一個(gè)更高級可視化工具就是Seaborn,它是在matplotlib的基礎(chǔ)上進(jìn)一步封裝的強(qiáng)大工具。對于直方圖而言,Seaborn有 distplot() 方法,可以將單變量分布的直方圖和kde同時(shí)繪制出來,而且使用及其方便,下面是實(shí)現(xiàn)代碼(以上面生成的d為例):
import?seaborn?as?snssns.set_style('darkgrid') sns.distplot(d)distplot方法默認(rèn)的會繪制kde,并且該方法提供了?fit?參數(shù),可以根據(jù)數(shù)據(jù)的實(shí)際情況自行選擇一個(gè)特殊的分布來對應(yīng)。
sns.distplot(d,?fit=stats.laplace,?kde=False)注意這兩個(gè)圖微小的區(qū)別。第一種情況你是在估計(jì)一個(gè)未知的概率密度函數(shù)(PDF),而第二種情況是你是知道分布的,并想知道哪些參數(shù)可以更好的描述數(shù)據(jù)。
總結(jié):通過seaborn實(shí)現(xiàn)直方圖,可使用seaborn.distplot(),seaborn也有單獨(dú)的kde繪圖seaborn.kde()。
在Pandas中的其它工具
除了繪圖工具外,pandas也提供了一個(gè)方便的.value_counts() 方法,用來計(jì)算一個(gè)非空值的直方圖,并將之轉(zhuǎn)變成一個(gè)pandas的series結(jié)構(gòu),示例如下:
>>>?import?pandas?as?pd>>>?data?=?np.random.choice(np.arange(10),?size=10000, ...?????????????????????????p=np.linspace(1,?11,?10)?/?60) >>>?s?=?pd.Series(data)>>>?s.value_counts() 9????1831 8????1624 7????1423 6????1323 5????1089 4?????888 3?????770 2?????535 1?????347 0?????170 dtype:?int64>>>?s.value_counts(normalize=True).head() 9????0.1831 8????0.1624 7????0.1423 6????0.1323 5????0.1089 dtype:?float64此外,pandas.cut() 也同樣是一個(gè)方便的方法,用來將數(shù)據(jù)進(jìn)行強(qiáng)制的分箱。比如說,我們有一些人的年齡數(shù)據(jù),并想把這些數(shù)據(jù)按年齡段進(jìn)行分類,示例如下:
>>>?ages?=?pd.Series( ...?????[1,?1,?3,?5,?8,?10,?12,?15,?18,?18,?19,?20,?25,?30,?40,?51,?52]) >>>?bins?=?(0,?10,?13,?18,?21,?np.inf)??#?邊界 >>>?labels?=?('child',?'preteen',?'teen',?'military_age',?'adult') >>>?groups?=?pd.cut(ages,?bins=bins,?labels=labels)>>>?groups.value_counts() child???????????6 adult???????????5 teen????????????3 military_age????2 preteen?????????1 dtype:?int64>>>?pd.concat((ages,?groups),?axis=1).rename(columns={0:?'age',?1:?'group'})age?????????group 0?????1?????????child 1?????1?????????child 2?????3?????????child 3?????5?????????child 4?????8?????????child 5????10?????????child 6????12???????preteen 7????15??????????teen 8????18??????????teen 9????18??????????teen 10???19??military_age 11???20??military_age 12???25?????????adult 13???30?????????adult 14???40?????????adult 15???51?????????adult 16???52?????????adult除了使用方便外,更加好的是這些操作最后都會使用?Cython?代碼來完成,在運(yùn)行速度的效果上也是非常快的。
總結(jié):其它實(shí)現(xiàn)直方圖的方法,可使用.value_counts()和pandas.cut()。
該使用哪個(gè)方法?
至此,我們了解了很多種方法來實(shí)現(xiàn)一個(gè)直方圖。但是它們各自有什么有缺點(diǎn)呢?該如何對它們進(jìn)行選擇呢?當(dāng)然,一個(gè)方法解決所有問題是不存在的,我們也需要根據(jù)實(shí)際情況而考慮如何選擇,下面是對一些情況下使用方法的一個(gè)推薦,僅供參考。
你的情況 | 推薦使用 | 備注 |
有清晰的整數(shù)型數(shù)據(jù)在列表,元組,或者集合的數(shù)據(jù)結(jié)構(gòu)中,并且你不想引入任何第三方那個(gè)庫 | 標(biāo)準(zhǔn)庫Collection.counter()提供了快速直接的頻數(shù)實(shí)現(xiàn)方法 | 這只是頻數(shù)的一個(gè)表,不存在histogram真正意義上的分箱 |
大的數(shù)組數(shù)據(jù),并且你只是想要計(jì)算含有分箱的直方圖(無可視化,純數(shù)學(xué)計(jì)算) | Numpy的np.histogram()和np.bincount()對于直方圖的純數(shù)學(xué)計(jì)算時(shí)非常有幫助的 | 更多請查閱np.digitize() |
數(shù)據(jù)存在于在Pandas的Series和DataFrame對象中 | Pandas方法,比如,?Series.plot.hist(),DataFrame.plot.hist(),Series.value_counts(),and?cut(),Series.plot.kde()?以及DataFrame.plot.kde() | 參考pandas的visualization章節(jié) |
從任意數(shù)據(jù)結(jié)構(gòu)中,創(chuàng)建一個(gè)高度定制化可調(diào)節(jié)的直方圖 | 推薦使用基于np.histogram()的Pyplot.hist()函數(shù),被頻繁使用,簡單易懂。 | Matplotlib可定制化 |
提前封裝的設(shè)計(jì)和集成(而非定制的) | Seaborn的distplot(),可以方便的結(jié)合直方圖和KDE繪圖 | 高級封裝 |
以上就是本篇所有內(nèi)容,直方圖的各種玩法你get到了嗎?
備注:公眾號菜單包含了整理了一本AI小抄,非常適合在通勤路上用學(xué)習(xí)。
往期精彩回顧適合初學(xué)者入門人工智能的路線及資料下載機(jī)器學(xué)習(xí)在線手冊深度學(xué)習(xí)在線手冊備注:加入本站微信群或者qq群,請回復(fù)“加群”獲取一折本站知識星球優(yōu)惠券,請回復(fù)“知識星球”喜歡文章,點(diǎn)個(gè)在看
總結(jié)
以上是生活随笔為你收集整理的5 种方法教你用Python玩转histogram直方图的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谈谈考研复试的一些经验和建议
- 下一篇: 评价指标:目标检测的评价指标 - mAP