【Python】なんちゃってDX:Y軸が2つのグラフを作成するプログラム

  • URLをコピーしました!
目次

DX(デジタルトランスフォーメーション)

前回、PythonではじめるなんちゃってDXの2おまけとして、ノイズが入ったかのようなデータを作成するプログラムを紹介しました。

今回はY軸の数値が大幅に違う場合に2軸で表示したいこともあるだろうということで、Y軸が2軸となるプログラムを作ってみたので紹介します。

まずはどんなプログラムか見てみましょう

このプログラムでできること

このプログラムでできることは「CSVファイルからY軸が2軸の折れ線グラフを作成すること」です。

準備するデータはこんな感じです。

(CSVファイルなので、テキストエディタで開くとカンマ区切りです)

少し数字が細かくてびっくりしたかもしれませんが、単純に最初の列が「X値」で、2列目以降が「Y値」になるような表データです。

また今回は「値1」、「値2」、「値3」と「値11」、「値12」、「値13」でグラフの傾きが逆になってい流のと、値が大きく異なっています。

つまりこの2グループでY軸を左右に分けるというわけです。

そしてこのようなデータが入っているCSVファイルから、こんなグラフを作成します。

左右の軸のデータで色が同じだったり、凡例がグラフと被ってしまったりしていますが、あくまでも簡易に出力するプログラムということで、そこのところはご勘弁ください。

それでは次に使い方を説明していきましょう。

使い方

まず外部ライブラリ(先に別途インストールが必要なプログラム)で「pandas」と「matplotlib」を使用するので、Macの方はターミナル、Windowsの方はコマンドプロンプトで下のコマンドを実行してください。

ちなみに前回インストールした方は、スキップしてもらって大丈夫です。

また逆にインストールできているか心配なら最初の行も実行しても大丈夫です。

pip install pandas
pip install matplotlib

インストールが完了したら、こちらのファイルをダウンロードして、展開してください。

展開すると「dx-3_TwoAxisPlotFromCSV.py」というファイルが出てきます。

この「dx-3_TwoAxisPlotFromCSV.py」と、処理したいファイル(下の例ではdx-3_Data1.csv、dx-3_Data2.csv、dx-3_Data3.csv)を同じフォルダに入れてください。

処理したいファイルの名前に制限があり、ファイル名の最初に「dx-3_」をつけてください。

「dx-3_」が付いていないと処理されません。

準備ができたら、「dx-3_TwoAxisPlotFromCSV.py」を実行します。

ダブルクリックで実行してもいいですし、ターミナルやコマンドプロンプトからこちらのコマンドを実行してもいいです。

python dx-3_TwoAxisPlotFromCSV.py

実行するとこんな感じになります。

ちなみに「このウインドウは10秒後に自動で閉じますので、そのままお待ちください。」とありますが、Macでは自動で閉じないようです。

ここら辺はご愛嬌ということで、各自で閉じてください。

次は「dx-3_header.txt」というファイルが作成されているので、テキストエディタで開きます。

開くと読み込んだCSVファイルに含まれる項目名(ここでは時間、値1、値2、値3、値4、値5、値6、値10、値11、値12、値13)と「X軸名:」、「Y1軸名:」、「Y2軸名:」凡例と書かれています。

X軸にしたい値を1つ、グラフ左のY軸(Y1軸)にしたい値を1つ、グラフ右のY軸(Y2軸)にしたい値を1つ以上選択し、それぞれの行の始めに「X:」、「Y1:」、「Y2:」を追加します。

また「X軸名:」、「Y1軸名:」、「Y2軸名:」には続けて、X軸に表記したい文字列、Y1軸に表記したい文字列、Y2軸に表記したい文字列を追加します。

凡例はそのままだと凡例を表示、消すと凡例を非表示になります。

今回はこんな感じにしてみました。

保存したら、再度「dx-3_TwoAxisPlotFromCSV.py」を実行します。

すると処理したCSVファイルの数だけ、このようなグラフのpngファイルが生成されます。

ちなみに列の項目名は同時に処理するファイル全てをまとめたものになります。

そのため、ファイルによっては抽出を選択した項目がない場合もありますが、その場合は単にその項目がスキップされます。

プログラムの解説

プログラム全体

それではプログラムの解説をしていきます。

プログラム全体としてはこんな感じです。

import os
import time
import datetime

import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import pandas as pd

plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Hiragino Maru Gothic Pro', 'Yu Gothic', 'Meirio', 'Takao', 'IPAexGothic', 'IPAPGothic', 'Noto Sans CJK JP']

