seabornによる統計データ可視化(ポケモン種族値を例に)(2)

前記事の続きです

mizti.hatenablog.com

3. Regression plots

2つの系列のデータを受け取り、回帰や残差を可視化します。回帰を可視化するregplotと残差を可視化するresiplotがあります。
regplot
plt.figure(figsize=(9, 9))
ax = sns.regplot(x=(pkmn["Total"] - pkmn["Sp. Atk"]), y=(pkmn["Total"] - pkmn["Attack"]))
d = ax.set(xlabel='Physical-Attacker Status', ylabel='Sp-Attacker Status') # d is just for supressing output
f:id:mizti:20171118193647p:plain 
regplotでは与えられた2つのデータから散布図を描き、回帰線を引きます。 散布プロットと回帰線の有無はそれぞれscatter / fit_regパラメータで切り替え可能です。
上記ではステータス合計値 "Total" から特殊攻撃力/攻撃力を引いた値をそれぞれX軸/Y軸とし、物理攻撃アタッカー性能/特殊アタッカー性能としてプロットしてみました。
デフォルトでは回帰の方法は線形回帰(order=1の多項式回帰)となっていますが、多項式回帰の次数を増やすことができます。また、yに真偽値を入れてlogistic=Trueを指定することでロジスティック回帰を用いることも可能です。
plt.figure(figsize=(9, 9))
ax = sns.regplot(x=pkmn["Total"] , y=pkmn["Legendary"], logistic=True)
 f:id:mizti:20171118193424p:plain
伝説のポケモン(y=1.0)とそれ以外(y=0.0)で分類し、ロジスティック回帰を行いました。
回帰線の周囲にある薄い色付きの領域は信頼区間で、デフォルトでは95%です。ciパラメータで%を指定することができます。今回のデータの場合は全てのサンプルが網羅されているのであまり意味がありませんが、母集団からの標本によって作ったデータの場合は、100回のサンプリング群のうち95回はこのピンク色の範囲に回帰線が含まれるだろう、と見ることができます。 
residplot
plt.figure(figsize=(9, 9))
ax = sns.residplot(x=(pkmn["Total"] - pkmn["Sp. Atk"]), y=(pkmn["Total"] - pkmn["Attack"]))
d = ax.set(xlabel='Physical-Attacker Status', ylabel="distance from regression line") # d is just for supressing output
f:id:mizti:20171118193306p:plain 
residplotは残差を散布図としてプロットします。(残差、とは予測値とデータの離れ具合を指す統計上の用語です)
すこし分かり辛いですが、y=0の線が線形回帰線にあたり、そこからどのくらい離れているかがy軸になっています。 何らかの回帰を行ったあとで、回帰線からの離れ方がx軸の系列とどのような関係にあるか見るのに適しています。
ここでは、上記のregplotの最初で書いた線形回帰と同じデータの残差を可視化してみました。
線形回帰だとregplotを見れば普通は十分ですが、多項式回帰などで回帰線が複雑な曲線を描くような場合には残差の分布がわかりづらくなるため、有用に使えると思います。 

4. Matrix plots

Matrix plotsは、複数のカテゴリデータ同士の関係を可視化します。
heatmap
 
s2 = pkmn["Type 2"]
labels2, levels2 = pd.factorize(s2)
labels1 = levels2.get_indexer(pkmn["Type 1"])
s = np.zeros*1
for i in range(len(labels1)):
    if(labels2[i]<0): # assign "N/A" as 18th in table
        labels2[i] = 18
    s[labels1[i] ,labels2[i]] += 1
 
plt.figure(figsize=(11,9))
ax = sns.heatmap( s, vmin=0, vmax=7, annot=True, cmap="magma", xticklabels=levels2, yticklabels=levels2)
d = ax.set(xlabel='Type 2', ylabel='Type 1') # d is just for supressing output 

f:id:mizti:20171118193140p:plain

heatmapはその名の通り受け取った配列をヒートマップで可視化します。
今回はタイプ同士の組み合わせ分布を可視化してみました。
データ解析周りでheatmapを一番良く使うのは、分類問題で予測ラベル/正解ラベルの組み合わせを可視化するケースだと思います。(この利用方法の場合、confusion matrixなどとも呼ばれます)
予測が正解に完全に一致していれば左上から右下に綺麗に赤いが一直線に並び、そこからはみ出した部分は予想誤りということになります。
色温度の組み合わせは用途に合わせて https://matplotlib.org/examples/color/colormaps_reference.html から自由に選択してください。
clustermap
import random
st = pkmn.loc[:, ['HP', 'Defense', 'Sp. Def']].sample(30, random_state=179)
plt.figure(figsize=(11,9))
ax = sns.clustermap(st, metric='euclidean', method='average') 

f:id:mizti:20171118193102p:plain

