よちよちpython

独習 python/Qpython/Pydroid3/termux/Linux

【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
  • 使用するデータファイル



ライブラリのインストールとインポート


◆外部ライブラリのインストール
pandasmatplotlibを使用しますので事前にインストールが必要です。

$ 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()

f:id:chayarokurokuro:20210522184038j:plain

横軸は数字が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()

f:id:chayarokurokuro:20210522184104j:plain

横軸が重なって塗り潰れてしまいました!
さあ、どうする?



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()

f:id:chayarokurokuro:20210522184129j:plain

横軸Seriesのデータ数は491あります。str型の時は塗り潰れていましたが、datetime型に変えると表示が少し減りました。
しかし、まだ重なっている。目盛りを斜めにすればいいんじゃね。



4. datetime型の日付列=横軸斜め目盛りのグラフ

plt.plot(X_dt, Y)
plt.xticks(rotation=30) #横軸目盛りを30度傾ける
plt.savefig("graph4.jpg")
plt.show()

f:id:chayarokurokuro:20210522184701j:plain

重なりが解消されました。
「日付列の型変換などせずに、最初から横軸の目盛りを斜めにすればよくね?」とやったのが次のグラフ。

plt.plot(X, Y) #横軸Xはstr型のまま
plt.xticks(rotation=30) #横軸目盛りを30度傾ける
plt.savefig("graph4_2.jpg")
plt.show()

f:id:chayarokurokuro:20210522184146j:plain

やはり日付を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()

f:id:chayarokurokuro:20210522184248j:plain

◆ ひと月おきの目盛りインターバル、自動フォーマットで斜め表示

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()

f:id:chayarokurokuro:20210522184306j:plain

◆ 目盛の自動フォーマットで斜め表示。これだけでも十分か。

import matplotlib.dates as mdates

plt.plot(df_dti)

# 日付目盛のフォーマットを自動でしてくれる
plt.gcf().autofmt_xdate()

plt.savefig("graph7.jpg")
plt.show()

f:id:chayarokurokuro:20210522184323j:plain



おわりに

目的の「目盛りの重なりを解消する」ことは達成できた。時系列データのDatetimeIndexではない、文字列の目盛りの表示変更方法については分からない。
調べたらまたその時に。



以上です。