Python matplotlib 時系列グラフ（時間軸の設定）

# はじめに
年末よりずっと忙しかったのですが、久しぶりに余裕ができて記事を書いています。
何年か前にやった仕事のフォローのため二週間の出張でマレーシアに来ています。お客さんに提出する報告書で久しぶりに時系列グラフを作成する必要があったので、その時調べたものをアップしたいと思います。（実はお客さん提出用グラフの時間軸はデフォルトで出してしまったのですが、提出後今後のために調べたものをアップしているのが本当のところです）

当方の環境は以下の通り。

+ 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://stackoverflow.com/questions/17452179/not-write-out-all-dates-on-an-axis-matplotlib)
+ [https://bunseki-train.com/setting_ticks_by_matplotlib_dates/](https://bunseki-train.com/setting_ticks_by_matplotlib_dates/)
+ [https://matplotlib.org/api/dates_api.html](https://matplotlib.org/api/dates_api.html)


作例は以下の通り。ただの時系列グラフですが、よくサイトで目にするものよりはスパンが長く、月単位に目盛りを入れているところがミソです。

![fig_qiita.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/4540a5ff-c108-4792-d637-4ed85d70d71f.jpeg)


# モジュールのインポート
下記のモジュールをインポート。この他、エクセルからデータを読み込むので、```xlrd``` を ```pip``` でインストールしておく必要があります。

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

# データ読み込み
データは２つのエクセルファイルから読み込みます。

## ファイル calvert.xlsx
１つめのデータは下に示すもので、作例グラフの緑線を描くためのもの。
エクセルで以下のような形で収納されています。これは自分でデータを打ち込んだので、打ち込みやすいよう、日付(dd)、月(mm)、年(yy)、値(Q) という並びにしています。
欠測期間が長く、グラフの線を連続させたくない場合は、欠測期間中のある一日の日付とデータとして ```nan``` をいれることにより、グラフの線を結ばないで描画してくれます。

<img width="955" alt="Screenshot 2019-04-15 09.15.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/f4118172-7f02-64b1-b861-dca810606447.png">

## ファイル v-notch.xlsx
２つめのデータは下に示すもので、作例グラフの濃い青線を描くためのもの。
これは既存のエクセルファイルがあったのでそれを用いていますが、ここで使用するのは、カラム A (Date)とカラム N (RWL) だけです。

<img width="968" alt="Screenshot 2019-04-15 09.14.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/57637e5a-b7dd-cccf-0bd5-3a73c9b05573.png">

## エクセルファイルからのデータ読み込み
エクセルからのデータ読み込みは pandas で行っています。
データ読み込み部のコードは以下の通り。

```py
    # 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]``` により必要なカラム（０と１３）のみを読み込み、```index_col=0``` として、カラム０をデータフォレームのインデックスとします。またインデックスを日付データにします。

calvert.xlsx からのデータをデータフレーム df, v-notch.xlsx からのデータをデータフレーム dfr に格納しています。


# 横軸最小値と最大値の指定
横軸最小値を2015年3月1日、横軸最大値を2019年3月31日に指定します。このとき下のように日付を示す文字列を作成し、日付型に変換し、これを用いてx軸範囲を指定しています。
日付の指定は、```%Y-%m-%d``` という形にしています。Python は日付はデフォルトでこの形で扱っているようですので。

```py
    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)```で、６ヶ月単位に指定します。また補助目盛の位置は```xaxis.set_minor_locator```の```MonthLocator(interval=1)```で１ヶ月単位に指定します。```autofmt_xdate()```は長い表記を適当に斜めに表示してくれます。

グリッドは主目盛・補助目盛双方に指定するとうっとおしいので主目盛のみ(major)としました。


```py
    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()```を用いた２軸利用のグラフとしていますが、上記記載は最後にしておく必要があります。```twinx()```の前に書いてもこの記載は有効にならずデフォルトの軸が表示されてしまいます。


# プログラム全文

```py
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](https://matplotlib.org/api/dates_api.html) 参照）

これで時系列グラフの時間軸書式をある程度好きなように表現できるようになった気がします。

この投稿が、何かしら皆様のお役にもたてば幸いです。

以　上