clustermapは、指定されたデータセットについて類似度によってクラスタリングを行った結果をデンドログラムで表示します。(各データについてはヒートマップのように色付けを行いながら)
例ではポケモンのHP / 防御力 / 特殊防御力を基準として類似度の近い者同士が近くなるように描画しています。 あまり数や次元が多いと計算に時間がかかるため、ランダムに抜き出した30種類に絞っています。
metricオプションによって、各データをベクトルと見做したときの、二つのデータ間の類似度の測り方を指定できます。例えば、 metric='euclidean' であればユークリッド距離、 metric='cosine'であればコサイン類似度によって、といった具合です。
また、methodオプションではクラスター同士をどのように結合していくかの規則を指定できます。 例えば、method='single'であれば、あるクラスターを結合する先を選ぶ際に、両方の含まれる最も近い点同士の距離が最も小さいクラスターと、method='average'であればクラスターに含まれる全てのデータ同士の距離の平均が最も小さいクラスターと結合されます。
実際に活用する場合では、素のデータをそのままクラスタリングするのではなく、何らかの形で特徴量抽出を行った上でクラスタリングすることが多いと思います。
 

5. Timeseries plots

tsplotの一種類のみです。時系列データを描画します。
tsplot
v = ["HP","Attack","Sp. Atk","Defense", "Sp. Def", "Speed"]
mean = pkmn.groupby(["Generation"])["HP","Attack","Sp. Atk","Defense", "Sp. Def", "Speed"].mean()
#std = pkmn.groupby(["Generation"])["HP","Attack","Sp. Atk","Defense", "Sp. Def", "Speed"].std()
plt.figure(figsize=(13,9))
ax = sns.tsplot(mean["HP"], color="Green")
ax = sns.tsplot(mean["Attack"], color="Red")
ax = sns.tsplot(mean["Sp. Atk"], color="Purple")
ax = sns.tsplot(mean["Defense"], color="Grey")
ax = sns.tsplot(mean["Sp. Def"], color="Blue")
ax = sns.tsplot(mean["Speed"], color="Orange") 

f:id:mizti:20171118193017p:plain

tsplotは文字通り時系列を可視化するためのplotです。
あまり時系列になりそうなものがなかったのですが、一応世代を時系列とみなして各ステータスの平均の推移をプロットしてみました。 これだけではあんまりなので、もう一つ、データを生成してプロットしてみます。
x=np.linspace(0,16,32)
sin = np.sin(x) + np.random.normal(0, 1.2,  (16,32))
cos = np.cos(x) + np.random.rand(16, 32) + np.random.randn(16, 1)
 
plt.figure(figsize=(13,9))
 
ax = sns.tsplot(sin,color='Green')
ax = sns.tsplot(cos,color='Reds')

f:id:mizti:20171118192947p:plain

それぞれsin, cosに対して正規分布や一様ランダムを加えて動きを与えたプロットです。 今回の場合で先程と異なるのは、各時系列に対して各系列16ずつの値のばらつきを持っていることです。このような場合、実線は各時の平均となり、信頼区間は色付きで表されます。(デフォルトでは68%信頼区間)

6. Miscellaneous plots

文字通り、「その他」のプロット。palplot一つしかありません。 
palplot
plt.figure(figsize=(15, 8))
ax = sns.palplot(sns.color_palette(n_colors=24), 2)
ax = sns.palplot(sns.color_palette("Set1", 24))
ax = sns.palplot(sns.color_palette("terrain", 5))
ax = sns.palplot(sns.color_palette("terrain", 8))
ax = sns.palplot(sns.color_palette("terrain", 15))
ax = sns.palplot(sns.color_palette("inferno", 15))

f:id:mizti:20171118192902p:plain

f:id:mizti:20171118192851p:plain

f:id:mizti:20171118192822p:plain 

f:id:mizti:20171118192752p:plain

f:id:mizti:20171118192747p:plain

f:id:mizti:20171118192734p:plain

palplotはデータ可視化用のプロットではなく、現在設定されているパレットの内容を確認するためのプロットです。 特に他者にデータを可視化する場合、見やすかったりデータのイメージに合った色を選ぶのが重要になるので色の調整等に 使いましょう
 

7. Axis grids

最後はAxis gridsカテゴリです。
このカテゴリは若干特殊で、複数の変数の組み合わせに対して今までにあげたplotを描くための枠を用意し、一度にグラフを描けるseabornを"便利に"使うためのツールがまとまっています。 このため、このカテゴリは上記で見てきたplotをオプションで指定して利用するものが多く含まれています。 
Facet Grid
plt.figure(figsize=(16,16))
g = sns.FacetGrid(pkmn, col="Generation")
 f:id:mizti:20171118192614p:plain FacetGridは、指定した要素ごとにプロット領域を定義するクラスです。これ単体ではプロットは行われません。 (matplotlibに馴染みのある人であればsubplotをまとめて定義してくれる、と表現すればわかりやすいかもしれません) FacetGridは後述のfactorplot, lmplotで継承されて利用されているようです。
