5種方法教你用Python玩轉histogram直方圖

2021-02-19 Python數據科學
點擊上方「Python數據科學」,選擇「置頂公眾號」

關鍵時刻,第一時間送達!

直方圖是一個可以快速展示數據概率分布的工具,直觀易於理解,並深受數據愛好者的喜愛。大家平時可能見到最多就是 matplotlib,seaborn 等高級封裝的庫包,類似以下這樣的繪圖。

本篇博主將要總結一下使用Python繪製直方圖的所有方法,大致可分為三大類(詳細劃分是五類,參照文末總結):

下面,我們來逐一介紹每種方法的來龍去脈。

當準備用純Python來繪製直方圖的時候,最簡單的想法就是將每個值出現的次數以報告形式展示。這種情況下,使用 字典 來完成這個任務是非常合適的,我們看看下面代碼是如何實現的。

>>> 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() 返回了一個字典,字典裡出現的鍵為目標列表裡面的所有唯一數值,而值為所有數值出現的頻率次數。hist[i] = hist.get(i, 0) + 1 實現了每個數值次數的累積,每次加一。

實際上,這個功能可以用一個Python的標準庫 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})

可以看到這個方法和前面我們自己實現的方法結果是一樣的,我們也可以通過 collection.Counter 來檢驗兩種方法得到的結果是否相等。

>>> recounted.items() == counted.items()
True

我們利用上面的函數重新再造一個輪子 ASCII_histogram,並最終通過Python的輸出格式format來實現直方圖的展示,代碼如下:

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]))

這個函數按照數值大小順序進行繪圖,數值出現次數用 (+) 符號表示。在字典上調用 sorted() 將會返回一個按鍵順序排列的列表,然後就可以獲取相應的次數 counted[k] 。

>>> import random
>>> random.seed(1)

>>> vals = [1, 3, 4, 6, 8, 9, 10]
>>> 
>>> 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內的數值是不重複的,並且每個數值出現的頻數是由我們自己定義的,在5和15之間隨機選擇。然後運用我們上面封裝的函數,就得到了純Python版本的直方圖展示。

總結:純python實現頻數表(非標準直方圖),可直接使用collection.Counter方法實現。

以上是使用純Python來完成的簡單直方圖,但是從數學意義上來看,直方圖是分箱到頻數的一種映射,它可以用來估計變量的概率密度函數的。而上面純Python實現版本只是單純的頻數統計,不是真正意義上的直方圖。

因此,我們從上面實現的簡單直方圖繼續往下進行升級。一個真正的直方圖首先應該是將變量分區域(箱)的,也就是分成不同的區間範圍,然後對每個區間內的觀測值數量進行計數。恰巧,Numpy的直方圖方法就可以做到這點,不僅僅如此,它也是後面將要提到的matplotlib和pandas使用的基礎。

舉個例子,來看一組從拉普拉斯分布上提取出來的浮點型樣本數據。這個分布比標準正態分布擁有更寬的尾部,並有兩個描述參數(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])

由於這是一個連續型的分布,對於每個單獨的浮點值(即所有的無數個小數位置)並不能做很好的標籤(因為點實在太多了)。但是,你可以將數據做 分箱 處理,然後統計每個箱內觀察值的數量,這就是真正的直方圖所要做的工作。

下面我們看看是如何用Numpy來實現直方圖頻數統計的。

>>> 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])

這個結果可能不是很直觀。來說一下,np.histogram() 默認地使用10個相同大小的區間(箱),然後返回一個元組(頻數,分箱的邊界),如上所示。要注意的是:這個邊界的數量是要比分箱數多一個的,可以簡單通過下面代碼證實。

>>> hist.size, bin_edges.size
(10, 11)

那問題來了,Numpy到底是如何進行分箱的呢?只是通過簡單的 np.histogram() 就可以完成了,但具體是如何實現的我們仍然全然不知。下面讓我們來將 np.histogram() 的內部進行解剖,看看到底是如何實現的(以最前面提到的a列表為例)。

>>> 
>>> first_edge, last_edge = a.min(), a.max()

>>> n_equal_bins = 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列表的最小值和最大值,然後設置默認的分箱數量,最後使用Numpy的 linspace 方法進行數據段分割。分箱區間的結果也正好與實際吻合,0到23均等分為10份,23/10,那麼每份寬度為2.3。

除了np.histogram之外,還存在其它兩種可以達到同樣功能的方法:np.bincount() 和 np.searchsorted(),下面看看代碼以及比較結果。

>>> bcounts = np.bincount(a)
>>> hist, _ = np.histogram(a, range=(0, a.max()), bins=a.max() + 1)

>>> np.array_equal(hist, bcounts)
True

>>> 
>>> dict(zip(np.unique(a), bcounts[bcounts.nonzero()]))
{0: 1, 1: 3, 2: 1, 3: 1, 7: 2, 23: 1}

