PythonというかMatplotlibでスケールや単位の異なる時系列データを整形して表示する


1つのソースの時系列データをグラフ化で特に悩むことはありません。が、ソースの異なる時系列データを表示する方法が分からなかったのでやってみました。結論からいうと得たい結果は得られましたが、いまいちです。

>結果は不本意なので、もっとスマートな方法があれば教えて下さい。

## やりたいことと課題

例としては、

* Webアクセスデータはアクセス毎に秒単位で存在(2016-06-2 00:00:00から2016-06-7 00:00:00取得）
* 同時期のCPUのデータ。1時間に1回、遅れて取得(2016-06-03 12:00:00から2016-06-09 17:00:00取得）


みたいな感じです。
各データを表示することは簡単ですが、取得期間も単位（件数、%等）も異なる場合は厄介です。

そもそもDBやCSVからデータを取得する方法は[こちら](http://qiita.com/zaburo/items/442d575d166be238e226)をご覧ください。

## Matplotlibの基本

細かな話の前に、そもそもMatploglibに慣れてないので、簡単におさらい。
最もシンプルなコードは下記のような感じ。subplotを利用しないともっとシンプルですが、以後の記述の互換性のために敢えてsubplot（複数の図をコントロールするための記述）で書いています。

```python
#coding:utf-8
import matplotlib.pyplot as plt

#白紙のキャンバス？を生成
fig = plt.figure()
#1行、1列の1個目の図を描くエリアを用意
ax = fig.add_subplot(1,1,1)

#dataを用意 xとyの個数は一致しておく必要あり
x = [0,1,2,3,4,5]
y = [54,35,32,44,74,45]

#データをセット（プロット）
ax.plot(x,y)

#図を表示
plt.show()
```

まあ、普通に表示されます。

![plt](http://www.bluecode.jp/qiita/plt/001.png)

>コードの補足や解説が下の方にありますので、見てみて下さい。

##課題の確認

2つの性質の違う2つのデータを準備します。
なお、データや日付はPythonの強力な関数で自動生成します。

###データ1

2016/6/2から6/7までの日次データ。データ数は6個。

```python
#coding:utf-8

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(1,1,1)

#0-100の間で乱数を発生させて6要素のリストを作成
y1 = np.random.randint(0,100,6)
#2016-06-02 00:00:00から1日毎のdatetimeを生成(6個生成)
x1 = pd.date_range('2016-06-02 00:00:00',periods=6,freq='d')

ax.plot(x1,y1)

plt.show()
```

こんな感じ。

![plt](http://www.bluecode.jp/qiita/plt/002.png)

###データ2

2016/6/3から6/9までの時次データ。データ数は150個。

```python
#coding:utf-8

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(1,1,1)

#5-40の間で乱数を発生させて150要素のリストを生成
y2 = np.random.randint(5,40,150)
#2016-06-03 12:00:00から1時間毎のdatetimeを生成(150個生成）
x2= pd.date_range('2016-06-03 12:00:00',periods=150,freq='H')

ax.plot(x2,y2)

plt.show()
```

時間単位なので、データ1より、激しい感じ。

![plt](http://www.bluecode.jp/qiita/plt/003.png)

上記2つのデータは期間こそかぶっている時期はあるものの、取得期間も性質も違う。
最低限、時間軸（x軸）を合わせて表示したい。

##とりあえず表示

とりあえず表示してみます。

```python
#coding:utf-8

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

fig = plt.figure()
#2行1列の図を描く場所を確保
ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(2,1,2)

#data1
y1 = np.random.randint(0,100,6)
x1 = pd.date_range('2016-06-02 00:00:00',periods=6,freq='d')

#data2
y2 = np.random.randint(5,40,150)
x2 = pd.date_range('2016-06-03 12:00:00',periods=150,freq='H')

#plot
ax1.plot(x1,y1)
ax2.plot(x2,y2)

plt.show()
```

それっぽいのですが、時系列が合っておらず、事実上無意味なグラフとなっています。

![plt](http://www.bluecode.jp/qiita/plt/004.png)



##対応方法

###不本意ながら

いろいろ試してみましたPython初心者の私にはとりあえず下記が限界。
両データの共通単位となるダミーデータ（ここではx0,y0）を生成し、それを利用するというもの。

ここでは6/1~6/10のデータを利用してみました。

```python
#coding:utf-8

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

fig = plt.figure()
#2行1列の図を描く場所を確保
ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(2,1,2)

#data0 基準とするダミーデータを生成
#yの値を0を10個含むリスト
#xの値は6/1 ~ 6/10までのdatetime
y0 = [0]*10
x0 = pd.date_range('2016-06-01 00:00:00',periods=10,freq='d')

#data1
y1 = np.random.randint(0,100,6)
x1 = pd.date_range('2016-06-02 00:00:00',periods=6,freq='d')

#data2
y2 = np.random.randint(5,40,150)
x2 = pd.date_range('2016-06-03 12:00:00',periods=150,freq='H')

#plot
#ax1
ax1.plot(x0,y0)
ax1.plot(x1,y1)
#ax2
ax2.plot(x0,y0)
ax2.plot(x2,y2)

plt.show()
```

どうやら、時間軸はあっている感じですが、キャプション等が被ったりして見難い。

![plt](http://www.bluecode.jp/qiita/plt/005.png)

###見た目を整える

```python
#coding:utf-8

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt

fig = plt.figure()
#2行1列の図を描く場所を確保
ax1 = fig.add_subplot(2,1,1)
ax2 = fig.add_subplot(2,1,2)

#data0
y0 = [0]*10
x0 = pd.date_range('2016-06-01 00:00:00',periods=10,freq='d')

#data1
y1 = np.random.randint(0,100,6)
x1 = pd.date_range('2016-06-02 00:00:00',periods=6,freq='d')

#data2
y2 = np.random.randint(5,40,150)
x2 = pd.date_range('2016-06-03 12:00:00',periods=150,freq='H')

#plot
#ax1
ax1.plot(x0,y0)
ax1.plot(x1,y1,'r')
#ax2
ax2.plot(x0,y0)
ax2.plot(x2,y2,'b')

#整形
#ax1
ax1.set_xticks(x0)
ax1.set_xticklabels(x0,rotation=90,size="small")
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax1.grid()
#ax2
ax2.set_xticks(x0)
ax2.set_xticklabels(x0,rotation=90,size="small")
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax2.grid()

#縦になったキャプションが被るのを防止
plt.subplots_adjust(hspace=0.7,bottom=0.2)

plt.show()
```

キャプションを縦にしたり、年月日だけ表示などにした。グラフの色も変えてみた。
個人的にはこれで用は足りる。

![plt](http://www.bluecode.jp/qiita/plt/006.png)

###1つのグラフにマージする

わざわざ2つのグラフに分ける必要も無い場合も多いので1つのグラフに表示してみる。

```python
#coding:utf-8

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import datetime as dt

fig = plt.figure()
#1行1列の図
ax1 = fig.add_subplot(1,1,1)
#レイヤー追加（みたいな？）
ax2 = ax1.twinx()

#data0
y0 = [0]*10
x0 = pd.date_range('2016-06-01 00:00:00',periods=10,freq='d')

#data1
y1 = np.random.randint(0,100,6)
x1 = pd.date_range('2016-06-02 00:00:00',periods=6,freq='d')

#data2
y2 = np.random.randint(5,40,150)
x2 = pd.date_range('2016-06-03 12:00:00',periods=150,freq='H')

#plot
#ax1
ax1.plot(x0,y0)
ax1.plot(x1,y1,'r')

#ax2
ax2.plot(x0,y0)
ax2.plot(x2,y2,'b')

#整形
#ax1
ax1.set_xticks(x0)
ax1.set_xticklabels(x0,rotation=90,size="small")
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax1.grid()
#軸のフォーマット調整
ax1.set_ylabel('pv', color='r')
for tl in ax1.get_yticklabels():
    tl.set_color('r')
#ax2
ax2.set_xticks(x0)
ax2.set_xticklabels(x0,rotation=90,size="small")
ax2.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax2.grid()
#軸のフォーマット調整
ax2.set_ylabel('cpu', color='b')
for tl in ax2.get_yticklabels():
    tl.set_color('b')

#縦になったキャプションが被るのを防止
plt.subplots_adjust(hspace=0.7,bottom=0.2)

plt.show()
```

まあ、こんな感じ。軸の色などを変え、どちらのデータなのかわかりやすくしてみました（わかりにくいけど）。

![plt](http://www.bluecode.jp/qiita/plt/007.png)

##補足・解説

###文字列からdatetimeへの変換

上記の例ではpandasのdate_range()でxデータを生成したため、はじめからdatetime型となっていますが、CSVやDBから取得した場合、文字列となっています。文字列でもそのままうまく処理できるようですが、必要に応じてdatetimeに変換します。

文字列リストからdatetimeリストへの変換は下記のようにします。

```python
y1 = [0.43,0.26,0.33]
d1 = ['2016-06-21 12:00:00','2016-06-23 09:00:00','2016-06-26 18:00:00']
x1 = [dt.datetime.strptime(d,'%Y-%m-%d %H:%M:%S') for d in d1]
```

###軸（X軸）の編集

x軸等の編集について解説します。

####キャプションを入れる位置を決める

日付だと少しわかりにくいのですが、例えば、横軸が0から1000の場合、300と600と900の3箇所だけに単位を表示したいとしたい場合、set_xticks([300,600,900])とします。

ここでは、6/1 ~ 6/10で日次でキャプションを入れたいのでダミーで生成したx0をそのまま代入しています。

```python
ax1.set_xticks(x0)
```

####キャプションの表示を決める

キャプションを入れる位置を決めたら、次は、その表示内容、表示書式などを決定します。
例えば上記で、位置としてはset_xticks([300,600,900])としたが、表示上は小、中、大としたい場合は、set_xticklabels(['小','中','大'])となします。rotationで文字の傾きを指定できます。90とすると縦になります。sizeはそのままです。

ここでは、6/1 ~ 6/10の日付をそのまま表示したいので、x0をそのまま代入しています。

```python
ax1.set_xticklabels(x0,rotation=90,size="small")
```

####表示形式をさらに調整

普通にx0を表示すると年月日 時分秒全てが表示され長いので、年月日だけが表示されるようにしています。

```python
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
```

###色を付ける

####線の色

データの線に色を付けたい場合は、下記のようにします。この例では赤になります。

```python
ax1.plot(x1,y1,'r')
```

####軸の色

軸やキャプションを指定し色を付けたい場合は、下記のようにします。この例では青になります。

```python
ax2.set_ylabel('cpu', color='b')
for tl in ax2.get_yticklabels():
    tl.set_color('b')
```

####余白の調整

hspaceはグラフ間の縦の間隔調整。フラフの高さが1.0とした場合の単位らしい。bottomは下の余白。

```python
plt.subplots_adjust(hspace=0.7,bottom=0.2)
```