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からデータを取得する方法はこちらをご覧ください。
Matplotlibの基本
細かな話の前に、そもそもMatploglibに慣れてないので、簡単におさらい。
最もシンプルなコードは下記のような感じ。subplotを利用しないともっとシンプルですが、以後の記述の互換性のために敢えてsubplot(複数の図をコントロールするための記述)で書いています。
#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()
まあ、普通に表示されます。
コードの補足や解説が下の方にありますので、見てみて下さい。
##課題の確認
2つの性質の違う2つのデータを準備します。
なお、データや日付はPythonの強力な関数で自動生成します。
###データ1
2016/6/2から6/7までの日次データ。データ数は6個。
#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()
こんな感じ。
###データ2
2016/6/3から6/9までの時次データ。データ数は150個。
#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より、激しい感じ。
上記2つのデータは期間こそかぶっている時期はあるものの、取得期間も性質も違う。
最低限、時間軸(x軸)を合わせて表示したい。
##とりあえず表示
とりあえず表示してみます。
#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()
それっぽいのですが、時系列が合っておらず、事実上無意味なグラフとなっています。
##対応方法
###不本意ながら
いろいろ試してみましたPython初心者の私にはとりあえず下記が限界。
両データの共通単位となるダミーデータ(ここではx0,y0)を生成し、それを利用するというもの。
ここでは6/1~6/10のデータを利用してみました。
#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()
どうやら、時間軸はあっている感じですが、キャプション等が被ったりして見難い。
###見た目を整える
#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()
キャプションを縦にしたり、年月日だけ表示などにした。グラフの色も変えてみた。
個人的にはこれで用は足りる。
###1つのグラフにマージする
わざわざ2つのグラフに分ける必要も無い場合も多いので1つのグラフに表示してみる。
#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()
まあ、こんな感じ。軸の色などを変え、どちらのデータなのかわかりやすくしてみました(わかりにくいけど)。
##補足・解説
###文字列からdatetimeへの変換
上記の例ではpandasのdate_range()でxデータを生成したため、はじめからdatetime型となっていますが、CSVやDBから取得した場合、文字列となっています。文字列でもそのままうまく処理できるようですが、必要に応じてdatetimeに変換します。
文字列リストからdatetimeリストへの変換は下記のようにします。
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をそのまま代入しています。
ax1.set_xticks(x0)
####キャプションの表示を決める
キャプションを入れる位置を決めたら、次は、その表示内容、表示書式などを決定します。
例えば上記で、位置としてはset_xticks([300,600,900])としたが、表示上は小、中、大としたい場合は、set_xticklabels(['小','中','大'])となします。rotationで文字の傾きを指定できます。90とすると縦になります。sizeはそのままです。
ここでは、6/1 ~ 6/10の日付をそのまま表示したいので、x0をそのまま代入しています。
ax1.set_xticklabels(x0,rotation=90,size="small")
####表示形式をさらに調整
普通にx0を表示すると年月日 時分秒全てが表示され長いので、年月日だけが表示されるようにしています。
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
###色を付ける
####線の色
データの線に色を付けたい場合は、下記のようにします。この例では赤になります。
ax1.plot(x1,y1,'r')
####軸の色
軸やキャプションを指定し色を付けたい場合は、下記のようにします。この例では青になります。
ax2.set_ylabel('cpu', color='b')
for tl in ax2.get_yticklabels():
tl.set_color('b')
####余白の調整
hspaceはグラフ間の縦の間隔調整。フラフの高さが1.0とした場合の単位らしい。bottomは下の余白。
plt.subplots_adjust(hspace=0.7,bottom=0.2)