Chapter 08

Seabornで分布を比較する

mimitako
mimitako
2022.10.10に更新

分布の比較

得られたデータを比較するために、ヒストグラムを重ねるなどの方法がありますが、昨今ではそれ以外の手法もありますのでいくつか紹介します。

ここで取り扱うデータ

ここでは Iris のデータを利用します。

モジュールのインポート

使い始める前にいくつかのモジュールをインポートしておきます。

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

データセットのインポート

今回使用するデータは iris(あやめ)のデータセットです。

iris_data = sns.load_dataset("iris")

データ構造は次の通りです。

sepal length はがくの長さ、sepal width はがくの幅、petal length は花びらの長さ、petal width は花びらの幅です。species は iris ことあやめの種類を示しています。

ヒストグラムによる比較

ヒストグラムで比較をする場合は histplot を利用します。今回はがくの幅を花の種別ごとに表示します。

fig, ax = plt.subplots(1,1,dpi = 300)
ax = sns.histplot(
  data = iris_data,
  x = "sepal_width",
  hue="species",
  palette="winter",
  alpha = 0.3,
)
ax.set_xlabel("sepal width")
ax.set_ylabel("count")
ax.set_title("Histgram of iris")

このように表示されます。2,3 種類を比較するならまだ良いのですが、それ以上の数を比較した場合、色が重なりすぎてよくわかりません。

多種を比較しようとした場合は、以降のグラフを利用すると良いでしょう。

カーネル密度を比較する

ヒストグラムを描写するときに kde と呼ばれる引数を設定できます。これはカーネル密度を表示するためのプロパティで、有限の標本点から全体の分布を推定する手法となります。

これを用いて、なめらかな関数を算出し比較を行います。ここで利用する関数は kdeplot です。

fig, ax = plt.subplots(1,1,dpi = 300)
ax = sns.kdeplot(
  data = iris_data,
  x = "sepal_width",
  hue="species",
  palette="winter",
  alpha = 0.7,
)
ax.set_xlabel("sepal width")
ax.set_ylabel("ratio")
ax.set_title("KDE of iris")

これでスッキリとした見た目になりました。おおよその平均位置なども推定しやすいですね。

箱ひげ図

箱ひげ図は分布をわかりやすく可視化する手法の一つで、平均値、最小、最大、25%点、75%点がひと目で分かるプロットです。

利用するのは boxplot 関数です。早速見てみましょう。

fig, ax = plt.subplots(1,1,dpi = 300)
ax = sns.boxplot(
  data = iris_data,
  x = "species",
  y="sepal_width",
  palette="winter",

)
ax.set_xlabel("species")
ax.set_ylabel("sepal width")
ax.set_title("Box Plot")

外れ値だろうと推定される場所は最大最小のキャップから離れた場所に点で指示されています。データの集まり具合が誰でも理解しやすいということで、よく利用されています。

バイオリンプロット

前述ではカーネル密度を推定することで、グラフが見やすくなりました。これを更に拡張した手法がバイオリンプロットと呼ばれるものです。

バイオリンプロットはその名の通り、バイオリンのような弦と左右対称な曲線が描かれることから名付けられています。

fig, ax = plt.subplots(1,1,dpi = 300)
ax = sns.violinplot(
  data = iris_data,
  x = "species",
  y="sepal_width",
  palette="winter",
  alpha = 0.7,
)
ax.set_xlabel("species")
ax.set_ylabel("sepal width")
ax.set_title("Violin Plot")

ここで、中央の白い点は平均値、少し太い黒線は四分位範囲(箱ひげ図の箱部分)、細い線の両端は最大と最小を示しています。

最大最小を超えて描写されているのは、カーネル密度の計算上、存在する確率があることを示しています。しかし、実施には黒い線上にしか実態が観測されていないことを理解しておく必要があります。

ビースウォームプロット

散布図のような点によってカテゴリごとの特徴を比較するグラフです。特徴としては、ヒストグラムのようにデータが区間情報になったり、カーネル密度のように仮想線が出現することのない純粋なデータ点の集合になります。

リアルな分布を調査するさいに役立ちます。関数としては swarmplot を指定します。