總結:通過Numpy實現直方圖,可直接使用np.histogram()或者np.bincount()。


使用Matplotlib和Pandas可視化Histogram

從上面的學習,我們看到了如何使用Python的基礎工具搭建一個直方圖,下面我們來看看如何使用更為強大的Python庫包來完成直方圖。Matplotlib基於Numpy的histogram進行了多樣化的封裝並提供了更加完善的可視化功能。

import matplotlib.pyplot as plt


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()

plt.ylim(ymax=np.ceil(maxfreq / 10) * 10 if maxfreq % 10 else maxfreq + 10)

之前我們的做法是,在x軸上定義了分箱邊界,y軸是相對應的頻數,不難發現我們都是手動定義了分箱的數目。但是在以上的高級方法中,我們可以通過設置 bins='auto' 自動在寫好的兩個算法中擇優選擇並最終算出最適合的分箱數。這裡,算法的目的就是選擇出一個合適的區間(箱)寬度,並生成一個最能代表數據的直方圖來。

如果使用Python的科學計算工具實現,那麼可以使用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數據中的每一列的直方圖。

總結:通過pandas實現直方圖,可使用Seris.plot.hist(),DataFrame.plot.hist(),matplotlib實現直方圖可以用matplotlib.pyplot.hist()


KDE(Kernel density estimation)是核密度估計的意思,它用來估計隨機變量的概率密度函數,可以將數據變得更平緩。

使用Pandas庫的話,你可以使用 plot.kde() 創建一個核密度的繪圖,plot.kde() 對於 Series和DataFrame數據結構都適用。但是首先,我們先生成兩個不同的數據樣本作為比較(兩個正太分布的樣本):

>>> 
>>> 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

以上看到,我們生成了兩組正態分布樣本,並且通過一些描述性統計參數對兩組數據進行了簡單的對比。現在,我們可以在同一個Matplotlib軸上繪製每個直方圖以及對應的kde,使用pandas的plot.kde()的好處就是:它會自動的將所有列的直方圖和kde都顯示出來,用起來非常方便,具體代碼如下:

