😺

業務で時系列データを可視化する時の話

2021/12/08に公開

はじめに

Unipos Advent Calender 2021、8日目の記事です。

今回はPythonを使って時系列データを可視化する際によく使う書き方についてお話します。
Uniposのデータはユーザーの時系列データが大半を占めており、分析する上でも基本的には時系列データとして扱うシーンが多いです。
分析した結果はチームメンバーや他部署の人に共有することも少なくなく、その時にいい感じに見やすい図を作成することを心がけています。

本記事は、「気軽にPythonで可視化したい!」「手早くいい感じのものが出したい!」「時系列データを可視化するのってちょっと面倒、、、」って人におすすめです。
僕好みで書いてるものなので、必ずしもベストな書き方ではないことは予めご了承いただきたいのと、もっとこういう書き方もあるよ!って方はぜひぜひコメントいただけると嬉しいです。

データの準備

まず時系列データを準備します。
今回はダミーデータとして、指数関数的に増加するデータを作りたいと思います。
以下が必要となるモジュールです。
すべてpipで簡単にインストールできます。

import japanize_matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

一番上のモジュールは、日本語をグラフ上で表示する際に必要なモジュールです。
ここでは割愛しますが、興味ある方はこちらの記事を御覧ください。

続きまして、データの生成を行います。

# 期間を指定
FROM_DATE = "2021-12-01"
TO_DATE = "2021-12-25"

# データフレームを作成
df = pd.DataFrame({"date": pd.date_range(start=FROM_DATE, end=TO_DATE, freq="D")})
df["group"] = "A"
df["values"] = np.exp(np.arange(0, df.shape[0] * 0.1, 0.1))

期間指定の部分を別で書いておくことで、他の期間を見たい時にすぐ対応できるのでおすすめです。
また、欠損値を含むデータを使用する際も、上記のような日付のマスターデータを作成し、マスターデータに対し結合してあげることもおすすめです。

これでデータの準備が終わったので、可視化の方に移りたいと思います。

可視化

まず、グラフ上の文字がデフォルトだと小さいのではじめに設定します。

plt.rcParams["font.size"] = 12

これを一度実行すると、更新しない限りすべてのグラフ上のフォントサイズがこのサイズになります。
サイズは好みですが、だいたいこれくらいあれば十分だと思って使ってます。

それではさっそく、上記の時系列データをプロットしてみます。
最初は時系列データでありがちな、折れ線グラフを表示してみます。

fig = plt.figure(figsize=(7, 5))
sns.lineplot(data=df, x="date", y="values")
plt.grid(axis="y", linestyle="--")
plt.xlabel("")
plt.ylabel("〇〇値")
fig.autofmt_xdate(rotation=75)
fig.tight_layout()

図1

きれいな指数関数曲線が描けましたね。
これくらいならseabornを使わずに、matplotlibだけで表記するほうが楽なケースもあるのですが、例えば異なる属性情報(性別や年齢層など)でグラフを分けて表示したいとなった場合、seabornだと一つのデータフレームからそのままhueパラメータを設定することで表示することができます。
個人的にはそういったケースと今回のケースを使い分ける方が面倒だったので、普段はよくseabornをそのまま使うようにしてます。
ただし、莫大なデータをプロットする際は、seabornだと若干重いので、matplotlibを使ったほうがいい時もあります。

図中のオプションですが、業務上で誰かに見せる際、各時点での値がどれくらいかを瞬時に把握できるよう、グリッドを追記するようにしてます(上記コードの3行目)。
また、時系列データの横軸は大概時刻情報になるので、今回のように文字列が横向きで表示される場合が多いです。
そのため、上記コードの下から2行目を書くことで、若干ではありますが見やすいよう斜めにしてます。
さらに、最終行で書いてるコードを入れることで、レイアウトが枠内ギリギリまで活用されるのでおすすめです。

続いて、棒グラフのバージョンも見ていきましょう。

fig = plt.figure(figsize=(7, 5))
sns.barplot(data=df, x="date", y="values")
plt.grid(axis="y", linestyle="--")
plt.xlabel("")
plt.ylabel("〇〇値")
fig.autofmt_xdate(rotation=75)
fig.tight_layout()

図2

カラフルな棒グラフができましたね。
seabornのいいところは、こんな感じで色合いを鮮やかにしてくれるので、見ててきれいです。
ただし、時系列データにおいては、時期ごとに色分けする必要はない(もちろん、月単位とかで色分けするのはある)ので、以下では色を統一しています。
また、棒グラフでプロットすると、横軸の日付情報がいい感じに表記されないため、事前に型変換しておく必要があります。
それらを修正したものが次のコードになります。

fig = plt.figure(figsize=(7, 5))
sns.barplot(data=df.astype({"date": str}), x="date", y="values", color="c") # "date"の型を"str"に変更し、optionで"color"を追加
plt.grid(axis="y", linestyle="--")
plt.xlabel("")
plt.ylabel("〇〇値")
fig.autofmt_xdate(rotation=75)
fig.tight_layout()

図3

いい感じですね。
また、折れ線グラフだと横軸に表記されてるものの間隔がいい感じに取れてるのですが、棒グラフの場合はここも自分で調整する必要があります。
その結果が以下のコードです。

fig = plt.figure(figsize=(7, 5))
sns.barplot(data=df.astype({"date": str}), x="date", y="values", color="c")
plt.grid(axis="y", linestyle="--")
plt.xlabel("")
plt.ylabel("〇〇値")
plt.xticks(np.arange(0, df.shape[0], 4)) # 4ステップごとに表示するように指定
fig.autofmt_xdate(rotation=75)
fig.tight_layout()

図4

見やすくなりましたね。
最後に、折れ線グラフの結果と棒グラフの結果を合わせて1つのグラフに表示するバージョンを見ていきます。

fig, axes = plt.subplots(1, 2, figsize=(12, 5))
sns.lineplot(data=df, x="date", y="values", ax=axes[0])
sns.barplot(data=df.astype({"date": str}), x="date", y="values", color="c", ax=axes[1])
axes[0].grid(axis="y", linestyle="--")
axes[1].grid(axis="y", linestyle="--")
axes[0].set_xlabel("")
axes[1].set_xlabel("")
axes[0].set_ylabel("〇〇値")
axes[1].set_ylabel("〇〇値")
axes[1].set_xticks(np.arange(0, df.shape[0], 4))
fig.autofmt_xdate(rotation=75)
fig.tight_layout()

図5

まとまりましたね。

おわりに

今回は業務で僕がよく使う、時系列データの可視化について書きました。
備忘録も兼ねて書いてるため、そんなの知ってるよ〜、とか、こっちの方がおすすめだよ〜、とかあればどしどしコメントいただけると幸いです。

さて、明日は同期でデザイナーのgokigen_patsujiが担当です。
引き続きUnipos Advent Calender 2021をお楽しみください。

参考

Discussion