rowパラメータにより横軸に展開する要素を、colパラメータで縦軸に展開する要素を指定できます。
In [32]:
plt.figure(figsize=(16,16))
g = sns.FacetGrid(pkmn, col="Generation", row="Legendary")
g = g.map(plt.hist, "HP")

f:id:mizti:20171118192539p:plain

FacetGridを代入した変数(上記ではg)にmapを適用することによって、matplotlib等でグラフを描画することもできます。
上の例では上段が非伝説、下段が伝説で、左から順にGenerationごとのHPの分布を可視化しています。 
factorplot
plt.figure(figsize=(16,16))
g = sns.factorplot(x="Generation", y="Speed", hue="Legendary", data=pkmn, kind="box")
 f:id:mizti:20171118192327p:plain
上の方の例でviolineplotやpointplotで、複数系列を同時に描画した例がありました。factorplotは、「複数の系列を同時に描画する」という目的でseabornの各種プロットを整理して呼び出せるようにしたもの、と考えられます。
対象にしたい連続値が一つで、多数のカテゴリに分類して状況を把握したい場合はこのプロットを使うと効率的に可視化できます。
plt.figure(figsize=(16,16))
g = sns.factorplot(x="Generation", y="Sp. Atk", hue="Legendary", data=pkmn, kind="lv", col="Type 1", col_wrap=3 

f:id:mizti:20171118192216p:plain

 
タイプ毎に分けたプロット領域に、世代/伝説であるかどうかを分けてグラフを一度に描画しています。
colに"Type 1"を指定してタイプごとに分かれたプロット領域を一直線に並べ、 col_wrapを4に指定することで、4つごとに改行させています。
プロットのタイプにはpoint, bar, count, box, violin, strip, lvといった、Categorical Plotのメソッドを選択することができます。
 
lmplot
plt.figure(figsize=(16,16))
g = sns.lmplot(x="Attack", y="Sp. Atk", data=pkmn, col="Generation", col_wrap=3)
f:id:mizti:20171118192117p:plain 
先程のfactorplotがCategorical plotsを一度に展開するものだったとすれば、lmplotはRegression plotsを指定した要素分類ごとに一度に描画するもの、と考えて良いと思います。
上記では世代毎のAttackとSp. Atkの回帰線付きの散布図にしました。col, row, col_wrapあたりの使い方はfactorplotと共通しています。また、回帰アルゴリズムの選択方法等はregplotと同様です。
Pairgrid & pairplot
実質的にはPairgridを継承したpairplotを使うことが多いと思われるため、一緒に記載します。 Facet Gridと同様、Pairgridで描画したsubplotにmatplotlibを使って直接様々なグラフを描画することも可能です
plt.figure(figsize=(16,16))
#d = pkmn.loc[pkmn.columns.isin(["HP", "Attack", "Sp. Atk", "Defense", "Sp. Def", "Speed"])]
d = pkmn[["HP", "Attack", "Sp. Atk", "Defense", "Sp. Def", "Speed"]]
g = sns.pairplot(data=d)
 

f:id:mizti:20171118192044p:plain

pairplotは与えられたデータの変数の要素の交差表を自動的に作成し、
  • 同一データ同士の交差する点(対角線)についてはヒストグラム
  • 異なるデータ同士が交差する点については散布図
を描画します。
上記では6種のステータスを取り出し、pairplotをしています。 異なるステータスが重なっている箇所では散布図が、同じステータス同士が重なっている箇所ではヒストグラムが描画されているのがわかると思います。
また、ヒストグラムと散布図が描画されている箇所はそれぞれkde、regressionで置き換えることも可能です。
pairplotは素性の分からない多数の種類のデータが与えられた時に、それぞれのデータ系列同士の相関性などを確認する上でとても有用になります。
In [37]:
plt.figure(figsize=(16,16))
d = pkmn[["HP", "Attack", "Sp. Atk", "Defense", "Sp. Def", "Speed"]]
g = sns.pairplot(data=d, kind="reg", diag_kind="kde")
 f:id:mizti:20171118191824p:plain
 
JointGrid & jointplot
JointGridとjointplotもPairGrid&pairplotと同様、一緒に記載します。 FacetGridやPairGirdと同様、JointGridで作ったsubplotにmatplotlibでグラフを描画することも可能ですが、本稿はseabornに限定するため省略します。
plt.figure(figsize=(16,16))
g = sns.jointplot(x = pkmn["HP"], y = pkmn["Attack"]) 

f:id:mizti:20171118191947p:plain

jointplotは2種類の連続値を受け取り、それぞれ片方ずつのヒストグラムと、両方を組み合わせた散布図を描画します。
散布図が描かれている部分については、kindによって各種散布図(“scatter” | “reg” | “resid” | “kde” | “hex”)を パターン指定できます。
plt.figure(figsize=(16,16))
g = sns.jointplot(x = pkmn["Defense"], y = pkmn["Sp. Def"], kind="hex") .set_axis_labels("Defense", "Sp.Defense")
 

f:id:mizti:20171118192008p:plain

 

*1:19,19