fig, ax = plt.subplots()
dist.plot.kde(ax=ax, legend=False, title=
dist.plot.hist(density=True, ax=ax)
ax.set_ylabel(
ax.grid(axis=
ax.set_facecolor(

總結:通過pandas實現kde圖,可使用Seris.plot.kde(),DataFrame.plot.kde()。


一個更高級可視化工具就是Seaborn,它是在matplotlib的基礎上進一步封裝的強大工具。對於直方圖而言,Seaborn有 distplot() 方法,可以將單變量分布的直方圖和kde同時繪製出來,而且使用及其方便,下面是實現代碼(以上面生成的d為例):

import seaborn as sns

sns.set_style('darkgrid')
sns.distplot(d)

distplot方法默認的會繪製kde,並且該方法提供了 fit 參數,可以根據數據的實際情況自行選擇一個特殊的分布來對應。

sns.distplot(d, fit=stats.laplace, kde=False)

注意這兩個圖微小的區別。第一種情況你是在估計一個未知的概率密度函數(PDF),而第二種情況是你是知道分布的,並想知道哪些參數可以更好的描述數據。

總結:通過seaborn實現直方圖,可使用seaborn.distplot(),seaborn也有單獨的kde繪圖seaborn.kde()。


除了繪圖工具外,pandas也提供了一個方便的.value_counts() 方法,用來計算一個非空值的直方圖,並將之轉變成一個pandas的series結構,示例如下:

>>> 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() 也同樣是一個方便的方法,用來將數據進行強制的分箱。比如說,我們有一些人的年齡數據,並想把這些數據按年齡段進行分類,示例如下:

>>> 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 代碼來完成,在運行速度的效果上也是非常快的。

總結:其它實現直方圖的方法,可使用.value_counts()和pandas.cut()

至此,我們了解了很多種方法來實現一個直方圖。但是它們各自有什麼有缺點呢?該如何對它們進行選擇呢?當然,一個方法解決所有問題是不存在的,我們也需要根據實際情況而考慮如何選擇,下面是對一些情況下使用方法的一個推薦,僅供參考。

你的情況

推薦使用

備註

有清晰的整數型數據在列表,元組,或者集合的數據結構中,並且你不想引入任何第三方那個庫

標準庫Collection.counter()提供了快速直接的頻數實現方法

這只是頻數的一個表,不存在histogram真正意義上的分箱

大的數組數據,並且你只是想要計算含有分箱的直方圖(無可視化,純數學計算)

Numpy的np.histogram()和np.bincount()對於直方圖的純數學計算時非常有幫助的

更多請查閱np.digitize()

數據存在於在Pandas的Series和DataFrame對象中

Pandas方法,比如, Series.plot.hist(),DataFrame.plot.hist(),Series.value_counts(),and cut(),Series.plot.kde() 以及DataFrame.plot.kde()

參考pandas的visualization章節

從任意數據結構中,創建一個高度定製化可調節的直方圖

推薦使用基於np.histogram()的Pyplot.hist()函數,被頻繁使用,簡單易懂。

Matplotlib可定製化

提前封裝的設計和集成(而非定製的)

Seaborn的distplot(),可以方便的結合直方圖和KDE繪圖

高級封裝

參考:https://realpython.com/python-histograms/

以上就是本篇所有內容,直方圖的各種玩法你get到了嗎?

如果想了解更多精彩內容,可以掃以下二維碼加入知識星球,星球將分享各種數據分析和挖掘的乾貨,以及一些實戰項目講解(Kaggle競賽項目,網際網路金融風險控制項目),以及轉行數據分析挖掘的經驗。

發送 學習資料,獲取經典書籍電子書

長按二維碼 關注Python數據科學

相關焦點

  • 5 種方法教你用Python玩轉histogram直方圖
    然後運用我們上面封裝的函數,就得到了純Python版本的直方圖展示。總結:純python實現頻數表(非標準直方圖),可直接使用collection.Counter方法實現。以上是使用純Python來完成的簡單直方圖,但是從數學意義上來看,直方圖是分箱到頻數的一種映射,它可以用來估計變量的概率密度函數的。
  • OpenCV-Python 直方圖-1:查找、繪製和分析|二十六
    目標學會使用OpenCV和Numpy函數查找直方圖使用OpenCV和Matplotlib函數繪製直方圖你將看到以下函數:cv.calcHist(),np.histogram()等。理論那麼直方圖是什麼?您可以將直方圖視為圖形或繪圖,從而可以總體了解圖像的強度分布。
  • 用Python為直方圖繪製擬合曲線的兩種方法
    ,用矩形的寬度和高度表示頻數分布,通過直方圖,用戶可以很直觀的看出數據分布的形狀、中心位置以及數據的離散程度等。在python中一般採用matplotlib庫的hist來繪製直方圖,至於如何給直方圖添加擬合曲線(密度函數曲線),一般來說有以下兩種方法。方法一:採用matplotlib中的mlab模塊mlab模塊是Python中強大的3D作圖工具,立體感效果極佳。
  • 圖像學習之如何理解方向梯度直方圖(Histogram Of Gradient)
    什麼樣子的特徵是有用的呢?假設我們想要預測一張圖片裡面衣服上面的扣子,扣子通常是圓的,而且上面有幾個洞,那你就可以用邊緣檢測(edge detector),把圖片變成只有邊緣的圖像,然後就可以很容易的分辨了,那麼對於這張圖邊緣信息就是有用的,顏色信息就是沒有用的。而且好的特徵應該能夠區分紐扣和其它圓形的東西的區別。方向梯度直方圖(HOG)中,梯度的方向分布被用作特徵。
  • Stata:讀懂直方圖
    好用的外部命令3.1 命令1:histbox3.2 命令2:historaj3.3 命令3:histoflogx3.4 命令4:eqprhistogram3.5 命令 5:spechist3.6 命令6:marhis3.7 命令7:bihist3.8 命令 8:byhist4
  • 騰訊大牛教你MySQL 8.0 PFS histogram解析與優化
    1PartⅡ MySQL 8.0 histogram 介紹上面提到的summary表中,比較籠統的記錄了語句執行的耗時等信息,包含了最大值、最小值、平均值等,但是這些信息可能不夠,不能直觀地看到同一語句的用時分布情況。
  • OpenCV-Python 直方圖-2:直方圖均衡|二十七
    import numpy as npimport cv2 as cvfrom matplotlib import pyplot as pltimg = cv.imread('wiki.jpg',0)hist,bins = np.histogram(img.flatten(),256,[0,256])cdf = hist.cumsum()cdf_normalized = cdf
  • OpenCL 學習step by step (7) 灰度圖Histogram計算(1)
    histogram翻譯成中文就是直方圖,在計算機圖像處理和視覺技術中,通常用histogram來進行圖像匹配,從而完成track,比如meanshift跟蹤算法中,經常要用到圖像的直方圖。灰度圖的histogram計算,首先要選擇bin(中文可以稱作槽)的數量,對於灰度圖,像素的範圍通常是[0-255],所以bin的數目就是256,然後我們循環整幅圖像,統計出每種像素值出現的次數,放到對應的bin中。
  • Python學習第89課-數據可視化之直方圖繪製
    【每天幾分鐘,從零入門python編程的世界!】假設你想投資某一個城市的房地產,你肯定需要對這個城市的人口分布、增長潛力做一個調查,這時我們就需要做一個直方圖(histogram plot)。實例:假設我們有一份C城市的人口年齡的數據,我們為方便起見,在這裡只列出很少的一部分數據,用一個列表City_C_Age表示這個城市的人口年齡數據:City_C_Age=[2,3,1,7,4,5,3,1,7,6,9,8,13,16,12,17,15,14,18,,20,23,28,25,23,27,25,29,26,24,25,30,34,37,36,38,32,35,37,36,41,42,47,47,48,43,46,44,53,54,58,52,51,55,56,69,60,63,67,64,72,74,78,75,89,85,83,90,93
  • OpenCV-Python 直方圖-4:直方圖反投影|二十九
    目標在本章中,我們將學習直方圖反投影。理論這是由Michael J. Swain和Dana H. Ballard在他們的論文《通過顏色直方圖索引》中提出的。用簡單的話說是什麼意思?它用於圖像分割或在圖像中查找感興趣的對象。
  • 用Keras和「直方圖均衡」為深度學習實現「圖像擴充」
    https://studentathome.wordpress.com/2013/03/27/local-histogram-equalization/在本文中,我們將討論三種用於提高圖像對比度的圖像擴充方法。
  • 獨家 | Tableau小技巧之分離Box plot和Unit Histogram(附連結)
    Could be》中探索了可視化jitterplot圖(帶有隨機jitter的點圖,jitter用來區分點)的替代方法:博客文章連結:https://www.datarevelations.com/betterthanjitterplot.html在本文中,我將展示如何創建與單位直方圖(unit histogram)區分的箱形圖
  • 深度學習必備---用Keras和直方圖均衡化---數據增強
    ,我借鑑使用keras深度學習庫為增強圖像提供的一些最常用的開箱即用方法,然後演示如何修改keras.preprocessing image.py文件以啟用直方圖均衡化方法。路徑(對於mac用戶)可能如下所示:/usr/local/lib/python3.5/dist-packages/keras/__init__.pyc這給了我們在本地機器上keras的路徑。
  • Python 線性分類模型簡介
    注意:取決於你使用的模型的種類,參數可能會多的多。但是在最底層,你會經常遇到4個參數化學習的基本模塊。一旦確定了這4個關鍵組件,我們就可以使用優化方法來找到評分函數的一組參數W,使得損失函數達到最小值(同時提升對數據的分類準確度)接下來,我們就將看到如何利用這些組件,搭建一個將輸入數據轉化為真實預測值的分類器。
  • 教你快速看懂直方圖
    提到照片的直方圖,我們肯定有印象,在相機或軟體中預覽每張照片時都在圖像信息欄見到過這樣一個數軸狀的圖表與照片一一對應。這就是代表圖片中各色調分配比例的直方圖67(histogram)。而這些色調,是由通過鏡頭的光線而決定的,因此我們要把直方圖和曝光聯繫起來討論。一、什麼是直方圖。
  • 直方圖是什麼,用Excel教你製作並分析最簡單的統計圖表
    什麼是直方圖直方圖(histogram),是一種二維統計圖,用於評估連續樣本數據的形狀和展開的圖形。可以在分析之前或在分析的同時創建直方圖,從而幫助確認假設並指導進一步的分析。反映的是數據在特定分組下的分布情況。
  • OpenCV系列之直方圖-2:直方圖均衡 | 二十七
    import numpy as npimport cv2 as cvfrom matplotlib import pyplot as pltimg = cv.imread('wiki.jpg',0)hist,bins = np.histogram(img.flatten(),256,[0,256])cdf = hist.cumsum()cdf_normalized
  • OpenCV 之 直方圖處理
    1.3.1  定義   直方圖均衡化,是將給定圖像的直方圖改造成均勻分布的直方圖,從而擴大像素灰度值的動態範圍,達到增強圖像對比度的效果。≈3,s(2)≈5,s(3)≈6,s(4)≈6,s(5)≈7,s(6)≈7,s(7)≈7  因為 r(k) -> s(k),所以 s(0)=1 對應有790個像素值。
  • OpenCV基礎 | 9.直方圖及直方圖均衡化
    作者丨小郭學數據來源丨快學Python學習視頻可參見pythpython+opencv3.3視頻教學 基礎入門outline1.圖像直方圖(histogram)一個灰度級在範圍[0,L-1]的數字圖像的 直 方圖是一個離散函數nk是圖像中灰度級為rk的像素個數 rk 是第k個灰度級,k = 0,1,2,…,L-1def plot_demo(image):
  • 【Python教程】用Python進行數據可視化
    下面我們就用上面這個簡單的數據集作為例子,展示用 Python 做出9種可視化效果,並附有相關代碼。散點圖最適合研究不同變量之間的關係:男性與女性人群中不同年齡階段得皮膚病的可能性IQ測試得分和GPA之間的相關性另外我們還要考慮:2.3.5 直方圖直方圖(Histogram