這篇文章主要給大家介紹了關(guān)于如何利用Python玩轉(zhuǎn)histogram直方圖的五種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
直方圖文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
直方圖是一個(gè)可以快速展示數(shù)據(jù)概率分布的工具,直觀易于理解,并深受數(shù)據(jù)愛好者的喜愛。大家平時(shí)可能見到最多就是 matplotlib,seaborn 等高級(jí)封裝的庫包,類似以下這樣的繪圖。文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
本篇博主將要總結(jié)一下使用Python繪制直方圖的所有方法,大致可分為三大類(詳細(xì)劃分是五類,參照文末總結(jié)):文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
- 純Python實(shí)現(xiàn)直方圖,不使用任何第三方庫
- 使用Numpy來創(chuàng)建直方圖總結(jié)數(shù)據(jù)
- 使用matplotlib,pandas,seaborn繪制直方圖
下面,我們來逐一介紹每種方法的來龍去脈。文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
純Python實(shí)現(xiàn)histogram文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
當(dāng)準(zhǔn)備用純Python來繪制直方圖的時(shí)候,最簡單的想法就是將每個(gè)值出現(xiàn)的次數(shù)以報(bào)告形式展示。這種情況下,使用 字典 來完成這個(gè)任務(wù)是非常合適的,我們看看下面代碼是如何實(shí)現(xiàn)的。文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
1 2 3 4 5 6 7 8 9 10 11 12 | >>> 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è)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
實(shí)際上,這個(gè)功能可以用一個(gè)Python的標(biāo)準(zhǔn)庫?collection.Counter
?類來完成,它兼容Pyhont 字典并覆蓋了字典的?.update()
?方法。文章源自四五設(shè)計(jì)網(wǎng)-http://www.4968ejs.cn/39579.html
1 2 3 4 5 | >>> from collections import Counter >>> recounted = Counter(a) >>> recounted Counter({ 0 : 1 , 1 : 3 , 3 : 1 , 2 : 1 , 7 : 2 , 23 : 1 }) |
可以看到這個(gè)方法和前面我們自己實(shí)現(xiàn)的方法結(jié)果是一樣的,我們也可以通過?collection.Counter?
來檢驗(yàn)兩種方法得到的結(jié)果是否相等。
1 2 | >>> recounted.items() = = counted.items() True |
我們利用上面的函數(shù)重新再造一個(gè)輪子 ASCII_histogram,并最終通過Python的輸出格式format來實(shí)現(xiàn)直方圖的展示,代碼如下:
1 2 3 4 5 | def ascii_histogram(seq) - > None : ? """A horizontal frequency-table/histogram plot.""" ? counted = count_elements(seq) ? for k in sorted (counted): ? print ( '{0:5d} {1}' . format (k, '+' * counted[k])) |
這個(gè)函數(shù)按照數(shù)值大小順序進(jìn)行繪圖,數(shù)值出現(xiàn)次數(shù)用 (+) 符號(hào)表示。在字典上調(diào)用?sorted()?
將會(huì)返回一個(gè)按鍵順序排列的列表,然后就可以獲取相應(yīng)的次數(shù)?counted[k]?
?。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | >>> import random >>> random.seed( 1 ) >>> vals = [ 1 , 3 , 4 , 6 , 8 , 9 , 10 ] >>> # `vals` 里面的數(shù)字將會(huì)出現(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)行升級(jí)。一個(gè)真正的直方圖首先應(yīng)該是將變量分區(qū)域(箱)的,也就是分成不同的區(qū)間范圍,然后對(duì)每個(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):
1 2 3 4 5 6 7 8 | >>> 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ù)型的分布,對(duì)于每個(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ì)的。
1 2 3 4 5 6 7 8 | >>> 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í)。
1 2 | >>> hist.size, bin_edges.size ( 10 , 11 ) |
那問題來了,Numpy到底是如何進(jìn)行分箱的呢?只是通過簡單的?np.histogram()?
就可以完成了,但具體是如何實(shí)現(xiàn)的我們?nèi)匀蝗徊恢O旅孀屛覀儊韺?code>?np.histogram()?的內(nèi)部進(jìn)行解剖,看看到底是如何實(shí)現(xiàn)的(以最前面提到的a列表為例)。
1 2 3 4 5 6 7 8 9 | >>> # 取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é)果。
1 2 3 4 5 6 7 8 9 | >>> 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)行了多樣化的封裝并提供了更加完善的可視化功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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軸是相對(duì)應(yīng)的頻數(shù),不難發(fā)現(xiàn)我們都是手動(dòng)定義了分箱的數(shù)目。但是在以上的高級(jí)方法中,我們可以通過設(shè)置?bins='auto'?
自動(dòng)在寫好的兩個(gè)算法中擇優(yōu)選擇并最終算出最適合的分箱數(shù)。這里,算法的目的就是選擇出一個(gè)合適的區(qū)間(箱)寬度,并生成一個(gè)最能代表數(shù)據(jù)的直方圖來。
如果使用Python的科學(xué)計(jì)算工具實(shí)現(xiàn),那么可以使用Pandas的?Series.histogram()?
,并通過?matplotlib.pyplot.hist()?
來繪制輸入Series的直方圖,如下代碼所示。
1 2 3 4 5 6 7 8 9 10 11 | import pandas as pd size, 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是一樣的,但生成的是對(duì)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()
?對(duì)于 Series和DataFrame數(shù)據(jù)結(jié)構(gòu)都適用。但是首先,我們先生成兩個(gè)不同的數(shù)據(jù)樣本作為比較(兩個(gè)正太分布的樣本):
1 2 3 4 5 6 7 8 9 10 11 12 | >>> # 兩個(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ù)對(duì)兩組數(shù)據(jù)進(jìn)行了簡單的對(duì)比。現(xiàn)在,我們可以在同一個(gè)Matplotlib軸上繪制每個(gè)直方圖以及對(duì)應(yīng)的kde,使用pandas的plot.kde()
的好處就是:它會(huì)自動(dòng)的將所有列的直方圖和kde都顯示出來,用起來非常方便,具體代碼如下:
1 2 3 4 5 6 | 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è)更高級(jí)可視化工具就是Seaborn,它是在matplotlib的基礎(chǔ)上進(jìn)一步封裝的強(qiáng)大工具。對(duì)于直方圖而言,Seaborn有?distplot()
?方法,可以將單變量分布的直方圖和kde同時(shí)繪制出來,而且使用及其方便,下面是實(shí)現(xiàn)代碼(以上面生成的d為例):
1 2 3 4 | import seaborn as sns sns.set_style( 'darkgrid' ) sns.distplot(d) |
distplot方法默認(rèn)的會(huì)繪制kde,并且該方法提供了 fit 參數(shù),可以根據(jù)數(shù)據(jù)的實(shí)際情況自行選擇一個(gè)特殊的分布來對(duì)應(yīng)。
1 | 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),示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | >>> 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)行分類,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | >>> 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.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 |
除了使用方便外,更加好的是這些操作最后都會(huì)使用 Cython 代碼來完成,在運(yùn)行速度的效果上也是非常快的。
總結(jié):其它實(shí)現(xiàn)直方圖的方法,可使用.value_counts()
和pandas.cut()
?。
該使用哪個(gè)方法?
至此,我們了解了很多種方法來實(shí)現(xiàn)一個(gè)直方圖。但是它們各自有什么有缺點(diǎn)呢?該如何對(duì)它們進(jìn)行選擇呢?當(dāng)然,一個(gè)方法解決所有問題是不存在的,我們也需要根據(jù)實(shí)際情況而考慮如何選擇,下面是對(duì)一些情況下使用方法的一個(gè)推薦,僅供參考。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)四五設(shè)計(jì)網(wǎng)的支持。


評(píng)論