mecobalamin’s diary

人間万事塞翁が馬

https://help.hatenablog.com/entry/developer-option

pythonで時系列の線グラフを作る


Pythonを使って上のようなグラフを作る
前回はヒストグラムだったが今回は線グラフ
mecobalamin.hatenablog.com

元のデータは以下のようなCSV形式

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

1行目はヘッダーで
2行目以降がデータになっている

データの型は
1列目に日付と時間、2-4列目は整数、
5列目は浮動小数点数である

スクリプトの流れはこのようにした

  1. 定数の定義
  2. 出力するディレクトリの作成
  3. CSVファイルの読み込み
  4. 1列目の型変換
  5. グラフの作成

また、ファイルを読み込むクラスと
グラフを作成するクラスを定義した
クラスの作成にはこちらの記事を参考にした
Pythonのオブジェクト指向プログラミングを完全理解 - Qiita

1. 定数の定義
まず入力するCSVファイル名やディレクトリを定義する
出力する画像ファイルに日付を付けるため、
日付から数字の文字列を作成する

date_today = datetime.date.today()
day_save_file = str(date_today.year) + str(date_today.month).zfill(2) + str(date_today.day).zfill(2)

zfill()は0埋めをするメソッド

2. 出力するディレクトリの作成

ReadFiles().get_current_path()

スクリプトのあるディレクトリに
グラフを保存するディレクトリを作成する
os.getcwd()、os.chdir()でスクリプトのあるディレクトリを
作業ディレクトリに変更する

ReadFiles().create_directories()

作業ディレクトリにグラフ画像を保存するディレクトリを作成するが
try文を使ってディレクトリの有無を確認している

3. CSVファイルの読み込み

ReadFiles().read_files()

CSVファイルの読み込みにも
try文を使ってファイルが開けなかった場合の
エラーコードを表示させている
ファイルの読み込みには
pandasのread_csv()を使って
CSVをDataFrameとして読み込む

4. 1列目の型変換
読み込んだデータの1列目は日付と時間だが
データの型は文字列になっている

print(type(df['Date'][1]))
<class 'str'>

to_datetime()を使って型を変換する

df['Date'] = pd.to_datetime(df['Date'])

変換するとTimestampになる

print(type(df['Date'][1]))
<class 'pandas._libs.tslibs.timestamps.Timestamp'>

5. グラフの作成
読み込んだDataFrameからグラフを作る

CreateGraphs(df).create_line_graph()

まだ理解がぼんやりしているが
CreateGraphs(df)でインスタンスを作成し、
create_line_graph()のメソッドを呼び出している
ということになるらしい

create_line_graph()でグラフを描画する
twinx()で左右に軸を描いている
plt.ylim()が2度で使われている
最初のが左のy軸で次のが右のy軸になる
plt.ylim()の次に実行する
ax1.plot()及びax2.plot()のylim()を変更している
同じコマンドなので理解に時間がかかった

plot()のあとに軸の書き換えをするのに慣れない
パラメータを決めて一度に図を描画するのではなく
コマンドを追加して次々に書き換えていく
なのでplot()コマンドを最後にするものだと
思い込んでいたがそうでもなかった

描画したグラフはpngファイルとして保存される

plt.savefig(path_output_dir + '\\' + output_graph_name)

スクリプト全文は以下のようになる
元のデータをpython_sample.csvとして
スクリプトと同じディレクトリに保存し
スクリプトを実効するとグラフを作れる

# -*- coding: utf-8 -*-

import os, sys, datetime
from pandas import Series, DataFrame
import pandas as pd

import matplotlib.pyplot as plt
import matplotlib.dates as mdates

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, ax1 = plt.subplots()
        plt.subplots_adjust(left=0.125, right=0.9, bottom=0.3, top=0.9)

        plt.ylim(50, 200)
        ax1.plot(df.index, df.iloc[:, 0], linestyle = '-', marker = 'o', color = 'r', label = df.columns[0])
        ax1.plot(df.index, df.iloc[:, 1], linestyle = ':', marker = '.', color = 'b', label = df.columns[1])
        ax1.plot(df.index, df.iloc[:, 2], linestyle = '--', marker = 's', color = 'y', label = df.columns[2])

        ax2 = ax1.twinx()
        plt.ylim(1000, 1030)
        ax2.plot(df.index, df.iloc[:, 3], linestyle = ':', marker = '+', color = 'gray', label = df.columns[3])

        locator = mdates.AutoDateLocator()
        formatter = mdates.DateFormatter('%m/%d')
        ax1.set_xticklabels(df.index, rotation = 90)
        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 left')

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 + '\\'

    ReadFiles().create_directories(path_output_dir)
    df = ReadFiles().read_files(path_input_file)

    df['Date'] = pd.to_datetime(df['Date'])
    df = df.set_index('Date')
    CreateGraphs(df).create_line_graph()
    plt.savefig(path_output_dir + '\\' + output_graph_name)
    plt.close()

    print('Operation completed')