Pythonヒストグラムプロット:NumPy、Matplotlib、Pandas&Seaborn

Pythonヒストグラムプロット:NumPy、Matplotlib、Pandas&Seaborn

このチュートリアルでは、さまざまな選択肢と機能を備えた、生産品質のプレゼンテーション対応のPythonヒストグラムプロットを作成できます。

Pythonと統計の中間知識の入門書がある場合は、NumPy、Matplotlib、Pandas、Seabornなどの科学スタックのライブラリを使用して、Pythonでヒストグラムを作成およびプロットするためのワンストップショップとしてこの記事を使用できます。

ヒストグラムは、ほとんどすべての聴衆が直感的に理解できるhttps://en.wikipedia.org/wiki/Probability_distribution [確率分布]をすばやく評価するための優れたツールです。 Pythonは、ヒストグラムを作成およびプロットするためのいくつかの異なるオプションを提供します。 ほとんどの人は、棒グラフに似たグラフィカルな表現でヒストグラムを知っています。

1000人の通勤者の通勤時間のヒストグラム、幅= 1152、 height = 888

この記事では、上記のようなプロットとより複雑なプロットを作成する方法を説明します。 カバーする内容は次のとおりです。

  • サードパーティのライブラリを使用せずに、純粋なPythonでヒストグラムを作成する

  • NumPyを使用してヒストグラムを作成し、基礎となるデータを要約します

  • Matplotlib、Pandas、およびSeabornを使用して結果のヒストグラムをプロットする

*無料ボーナス:*時間不足? ここをクリックして、無料の2ページのPythonヒストグラムチートシートにアクセスしてください。このチュートリアルで説明されているテクニックを要約しています。

Pure Pythonのヒストグラム

ヒストグラムをプロットする準備をしているとき、ビンの観点から考えるのではなく、各値が出現する回数を報告するのが最も簡単です(度数分布表)。 Python dictionaryは、このタスクに最適です。

>>>

>>> # Need not be sorted, necessarily
>>> 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()`は、シーケンスの一意の要素をキーとして、その頻度(カウント)を値として持つ辞書を返します。 ` seq `のループ内で、 ` hist [i] = hist.get(i、0)+ 1 `は、「シーケンスの各要素について、 ` hist +`の対応する値を1増やします。 」

実際、これはPythonの標準ライブラリの `+ collections.Counter `クラスによって正確に行われます。これはhttps://github.com/python/cpython/blob/7f1bcda9bc3c04100cb047373732db0eba00e581/Lib/collections/__init__.py#L466 [サブクラス] Python辞書とその ` .update()+`メソッドをオーバーライドします:

>>>

>>> from collections import Counter

>>> recounted = Counter(a)
>>> recounted
Counter({0: 1, 1: 3, 3: 1, 2: 1, 7: 2, 23: 1})

手作りの関数が2つの間の等価性をテストすることで、 `+ collections.Counter +`と実質的に同じことを行うことを確認できます。

>>>

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

技術的な詳細:上記の `+ count_elements()`からのマッピングは、より高度に最適化されたhttps://github.com/python/cpython/blob/a5c42284e69fb309bdd17ee8c1c120d1be383012/Modules/_collectionsmodule.c#L2250[C function] if利用できます。 Python関数 ` count_elements()`内で行うことができる1つのマイクロ最適化は、forループの前に ` get = hist.get +`を宣言することです。 これにより、メソッドを変数にバインドして、ループ内の呼び出しを高速化できます。

より複雑な機能を理解するための最初のステップとして、簡素化された機能をゼロから作成すると役立ちます。 Pythonのhttps://docs.python.org/tutorial/inputoutput.html#fancier-output-formatting [出力フォーマット]を活用するASCIIヒストグラムを使用して、ホイールをさらに再発明しましょう。

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] `でそれぞれの対応する値にアクセスします。 これを実際に見るために、Pythonの ` random +`モジュールを使用してわずかに大きなデータセットを作成できます。

>>>

>>> # No NumPy ... yet
>>> import random
>>> random.seed(1)

>>> vals = [1, 3, 4, 6, 8, 9, 10]
>>> # Each number in `vals` will occur between 5 and 15 times.
>>> 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 ++++++++++++

