【Python】csvから極座標カラーマップと線グラフを作成してexcelに自動で貼付

![polor_color_line.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000399/346f5523-e1e5-4acb-b8ea-4e596faa48aa.png)

Create polar color maps and line graphs from csv in python and automatically paste them into excel.

# はじめに

こんにちは。
今回はいろいろなサイトを参考にしてエクセル作業を自動化するプログラムを作成しました。

作った経緯ですが円形のデータ整理を行っていると、表計算ソフトやらカラーマップ作成ソフトを使ってデータを可視化する場合もあります。それらをまとめとしてExcelに貼り付けたりすると、一つ一つ画像ファイルを挿入するのに時間がかかってしょうがない。。。

そこでcsvから極座標カラーマップと線グラフを作成してexcelに自動で貼り付けるプログラムをPythonで作ってみました。

## サンプル

↓からサンプルがダウンロードできます。
https://github.com/MasayaUmezaki/polar_colormap_line

サンプルの中身はこんな感じになっています。datasにはサンプルデータが入っています。
![sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000399/41d6693e-dfd7-41be-acb0-e98e54406ae8.png)

↓動画でも紹介しています。
https://youtu.be/5cTBfeNySrs

動作環境はVScodeとAnaconda（Python3.9.7)で確認しています。
もし該当ソフトのインストールがお済みでない場合はリンク先からダウンロードできます。
VScode→https://code.visualstudio.com/Download
Anaconda3→https://www.anaconda.com/

## プログラム内容

サンプルプログラムの内容を説明します。
まず作成される画像を置いておくルートディレクトリを作成します。

    import os
    
    #画像を置いておくルートディレクトリを作成
    IMG_DIR = 'images'
    COLORMAP_DIR = 'images/colormap/'
    LINE_DIR = 'images/line/'
    
    os.mkdir(IMG_DIR)
    os.mkdir(COLORMAP_DIR)
    os.mkdir(LINE_DIR)

実行するとプログラム動作時に画像の入るフォルダが作成されます。
![mkdir.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000399/4ac873ad-5456-4229-a31a-e4207b3d2f70.png)


次にカラーマップを作成します。コードは以下のようになっています。

    """
    サンプルデータの取得→カラーマップ作成・保存
    """
    import numpy as np
    import pandas as pd
    import os
    import matplotlib.pyplot as plt
    
    # 定数設定
    INPUT_SAMPLE_DATE_DIR = 'datas/sample' # データを置いておくルートディレクトリ
    COLORMAP_DIR = 'images/colormap/' # カラーマップを保存するディレクトリ
    
    def get_file_names(set_dir_name):
        """
        ディレクトリ内のファイル名取得（ファイル名のみの一覧を取得）
        """
        file_names = os.listdir(set_dir_name)
        temp_full_file_names = [os.path.join(set_dir_name, file_name) for file_name in file_names if os.path.isfile(os.path.join(set_dir_name, file_name))] # ファイルかどうかを判定
        return temp_full_file_names
    
    def make_colormap_img(target_full_file_names):
        """
        カラーマップの作成と保存
        """
        target_full_file_names.sort() # ファイル名でソート
        for target_file in target_full_file_names:
                print( target_file + 'を作成')
                basename = os.path.splitext(os.path.basename(target_file))[0] # ファイル名の部分だけを取得
                df = pd.read_csv(target_file, names=("theta", "radii", "z_value"),header=0) # csvを読み込み
                df = df.sort_values(by=["theta", "radii"]) # theta, radiiの順にソート
                x = np.array(sorted(set(df.theta*np.pi/180))) # setで重複を除いたのち、thetaをソートする
                y = np.array(sorted(set(df.radii))) # setで重複を除いたのち、radiiをソートする
                z = np.array(df.z_value)
                X, Y = np.meshgrid(x, y)
                Z = z.reshape(len(x), -1).T # (x, y)の位置にデータを置くために、zをx軸の長さ×y軸の長さを持つ行列にする
                
                plt.rcParams["figure.figsize"] = [3,4]
                ax = plt.subplot(111, polar=True) # axesの設定
                ax.set_rgrids(np.linspace(0, 150, 4), angle=20, fontname="Arial", fontsize=10) # グリッド作成
                ax.set_title(basename, fontname="Arial", fontsize=14) # タイトル作成
                ctf = ax.contourf(X, Y, Z, levels=np.linspace(100,250,31), vmin=100, vmax=250, cmap="jet") # コンター
                colb = plt.colorbar(ctf, pad=0.12, orientation="horizontal", shrink=1.1) # カラーバー
                colb.set_label("**", fontname="Arial", fontsize=12) # カラーバーのラベル
                # 指定の場所に保存
                plt.savefig(fname=COLORMAP_DIR+basename+".jpg", facecolor="white", bbox_inches='tight', pad_inches=0.05)
    
    f_names = get_file_names(INPUT_SAMPLE_DATE_DIR) # ファイル名取得
    make_colormap_img(f_names)

