質問: 「Jupiter notebook」にて、分布図の確認を行っていたのですが、確率密度?の見方が分からなかったため、質問させてもらいました。 以下はデータの一覧(5つのレコードまで)です。 ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/f9f4bba9277a82959125838ccb9fcd24.png) そして、分布図が以下となります。 ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/11af1ea738001c96c4156ce544e14c3b.png) こちらのヒストグラムの見方ですが、横軸がデータ(値)であるとの認識でおります。 その場合、「0.3 ~ 0.4」の範囲で、縦軸の数値「1」が対応しているかと思うのですが、この「1」は何を示しているのでしょうか...? 縦軸は確率密度らしいのですが、いまいち確率密度というのが分からなかった為、どなたかアドバイス頂けますと幸いです。 よろしくお願いします。 **追記です** ご回答による頂いたコードをjupyter notebookに記載し、実行してみました。 ![イメージ説明](https://teratail-v2.storage.googleapis.com/uploads/contributed_images/6552daa8d308979a05717562febbce8a.png)
回答: グラフの見方について 青のバーと青の折れ線は分けて考えてください。 * 青のバー: データから作成したヒストグラムです。 * 青の折れ線: データの母集団分布をカーネル密度推定 (kernel density estimation, KDE) という手法で推定した確率密度関数を描画したものです。 seaborn.distplot の kde=True (デフォルト) の場合に、ヒストグラムの他にこのグラフが描画されます。 実装上は statsmodels がインストールされている場合は statsmodels.nonparametric.kde.KDEUnivariate、そうでない場合は scipy.stats.gaussian_kde に処理を投げるようになっています。 確率密度関数について 連続型の確率変数 X があるとき、X がある値 a 以下をとる確率を P(X <= a) としたとき、 !イメージ説明 で定義される f(x) が確率密度関数です。 ※ 確率変数 = とり得る値が確率によって決まる変数。例えば、X をサイコロの目としたとき、X = 1 (1の目) は、1/ 6 の確率で出る。 注意したいのは、f(c) の値は、X = c となる確率ではありません。 X = c となる確率 P(X = c) は定義に従って計算すると、0 になります。 確率密度関数の見方としては、**確率密度関数 f(x) の値が大きいところは、そのあたりの x の値が現れる可能性が高い**という理解でいいと思います。 体系的に理解したい場合は 統計学 の教科書を当たると、最初のほうで必ず紹介されていると思うので、それを参照してください。 追記 ストーリーとしては、以下のようになります。 いくつかのデータが得られた ↓ データが得られた背景となる確率分布があるけど、それはわからない ↓ データから確率分布を推定する 統計学の考え方としては、まずなんらかの真の確率分布があり (現実ではこれはわからない)、データはその確率分布から得られた標本 (値の例) であると考えます。 以下、例として、標準正規分布としておきます。 ``` from scipy.stats import norm, gaussian_kde import numpy as np import matplotlib.pyplot as plt # 平均0、分散1の1変量正規分布に従う確率変数 rv = norm(loc=0, scale=1) fig, ax = plt.subplots() ax.plot(xs, rv.pdf(xs), label="確率密度関数") ax.legend(loc="upper left") ax.set_xlim(-5, 5) plt.show() ``` !イメージ説明 このような分布があって、そこから1000 個の値が得られたとします。 ヒストグラムを描画すると以下のようになります。 !イメージ説明 現実では、いくつかのデータが得られたとして、真の確率分布というのはわからないわけです。 例えば、100人に身長を聞いて、168, 172, 180, ...] と100人分のデータが得られたとして、その元となった真の確率分布というのはわかりません。 わからないので、それを今持っているデータから推定するというのが、統計学の1つの目的になります。 カーネル密度推定はその手法の1つです。 カーネル密度推定を使って推定すると、以下のようになります。 元の分布 (青) がわからなくても、データからある程度それに近い分布 (オレンジ) が得られました。 ``` # この得られた標本から元となる確率密度関数を推定する。 kernel = gaussian_kde(samples) # 確率密度関数を描画する。 xs = np.linspace(-5, 5, 1000) fig, ax = plt.subplots() ax.plot(xs, rv.pdf(xs), label="確率密度関数") ax.plot(xs, kernel(xs), label="推定した確率密度関数") ax.legend(loc="upper left") ax.set_xlim(-5, 5) plt.show() ``` ![イメージ説明 追記 > > 2つのグラフで「y」の値が、一方は少数、一方は整数となっておりますが、これは何故かがわかりません。 > > > グラフの目盛りがすべて整数なら整数、小数が含まれるなら小数で表示するという matplotlib の仕様によるものなので、整数か小数かはグラフの表示上の問題であり、重要ではありません。 注目するべきは、y 軸のスケールが左と右で大きく違うことでしょう。 これは、他の回答者様のコメント欄で hayataka2049 さんがご指摘されていますが、ヒストグラムの正規化 (デフォルトで有効) によるものです。 通常、ヒストグラムは y 軸は各ビンに属するデータ数 (頻度値) ですが、この正規化が有効の場合、棒の面積の合計が1となるように棒の高さが調整されます。 数式で表すと、n 個のビンの幅が w1, w2, ..., wn、頻度値が f1, f2, ..., fn としたとき、 w1 * f1 + w2 * f2 + ... + wn + fn = 1 となるように f1, f2, ..., fn の値を正規化します。 左のほうが右より棒の高さが高いのは、左の方はビンの幅が小さいので、棒の面積の合計が1にするためには棒の高さをその分高くする必要があるからです。 棒の高さをそのビンに属するデータ数として解釈したい場合は distplot() で norm_hist=False を指定してください。 この正規化の仕様は、内部でヒストグラム作成に使用している numpy.histogram から来ています。 > > また、can110さんのご回答で、「描かれた曲線とx軸とを囲む領域の面積がデータ値の出現個数の総数になるように補正、正規化された値だということのようです。」とあるのですが、 > > 仮に左側の画像の総面積を求めてみますと、x軸が「0.0 ~ 0.3」の為、「0.3」y軸を大体ではありますが「6」と考えた場合、「0.3 × 6 ÷ 2」 = 「0.9」となります。 > > こちらのデータ値の出現個数の総数が「0.9」であるという意味がいまいち分からない為、ご助言頂けましたら幸いです... > > > 曲線のほうは、先の回答の通り、推定した確率密度関数です。 「描かれた曲線とx軸とを囲む領域の面積」とは、確率密度関数を (-∞, +∞) の区間で積分すると求められ、これは1になります。 !イメージ説明 この計算の解釈は、確率変数 X は実数の値をとるので、(-∞, +∞) の間の値をとる確率は100%であるということを言っています。 補足 正規化されたヒストグラム、カーネル密度推定で推定した確率密度関数ですが、どちらも(不明な)真の確率密度関数を近似するのが目的です。 曲線とx軸の間の面積を求めるのに、x 軸を細かい区間で分割して、長方形を敷き詰めて、面積を計算するというのが、リーマン積分の考え方です。 なので、大量のデータを用意して、ビンの幅を小さくした正規化されたヒストグラムを作成すれば、それで真の確率密度関数を近似できます。 !イメージ説明 リーマン積分の考え方 ``` from scipy.stats import norm, gaussian_kde import numpy as np import matplotlib.pyplot as plt # 平均0、分散1の1変量正規分布に従う確率変数 rv = norm(loc=0, scale=1) # 大量の標本を生成する。 samples = rv.rvs(300000) # この得られた標本から元となる確率密度関数を推定する。 kernel = gaussian_kde(samples) fig, ax = plt.subplots() # 確率密度関数を描画する。 xs = np.linspace(-5, 5, 1000) ax.plot(xs, rv.pdf(xs), label="確率密度関数") ax.plot(xs, kernel(xs), label="KDE で推定した確率密度関数") # ヒストグラムを描画する。 bins = np.linspace(-5, 5, 1000) ax.hist(samples, bins, density=True) ax.legend(loc="upper left") plt.show() ``` 「カーネル密度推定の確率密度関数 (オレンジ)」は「真の確率密度関数」と一致して重なっている。 「正規化されたヒストグラム(緑)」も「真の確率密度関数」とほぼ一致している。 !イメージ説明 追記 distplot の仕様確認 引数の優先度としては、norm_hist < kde < hist_kws のようですので、 正規化したくない場合は、`kde=False` または `hist_kws={"density": False}` を指定するのがよさそうです。 | kde | density | ヒストグラム | | --- | --- | --- | | False | False | 正規化されない | | False | True | 正規化される | | True | False | 正規化されない | | True | True | 正規化される | ``` import matplotlib.pyplot as plt import numpy as np import seaborn as sns sns.set() np.random.seed(0) x = np.random.randn(1000) fig, axes = plt.subplots(2, 2, figsize=(8, 8)) axes = axes.ravel() axes0].set_title("kde=False, density=False") sns.distplot(x, kde=False, hist_kws={"density": False}, ax=axes[0]) axes[1].set_title("kde=False, density=True") sns.distplot(x, kde=False, hist_kws={"density": True}, ax=axes[1]) axes[2].set_title("kde=True, density=False") sns.distplot(x, kde=True, hist_kws={"density": False}, ax=axes[2]) axes[3].set_title("kde=True, density=True") sns.distplot(x, kde=True, hist_kws={"density": True}, ax=axes[3]) plt.show() ``` ![イメージ説明 | kde | norm_hist | ヒストグラム | | --- | --- | --- | | False | False | 正規化されない | | False | True | 正規化される | | True | False | 正規化される | | True | True | 正規化される | ``` import matplotlib.pyplot as plt import numpy as np import seaborn as sns sns.set() np.random.seed(0) x = np.random.randn(1000) fig, axes = plt.subplots(2, 2, figsize=(8, 8)) axes = axes.ravel() axes0].set_title("kde=False, norm_hist=False") sns.distplot(x, kde=False, norm_hist=False, ax=axes[0]) axes[1].set_title("kde=False, norm_hist=True") sns.distplot(x, kde=False, norm_hist=True, ax=axes[1]) axes[2].set_title("kde=True, norm_hist=False") sns.distplot(x, kde=True, norm_hist=False, ax=axes[2]) axes[3].set_title("kde=True, norm_hist=True") sns.distplot(x, kde=True, norm_hist=True, ax=axes[3]) plt.show() ``` ![イメージ説明