ここでは、 + freq +(https://www.python.org/dev/peps/pep-0289/[generator expression])で指定された頻度で、 `+ vals `からの抜き取りをシミュレートしています。 結果のサンプルデータは、「 vals +」からの各値を5〜15の特定の回数繰り返します。

:https://docs.python.org/library/random.html#random.seed [+ random.seed()+]は、基になる擬似乱数ジェネレーター(https://en.wikipedia.org/wiki/Pseudorandom_number_generator[PRNG]) `+ random `によって使用されます。 矛盾のように聞こえるかもしれませんが、これはランダムなデータを再現可能かつ決定論的にする方法です。 つまり、ここにコードをそのままコピーすると、ジェネレーターをシードした後の ` random.randint()+`の最初の呼び出しでhttps://を使用して同一の「ランダム」データが生成されるため、まったく同じヒストグラムが得られます。 en.wikipedia.org/wiki/Mersenne_Twister[Mersenne Twister]。

ベースからの構築:NumPyでのヒストグラム計算

これまでは、「頻度テーブル」と呼ばれるのが最善の方法で作業してきました。しかし数学的には、ヒストグラムはビン(間隔)の頻度へのマッピングです。 より技術的には、基になる変数の確率密度関数(https://en.wikipedia.org/wiki/Probability_density_function[PDF])を近似するために使用できます。

上記の「度数分布表」から移動すると、真のヒストグラムはまず値の範囲を「ビン化」し、次に各ビンに入る値の数をカウントします。 これはhttps://realpython.com/numpy-array-programming/[NumPy’s] `+ histogram()+`関数が行うことであり、これはMatplotlibなどのPythonライブラリで後述する他の関数の基礎となりますとパンダ。

Laplace distributionから引き出されたフロートのサンプルを検討してください。 この分布には、正規分布よりも裾が広く、2つの記述パラメーター(位置とスケール)があります。

>>>

>>> import numpy as np
>>> # `numpy.random` uses its own PRNG.
>>> 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])

この場合、連続分布で作業しているため、各フロートを独立して、小数点以下10桁まで集計することはあまり役に立ちません。 代わりに、データをビンまたは「バケット化」し、各ビンに入る観測値をカウントできます。 ヒストグラムは、各ビン内の値の結果カウントです。

>>>

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

この結果はすぐに直観的ではないかもしれません。 https://docs.scipy.org/doc/numpy/reference/generated/numpy.histogram.html [+ np.histogram()+]はデフォルトで10個の同じサイズのビンを使用し、頻度カウントのタプルと対応するビンのエッジ。 これらは、ヒストグラムのメンバーよりもビンエッジが1つ多いという意味でのエッジです。

>>>

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

技術的な詳細:最後の(右端の)ビン以外はすべて半分開いています。 つまり、最後のビンを除くすべてのビンが[包括的、排他的]であり、最後のビンが[包括的、包括的]です。

ビンの構築方法の非常に凝縮された内訳https://github.com/numpy/numpy/blob/6a58e25703cbecb6786faa09a04ae2ec8221348b/numpy/lib/function_base.py#L432-L844[by NumPy]:

>>>

>>> # The leftmost and rightmost bin edges
>>> first_edge, last_edge = a.min(), a.max()

>>> n_equal_bins = 10  # NumPy's default
>>> 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. ])

上記のケースは非常に理にかなっています。23のピークツーピーク範囲で10個の等間隔ビンは、幅2.3の間隔を意味します。

そこから、関数はhttps://docs.scipy.org/doc/numpy/reference/generated/numpy.bincount.html [+ np.bincount()+]またはhttps://docs.scipyに委任します。 .org/doc/numpy/reference/generated/numpy.searchsorted.html [+ np.searchsorted()+]。 `+ bincount()+`自体を使用して、ここから始めた「頻度テーブル」を効果的に作成できます。ただし、ゼロオカレンスの値が含まれる点が異なります。

>>>

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

注意:ここでの `+ hist `は、「離散的な」カウントではなく、幅1.0のビンを実際に使用しています。 したがって、これは整数のカウントに対してのみ機能し、 ` [3.9、4.1、4.15] +`のような浮動小数点数には機能しません。

MatplotlibとPandasを使用したヒストグラムの視覚化

Pythonでヒストグラムをゼロから作成する方法を確認したので、他のPythonパッケージがどのように機能するかを見てみましょう。 Matplotlibは、NumPyの `+ histogram()+`の汎用ラッパーを使用して、箱から出してすぐにPythonヒストグラムを視覚化する機能を提供します。

import matplotlib.pyplot as plt

