HOWTO · Matplotlib
Matplotlib でデータのリストのヒストグラムをプロットする方法
Matplotlib の plt.hist() を使って Python リストからヒストグラムをプロットし、そのビン、密度、色を調整します。
このページの内容
Python リストからヒストグラムをプロットするには、リストを Matplotlib の plt.hist() 関数に直接渡します。この関数は値をビンと呼ばれる区間にグループ化し、各区間に含まれる値の数を数えて、区間ごとにバーを 1 本描くため、グラフの形から値の分布が分かります。入力には通常の Python リストが使えます。事前に NumPy 配列や pandas の Series に変換する必要はありません。この記事では 0 から 9 までの整数 21 個のリストをプロットし、次にビン、バーの高さ、バーの色を調整し、最後に無効なビン数と空のリストの場合に何が起こるかを示します。
plt.hist() でリストからヒストグラムを描く
追加の引数を指定しない場合、plt.hist() はデータ範囲を等幅の 10 個のビンに分割します。この例では、関数が返す 3 つの値も取得して出力し、リストがどのようにグループ化されたかを正確に確認できます。
import matplotlib.pyplot as plt
data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
counts, bin_edges, patches = plt.hist(data)
print("counts:", counts.tolist())
print("bin_edges:", bin_edges.tolist())
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Histogram of data")
plt.show()
counts: [1.0, 1.0, 2.0, 2.0, 5.0, 3.0, 3.0, 2.0, 1.0, 1.0]
bin_edges: [0.0, 0.9, 1.8, 2.7, 3.6, 4.5, 5.4, 6.3, 7.2, 8.1, 9.0]
plt.hist() は 3 つの値を返します。counts は各ビン内の観測数、bin_edges は区間の境界、patches は後で個々のバーのスタイルを変更したい場合に使うバーのアーティストを含みます。counts の要素数はビンの数と一致し、bin_edges は各区間の両端を示すため常に 1 つ多くなります。ここではデータ範囲が 0 から 9 のため、デフォルトの 10 ビンは各 0.9 の幅になり、最も高いバーは値 4 の 5 回の出現を含む 3.6 から 4.5 の区間です。グラフだけが必要な場合は、代入を省略して plt.hist(data) とだけ呼び出すこともできます。
ビンの数またはビンの境界を設定する
bins 引数は、データの分割方法を制御します。bins=20 のような整数を渡すと、データ範囲全体にその数の等幅区間が作成されます。
import matplotlib.pyplot as plt
data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
counts, bin_edges, patches = plt.hist(data, bins=20)
print("counts:", counts.tolist())
print("number of bins:", len(counts))
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Histogram with 20 bins")
plt.show()
counts: [1.0, 0.0, 1.0, 0.0, 2.0, 0.0, 2.0, 0.0, 5.0, 0.0, 0.0, 3.0, 0.0, 3.0, 0.0, 2.0, 0.0, 1.0, 0.0, 1.0]
number of bins: 20
この小さなリストには 20 ビンは多すぎます。値が整数のため、1 つおきのビンが空になり、分布が実際よりもノイズが多く見えます。一般に、ビンが少なすぎるとデータの構造が隠れてしまい、狭いビンが多すぎると小さなデータセットがぎざぎざに見えるため、リストのサイズとばらつきに合ったビン数を選んでください。整数の代わりに bins=[0, 2, 4, 6, 8, 10] のような境界値のシーケンスを渡して、区間の境界を自分で設定することもできます。その場合、区間の幅はそれぞれ異なっていてもかまいません。カウントの範囲が広い場合は、オプションの引数 log=True を使うとカウント軸が対数スケールに切り替わり、高いバーの横でも小さいバーが見えるようになります。これは軸のスケールだけを変更し、入力値やビン分割は変更しません。
カウントの代わりに確率密度を表示する
デフォルトでは、バーの高さはカウントです。代わりに確率密度が必要な場合は density=True を設定します。Matplotlib はバーの総面積が 1 になるように高さをスケーリングします。
import matplotlib.pyplot as plt
data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
counts, bin_edges, patches = plt.hist(data, bins=10, density=True)
print("densities:", [round(d, 4) for d in counts.tolist()])
area = sum(d * (bin_edges[i + 1] - bin_edges[i]) for i, d in enumerate(counts))
print("total area:", round(area, 6))
plt.xlabel("Value")
plt.ylabel("Probability density")
plt.title("Probability histogram of data")
plt.show()
densities: [0.0529, 0.0529, 0.1058, 0.1058, 0.2646, 0.1587, 0.1587, 0.1058, 0.0529, 0.0529]
total area: 1.0
density=True を指定すると、戻り値タプルの最初の値にはカウントではなく密度が入り、この例では各密度にビン幅を掛けて面積を合計することで正規化を検証し、ちょうど 1 になることを確認しています。個々のバーの高さは密度であり、その区間の確率ではないことに注意してください。区間の確率はバーの面積、つまり高さに幅を掛けたものです。この違いはビンの幅が異なる場合に重要です。幅の広いバーは、より高い狭いバーよりも大きな確率を表すことがあるためです。例のように、切り替え時には縦軸のラベルを更新し、グラフがバーの高さの意味を説明するようにしてください。
バーの色を変更する
color 引数はバーの塗りつぶし色を設定します。
import matplotlib.pyplot as plt
data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
plt.hist(data, bins=10, color="red")
plt.xlabel("Value")
plt.ylabel("Count")
plt.title("Red histogram of data")
plt.show()
"red" のような名前、"#1f77b4" のような 16 進文字列、1 文字のコードなど、Matplotlib の色指定はすべて使えます。表示オプションは 1 回の呼び出しで自由に組み合わせられます。たとえば plt.hist(data, bins=20, density=True, color="red") は密度の高さを持つ赤いバーを 20 本描きます。どのオプションを組み合わせる場合でも、タイトルと軸ラベルをそれに合わせてください。密度グラフにカウントのラベルを残したり、20 ビンのグラフがデフォルトの 10 ビンを表示しているように見せたりしてはいけません。
無効なビン数と空のリストに対処する
驚く前に知っておきたい入力ミスが 2 つあります。1 つ目は、正でないビン数です。
import matplotlib.pyplot as plt
data = [3, 4, 2, 3, 4, 5, 4, 7, 8, 5, 4, 6, 2, 1, 0, 9, 7, 6, 6, 5, 4]
plt.hist(data, bins=0)
Traceback (most recent call last):
...
ValueError: `bins` must be positive, when an integer
bins=0(および負の整数)は ValueError を発生させ、何も描画されません。ビン数がユーザー入力や計算に由来する場合は、プロット前に正の整数であることを検証するか、代わりに境界のシーケンスを渡してください。2 つ目のケースである空のリストは、まったく異なる動作をします。エラーにはならないのです。
import matplotlib.pyplot as plt
counts, bin_edges, patches = plt.hist([])
print("counts:", counts.tolist())
print("bin_edges:", [round(e, 2) for e in bin_edges.tolist()])
counts: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
bin_edges: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
データがない場合、plt.hist() は 0 から 1 のデフォルト範囲にフォールバックし、ゼロのカウントを 10 個返して、例外を発生させずに空の軸を描きます。そのため空のリストはサイレントな失敗モードになります。データがファイル、クエリ、フィルターに由来する場合は、プロット前にリストが空でないことを確認し、欠けたデータセットが有効な結果に見える空白のグラフに変わってしまわないようにしてください。