このコードは以下のサイトが大変参考になりました。
・matplotlibでcsvからpolar-colormap(極座標カラーマップ)を作成する - 湯船ったら湯船 (fc2.com)
http://0fulo.blog.fc2.com/blog-entry-27.html

実行するとカラーマップがimages/colormapに保存されます。

続けて線グラフを作成します。コードは以下のようになっています。

    """
    サンプルデータの取得→線グラフ作成
    """
    import numpy as np
    import pandas as pd
    import os
    import matplotlib.pyplot as plt
    
    # 定数設定
    INPUT_SAMPLE_DATE_DIR = 'datas/sample' # データを置いておくルートディレクトリ
    LINE_DIR = 'images/line/'
    
    def get_file_names(set_dir_name):
        """
        ディレクトリ内のファイル名取得（ファイル名のみの一覧を取得）
        """
        file_names = os.listdir(set_dir_name)
        temp_full_file_names = [os.path.join(set_dir_name, file_name) for file_name in file_names if os.path.isfile(os.path.join(set_dir_name, file_name))] #  ファイルかどうかを判定
        return temp_full_file_names
    
    def make_line_img(target_full_file_names):
        """
        グラフの作成と保存
        """
        target_full_file_names.sort() # ファイル名でソート
        for target_file in target_full_file_names:
                print( target_file + 'を作成')
                basename = os.path.splitext(os.path.basename(target_file))[0] # ファイル名の部分だけを取得
                df = pd.read_csv(target_file, names=("theta", "radii", "z_value"),header=0) # csvを読み込み
                df = df.sort_values(by=["theta", "radii"]) # theta, radiiの順にソート
                thetas = np.array(sorted(set(df.theta))) # setで重複を除いたのち、ソート
                df_i = df.set_index(df.theta) # thetaでインデックス化
                for a_theta in thetas:
                    d = dict()
                    d[a_theta] = df_i.loc[a_theta]  # a_thetaをの要素を抽出
                    d[a_theta] = d[a_theta].sort_values(by=["radii"], ascending=False) # radii(半径)でソートして再代入
                    if a_theta < 180: # 180deg以下の時のみ処理
                        plus180 = a_theta + 180
                        d[plus180] = df_i.loc[plus180] # a_theta+180degの要素を抽出
                        d[plus180] = d[plus180].sort_values(by=["radii"]) # radii(半径)でソートして再代入
                        x0 = -d[a_theta].radii # 180deg以下のときradii(半径)にマイナスをかける
                        x180 = +d[plus180].radii
                        y0 = d[a_theta].z_value
                        y180 = d[plus180].z_value
                        x = np.hstack([x0,x180]) # 円の中心を通る一直線分のradiiを結合する
                        y = np.hstack([y0,y180]) # 円の中心を通る一直線分のz_valueを結合する
                        label = str(a_theta) + '-' + str(plus180)
                        plt.rcParams["figure.figsize"] = [3,3] # 画像サイズを調整
                        plt.xlabel("radii [**]", fontsize=10)
                        plt.ylabel("z_value [**]", fontsize=10)
                        plt.xlim(-160,160) # x軸範囲指定
                        plt.ylim(100,250) # y軸範囲指定
                        plt.plot(x,y,label=label)
                        plt.grid(c='gainsboro', zorder=9)
                        plt.legend(bbox_to_anchor=(1, 1.45), ncol=2)
                              
                # 指定の場所に保存
                plt.savefig(fname=LINE_DIR+basename+"_line.jpg", facecolor="white", bbox_inches='tight', pad_inches=0.05)
                plt.figure()
    
    f_names = get_file_names(INPUT_SAMPLE_DATE_DIR) # ファイル名取得
    make_line_img(f_names)

実行すると線グラフがimages/lineに保存されます。