# An "interface" to matplotlib.axes.Axes.hist() method
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()
# Set a clean upper y-axis limit.
plt.ylim(ymax=np.ceil(maxfreq/10) *10 if maxfreq % 10 else maxfreq + 10)

Histogram

前に定義したように、ヒストグラムのプロットでは、x軸にビンのエッジを使用し、y軸に対応する周波数を使用します。 上記のチャートでは、「+ bins = 'auto' +」を渡すと、「理想的な」ビン数を推定するために2つのアルゴリズムから選択されます。 高いレベルでのアルゴリズムの目標は、データの最も忠実な表現を生成するビン幅を選択することです。 かなり技術的になる可能性のあるこのテーマの詳細については、Astropy docsのhttp://docs.astropy.org/en/stable/visualization/histogram.html [ヒストグラムビンの選択]をご覧ください。

Pythonの科学スタックであるPandasの + Series.histogram()+ uses + matplotlib。入力シリーズのMatplotlibヒストグラムを描画するpyplot.hist()+ `

import pandas as pd

# Generate data on commute times.
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)

1000人の通勤者の通勤時間のヒストグラム、幅= 1152、 height = 888

`+ pandas.DataFrame.histogram()+`も同様ですが、DataFrameのデータの各列のヒストグラムを生成します。

カーネル密度推定(KDE)のプロット

このチュートリアルでは、統計的に言えば、サンプルを操作しました。 データが離散的であろうと連続的であろうと、ほんの数個のパラメーターで記述された真の正確な分布を持つ母集団から派生したものと想定されます。

カーネル密度推定(KDE)は、サンプルの「基礎となる」確率変数の確率密度関数(PDF)を推定する方法です。 KDEはデータの平滑化の手段です。

Pandasライブラリに固執して、 `+ plot.kde()`を使用して密度プロットを作成およびオーバーレイできます。これは、 ` Series `および ` DataFrame +`オブジェクトの両方で使用できます。 しかし、最初に、比較のために2つの異なるデータサンプルを生成しましょう。

>>>

>>> # Sample from two different normal distributions
>>> 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軸に各ヒストグラムをプロットするには:

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

Histogram

これらのメソッドは、SciPyのhttps://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.gaussian_kde.html [+ gaussian_kde()+]を活用して、より滑らかなPDFを作成します。

この関数を詳しく見ると、1000個のデータポイントの比較的小さなサンプルで「真の」PDFをどれだけ近似しているかがわかります。 以下では、最初に「+ scipy.stats.norm()」を使用して「分析」ディストリビューションを構築できます。 これは、統計標準正規分布、そのモーメント、および説明関数をカプセル化するクラスインスタンスです。 そのPDFは、「 norm.pdf(x)= exp(-x* 2/2)/sqrt(2 pi)+ `」と正確に定義されているという意味で「正確」です。

そこから構築して、この分布から1000個のデータポイントのランダムサンプルを取得し、 `+ scipy.stats.gaussian_kde()+`を使用してPDFの推定に戻ろうとすることができます。

from scipy import stats

# An object representing the "frozen" analytical distribution
# Defaults to the standard normal distribution, N~(0, 1)
dist = stats.norm()

# Draw random samples from the population you built above.
# This is just a sample, so the mean and std. deviation should
# be close to (1, 0).
samp = dist.rvs(size=1000)

# `ppf()`: percent point function (inverse of cdf — percentiles).
x = np.linspace(start=stats.norm.ppf(0.01),
                stop=stats.norm.ppf(0.99), num=250)
gkde = stats.gaussian_kde(dataset=samp)

# `gkde.evaluate()` estimates the PDF itself.
fig, ax = plt.subplots()
ax.plot(x, dist.pdf(x), linestyle='solid', c='red', lw=3,
        alpha=0.8, label='Analytical (True) PDF')
ax.plot(x, gkde.evaluate(x), linestyle='dashed', c='black', lw=2,
        label='PDF Estimated via KDE')
ax.legend(loc='best', frameon=False)
ax.set_title('Analytical vs. Estimated PDF')
ax.set_ylabel('Probability')
ax.text(-2., 0.35, r'$f(x) = \frac{\exp(-x^2/2)}{\sqrt{2*\pi}}$',
        fontsize=12)

Chart

