【matplotlib】時系列グラフ横軸目盛りの塗り潰れ解消法
今回は、時系列データグラフにおいて日付フォーマットや表示を変更する方法。
matplotlibで折れ線グラフを描いたとき、横軸の文字が重なって塗り潰れることがあります。
時系列データの横軸(日付)に限定ですが、日付を短く表示しそれを解消する方法のメモ。
結論から書くと、
DataFrameインデックスを横軸をdatetime型日付にした後、- グラフ作成で次の「追加」文を加える。
# 時系列データの時間軸用を追加すること! import matplotlib.dates as mdates plt.plot(データ) # 普通にグラフを描く # フォーマット変更はこれ追加 / ("%y/%m")をいじる plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%y/%m")) # 目盛のインターバル変更はこれ追加 /(interval=2)の数値をいじる plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=2)) # 自動的にフォーマットを変えるならこれを追加 / 目盛が自動で傾いたり短くなったり plt.gcf().autofmt_xdate()
上の例だと、グラフ横軸の日付目盛り「2021/05/22」を「21/05」のように短く変えられる → 重ならなくなる。
【実行環境】
- Windows10 WSL:Ubuntu
- VSCode上でJupyter(WSL:Ubuntuリモート)
- Python3.8
- 外部ライブラリ
- pandas、matplotlib
- 使用するデータファイル
ライブラリのインストールとインポート
◆外部ライブラリのインストール
pandas
とmatplotlib
を使用しますので事前にインストールが必要です。
$ pip install pandas matplotlib
など、環境に合わせて行っておきます。
◆ライブラリのインポート
import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates
日付のフォーマットを変更する為に、上の3つ目のmatplotlib.dates
というものを使います。
長い日付表示を短くすれば重なりが解消できるだろう!グハハ!(謎テンション
CSVデータファイルの入手
時系列データなら何でも構いませんが、昨日の投稿こちらに載せている厚生労働省オープンデータからPCR陽性者数のCSVファイルをPandasでダウンロードします。
# 厚労省 PCR陽性者数ファイル URL = "https://www.mhlw.go.jp/content/pcr_positive_daily.csv" # ファイル保存名 savename = URL.split('/')[-1] # PandasでPandasでダウンロード保存 df = pd.read_csv(URL) # DL df.to_csv(savename, index=False) # Save(ローカル保存不要ならコメントアウト)
これでpcr_positive_daily.csv
というファイルが作業ディレクトリに保存されました。
DataFrameの変数dfをこのまま次で使い回します。
データの中身の確認
# 保存ファイル読込時コメントアウト削除 #fname = './pcr_positive_daily.csv' #df = pd.read_csv(fname) df
日付 | PCR 検査陽性者数(単日) | |
---|---|---|
0 | 2020/1/16 | 1 |
1 | 2020/1/17 | 0 |
2 | 2020/1/18 | 0 |
3 | 2020/1/19 | 0 |
4 | 2020/1/20 | 0 |
... | ... | ... |
486 | 2021/5/16 | 5247 |
487 | 2021/5/17 | 3677 |
488 | 2021/5/18 | 5229 |
489 | 2021/5/19 | 5811 |
490 | 2021/5/20 | 5711 |
491 rows × 2 columns
(491行、2列)のデータが入っています。
インデックスが0番から連番でついています。(Pandasが自動でつけた)
このままplt.plot(Seriesデータ)
でグラフを描きますと、
- 横軸:インデックス(0からの連番)
- 縦軸:Seriesデータ(今回は「PCR 検査陽性者数(単日)」の列)
になります。グラフを描いて確認します。
グラフ作成
1. インデックス0から連番=横軸のグラフ
plt.plot(df.iloc[:,1]) #または plt.plot(df["PCR 検査陽性者数(単日)"]) plt.savefig("graph1.jpg") plt.show()
横軸は数字が500まで入ってます。
縦軸用のSeriesだけを
plt.plot(縦軸Series)
という風に渡すと、自動的にインデックスが横軸になる。
2. 日付列を横軸にしたグラフ
plt.plot(X軸, Y軸)
とすれば、横・縦軸を指定できます。
- 横軸:日付列のSeriesデータ
- 縦軸:PCR 検査陽性者数(単日)のSeriesデータ
# X,YのSeriesデータを準備 X = df.iloc[:, 0] #または X = df['日付'] Y = df.iloc[:, 1] #または Y = df['PCR 検査陽性者数(単日)'] # グラフ作図 plt.plot(X, Y) plt.savefig("graph2.jpg") plt.show()
横軸が重なって塗り潰れてしまいました!
さあ、どうする?
3. datetime型の日付列=横軸のグラフ
日付列をdatetime型に変えて描けば少しマシになる。
元の日付列はstr型のデータが入っています。確認します。
X = df.iloc[:, 0] #または X = df['日付'] print(X[0]) print(type(X[0]))
2020/1/16
<class 'str'>
日付列の各要素はstr型です。これをdatetime型に変換します。
# 日付列をstr型 => datetime型 変換 X_dt = pd.to_datetime(df['日付']) # 確認 print("◆ 変換後の日付Series\n", X_dt) print("\n◆ 要素数:",len(X_dt)) print("\n◆ 0番目要素:",X_dt[0]) print("\n◆ 型:",type(X_dt[0]))
◆ 変換後の日付Series
0 2020-01-16
1 2020-01-17
2 2020-01-18
3 2020-01-19
4 2020-01-20
...
486 2021-05-16
487 2021-05-17
488 2021-05-18
489 2021-05-19
490 2021-05-20
Name: 日付, Length: 491, dtype: datetime64[ns]
◆ 要素数: 491
◆ 0番目要素: 2020-01-16 00:00:00
◆ 型: <class 'pandas._libs.tslibs.timestamps.Timestamp'>
str型の日付列Seriesがdatetime64[ns]
=timestamps.Timestamp
型に変換されました。
- 横軸:日付列(timestamps.Timestamp型Seriesデータ)
- 縦軸:PCR 検査陽性者数(単日)列(Seriesデータ)
でグラフを描いてみます。
#横軸 X_dt #縦軸 Y = df.iloc[:, 1] #または Y = df['PCR 検査陽性者数(単日)'] #グラフ作図 plt.plot(X_dt, Y) plt.savefig("graph3.jpg") plt.show()
横軸Seriesのデータ数は491あります。str型の時は塗り潰れていましたが、datetime型に変えると表示が少し減りました。
しかし、まだ重なっている。目盛りを斜めにすればいいんじゃね。
4. datetime型の日付列=横軸斜め目盛りのグラフ
plt.plot(X_dt, Y) plt.xticks(rotation=30) #横軸目盛りを30度傾ける plt.savefig("graph4.jpg") plt.show()
重なりが解消されました。
「日付列の型変換などせずに、最初から横軸の目盛りを斜めにすればよくね?」とやったのが次のグラフ。
plt.plot(X, Y) #横軸Xはstr型のまま plt.xticks(rotation=30) #横軸目盛りを30度傾ける plt.savefig("graph4_2.jpg") plt.show()
やはり日付をdatetime型に変換し、自動的に表示を減らした上で斜めにする必要がある。
5. datetime型日付インデックス=横軸のグラフ
時系列データでグラフを描く際は横軸が時間軸になりますので、最初にインデックスをdatetime型日付に変換した方が便利です。
DatetimeIndexという。
- str型日付列を
pd.to_datetime(日付列)
でdatetime型にして置換し、 df.set_index('日付列')
でインデックスに置換する。
# str型日付列をdatetime型にして置換する df['日付'] = pd.to_datetime(df['日付']) df
日付 | PCR 検査陽性者数(単日) | |
---|---|---|
0 | 2020-01-16 | 1 |
1 | 2020-01-17 | 0 |
2 | 2020-01-18 | 0 |
3 | 2020-01-19 | 0 |
4 | 2020-01-20 | 0 |
... | ... | ... |
486 | 2021-05-16 | 5247 |
487 | 2021-05-17 | 3677 |
488 | 2021-05-18 | 5229 |
489 | 2021-05-19 | 5811 |
490 | 2021-05-20 | 5711 |
491 rows × 2 columns
# 型確認 print(type(df['日付'][0]))
<class 'pandas._libs.tslibs.timestamps.Timestamp'>
# datetime型日付列をインデックスに置換する df_dti = df.set_index('日付') print(type(df_dti)) df_dti
<class 'pandas.core.frame.DataFrame'>
PCR 検査陽性者数(単日) | |
---|---|
日付 | |
2020-01-16 | 1 |
2020-01-17 | 0 |
2020-01-18 | 0 |
2020-01-19 | 0 |
2020-01-20 | 0 |
... | ... |
2021-05-16 | 5247 |
2021-05-17 | 3677 |
2021-05-18 | 5229 |
2021-05-19 | 5811 |
2021-05-20 | 5711 |
491 rows × 1 columns
日付列が消えて1列だけのデータフレームになり、日付列がインデックスに変わりました。
ここからが最終目的の「日付のフォーマットを変えて短く表示する」
ここまで長々と来た割にあっさり終わってしまうが、
import matplotlib.dates as mdates # プロット(引数は、1列のデータフレームなので) plt.plot(df_dti) # 日付表示のフォーマット plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%y/%m")) plt.savefig("graph5.jpg") plt.show()
◆ ひと月おきの目盛りインターバル、自動フォーマットで斜め表示
import matplotlib.dates as mdates plt.plot(df_dti) # 日付表示フォーマット変更用 plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%y/%m")) # 日付目盛インターバル変更用 plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=1)) # 日付目盛のフォーマットを自動でしてくれる plt.gcf().autofmt_xdate() plt.savefig("graph6.jpg") plt.show()
◆ 目盛の自動フォーマットで斜め表示。これだけでも十分か。
import matplotlib.dates as mdates plt.plot(df_dti) # 日付目盛のフォーマットを自動でしてくれる plt.gcf().autofmt_xdate() plt.savefig("graph7.jpg") plt.show()
おわりに
目的の「目盛りの重なりを解消する」ことは達成できた。時系列データのDatetimeIndexではない、文字列の目盛りの表示変更方法については分からない。
調べたらまたその時に。
以上です。