fig, ax = plt.subplots(1,1,dpi = 300)
ax = sns.swarmplot(
  data = iris_data,
  x = "species",
  y="sepal_width",
  palette="winter",
  alpha = 0.7,
)
ax.set_xlabel("species")
ax.set_ylabel("sepal width")
ax.set_title("Beeswarm Plot")

外れ値や密集している場所がひと目でわかりますね。

2 次元的比較法

今までは 1 次元の比較を行っていましたが、次に 2 次元的な分布を比較してみましょう。まずは、散布図のような一般的な手法から説明していきます。

今回はがくの幅と長さの関係性に対して花の種類ごとの特徴について調べてみます。

散布図による分布比較

おそらく、誰もが最初にやることではないでしょうか?では早速散布図を描写してみましょう。関数は scatterplot を呼び出して利用します。

花の種類は hue によって識別されます。

fig, ax = plt.subplots(1,1,dpi = 300)
ax = sns.scatterplot(
  data = iris_data,
  x = "sepal_length",
  y="sepal_width",
  palette="winter",
  hue = "species",
  s = 10, # 散布図の点サイズを変更します。
)
ax.set_xlabel("sepal length")
ax.set_ylabel("sepal width")
ax.set_title("Scatter Plot")
ax.set_xlim(0, 9)
ax.set_ylim(0, 9)
ax.set_aspect("equal")

散布図+カーネル密度

seaborn では散布図とカーネル密度を合わせて表示することができます。x 軸と y 軸の分布状況を見ながら比較できますので便利です。

今回は関数として jointplot を利用します。Type が matplotlib 系ではないので、これまでと同じ記法が利用できません。

fig = sns.jointplot(
  data = iris_data,
  x = "sepal_length",
  y="sepal_width",
  palette="winter",
  hue = "species",
  color = "g",
  xlim=(0,9), # 最大値にNoneを利用できません。
  ylim=(0,9),
  s = 10, # 散布図の点サイズを変更します。
)

このように散布図とカーネル密度が表示されることで、重なりがどの程度ありそうなのかをうまく捉えることができます。

散布図+ヒストグラム

カーネル密度の代わりにヒストグラムでも表現できます。細かい調整を行うため、今回は JointGrid クラスを呼び出して、そこからプロットします。

fig = sns.JointGrid(
  data = iris_data,
  x = "sepal_length",
  y = "sepal_width",
  hue = "species",
  palette="winter",
  xlim=(0,9),
  ylim=(0,9)
  )
fig.plot(sns.scatterplot, sns.histplot, alpha= 0.7)

jointplot は簡単ですが、若干クセがあります。好みの表現方法があるのであれば JointGrid クラスを利用したほうが良いでしょう。

等高線のようなカーネル密度関数

カーネル密度の広がりを見たいときに最適な手法が 2 次元のカーネル密度グラフです。ではやってみましょう。

fig = sns.JointGrid(xlim=(0,9), ylim=(0,9))
sns.kdeplot(data = iris_data, x = "sepal_length", y = "sepal_width", hue = "species", palette="rainbow", ax=fig.ax_joint)
sns.histplot(data = iris_data, x = "sepal_length", hue = "species", palette="rainbow", alpha = 0.3, ax=fig.ax_marg_x, legend=False)
sns.histplot(data = iris_data, y = "sepal_width", hue = "species", palette="rainbow", alpha = 0.3, ax=fig.ax_marg_y, legend=False)

少し複雑ですが、fig = sns.JointGrid で JointGrid クラスを宣言しています。宣言と同時に x,ylim を設定します。

続いて、kdeplot 関数で等高線を表示します。中身は今まで出てきたものと殆ど変わりませんが、ax=fig.ax_joint だけは忘れないようにしてください。これは中央の 2 次元グラフを描画するために必要な関数となります。

同じように上部のグラフを ax=fig.ax_marg_x, 右隣を ax=fig.ax_marg_y で設定します。このとき hue を設定しているのであれば凡例も表示されますので、legend=False とすればよいでしょう。

結果は次の通りです。

これができればかなりグラフ作成の幅が広がりますね。

データを比較するということ

検定などで明らかに優位な差があると出ても、グラフ上ではそのように見えないことがあります。このようなときはうまくグラフを作って、その違いを訴えていきたいですね。