これはコードの大きな塊なので、いくつかの重要な行に触れてみましょう。

  • SciPyのhttps://docs.scipy.org/doc/scipy/reference/stats.html [`+ stats `サブパッケージ]を使用すると、サンプリングして実際のデータを作成できる分析分布を表すPythonオブジェクトを作成できます。 したがって、「 dist = stats.norm()」は通常の連続ランダム変数を表し、「 dist.rvs()+」を使用して乱数を生成します。

  • 分析PDFとGaussian KDEの両方を評価するには、分位数の配列「+ x 」が必要です(正規分布の場合は、標準偏差が平均より上/下)。 ` stats.gaussian_kde()+`は、この場合視覚的に意味のある何かを生成するために配列で評価する必要がある推定PDFを表します。

  • 最後の行にはhttps://matplotlib.org/users/usetex.html[LaTex]が含まれており、これはMatplotlibとうまく統合されています。

Seabornのファンシーオルタナティブ

もう1つのPythonパッケージを組み合わせてみましょう。 Seabornには、1ステップで単変量分布のヒストグラムとKDEをプロットする `+ displot()`関数があります。 ealierのNumPy配列 ` d +`を使用します。

import seaborn as sns

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

Seaborn’s distplot、width = 1280、height = 960

上記の呼び出しはKDEを生成します。 特定の分布をデータに適合させるオプションもあります。 これはKDEとは異なり、汎用データのパラメーター推定と指定された分布名で構成されます。

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

繰り返しますが、わずかな違いに注意してください。 最初のケースでは、未知のPDFを推定しています。 2つ目は、既知の分布を取得し、経験的データを考慮して、それを最もよく表すパラメーターを見つけます。

パンダのその他のツール

プロットツールに加えて、Pandasは便利な `+ .value_counts()`メソッドも提供します。このメソッドは、Pandas ` Series +`に対してnull以外の値のヒストグラムを計算します。

>>>

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

他の場所では、https://pandas.pydata.org/pandas-docs/stable/generated/pandas.cut.html [+ 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)  # The edges
>>> 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

素晴らしいのは、これらの操作の両方が最終的にhttps://github.com/pandas-dev/pandas/tree/master/pandas/_libs[Cythonコードを利用する]であり、柔軟性を維持しながら速度を競うことです。

さて、どちらを使用する必要がありますか?

この時点で、Pythonヒストグラムをプロットするために選択できる関数とメソッドがいくつかあります。 彼らはどうやって比較しますか? 要するに、「万能」というものはありません。これまでに取り上げた関数とメソッドの要約を以下に示します。これらはすべて、Pythonでの分布の分類と表現に関するものです。

You Have/Want To Consider Using Note(s)

Clean-cut integer data housed in a data structure such as a list, tuple, or set, and you want to create a Python histogram without importing any third party libraries.

collections.Counter() from the Python standard library offers a fast and straightforward way to get frequency counts from a container of data.

This is a frequency table, so it doesn’t use the concept of binning as a “true” histogram does.

Large array of data, and you want to compute the “mathematical” histogram that represents bins and the corresponding frequencies.

NumPy’s np.histogram() and np.bincount() are useful for computing the histogram values numerically and the corresponding bin edges.

For more, check out np.digitize().

Tabular data in Pandas’ Series or DataFrame object.

Pandas methods such as Series.plot.hist(), DataFrame.plot.hist(), Series.value_counts(), and cut(), as well as Series.plot.kde() and DataFrame.plot.kde().

Check out the Pandas visualization docs for inspiration.

Create a highly customizable, fine-tuned plot from any data structure.

pyplot.hist() is a widely used histogram plotting function that uses np.histogram() and is the basis for Pandas’ plotting functions.

Matplotlib, and especially its object-oriented framework, is great for fine-tuning the details of a histogram. This interface can take a bit of time to master, but ultimately allows you to be very precise in how any visualization is laid out.

Pre-canned design and integration.

Seaborn’s distplot(), for combining a histogram and KDE plot or plotting distribution-fitting.

Essentially a “wrapper around a wrapper” that leverages a Matplotlib histogram internally, which in turn utilizes NumPy.

*無料ボーナス:*時間不足? ここをクリックして、無料の2ページのPythonヒストグラムチートシートにアクセスしてください。このチュートリアルで説明されているテクニックを要約しています。

Real Pythonマテリアルページのhttps://github.com/realpython/materials/tree/master/histograms[script]でこの記事のコードスニペットを一緒に見つけることもできます。

それで、幸運にもヒストグラムを作成します。 上記のツールの1つがニーズに合うことを願っています。 何をするにしても、http://www.businessinsider.com/pie-charts-are-the-worst-2013-6 [円グラフを使用しない]だけです。