最後に画像をエクセルにまとめます。※Anacondaの初期状態ではcv2のインポートが出来ないので、AnacondaPromptなどでopencvのインストールが必要です。
コードは以下のようになっています。

    """
    result.xslxに画像の貼り付け
    """
    import os
    import glob
    import imghdr
    import openpyxl
    import cv2
    
    # 定数設定
    IMG_DIR = 'images' # 貼り付ける画像を置いておくルートディレクトリ
    SHEET_TITLE = 'result' # シート名の設定
    RESULT_FILE_NAME = 'result.xlsx' # 結果を保存するファイル名
     
    # 変数
    max_width = [] # 各行の画像の高さの最大値を保持
    
    def get_file_names(set_dir_name):
        """
        ディレクトリ内のファイル名取得（ファイル名のみの一覧を取得）
        """
        file_names = os.listdir(set_dir_name)
        temp_full_file_names = [os.path.join(set_dir_name, file_name) for file_name in file_names if os.path.isfile(os.path.join(set_dir_name, file_name))] # ファイルかどうかを判定
        return temp_full_file_names
     
    def attach_img(target_full_file_names, set_row_idx, set_dir_name):
        """
        画像を呼び出して、Excelに貼り付け
        """
        set_column_idx = 1
        ws.cell(row=set_row_idx, column=1).value = set_dir_name # 各列の1行目に、貼り付ける画像があるディレクトリ名を入力
        max_height = 0 # 画像の高さの最大値を保持するための変数
        target_full_file_names.sort() # ファイル名でソート
        for target_file in target_full_file_names:
            if imghdr.what(target_file) != None: # 画像ファイルかどうかの判定
                img = openpyxl.drawing.image.Image(target_file)
                column_letter = ws.cell(row=set_row_idx, column=set_column_idx+1).coordinate[:-1] # セルの行列番号から、そのセルの列番号の文字列を取得。最後の1文字は消す。
                print('[' + str(column_letter) + '][' + str(set_row_idx) + ']' + target_file + 'を貼り付け')
     
                # 画像のサイズを取得して、セルの大きさを合わせる（画像同士が重ならないようにするため）
                size_img = cv2.imread(target_file)
                height, width = size_img.shape[:2]
                if max_height < height:
                    max_height = height
                if not max_width[set_column_idx-1:set_column_idx]: # 配列「max_width」において、「set_column_idx」番目の要素が存在しなければ、挿入
                    max_width.insert(set_column_idx-1, width)
                if max_width[set_column_idx-1] < width:
                    max_width[set_column_idx-1] = width
                ws.row_dimensions[set_row_idx].height = max_height * 0.75
                ws.column_dimensions[column_letter].width = max_width[set_column_idx-1] * 0.13
                ws.column_dimensions['A'].width = 25
                
                cell_address = ws.cell(row=set_row_idx, column=set_column_idx+1).coordinate # セルの行列番号から、そのセルの番地を取得
                img.anchor = cell_address
                ws.add_image(img) # シートに画像貼り付け
            set_column_idx += 1
    
    # ワークブック設定
    wb = openpyxl.Workbook()
    ws = wb.worksheets[0] # 1番目のシートを編集対象にする
    ws.title = SHEET_TITLE # 1番目のシートに名前を設定
     
    # 貼り付ける画像を置いておくルートディレクトリ内のディレクトリ名を再帰的に取得
    dirs = glob.glob(os.path.join(IMG_DIR, '**' + os.sep), recursive=True)
     
    row_idx = 1
    # 各ディレクトリについて操作
    for dir_name in dirs:
        f_names = get_file_names(dir_name) # ファイル名取得
        attach_img(f_names, row_idx, dir_name) # 画像貼り付け設定
        row_idx += 1 # 次の列へ・・・
     
    # ファイルへの書き込み
    wb.save(RESULT_FILE_NAME)

このコードは以下のサイトが大変参考になりました。
・Pythonで大量の画像ファイルをExcelに貼り付け (cresco.co.jp)
https://www.cresco.co.jp/blog/entry/11916/

実行するとresult.xlsxが作成されます。

出力結果

result.xlsxの中身はこのようになっています。
![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4000399/ac5be157-9fdc-4b3a-af7f-d9e44e14991e.png)

極座標の半径、角度、Z値のデータから極座標カラーマップと線グラフを作成できました。
以上、プログラムのご紹介でした。ご一読ありがとうございました。