def filenameGet():
    csv_list = []; text_list =[]
    for filename in os.listdir('./'):
        if filename.startswith('dx-3_'):
            if filename.endswith('.csv'):
                csv_list.append(filename[:-4])
            elif filename == 'dx-3_header.txt':
                text_list.append(filename)
            
    return csv_list, text_list

def fileProcess(csv_list, text_list, timenow):
    if 'dx-3_header.txt' in os.listdir('./'):
        x_list = []; y1_list = [];y2_list = []; legend = 'off'
        with open('./dx-3_header.txt', 'r') as f_in:
            for row in f_in:
                if row.startswith('X:'):
                    x_list.append(row.split(':')[1].replace('\n',''))
                elif row.startswith('Y1:'):
                    y1_list.append(row.split(':')[1].replace('\n',''))
                elif row.startswith('Y2:'):
                    y2_list.append(row.split(':')[1].replace('\n',''))
                elif row.startswith('X軸名:'):
                    x_label = row.split(':')[1].replace('\n','')
                elif row.startswith('Y1軸名:'):
                    y1_label = row.split(':')[1].replace('\n','')
                elif row.startswith('Y2軸名:'):
                    y2_label = row.split(':')[1].replace('\n','')
                elif row.startswith('凡例'):
                    legend = 'on'
                    
        if len(x_list) == 0:
            print('X値が設定されていません。dx-3_header.txtのX値を設定したい項目の先頭に X: をつけて再度実行してください。')
        elif len(x_list) >= 2:
            print('X値が2つ以上設定されています。設定できるX値の数は1つです。')
        elif len(y1_list) == 0 or len(y2_list) == 0:
            print('Y1値、またはY2値が設定されていません。dx-3_header.txtのY1値を設定したい項目の先頭に Y1: を、Y2値を設定したい項目の先頭に Y2: をつけて再度実行してください。')
        else:
            print(f'X値:{x_list}、X軸ラベル名:{x_label}')
            print(f'Y1値:{y1_list}、Y1軸ラベル名:{y1_label}')
            print(f'Y2値:{y2_list}、Y2軸ラベル名:{y2_label}')
            
            if legend == 'on':
                print('凡例:表示')
            elif legend == 'off':
                print('凡例:非表示')
            
            for csv in csv_list:
                print(f'{csv}.csvファイルのグラフを作成します。')
                df = pd.read_csv(f'./{csv}.csv', encoding='utf-8', index_col=0)

                fig = plt.figure(figsize=(8,6))
                plt.clf()
                
                ax1 = fig.subplots()
                ax2 = ax1.twinx()

                for y1_name in y1_list:
                    if y1_name in df.keys():
                        ax1.plot(df[x_list[0]], df[y1_name], label=y1_name)
                        
                for y2_name in y2_list:
                    if y2_name in df.keys():
                        ax2.plot(df[x_list[0]], df[y2_name], ls='--', label=y2_name)
                        
                if legend == 'on':
                    h1, l1 = ax1.get_legend_handles_labels()
                    h2, l2 = ax2.get_legend_handles_labels()
                    ax1.legend(h1 + h2, l1 + l2, fontsize=14, loc='best')
                
                ax1.tick_params(labelsize=16);ax2.tick_params(labelsize=16)
                
                if x_label != '':
                    ax1.set_xlabel(x_label, fontsize=18)
                if y1_label != '':
                    ax1.set_ylabel(y1_label, fontsize=18)
                if y2_label != '':
                    ax2.set_ylabel(y2_label, fontsize=18)
                
                plt.tight_layout()
                plt.savefig(f'./{csv}.png')
                print(f'{csv}.csvファイルのグラフを保存しました。')
                
            print('処理が完了しました。')
        
        
    else:
        key_list = []
        for csv in csv_list:
            df = pd.read_csv(f'./{csv}.csv', encoding='utf-8', index_col=0)
            for key in df.keys():
                if not key in key_list:
                    key_list.append(key)

        with open('dx-3_header.txt', 'w', encoding='utf-8') as f_out:
            for key in key_list:
                f_out.write(f'{key}\n')
                
            f_out.write('\n')
            f_out.write('X軸名:\n')
            f_out.write('Y1軸名:\n')
            f_out.write('Y2軸名:\n')
            f_out.write('凡例')
            
        print('dx-3_header.txtに項目名を出力しました。')
        print('ファイルを開き、出力するグラフのX値の前に X: を追加してください。')
        print('また左のY軸を使う値に Y1: を、右のY軸を使う値に Y2: を追加し、再度実行してください。')
        print('X値は必ず1つ必要で、Y1値、Y2値の個数には制限はありません。')
        print('また、どちらも付けられていない値に関してはプロットされません。')
        print('X軸名、Y1軸名、Y2軸名が必要な場合はそれぞれ X軸名: 、Y1軸名: 、Y2軸名: の後ろに記入してください。')
        print('凡例がいらない場合は、凡例の行を消してください。')

