Pythonで時系列グラフに水平線と矢印を入れる
以前Pythonで時系列グラフを作成した
mecobalamin.hatenablog.com
このグラフに矢印と水平線を加える
つまりこのグラフの任意の場所に矢印と水平線を加えて
このようにする
元のデータはCSVで500行ぐらいある
その一部を抜粋
Date, a, b, c, d 2020/11/24 16:42:20, 143, 101, 63, 2020/11/24 17:27:19, 137, 101, 65, 2020/11/25 06:31:48, 134, 90, 65, 2020/11/25 11:21:57, 142, 109, 67, 2020/11/25 14:06:42, 131, 94, 74, 2020/11/25 18:54:18, 120, 91, 69, 2020/11/25 20:42:53, 114, 78, 72, 2020/11/26 09:09:53, 113, 84, 76, 2020/11/26 09:17:28, 125, 70, 70, 1021.67 2020/11/26 16:12:06, 126, 89, 65, 1019.64 2020/11/27 06:37:47, 117, 81, 69, 1020.66 2020/11/27 13:31:37, 123, 88, 98, 1019.98 2020/11/27 14:22:03, 126, 86, 89, 1019.64 2020/11/27 17:58:24, 120, 86, 84, 1020.32 2020/11/27 19:35:42, 116, 81, 81, 1021 2020/11/27 21:35:24, 120, 86, 78, 1021.33 2020/11/28 08:30:20, 133, 98, 69, 1022.69 2020/11/28 12:59:11, 127, 82, 72, 1022.01 2020/11/28 20:59:20, 119, 86, 67, 1022.69 2020/11/28 22:06:02, 112, 75, 66, 1023.03
dfという変数に格納されている
今回のポイントは
- axesオブジェクトを返す
- 矢印をいれる関数を作成する
- 水平線はplot関数を使う
1. axesオブジェクトを返す
グラフを作成する関数を書いてある
関数の内部に矢印を入れる命令を書いてもいいのだが
汎用性をもたせたいのでグラフと矢印の描画を分けたい
グラフを以下のようにして描く
fig = plt.figure(figsize = (12.0, 9.0)) ax1 = fig.subplots()
グラフを書いたaxesオブジェクトをretrunで戻す
return ax1
matplotlibについてはこちらを参考にした
matplotlibの描画の基本 - figやらaxesやらがよくわからなくなった人向け - Qiita
2. 矢印をいれる関数を作成する
グラフのデータdfはgraph_dataに入っている
関数の引数として
矢印を書き込むaxesオブジェクト、
矢印の色、矢印の長さ
を渡す
矢印は上下方向にしか引かないので
矢印の長さで向きを示す
正の値が下向き、負の値が上向きになる
日付と矢印を入れるy軸の値で
インスタンスを生成し、
矢印を書き込む関数(メソッド)を呼び出す
CreateGraphs({'2020-12-03 10:00:00':'15', '2020-12-11 10:00:00':'15', '2020-12-18 10:00:00':'15', '2021-01-08 10:00:00':'15', '2021-01-15 10:00:00':'15', '2021-01-22 10:00:00':'15', '2021-02-12 10:00:00':'15', '2021-02-19 10:00:00':'15'}).add_arrows(ax, '#ff4500', -10)
この場合は8個の矢印を描く
矢印を書き込む関数は以下の通り
def add_arrows(self, ax, arrow_color, arrow_direction): values_infection = self.graph_data date_infection = values_infection.keys() for key in date_infection: ax.annotate("", xy = (pd.to_datetime(key), int(values_infection[key])), xytext = (pd.to_datetime(key), int(values_infection[key]) + arrow_direction), arrowprops = dict(shrink = 0, width = 1, headwidth = 8, headlength = 10, connectionstyle = 'arc3', facecolor = arrow_color, edgecolor = arrow_color))
iOSのPythonista3ではheadlengthに関してエラーが出る
環境によってはarrowpropsを以下のように書き換える必要があるかも
arrowprops = dict(arrowstyle = '->, head_width = 0.2, head_length = 0.5', connectionstyle = 'arc3', facecolor = arrow_color, edgecolor = arrow_color)
3. 水平線はplot関数を使う
水平線を入れるメソッドhlinesもあるが
グラフの値を重ねられてしまう
ax.hlines([128], min(df.index), max(df.index), "blue", linestyles = 'dashed')
コードの最後に書いてもグラフに隠れる
そこでplot()を使って水平線を描く
ax.plot([min(df.index), max(df.index)],[128, 128], "blue", linestyle='dashed')
コードをまとめるとこの様になる
# -*- coding: utf-8 -*- import os, sys, datetime from pandas import Series, DataFrame import pandas as pd import numpy as np import matplotlib.pyplot as plt import matplotlib.dates as mdates import seaborn as sns class ReadFiles: def __init__(self): pass def get_current_path(self): current_path = os.getcwd() current_path = os.chdir(os.path.dirname(os.path.abspath(__file__))) current_path = os.getcwd() return(current_path) def create_directories(self, path_to_dir): try: os.makedirs(path_to_dir) except FileExistsError: pass def read_files(self, path_to_file): print('open a csv file.') try: df = pd.read_csv(path_to_file) except Exception as e: print('cannot open file: ', path_to_file) print(str(e)) sys.exit(1) else: print('Success!') return(df) class CreateGraphs: def __init__(self, graph_data): self.graph_data = graph_data def create_line_graph(self): print('Create line plot') df = self.graph_data fig = plt.figure(figsize = (12.0, 9.0)) ax1 = fig.subplots() plt.subplots_adjust(left=0.125, right=0.9, bottom=0.3, top=0.9) plt.ylim(0, 200) plt.ylabel('a/b') ax1.plot(df.index, df.iloc[:, 0], linestyle = '-', marker = 'o', color = '#ff7f50', label = df.columns[0]) ax1.plot(df.index, df.iloc[:, 1], linestyle = ':', marker = '+', color = '#00ff7f', label = df.columns[1]) ax2 = ax1.twinx() plt.ylim(40, 300) plt.ylabel('c') ax2.plot(df.index, df.iloc[:, 2], linestyle = '--', marker = '.', color = '#00bfff', label = df.columns[2]) locator = mdates.AutoDateLocator() formatter = mdates.ConciseDateFormatter(locator) ax1.xaxis.set_major_locator(locator) ax1.xaxis.set_major_formatter(formatter) handles1, labels1 = ax1.get_legend_handles_labels() handles2, labels2 = ax2.get_legend_handles_labels() ax1.legend(handles1 + handles2, labels1 + labels2, loc = 'upper right') return ax1 def add_arrows(self, ax, arrow_color, arrow_direction): values_infection = self.graph_data date_infection = values_infection.keys() for key in date_infection: ax.annotate("", xy = (pd.to_datetime(key), int(values_infection[key])), xytext = (pd.to_datetime(key), int(values_infection[key]) + arrow_direction), arrowprops = dict(shrink = 0, width = 1, headwidth = 8, headlength = 10, connectionstyle = 'arc3', facecolor = arrow_color, edgecolor = arrow_color)) if __name__ == '__main__': date_today = datetime.date.today() day_save_file = str(date_today.year) + str(date_today.month).zfill(2) + str(date_today.day).zfill(2) input_file_name = 'Python_Sample.csv' output_dir_name = 'graph' output_graph_name = day_save_file + '_graph_sample.png' path_input_file = ReadFiles().get_current_path() + '\\' + input_file_name path_output_dir = ReadFiles().get_current_path() + '\\' + output_dir_name + '\\' print(path_input_file) ReadFiles().create_directories(path_output_dir) df = ReadFiles().read_files(path_input_file) print(df.iloc[-1]) print(df.mean()) df['Date'] = pd.to_datetime(df['Date']) df = df.set_index('Date') ax = CreateGraphs(df).create_line_graph() CreateGraphs({'2020-12-03 10:00:00':'15', '2020-12-11 10:00:00':'15', '2020-12-18 10:00:00':'15', '2021-01-08 10:00:00':'15', '2021-01-15 10:00:00':'15', '2021-01-22 10:00:00':'15', '2021-02-12 10:00:00':'15', '2021-02-19 10:00:00':'15'}).add_arrows(ax, '#ff4500', -10) CreateGraphs({'2020-12-25 10:00:00':'15', '2021-01-01 10:00:00':'15', '2021-01-29 10:00:00':'15', '2021-02-26 10:00:00':'15'}).add_arrows(ax, '#a9a9a9', -10) CreateGraphs({'2021-02-03 10:00:00':'15'}).add_arrows(ax, '#00008b', -10) ax.plot([min(df.index), max(df.index)],[128, 128], "blue", linestyle='dashed') plt.savefig(path_output_dir + '\\' + output_graph_name) plt.close() print('Operation completed')