您好,登錄后才能下訂單哦!
本文章向大家介紹如何在Python中實現(xiàn)一個histogram直方圖功能的基本知識點總結(jié)和需要注意事項,具有一定的參考價值,需要的朋友可以參考一下。
Python主要應(yīng)用于:1、Web開發(fā);2、數(shù)據(jù)科學(xué)研究;3、網(wǎng)絡(luò)爬蟲;4、嵌入式應(yīng)用開發(fā);5、游戲開發(fā);6、桌面應(yīng)用開發(fā)。
純Python實現(xiàn)histogram
當(dāng)準(zhǔn)備用純Python來繪制直方圖的時候,最簡單的想法就是將每個值出現(xiàn)的次數(shù)以報告形式展示。這種情況下,使用 字典 來完成這個任務(wù)是非常合適的,我們看看下面代碼是如何實現(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()
返回了一個字典,字典里出現(xiàn)的鍵為目標(biāo)列表里面的所有唯一數(shù)值,而值為所有數(shù)值出現(xiàn)的頻率次數(shù)。hist[i] = hist.get(i, 0) + 1
實現(xiàn)了每個數(shù)值次數(shù)的累積,每次加一。
實際上,這個功能可以用一個Python的標(biāo)準(zhǔn)庫 collection.Counter
類來完成,它兼容Pyhont 字典并覆蓋了字典的 .update()
方法。
>>> from collections import Counter >>> recounted = Counter(a) >>> recounted Counter({0: 1, 1: 3, 3: 1, 2: 1, 7: 2, 23: 1})
可以看到這個方法和前面我們自己實現(xiàn)的方法結(jié)果是一樣的,我們也可以通過 collection.Counter
來檢驗兩種方法得到的結(jié)果是否相等。
>>> recounted.items() == counted.items() True
我們利用上面的函數(shù)重新再造一個輪子 ASCII_histogram,并最終通過Python的輸出格式format來實現(xiàn)直方圖的展示,代碼如下:
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]))
這個函數(shù)按照數(shù)值大小順序進(jìn)行繪圖,數(shù)值出現(xiàn)次數(shù)用 (+) 符號表示。在字典上調(diào)用 sorted()
將會返回一個按鍵順序排列的列表,然后就可以獲取相應(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 ++++++++++++
這個代碼中,vals內(nèi)的數(shù)值是不重復(fù)的,并且每個數(shù)值出現(xiàn)的頻數(shù)是由我們自己定義的,在5和15之間隨機(jī)選擇。然后運(yùn)用我們上面封裝的函數(shù),就得到了純Python版本的直方圖展示。
總結(jié):純python實現(xiàn)頻數(shù)表(非標(biāo)準(zhǔn)直方圖),可直接使用collection.Counter
方法實現(xiàn)。
使用Numpy實現(xiàn)histogram
以上是使用純Python來完成的簡單直方圖,但是從數(shù)學(xué)意義上來看,直方圖是分箱到頻數(shù)的一種映射,它可以用來估計變量的概率密度函數(shù)的。而上面純Python實現(xiàn)版本只是單純的頻數(shù)統(tǒng)計,不是真正意義上的直方圖。
因此,我們從上面實現(xiàn)的簡單直方圖繼續(xù)往下進(jìn)行升級。一個真正的直方圖首先應(yīng)該是將變量分區(qū)域(箱)的,也就是分成不同的區(qū)間范圍,然后對每個區(qū)間內(nèi)的觀測值數(shù)量進(jìn)行計數(shù)。恰巧,Numpy的直方圖方法就可以做到這點,不僅僅如此,它也是后面將要提到的matplotlib和pandas使用的基礎(chǔ)。
舉個例子,來看一組從拉普拉斯分布上提取出來的浮點型樣本數(shù)據(jù)。這個分布比標(biāo)準(zhǔn)正態(tài)分布擁有更寬的尾部,并有兩個描述參數(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])
由于這是一個連續(xù)型的分布,對于每個單獨(dú)的浮點值(即所有的無數(shù)個小數(shù)位置)并不能做很好的標(biāo)簽(因為點實在太多了)。但是,你可以將數(shù)據(jù)做 分箱 處理,然后統(tǒng)計每個箱內(nèi)觀察值的數(shù)量,這就是真正的直方圖所要做的工作。
下面我們看看是如何用Numpy來實現(xiàn)直方圖頻數(shù)統(tǒng)計的。
>>> 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])
這個結(jié)果可能不是很直觀。來說一下,np.histogram()
默認(rèn)地使用10個相同大小的區(qū)間(箱),然后返回一個元組(頻數(shù),分箱的邊界),如上所示。要注意的是:這個邊界的數(shù)量是要比分箱數(shù)多一個的,可以簡單通過下面代碼證實。
>>> hist.size, bin_edges.size (10, 11)
那問題來了,Numpy到底是如何進(jìn)行分箱的呢?只是通過簡單的 np.histogram()
就可以完成了,但具體是如何實現(xiàn)的我們?nèi)匀蝗徊恢?。下面讓我們來?code> np.histogram() 的內(nèi)部進(jìn)行解剖,看看到底是如何實現(xiàn)的(以最前面提到的a列表為例)。
>>> # 取a的最小值和最大值 >>> first_edge, last_edge = a.min(), a.max() >>> n_equal_bins = 10 # NumPy得默認(rèn)設(shè)置,10個分箱 >>> 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é)果也正好與實際吻合,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實現(xiàn)直方圖,可直接使用np.histogram()
或者np.bincount()
。
使用Matplotlib和Pandas可視化Histogram
從上面的學(xué)習(xí),我們看到了如何使用Python的基礎(chǔ)工具搭建一個直方圖,下面我們來看看如何使用更為強(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'
自動在寫好的兩個算法中擇優(yōu)選擇并最終算出最適合的分箱數(shù)。這里,算法的目的就是選擇出一個合適的區(qū)間(箱)寬度,并生成一個最能代表數(shù)據(jù)的直方圖來。
如果使用Python的科學(xué)計算工具實現(xiàn),那么可以使用Pandas的 Series.histogram()
,并通過 matplotlib.pyplot.hist()
來繪制輸入Series的直方圖,如下代碼所示。
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是一樣的,但生成的是對DataFrame數(shù)據(jù)中的每一列的直方圖。
總結(jié):通過pandas實現(xiàn)直方圖,可使用Seris.plot.hist()
,DataFrame.plot.hist()
,matplotlib實現(xiàn)直方圖可以用matplotlib.pyplot.hist()
。
繪制核密度估計(KDE)
KDE(Kernel density estimation)是核密度估計的意思,它用來估計隨機(jī)變量的概率密度函數(shù),可以將數(shù)據(jù)變得更平緩。
使用Pandas庫的話,你可以使用 plot.kde()
創(chuàng)建一個核密度的繪圖,plot.kde()
對于 Series和DataFrame數(shù)據(jù)結(jié)構(gòu)都適用。但是首先,我們先生成兩個不同的數(shù)據(jù)樣本作為比較(兩個正太分布的樣本):
>>> # 兩個正太分布的樣本 >>> 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)計參數(shù)對兩組數(shù)據(jù)進(jìn)行了簡單的對比?,F(xiàn)在,我們可以在同一個Matplotlib軸上繪制每個直方圖以及對應(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實現(xiàn)kde圖,可使用Seris.plot.kde()
,DataFrame.plot.kde()
。
使用Seaborn的完美替代
一個更高級可視化工具就是Seaborn,它是在matplotlib的基礎(chǔ)上進(jìn)一步封裝的強(qiáng)大工具。對于直方圖而言,Seaborn有 distplot()
方法,可以將單變量分布的直方圖和kde同時繪制出來,而且使用及其方便,下面是實現(xiàn)代碼(以上面生成的d為例):
import seaborn as sns sns.set_style('darkgrid') sns.distplot(d)
distplot方法默認(rèn)的會繪制kde,并且該方法提供了 fit 參數(shù),可以根據(jù)數(shù)據(jù)的實際情況自行選擇一個特殊的分布來對應(yīng)。
sns.distplot(d, fit=stats.laplace, kde=False)
注意這兩個圖微小的區(qū)別。第一種情況你是在估計一個未知的概率密度函數(shù)(PDF),而第二種情況是你是知道分布的,并想知道哪些參數(shù)可以更好的描述數(shù)據(jù)。
總結(jié):通過seaborn實現(xiàn)直方圖,可使用seaborn.distplot()
,seaborn也有單獨(dú)的kde繪圖seaborn.kde()
。
在Pandas中的其它工具
除了繪圖工具外,pandas也提供了一個方便的.value_counts()
方法,用來計算一個非空值的直方圖,并將之轉(zhuǎn)變成一個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()
也同樣是一個方便的方法,用來將數(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é):其它實現(xiàn)直方圖的方法,可使用.value_counts()
和pandas.cut()
。
該使用哪個方法?
至此,我們了解了很多種方法來實現(xiàn)一個直方圖。但是它們各自有什么有缺點呢?該如何對它們進(jìn)行選擇呢?當(dāng)然,一個方法解決所有問題是不存在的,我們也需要根據(jù)實際情況而考慮如何選擇,下面是對一些情況下使用方法的一個推薦,僅供參考。
以上就是小編為大家?guī)淼娜绾卧赑ython中實現(xiàn)一個histogram直方圖功能的全部內(nèi)容了,希望大家多多支持億速云!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。