読者です 読者をやめる 読者になる 読者になる

StatsFragments

Python, R, Rust, 統計, 機械学習とか

Python pandas プロット機能を使いこなす

pandas は可視化のための API を提供しており、折れ線グラフ、棒グラフといった基本的なプロットを簡易な API で利用することができる。一般的な使い方は公式ドキュメントに記載がある。

これらの機能は matplotlib に対する 薄い wrapper によって提供されている。ここでは pandas 側で一処理を加えることによって、ドキュメントに記載されているプロットより少し凝った出力を得る方法を書きたい。

補足 サンプルデータに対する見せ方として不適切なものがあるが、プロットの例ということでご容赦ください。

パッケージのインポート

import matplotlib.pyplot as plt
plt.style.use('ggplot')

import matplotlib as mpl
mpl.__version__
# '1.5.0'

import numpy as np
np.__version__
# '1.10.1'

import pandas as pd
pd.__version__
# u'0.17.0'

折れ線グラフ、棒グラフ系

サンプルデータとして 気象庁から 東京、札幌、福岡の 2014 年の日別平均気温データをダウンロードし、pd.read_csv で読み込んだ。

df = pd.read_csv('data.csv', index_col=0, header=[0, 1, 2], skiprows=[0], encoding='shift-jis')
df = df.iloc[:, [0, 3, 6]]
df.columns = [u'東京', u'札幌', u'福岡']
df.index = pd.to_datetime(df.index)
df.head()

f:id:sinhrks:20151115214421p:plain

まずは単純な折れ線グラフを描く。

df.plot()

f:id:sinhrks:20151115214525p:plain

次に、適当な単位 (ここでは月次) でグルーピングして棒グラフを書きたい。集計には pd.Grouper を利用する。

参考 Python pandas アクセサ / Grouperで少し高度なグルーピング/集計 - StatsFragments

monthly = df.groupby(pd.Grouper(level=0, freq='M')).mean()
monthly

f:id:sinhrks:20151115215001p:plain

このとき、index が 時系列 ( DatetimeIndex ) の場合、そのままでは x 軸がタイムスタンプとして表示されてしまう。

monthly.plot.bar(color=['#348ABD', '#7A68A6', '#A60628'])

f:id:sinhrks:20151115215123p:plain

x軸 を簡単にフォーマットしたい場合、Index.format() メソッドを利用して index を文字列表現に変換するとよい。

monthly.index.format()
# ['2014-01-31', '2014-02-28', '2014-03-31', '2014-04-30', '2014-05-31', '2014-06-30',
#  '2014-07-31', '2014-08-31', '2014-09-30', '2014-10-31', '2014-11-30', '2014-12-31']
monthly.index = monthly.index.format()
monthly.plot.bar(color=['#348ABD', '#7A68A6', '#A60628'])

f:id:sinhrks:20151115214908p:plain

また、各棒の塗り分けには colormap を指定することもできる。

monthly.plot.bar(cmap='rainbow')

f:id:sinhrks:20151115215206p:plain

折れ線グラフ / 棒グラフを一つのプロットとして描画する場合は以下のようにする。.plot メソッドmatplotlib.axes.Axes インスタンスを返すため、続くプロットの描画先として その Axes を指定すればよい。

ax = monthly[u'札幌'].plot(legend=True)
monthly[[u'東京', u'福岡']].plot.bar(ax=ax, rot=30)

f:id:sinhrks:20151115215325p:plain

また、matplotlib 側で 極座標Axes を作成しておけば、レーダーチャートのような描画もできる。

補足 一部 線が切れているのは負値のため。

fig = plt.figure()
ax = fig.add_axes([0.1, 0.1, 0.9, 0.9], polar=True)
indexer = np.arange(1, 13) * 2 * np.pi / 12
monthly.index = indexer
monthly.append(monthly.iloc[0]).plot(ax=ax)
ax.set_xticks(indexer)
ax.set_xticklabels(np.arange(1, 13));

f:id:sinhrks:20151115215334p:plain

分布描画系

各都市の気温の分布が見たい、といった場合にはヒストグラムを描画する。

df.plot.hist(bins=100, alpha=0.5)

f:id:sinhrks:20151115215608p:plain

より細かい単位でグループ分けしてグラフを描きたい場合は、まず plt.subplots で必要な Axes を生成する。続けて、pandas でグループ化したデータを各 Axes に対して描画すればよい。

ここでは月ごとに サブプロットにして カーネル密度推定したグラフを描画してみる。

