7.3 より複雑なデータを把握するには

しかし, 例えば,

  1. x, y のヒストグラムを, 棒グラフのように並べるのではなくオーバーレイさせたい. さらに変数 zグループ分けして個別に描きたい
  2. ラインプロットを z, でグループ分けして個別に描きたい
  3. 散布図を, z, w の違いが分かるように色分けして描きたい.

と言うふうにより細かく分けてデータを見たい時, pandas のプロット機能だけでは困難です. pandas のグラフは matplotlib を使って描かれているため, matplotlib を使えば細かい調整も可能です. これは, 例えばmatplotlibより「便利な」グラフツールとしてよく紹介される seaborn にも見られる傾向です. seaborn の傾向として, 2つ以上の複数列のデータを同時にプロットする際には便利であることが多いですが, 一方でグループ化の機能や細かい調整となると, やはり書くのが大変になります.

そこで, plotnine を紹介します. たとえばヒストグラムの場合, 以下のように書くことができます. 最初に読み込みと, 画像をpdfで出力する際に日本語を文字化けせず埋め込むための設定をします18.

import seaborn as sns

from plotnine import *
theme_set(
  theme_grey(base_family="Noto Serif CJK JP")
)

plotnine でグループごとのグラフを描くには, データフレームをロング形式にする必要があります.

ggplot(df.melt(id_vars=['z', 'w']),
           aes(x='value', group='variable', fill='variable')
      ) + geom_histogram(
    bins=10, position='identity', alpha=.5
) + facet_wrap('z', scales='free_y') + labs(
  title='plotnine によるヒストグラム')

同様のことを pandas, seaborn, matplotlib を使って書くと, おそらく seabornmatplotlib を組み合わせて書くのが一番簡単です19. しかし, これは参照透過性の点でよくありません. また, ヒストグラムのビン幅および位置がずれます. 同じビンに調整するとさらに記述が長くなるため今回は省略します.

fig, axes = plt.subplots(ncols=df['z'].unique().shape[0], sharex=True)
for c in ['x', 'y']:
    for ind_z, z in enumerate(df['z'].unique()):
        sns.distplot(df.loc[lambda d: d.z==z, c],
                     bins=10, kde=False, label=c, ax=axes[ind_z])
fig.suptitle('seaborn によるヒストグラム')
plt.legend()

それぞれの描き方で作成したグラフは図 7.1のようになります.

(左) plotnine によるグラフ (右) seaborn によるグラフ(左) plotnine によるグラフ (右) seaborn によるグラフ

図 7.1: (左) plotnine によるグラフ (右) seaborn によるグラフ

 (2), (3) についても plotnine とそれ以外を比較してみます.

 (2) では, pandas は色分けができませんが, 一方で行インデックスをx軸に暗黙に指定できました. plotnineseaborn.lineplot() では後者ができないため, 入力データの加工が必要になります. seabornFacetGrid を利用することになります. seaborn は不具合が多いため, plotnine の結果のみ 図 7.2 に掲載します.

ggplot(df.reset_index().melt(id_vars=['index', 'z', 'w']),
       aes(x='index', y='value', group='variable', color='variable')
      ) + geom_line() + facet_wrap('z')

sns.FacetGrid(data=df.reset_index().melt(id_vars=['index', 'z', 'w']),
                     col='z', hue='variable'
             ).map(sns.lineplot, 'index', 'value').add_legend()

plotnine による折れ線グラフ

図 7.2: plotnine による折れ線グラフ

 (3) もまた, pandas のプロットでは大変なので seaborn を使います. 出力結果は図7.3です. なお, 本稿で使用している seaborn のバージョンでは不具合により変数名を指定できないため, 冗長な書き方になります.

ggplot(df, aes(x='x', y='y', color='z', shape='w')) + geom_point()

sns.scatterplot(x='x', y='y',
    hue=df.z.values.tolist(),
    style=df.z.values.tolist(), data=df)

plotnine (上) と seaborn (下) による散布図plotnine (上) と seaborn (下) による散布図

図 7.3: plotnine (上) と seaborn (下) による散布図


  1. これは, plotnine のテーマの多くがデフォルトでフォントを DejaVu Sans で上書きしてしまうためです. よって, None を指定することで rcParams に設定した日本語フォントを上書きしないようにしています. ここでは Ubuntu の標準日本語フォントである Noto Serif CJK JP を指定していますが, Windows なら Yu Gothic, Mac なら Hiragino Sans などが標準フォントの1つです.↩︎

  2. seaborn.FacetGrid() という分割してグラフを描くための関数がありますが, さらにヒストグラムを色分けするという動作を seaborn が想定していないため使えません.↩︎