農林漁牧網

您現在的位置是:首頁 > 農業

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

2022-02-17由 故事你真的zai聽嗎 發表于 農業

頻數分佈直方圖怎麼做

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

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

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

純Python實現直方圖,不使用任何第三方庫

使用Numpy來建立直方圖總結資料

使用matplotlib,pandas,seaborn繪製直方圖

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

純Python實現histogram

當準備用純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]

>>

> # `vals` 裡面的數字將會出現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內的數值是不重複的,並且每個數值出現的頻數是由我們自己定義的,在5和15之間隨機選擇。然後運用我們上面封裝的函式,就得到了純Python版本的直方圖展示。

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

使用Numpy實現histogram

以上是使用純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列表為例)。

>>> # 取a的最小值和最大值

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

>>> n_equal_bins = 10 # NumPy得預設設定,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

>>

> # Reproducing `collections。Counter`

>>

> 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

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

# 設定y軸的上限

plt。ylim(ymax=np。ceil(maxfreq / 10) * 10

if

maxfreq % 10

else

maxfreq + 10)

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

之前我們的做法是,在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)

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

pandas。DataFrame。histogram() 的用法與Series是一樣的,但生成的是對DataFrame資料中的每一列的直方圖。

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

繪製核密度估計(KDE)

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=‘Histogram: A vs。 B’)

dist。plot。hist(density=True, ax=ax)

ax。set_ylabel(‘Probability’)

ax。grid(axis=‘y’)

ax。set_facecolor(‘#d8dcd6’)

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

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

使用Seaborn的完美替代

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

import

seaborn

as

sns

sns。set_style(‘darkgrid’)

sns。distplot(d)

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

distplot方法預設的會繪製kde,並且該方法提供了 fit 引數,可以根據資料的實際情況自行選擇一個特殊的分佈來對應。

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

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

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

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

在Pandas中的其它工具

除了繪圖工具外,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()。

該使用哪個方法?

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

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