はじめに
年末よりずっと忙しかったのですが、久しぶりに余裕ができて記事を書いています。
何年か前にやった仕事のフォローのため二週間の出張でマレーシアに来ています。お客さんに提出する報告書で久しぶりに時系列グラフを作成する必要があったので、その時調べたものをアップしたいと思います。(実はお客さん提出用グラフの時間軸はデフォルトで出してしまったのですが、提出後今後のために調べたものをアップしているのが本当のところです)
当方の環境は以下の通り。
- MacBook Pro (Retina, 13-inch, Mid 2014)
- macOS MOjave
- Python 3.7.2
時間軸設定には以下のサイトを参考にしました。
- https://stackoverflow.com/questions/17452179/not-write-out-all-dates-on-an-axis-matplotlib
- https://bunseki-train.com/setting_ticks_by_matplotlib_dates/
- https://matplotlib.org/api/dates_api.html
作例は以下の通り。ただの時系列グラフですが、よくサイトで目にするものよりはスパンが長く、月単位に目盛りを入れているところがミソです。
モジュールのインポート
下記のモジュールをインポート。この他、エクセルからデータを読み込むので、xlrd
を pip
でインストールしておく必要があります。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import datetime
import matplotlib.dates as mdates
データ読み込み
データは2つのエクセルファイルから読み込みます。
ファイル calvert.xlsx
1つめのデータは下に示すもので、作例グラフの緑線を描くためのもの。
エクセルで以下のような形で収納されています。これは自分でデータを打ち込んだので、打ち込みやすいよう、日付(dd)、月(mm)、年(yy)、値(Q) という並びにしています。
欠測期間が長く、グラフの線を連続させたくない場合は、欠測期間中のある一日の日付とデータとして nan
をいれることにより、グラフの線を結ばないで描画してくれます。
ファイル v-notch.xlsx
2つめのデータは下に示すもので、作例グラフの濃い青線を描くためのもの。
これは既存のエクセルファイルがあったのでそれを用いていますが、ここで使用するのは、カラム A (Date)とカラム N (RWL) だけです。
エクセルファイルからのデータ読み込み
エクセルからのデータ読み込みは pandas で行っています。
データ読み込み部のコードは以下の通り。
# discharge data input
fnameR='calvert.xlsx'
df = pd.read_excel(fnameR,sheet_name='data')
ss=[]
for sd,sm,sy in zip(df['dd'],df['mm'],df['yy']):
s1='{0:0>2d}'.format(sd)
s2='{0:0>2d}'.format(sm)
s3='{0:0>4d}'.format(sy)
ss=ss+[s1+'/'+s2+'/'+s3]
df.index=pd.to_datetime(ss, format='%d/%m/%Y')
# reservoir water level input
fnameR='v-notch.xlsx'
dfr = pd.read_excel(fnameR,usecols=[0,13],index_col=0)
dfr.index = pd.to_datetime(dfr.index, format='%d/%m/%Y')
calvert.xlsx の読み込みでは、日付・月・年の個別の文字列を連結した文字列リスト [ss] を作り、これを日付データとしてデータフレームのインデックスとします。
v-notch.xlsx の読み込みでは、usecols=[0,13]
により必要なカラム(0と13)のみを読み込み、index_col=0
として、カラム0をデータフォレームのインデックスとします。またインデックスを日付データにします。
calvert.xlsx からのデータをデータフレーム df, v-notch.xlsx からのデータをデータフレーム dfr に格納しています。
横軸最小値と最大値の指定
横軸最小値を2015年3月1日、横軸最大値を2019年3月31日に指定します。このとき下のように日付を示す文字列を作成し、日付型に変換し、これを用いてx軸範囲を指定しています。
日付の指定は、%Y-%m-%d
という形にしています。Python は日付はデフォルトでこの形で扱っているようですので。
sxmin='2015-03-01'
sxmax='2019-03-31'
xmin = datetime.datetime.strptime(sxmin, '%Y-%m-%d')
xmax = datetime.datetime.strptime(sxmax, '%Y-%m-%d')
plt.xlim([xmin,xmax])
横軸の書式設定
横軸の書式設定は、以下のようにxaxis.set_major_formatter
とxaxis.set_minor_locator
を用います。
マレーシアでは通常は、日・月・年の順で表示するのでDateFormatter
で%d-%b-%Y
を指定します。月の%b
は短縮形英語表現です。月と日付の順番が紛らわしいので、これではっきりします。
主目盛の位置はxaxis.set_major_locator
のMonthLocator(interval=6)
で、6ヶ月単位に指定します。また補助目盛の位置はxaxis.set_minor_locator
のMonthLocator(interval=1)
で1ヶ月単位に指定します。autofmt_xdate()
は長い表記を適当に斜めに表示してくれます。
グリッドは主目盛・補助目盛双方に指定するとうっとおしいので主目盛のみ(major)としました。
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%d-%b-%Y'))
plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=6))
plt.gca().xaxis.set_minor_locator(mdates.MonthLocator(interval=1))
plt.gcf().autofmt_xdate()
この事例では、twinx()
を用いた2軸利用のグラフとしていますが、上記記載は最後にしておく必要があります。twinx()
の前に書いてもこの記載は有効にならずデフォルトの軸が表示されてしまいます。
プログラム全文
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import datetime
import matplotlib.dates as mdates
def main():
# discharge data input
fnameR='calvert.xlsx'
df = pd.read_excel(fnameR,sheet_name='data')
ss=[]
for sd,sm,sy in zip(df['dd'],df['mm'],df['yy']):
s1='{0:0>2d}'.format(sd)
s2='{0:0>2d}'.format(sm)
s3='{0:0>4d}'.format(sy)
ss=ss+[s1+'/'+s2+'/'+s3]
df.index=pd.to_datetime(ss, format='%d/%m/%Y')
# reservoir water level input
fnameR='v-notch.xlsx'
dfr = pd.read_excel(fnameR,usecols=[0,13],index_col=0)
dfr.index = pd.to_datetime(dfr.index, format='%d/%m/%Y')
fsz=12
plt.figure(figsize=(10,6), dpi=200,facecolor='w')
plt.rcParams['font.size']=fsz
sxmin='2015-03-01'
sxmax='2019-03-31'
xmin = datetime.datetime.strptime(sxmin, '%Y-%m-%d')
xmax = datetime.datetime.strptime(sxmax, '%Y-%m-%d')
plt.xlim([xmin,xmax])
plt.ylim([0,500])
plt.xlabel('Date')
plt.ylabel('Discharge (Liter/min)')
plt.grid(which='major',axis='both',color='#999999',linestyle='--')
plt.plot(df.index,df['Q'],'-',color='#ff0000',label='Discharge')
qs=165.0; spot='2016-05-31'
dsp = datetime.datetime.strptime(spot, '%Y-%m-%d')
plt.plot([dsp],[qs],'o',color='#ff0000')
ss='31/05/2016\n{0:.0f}L/min'.format(qs)
plt.text(dsp,qs-10,ss,va='top',ha='center',fontsize=fsz-2)
plt.plot([0],[0],'-',lw=2,color='#0000ff',label='RWL')
plt.title('Discharge of Calvert at River Outlet Bay',loc='left',fontsize=fsz)
plt.legend(loc='lower right',fontsize=fsz)
plt.twinx()
plt.ylim([65,90])
plt.ylabel('Reservoir Water Level (EL.m)')
plt.plot(dfr.index,dfr['RWL'],'-',lw=2,color='#0000ff')
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%d-%b-%Y'))
plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=6))
plt.gca().xaxis.set_minor_locator(mdates.MonthLocator(interval=1))
plt.gcf().autofmt_xdate()
fnameF='fig_qiita.jpg'
plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
plt.show()
#==============
# Execution
#==============
if __name__ == '__main__': main()
雑感
普段からエクセルは好んでは使わないのですが、データ入力などには向いている。テキストエディタでブランクをいれながら入力するよりは、エクセルのような表計算形式の入力のほうが楽です。またエクセル入力したファイルもpandasで楽々読み込みできるので、とても便利。
軸の単位を示すのに、xaxis.set_major_locator
には今回用いたMonthLocator
の他、WeekdayLocator, DayLocator, HourLocator, MinuteLocator, SecondLocator
があり、色々な単位に対応できるようです。(前出:https://matplotlib.org/api/dates_api.html 参照)
これで時系列グラフの時間軸書式をある程度好きなように表現できるようになった気がします。
この投稿が、何かしら皆様のお役にもたてば幸いです。
以 上