def main():
    timenow = timenow = datetime.datetime.now().strftime("%Y%m%d%H%M%S")

    csv_list, text_list = filenameGet()
    print(f'CSVファイル:{csv_list}')
    print(f'TEXTファイル:{text_list}')
    
    fileProcess(csv_list, text_list, timenow)
    
    print('このウインドウは10秒後に自動で閉じますので、そのままお待ちください。')
    time.sleep(10)
    
if __name__ == '__main__':
    main()

ライブラリのインポート、matplotlibのフォント設定、filenameGet関数は前回と同じなので、解説をスキップします。

fileProcess関数

ファイルを処理するための関数です。

処理が大きく4つに分かれているので、別々に見ていきましょう。

fileProcess関数 その1:データの抽出

fileProcess関数 その1は「dx-3_header.txt」から出力する項目を取得する部分です。

    if 'dx-3_header.txt' in os.listdir('./'):
        x_list = []; y1_list = [];y2_list = []; legend = 'off'
        with open('./dx-3_header.txt', 'r') as f_in:
            for row in f_in:
                if row.startswith('X:'):
                    x_list.append(row.split(':')[1].replace('\n',''))
                elif row.startswith('Y1:'):
                    y1_list.append(row.split(':')[1].replace('\n',''))
                elif row.startswith('Y2:'):
                    y2_list.append(row.split(':')[1].replace('\n',''))
                elif row.startswith('X軸名:'):
                    x_label = row.split(':')[1].replace('\n','')
                elif row.startswith('Y1軸名:'):
                    y1_label = row.split(':')[1].replace('\n','')
                elif row.startswith('Y2軸名:'):
                    y2_label = row.split(':')[1].replace('\n','')
                elif row.startswith('凡例'):
                    legend = 'on'

「dx-3_header.txt」を1行ずつ読み込み、先頭に「X:」、「Y1:」、「Y2:」、「X軸名:」、「Y1軸名:」、「Y2軸名:」、「凡例」がついているものをそれぞれリストに格納します。

またその際、改行コード(\n)を削除するため、「.replace(‘\n’,”)」で改行コードを置換しています。

fileProcess関数 その2:データの判定

fileProcess関数 その2はその1で取得した項目が正しく設定されているか判定する部分です。

                    
        if len(x_list) == 0:
            print('X値が設定されていません。dx-3_header.txtのX値を設定したい項目の先頭に X: をつけて再度実行してください。')
        elif len(x_list) >= 2:
            print('X値が2つ以上設定されています。設定できるX値の数は1つです。')
        elif len(y1_list) == 0 or len(y2_list) == 0:
            print('Y1値、またはY2値が設定されていません。dx-3_header.txtのY1値を設定したい項目の先頭に Y1: を、Y2値を設定したい項目の先頭に Y2: をつけて再度実行してください。')
        else:
            print(f'X値:{x_list}、X軸ラベル名:{x_label}')
            print(f'Y1値:{y1_list}、Y1軸ラベル名:{y1_label}')
            print(f'Y2値:{y2_list}、Y2軸ラベル名:{y2_label}')
            
            if legend == 'on':
                print('凡例:表示')
            elif legend == 'off':
                print('凡例:非表示')

重要なのはX値が1つだけ設定されていること、つまり設定されていなかったり、2つ以上だった場合はエラーとなることです。

またY1値とY2値も設定されているかどうかを判定して、設定されていない場合はエラーの表示をしています。

ただしこの後グラフを作成する際、データによってはY1値、Y2値に当たるデータがない場合もあります。

その場合は単純にグラフ上に表示されません。

軸名や凡例に関しては、グラフを作成するのに特に重要ではないので判定せず、とりあえず表示させています。

fileProcess関数 その3:グラフ作成部分

fileProcess関数 その3はCSVファイルを開き、グラフを作成する部分です。

            for csv in csv_list:
                print(f'{csv}.csvファイルのグラフを作成します。')
                df = pd.read_csv(f'./{csv}.csv', encoding='utf-8', index_col=0)

                fig = plt.figure(figsize=(8,6))
                plt.clf()
                
                ax1 = fig.subplots()
                ax2 = ax1.twinx()

                for y1_name in y1_list:
                    if y1_name in df.keys():
                        ax1.plot(df[x_list[0]], df[y1_name], label=y1_name)
                        
                for y2_name in y2_list:
                    if y2_name in df.keys():
                        ax2.plot(df[x_list[0]], df[y2_name], ls='--', label=y2_name)
                        
                if legend == 'on':
                    h1, l1 = ax1.get_legend_handles_labels()
                    h2, l2 = ax2.get_legend_handles_labels()
                    ax1.legend(h1 + h2, l1 + l2, fontsize=14, loc='best')
                
                ax1.tick_params(labelsize=16);ax2.tick_params(labelsize=16)
                
                if x_label != '':
                    ax1.set_xlabel(x_label, fontsize=18)
                if y1_label != '':
                    ax1.set_ylabel(y1_label, fontsize=18)
                if y2_label != '':
                    ax2.set_ylabel(y2_label, fontsize=18)
                
                plt.tight_layout()
                plt.savefig(f'./{csv}.png')
                print(f'{csv}.csvファイルのグラフを保存しました。')
                
            print('処理が完了しました。')