fig, axes = plt.subplots(4, 3, figsize=(8, 6))
plt.subplots_adjust(wspace=0.5, hspace=0.5)
for (ax, (key, group)) in zip(axes.flatten(), df.groupby(pd.Grouper(level=0, freq='M'))):
    ax = group.plot.kde(ax=ax, legend=False, fontsize=8)
    ax.set_ylabel('')
    ax.set_title(key, fontsize=8)

f:id:sinhrks:20151115215728p:plain

同様に箱ヒゲ図も描ける。

fig, axes = plt.subplots(4, 3, figsize=(8, 6))
plt.subplots_adjust(wspace=0.5, hspace=0.5)
for (ax, (key, group)) in zip(axes.flatten(), df.groupby(pd.Grouper(level=0, freq='M'))):
    ax = group.plot.box(ax=ax)
    ax.set_ylabel('')
    ax.set_title(key, fontsize=8)

f:id:sinhrks:20151115215752p:plain

散布図系

通常の散布図は以下のようにして描ける。

df.plot(kind='scatter', x=u'札幌', y=u'福岡')

f:id:sinhrks:20151115220121p:plain

各点を適当に色分けしたい場合、c キーワードで各点の色を指定する。また、colormap も合わせて指定できる。ここで指定した値は連続値として扱われるため colorbar が表示されている (非表示にすることもできる)。

df.plot(kind='scatter', x=u'札幌', y=u'福岡', c=df.index.month, cmap='winter')

f:id:sinhrks:20151115220154p:plain

各点をグループ別に塗り分ける ( 離散値として扱う ) 場合は、単一の Axes に対して グループ化したデータを順番に描画していけばよい。

cmap = plt.get_cmap('rainbow')
colors = [cmap(c / 12.0) for c in np.arange(1, 13)]
colors
# [(0.33529411764705885, 0.25584277759443558, 0.99164469551074275, 1.0),
#  (0.17058823529411765, 0.49465584339977881, 0.96671840426918743, 1.0),
#  ...
#  (1.0, 0.25584277759443586, 0.12899921653020341, 1.0),
#  (1.0, 1.2246467991473532e-16, 6.123233995736766e-17, 1.0)]

fig, ax = plt.subplots(1, 1)
for i, (key, group) in enumerate(df.groupby(pd.Grouper(level=0, freq='M')), start=1):
    group.plot(kind='scatter', x=u'札幌', y=u'福岡', color=cmap(i / 12.0), ax=ax, label=i)

f:id:sinhrks:20151115220859p:plain

また、ダミーのカラムを作成することで月次の気温の分布を散布図としてみることもできる。

df['month'] = df.index.month
df.plot(kind='scatter', x='month', y=u'札幌')

f:id:sinhrks:20151115220521p:plain

x 軸方向に散らす。

df['month2'] = df.index.month + np.random.normal(loc=0, scale=0.05, size=len(df.index.month))
df.plot(kind='scatter', x='month2', y=u'札幌')

f:id:sinhrks:20151115220531p:plain

バブルチャートも描ける。前処理として札幌の気温を離散化し、月次のデータ数を集計する。

df2 = df.copy()
df2[u'札幌2'] = df2[u'札幌'] // 10 * 10 
df2['count'] = 1
df2 = df2.groupby(['month', u'札幌2'])['count'].count().reset_index()
df2.head()

f:id:sinhrks:20151115220541p:plain

バブルの大きさは s キーワードで指定する。

df2.plot.scatter(x='month', y=u'札幌2', s=df2['count'] * 10)
df.plot(kind='scatter', x='month2', y=u'札幌')

f:id:sinhrks:20151115220549p:plain

さらに変形して plt.imshow でヒートマップを描画する。imshowpandasAPI にはないため、DataFrame.pipe でデータを渡す。

piv = pd.pivot_table(df, index=u'札幌2', columns=df.index.month, values=u'札幌', aggfunc='count')
piv = piv.fillna(0)
piv

f:id:sinhrks:20151115220601p:plain

piv.pipe(plt.imshow, cmap='winter')
ax = plt.gca()
ax.invert_yaxis()
ax.set_yticks(np.arange(4))
ax.set_yticklabels(piv.index);

f:id:sinhrks:20151115220608p:plain

まとめ

pandas での前処理 + 可視化機能の組み合わせを利用して、より柔軟にプロットを行う方法を記載した。pandas の裏側は ndarray のため、最後の例のように pandas 側に API がないプロットも簡単に描ける。

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理

Pythonによるデータ分析入門 ―NumPy、pandasを使ったデータ処理