matplotlibで横軸(X 軸)を日時にする

意外とハマってしまったので、今日は matplotlib で横軸(X 軸)を日時にする方法を備忘録。初歩的な内容。

といっても話は簡単で、numpy.datetime64または datetime.datetime のリストを使えば matplotlib 側が日時を名義尺度ではなく間隔尺度として扱ってくれる、というだけ。今日は元データの日付文字列を日時オブジェクトに変換し忘れたままグラフの X 軸に使っており、大量の「日時風の文字列」が X 軸に並ぶので困惑していた。まあ、分かってしまえば何ということは無いね。つまらない凡ミスだった。

以下では例として、厚生労働省が公開する新型コロナウイルスのオープンデータから陽性者数・死者数をダウンロードしてグラフに描画するコードと、その実行結果を書いておく。

from datetime import datetime

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

plt.style.use("ggplot")

# データをダウンロード。
# 元データでは日付が YYYY/MM/DD という書式で ISO 8601 と異なるため
# 明示的に parse_dates オプションを使い pandas.Timestamp に変換。
url_prefix = "https://www.mhlw.go.jp/content"
positive_daily = pd.read_csv(f"{url_prefix}/pcr_positive_daily.csv",
                             parse_dates=[0])
death_total = pd.read_csv(f"{url_prefix}/death_total.csv",
                          parse_dates=[0])
df = pd.merge(positive_daily, death_total, on="日付")
df.rename(columns={"日付": "date",
                   "PCR 検査陽性者数(単日)": "pcr_positive_daily",
                   "死亡者数": "death_total"},
          inplace=True)
print("*** [df] ***\n{}".format(df))
print("*** [df.dtypes]***\n{}".format(df.dtypes))

# グラフプロット用にデータを取り出す
PREFER_NUMPY = False  # お好きに
if PREFER_NUMPY:
    x = np.array(df.date, dtype=np.datetime64)
    y1 = np.array(df.pcr_positive_daily, dtype=np.int64)
    y2 = np.array(df.death_total, dtype=np.int64)
else:
    x = df.date
    y1 = df.pcr_positive_daily
    y2 = df.death_total
print("-" * 40)
print("x:", x.dtype, type(x[0]))
print("y1:", y1.dtype)
print("y2:", y2.dtype)

# 日付を横軸 (X 軸)、人数を1縦軸 (Y 軸) にグラフをプロット
fig, ax = plt.subplots(1, 1)
ax.plot(x, y1, color="C0", label="PCR Positive (daily)")
ax.bar(x, y2, color="C1", label="Death (cumulative)")
ax.legend()
fig.savefig("foobar.png")  # 画像出力
# plt.show()  # Jupyter Notebook または GUI に表示

これを実行すると、次のように適度な間隔で日時ラベルが付いたグラフが描画され:

f:id:sgryjp:20200811235415p:plain

コンソールには次のようなメッセージが表示される:

*** [df] ***
          date  pcr_positive_daily  death_total
0   2020-02-14                   7            1
1   2020-02-15                  12            1
2   2020-02-16                   6            1
3   2020-02-17                   7            1
4   2020-02-18                   7            1
..         ...                 ...          ...
174 2020-08-06                1479         1032
175 2020-08-07                1595         1038
176 2020-08-08                1523         1039
177 2020-08-09                1486         1046
178 2020-08-10                 836         1051

[179 rows x 3 columns]
*** [df.dtypes]***
date                  datetime64[ns]
pcr_positive_daily             int64
death_total                    int64
dtype: object
----------------------------------------
x: datetime64[ns] <class 'pandas._libs.tslibs.timestamps.Timestamp'>
y1: int64
y2: int64