今回はY軸を2軸にするため、こちらの記事を参考にプログラムを作成しています。

基本的には前回のグラフと同じですが、「ax1 = fig.subplots()」と「ax2 = ax1.twinx()」を使うことでY軸を2軸にしています。

そしてその後、値をプロットする際、「ax1.plot(df[x_list[0]], df[y1_name], label=y1_name)」、「ax2.plot(df[x_list[0]], df[y2_name], ls=’–‘, label=y2_name)」とすることで、それぞれの軸にプロットしています。

またax2に対しては、「ls=’–‘」で点線の表示にしています。

fileProcess関数 その4:項目ファイルの作成

fileProcess関数 その4は、項目ファイルである「dx-3_header.txt」を作成している部分です。

    else:
        key_list = []
        for csv in csv_list:
            df = pd.read_csv(f'./{csv}.csv', encoding='utf-8', index_col=0)
            for key in df.keys():
                if not key in key_list:
                    key_list.append(key)

        with open('dx-3_header.txt', 'w', encoding='utf-8') as f_out:
            for key in key_list:
                f_out.write(f'{key}\n')
                
            f_out.write('\n')
            f_out.write('X軸名:\n')
            f_out.write('Y1軸名:\n')
            f_out.write('Y2軸名:\n')
            f_out.write('凡例')
            
        print('dx-3_header.txtに項目名を出力しました。')
        print('ファイルを開き、出力するグラフのX値の前に X: を追加してください。')
        print('また左のY軸を使う値に Y1: を、右のY軸を使う値に Y2: を追加し、再度実行してください。')
        print('X値は必ず1つ必要で、Y1値、Y2値の個数には制限はありません。')
        print('また、どちらも付けられていない値に関してはプロットされません。')
        print('X軸名、Y1軸名、Y2軸名が必要な場合はそれぞれ X軸名: 、Y1軸名: 、Y2軸名: の後ろに記入してください。')
        print('凡例がいらない場合は、凡例の行を消してください。')

こちらではfileProcess関数の前半部分で使用したコマンドばかりなので、多くは解説しません。

やっていることとしては、それぞれのCSVファイルを読み込み、列の項目名を抽出し、「dx-3_header.txt」に書き込んでいます。

main関数

main関数ではfilenameGet関数とfileProcess関数を実行しています。

def main():
    timenow = timenow = datetime.datetime.now().strftime("%Y%m%d%H%M%S")

    csv_list, text_list = filenameGet()
    print(f'CSVファイル:{csv_list}')
    print(f'TEXTファイル:{text_list}')
    
    fileProcess(csv_list, text_list, timenow)
    
    print('このウインドウは10秒後に自動で閉じますので、そのままお待ちください。')
    time.sleep(10)
    
if __name__ == '__main__':
    main()

注意すべき点

注意すべき点としては、以下の通りです。

  • 処理できるのはCSVファイルのみ
  • CSVファイルの形式が制限
  • X軸1つまでY軸2つまでに制限
  • WindowsとMacでは改行コード(Macでは「\n」)が違う可能性

注意すべき点に関しては前回とほぼ同様ですが、「X軸1つまでY軸2つまでに制限」というY軸の制限数だけが変わっています。

前々回、今回とグラフを作成するプログラムを紹介しました。

ただグラフを作成するプログラムができたものの、前々回もお話しした通り、例えばデータの出どころが測定器だった場合、最初に測定器特有のヘッダーが追加されたりします。

その場合はこのプログラムでは正しく処理することはできません。

また1列目が項目名で、2列目以降が値という縦と横が入れ替わっている場合も処理できません。

ということで入力するデータの形式を気にする必要があるわけです。

次回からは、前々回、今回と紹介したグラフ作成プログラムにデータを渡すためのデータ形式変換プログラムを紹介していきましょう。

ということで今回のプログラムも良かったら、使ってみたり、いじってみたりしてください。

ではでは今回はこんな感じで。

よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

目次