matplotlib 時系列グラフにテキストを書き込む

# はじめに
時系列グラフを作ったのですが、その中の指定位置にテキストを表示する方法でつまずき、調べたのでアップします。当方の環境は以下の通り。作業は Juputer notebook 上で行っています。

+ iMac (Retina 4K, 21.5-inch, 2017)
+ macOS Catalina
+ Python 3.8,1
+ matplotlib 3.2.1



# 作例

![fig_test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129300/03c11c7c-834a-7520-4521-80e4c6fbf902.png)

# 工夫したところ
作例に示すグラフのなかで、流量データの欠測期間は灰色で塗りつぶし、その期間をテキストで表示しています。この欠測期間のテキスト表示が今回の投稿の目玉です。

時系列グラフの軸設定については、私が以前投稿した以下の記事に従ってやっています。

[https://qiita.com/damyarou/items/19f19658b618fd05b3b6](https://qiita.com/damyarou/items/19f19658b618fd05b3b6)

## 時系列グラフへのテキスト表示方法
### 欠測期間の開始・終点を示す文字列リストを作成し、datetime型に変換

```python
    # start of no discharge data（欠測期間始点）
    _sss=['2014-10-29',
          '2014-12-01',
          '2017-08-01',
          '2018-01-01',
          '2018-06-01',
          '2019-06-01']
    # end of no discharge data（欠測期間終点）
    _sse=['2014-10-31',
          '2014-12-31',
          '2017-12-31',
          '2018-04-30',
          '2018-08-31',
          '2019-12-31']
    sss=[]
    sse=[]
    for ss,se in zip(_sss,_sse):
        ss = datetime.datetime.strptime(ss, '%Y-%m-%d')
        se = datetime.datetime.strptime(se, '%Y-%m-%d')
        sss=sss+[ss]
        sse=sse+[se]
```

### 欠測期間別にループを回しながら必要箇所を灰色で塗りつぶしテキストを表示

```python
        for ss,se in zip(sss,sse):
            xx=dates.date2num(ss)+(dates.date2num(se)-dates.date2num(ss))/2 # テキスト表示位置の中心x座標を指定
            x1=dates.date2num(xmin) # グラフのx座標始点
            x2=dates.date2num(xmax) # グラフのx座標終点
            if x1<xx<x2: # テキスト表示位置がグラフの始点と終点の間にあればテキスト描画
                plt.axvspan(ss,se,color='#cccccc')
                xs=dates.num2date(xx)
                ys=ymin+0.3*(ymax-ymin)
                tstr1 = ss.strftime('%Y/%m/%d')
                tstr2 = se.strftime('%Y/%m/%d')
                sstr=tstr1+'~'+tstr2+'\nno discharge data'        
                plt.text(xs,ys,sstr,ha='center',va='bottom',fontsize=fsz,rotation=90,linespacing=1.5)
```

灰色での塗つぶしは、```axvspan``` でやっています。

テキスト表示位置の指定には、```import matplotlib.dates as dates``` しておき、```dates.date2num()```（西暦1年1月1日午前0時＋1日からの日数を浮動小数点に変換）および ```dates.num2date()```（浮動小数点を```datetime```に変換） という関数を使っています。

プログラムでは、複数年で１年毎に同じような図を作っているので、「グラフ毎にx軸の始点と終点を確認し、テキスト表示位置がその間に入っているときのみ描画する」という処理をしないと、とんでもないところにテキストを表示してしまうため、このようなやり方をしています。

テキストは２行にわたり１つの```plt.text()```で出力しています。この行間は```linespacing=1.5```により調整しています。

関数については以下を参照。

+ [matplotlibの時系列グラフに図、文字、注釈等を入れる方法
](https://ja.stackoverflow.com/questions/44600/matplotlib%E3%81%AE%E6%99%82%E7%B3%BB%E5%88%97%E3%82%B0%E3%83%A9%E3%83%95%E3%81%AB%E5%9B%B3-%E6%96%87%E5%AD%97-%E6%B3%A8%E9%87%88%E7%AD%89%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B%E6%96%B9%E6%B3%95)
+ [https://matplotlib.org/api/dates_api.html](https://matplotlib.org/api/dates_api.html)


# 気がついているがやっていないこと
グラフをよく見ると、１０月末の欠測期間の前後および１２月の欠測期間の前に１日分の空白が空いているのがわかると思います。
これは、データの示す日付の時刻がその日の午前０時となっているため、その日の０時から２４時までの間が塗りつぶされずに残っているためです。
プロットデータは、日単位データなので、```bar```で塗りつぶすのが本来なのですが、描画にとても時間がかかるため、```fill_between``` で塗りつぶしを行い、よしとしています。




# プログラム全文

```python
# Time series drawing
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import datetime
import matplotlib.dates as dates
import matplotlib.dates as mdates


def inp_rf():
    fnameR='xls_rdata_20190630.xlsx'
    df = pd.read_excel(fnameR,index_col=0)
    df.index = pd.to_datetime(df.index, format='%Y/%m/%d')
    df_rf=df['2013/01/01':'2019/12/31']
    return df_rf


def inp_qq():
    fnameR='df_qq1.csv'
    df = pd.read_csv(fnameR,index_col=0)
    df.index = pd.to_datetime(df.index, format='%Y/%m/%d')
    df_qq=df['2013/01/01':'2019/12/31']
    return df_qq

    
def drawfig(df_rf,df_qq):
    # start of no discharge data
    _sss=['2014-10-29',
          '2014-12-01',
          '2017-08-01',
          '2018-01-01',
          '2018-06-01',
          '2019-06-01']
    # end of no discharge data
    _sse=['2014-10-31',
          '2014-12-31',
          '2017-12-31',
          '2018-04-30',
          '2018-08-31',
          '2019-12-31']
    sss=[]
    sse=[]
    for ss,se in zip(_sss,_sse):
        ss = datetime.datetime.strptime(ss, '%Y-%m-%d')
        se = datetime.datetime.strptime(se, '%Y-%m-%d')
        sss=sss+[ss]
        sse=sse+[se]
    yyyy=np.array([2013,2014,2015,2016,2017,2018,2019])
    fsz=12
    st='01-01'
    ed='12-31'
    for year in yyyy:
        sxmin=str(year)+'-'+st
        sxmax=str(year)+'-'+ed
        plt.figure(figsize=(16,6),facecolor='w')
        plt.rcParams['font.size']=fsz
        xmin = datetime.datetime.strptime(sxmin, '%Y-%m-%d')
        xmax = datetime.datetime.strptime(sxmax, '%Y-%m-%d')
        ymin=0
        ymax=400
        plt.xlim([xmin,xmax])
        plt.ylim([ymin,ymax])
        sxlabel='Date ({0})'.format(year)
        plt.xlabel(sxlabel)
        plt.ylabel('Daily discharge Q (m$^3$/s)')
        plt.grid(which='major',axis='both',color='#999999',linestyle='--')
        for ss,se in zip(sss,sse):
            xx=dates.date2num(ss)+(dates.date2num(se)-dates.date2num(ss))/2
            x1=dates.date2num(xmin)
            x2=dates.date2num(xmax)
            if x1<xx<x2:
                plt.axvspan(ss,se,color='#cccccc')
                xs=dates.num2date(xx)
                ys=ymin+0.3*(ymax-ymin)
                tstr1 = ss.strftime('%Y/%m/%d')
                tstr2 = se.strftime('%Y/%m/%d')
                sstr=tstr1+'~'+tstr2+'\nno discharge data'        
                plt.text(xs,ys,sstr,ha='center',va='bottom',fontsize=fsz,rotation=90,linespacing=1.5)

        plt.fill_between(df_qq.index,df_qq['q_tot'],0,color='#ff00ff',label='Q (total)')
        plt.fill_between(df_qq.index,df_qq['q_lll']+df_qq['q_rrr'],0,color='#00ff00',label='Q (Right)')
        plt.fill_between(df_qq.index,df_qq['q_lll'],0,color='#ff0000',label='Q (Left)')
        plt.twinx()
        plt.ylim([ymax,ymin])
        plt.ylabel('Daily rainfall RF (mm/day)')
        plt.fill_between(df_rf.index,df_rf['RF'],0,color='#0000ff',label='RF in basin by JWA')
        plt.fill_between([0],[0],0,color='#ff00ff',label='Q (total)')
        plt.fill_between([0],[0],0,color='#00ff00',label='Q (Right)')
        plt.fill_between([0],[0],0,color='#ff0000',label='Q (Left)')
        plt.legend(bbox_to_anchor=(1, 1.01), loc='lower right', borderaxespad=0.1, ncol=4, shadow=True, fontsize=fsz-2)
    
        #plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%d-%b-%Y'))
        plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%d-%b'))
        plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=1))
        plt.gca().xaxis.set_minor_locator(mdates.MonthLocator(interval=1))
        plt.gcf().autofmt_xdate()

        fnameF='fig_'+str(year)+'.png'
        plt.savefig(fnameF, dpi=100, bbox_inches="tight", pad_inches=0.1)
        plt.show()


def main():
    df_rf=inp_rf()
    df_qq=inp_qq()
    drawfig(df_rf,df_qq)
    

#==============
# Execution
#==============
if __name__ == '__main__': main()

```

以　上