📊

複数のグラフを一度に表示するsubplotについて調べてみた

2020/11/28に公開

実務でもデータ分析コンペでも、EDA(探索的データ分析)は必須感あります。

DataFrameをグラフ化するとき、pairplotや.scatter_matrixを使うと一度に散布図をグラフ化できて便利です。

ですが、カラム数が多いと1つ1つの散布図が小さくなりすぎてしまいますし、散布図以外のグラフを複数出したいときもあります。

subplotを使うと複数の任意のグラフを一度に表示できるので便利です。

個人的にsubplotの使い方にいつも戸惑ってしまうのでまとめました。

詳細なコードはこちらでご確認ください。

こちらのメールマーケティングのデータセットを使用しています。

https://blog.minethatdata.com/2008/03/minethatdata-e-mail-analytics-and-data.html

一度に出力させたいカラムのリストを作成

数値データが入ったカラムだけの配列 num_list を作成します。

df = pd.read_csv("http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv")

num_list = list(df.select_dtypes(exclude=object).columns)

出力:
['recency', 'history', 'mens', 'womens', 'newbie', 'visit', 'conversion', 'spend']

subplotとfor文を使って複数のグラフを1列に表示する

subplotの引数は、(行, カラム, インデックス)となっております。

1列×8行(計8項目)のグラフにしたい場合はsubplot(1,8,i)となります。

また、i は各グラフのインデックスになります。

したがって1番目のグラフは(1,8,1)、2番目のグラフは(1,8,2)という感じになります。

https://matplotlib.org/api/_as_gen/matplotlib.pyplot.subplot.html

今回のデータセットの数値データは8カラムあります。

subplotで設定するグラフの行数は、len(num_list)等を使うとよいです。

また、グラフのタイトルがないと何のグラフかわからなくなりますので、plt.title()を使うとよいです。

fig = plt.figure(figsize=(10,30)) # 全体のサイズは任意に変更ください

for i in range(len(num_list)):
    plt.subplot(len(num_list), 1, i+1)
    plt.title(num_list[i])
    plt.hist(df[num_list[i]])

plt.tight_layout() # グラフ間の隙間を調整して座標部分が重なりを解消できる

2列表示のグラフに変更する

繰り返しになりますが、subplotの引数は、(行, カラム, インデックス)となっております。

2列×4行(計8項目)のグラフにしたい場合はsubplot(2,4,i)となります。

行数はlen(num_list)/2、奇数の場合は切り上げが必要なので、math.ceil()を使います。

math.ceil(len(num_list)/2) と処理してあげましょう。

fig = plt.figure(figsize=(20,20))

for i in range(len(num_list)):
    
    plt.subplot(math.ceil(len(num_list)/2), 2, i+1)
    plt.title(num_list[i])
    plt.hist(df[num_list[i]])

plt.tight_layout()

通常のヒストグラムと、対数変換後のヒストグラムを並べて表示する

0 は対数変換できません。

今回の場合ですと、下記のとおり、recency と history 以外は0を含みますので、そのまま対数変換するとエラーになります。

(df[num_list]==0).sum()

出力:

カラム名 0の数
recency 0
history 0
mens 28734
womens 28818
newbie 31856
visit 54606
conversion 63422
spend 63422
dtype: int64

そこで、下記サイトを参考に 0 を 0.5に置き換えてやります。

df2 = df.mask(df == 0, 0.5)

「個体数がゼロ」というデータが含まれているときには対数変換は計算できない。loge(0) は計算できないからである。このとき従来は個体数に1を足してから対数変換を行っていた。
つまり loge(x + 1) といった形の変換である。 しかし「1を足すのがよい」といった理由は存在しない。
離散分布を連続分布で近似するという考え方からすれば,1でなく 0.5 を足す方がよい(図1)。
数値積分により等分散化の効果を調べたところ,確かに 0.5 を足した方が1を足すよりも「変数変換」の効果が大きいことが確かめられた(図2~5)。

http://cse.naro.affrc.go.jp/yamamura/topic8.html

なお、今回のデータセットのカラムは、0 か 1 しかない、実質カテゴリデータのカラムもあり、対数変換する意味が乏しいものもありますが、ご容赦ください。

さてsubplotの引数ですが、元の数値のグラフの隣に対数変換したグラフがくるようにします。

そこで、インデックスは元の数値のグラフを(行, 2, i2+1)、対数変換したグラフを(行, 2, i2+2)としてやります。

こうすれば、元の数値のグラフの隣に対数変換したグラフを並べることができます。

fig = plt.figure(figsize=(20,40))

for i in range(len(num_list)):
    
    plt.subplot(len(num_list), 2, i*2+1)
    plt.title(num_list[i])
    plt.hist(df[num_list[i]])

    # 対数変換後
    plt.subplot(len(num_list), 2, i*2+2)
    plt.title(num_list[i]+" log_transform")
    plt.hist(df2[num_list[i]].apply(np.log))
    
plt.tight_layout()

subplots を使ってインデックスの指定処理をわかりやすくする

出力される結果は変わりませんが、subplot ではなく subplots を使うことでインデックスの指定処理をもう少し分かりやすくすることができます。

subplotsを使うことで、配列を使ったインデックス処理になります。

私個人の意見ですが、今回のような修正前と修正後のグラフを並べて比較したい場合は、subplots を使った方が便利のように思います。

### 4行×2列で subplot を使う場合

plt.subplot(4, 2, 1)
plt.グラフメソッド

plt.subplot(4, 2, 2)
plt.グラフメソッド

()

plt.subplot(4, 2, 8)
plt.グラフメソッド

### 4行×2列で subplots を使う場合

fig, axes= plt.subplots(4, 2)

axes[0][0].グラフメソッド
axes[0][1].グラフメソッド
()
axes[3][1].グラフメソッド
fig, axes= plt.subplots(len(num_list), 2, figsize=(20,40))

for i in range(len(num_list)):
    axes[i][0].set_title(num_list[i])
    axes[i][0].hist(df[num_list[i]])

    axes[i][1].set_title(num_list[i]+ "log")
    axes[i][1].hist(df2[num_list[i]].apply(np.log))

plt.tight_layout()

以上になります、最後までお読みいただきありがとうございました。

参考サイト

https://qiita.com/mitsumizo/items/efddc80f166e900f97ef

https://note.nkmk.me/python-seaborn-pandas-pairplot/

https://qiita.com/kenichiro_nishioka/items/8e307e164a4e0a279734

https://qiita.com/cnloni/items/20b5908fbae755192498

https://qiita.com/zkr/items/90167693f142ebb55a7d

Discussion