itsuna's blog

日々のアウトプットと備忘録

excelデータからmatplotlibで角度分布グラフをつくる覚書

概要

Pythonのmatplotlibで,Excelのデータから角度分布図を作成する覚書
こんな感じのを作りたかった.
f:id:itsuna:20181024234514p:plain

Excel Fileの中身

例えば,画像から得られた2群の直線の角度の分布を知りたいとする.
Excelに解析したいデータを以下のような形式で保存する.

Sample Angle
A 10.44054435
A 171.0808987
... ...
A 102.2847371
B 74.4935218
B 81.94229183
... ...
B 148.2852568

完全なファイルはこの中
github.com

グラフを作成する

ソースコード
# モジュールをインポートする
# 数値計算を効率的に行う"numpy"というモジュールを"np"という名前でインポート
import numpy as np
# データ解析をサポートする"pandas"というモジュールを"pd"という名前でインポート
import pandas as pd
# グラフ作成に必要な"matplotlib"の中の"pyplot"を"plt"という名前でインポート
import matplotlib.pyplot as plt

# グラフにしたいデータの入っているExcelファイル("angle_distribution_ex.xlsx)の場所と名前を入れる
excel_path = ".\\"
excel_file_name = "angle_distribution_ex.xlsx"
# そのExcelファイルをexcel_dataという名前で読み込む
excel_data = pd.ExcelFile(excel_path + excel_file_name)
# 一番始めのSheetの中身をdataframe型にする
df = excel_data.parse(excel_data.sheet_names[0])
# 項目"Sample"の中で"A"が入っている列だけ取り出し保存
df_A = df[df["Sample"]=="A"]
# 項目"Sample"の中で"B"が入っている列だけ取り出し保存
df_B = df[df["Sample"]=="B"]

# 角度の度数分布表を作成する関数
def create_angular_frequency_distribution_table(df, div_num, MAX_DEGREE=180):
    # 1つのエリアの幅を計算する。(ex. MAX_DEGREE=180°かつdiv_num=9の場合, 1つのエリアの幅は20°)
    each_angle_size = MAX_DEGREE / div_num
    # 各エリアの角度を入れる。(+1により最後の角度も入る)
    angle_list = [i * each_angle_size for i in range(div_num + 1)]
    # 度数分布表を辞書型で初期化する
    angular_distribution = {angle: 0 for angle in angle_list[:-1]}
    # dataframeを一行ずつ読み込む
    for index, row in df.iterrows():
        # ある行の角度が度数分布表のどこに入るかを探す
        for i in range(len(angle_list)-1):
            # もしある行の角度が, angle_list[i]以上angle_list[i+1]未満ならその度数分布表を+1してループを抜ける
            if ((row["Angle"] >= angle_list[i]) and (row["Angle"] < angle_list[i+1])):
                angular_distribution[angle_list[i]] += 1
                break
    print(angular_distribution)
    return [angular_distribution, angle_list]

def plot_angular_distribution(ax, angle_dist, angle_list, r_max, r_tick_width, MAX_DEGREE=180):
    import math
    # 偏角Θの範囲を決める
    ax.set_xlim([0, np.pi*MAX_DEGREE/180])
    # 30°刻みにtickを入れる
    ax.set_xticks([i * np.pi / 6 for i in range(int(math.floor(MAX_DEGREE/30)+1))])
    # 動径rの範囲を引数r_maxから設定する
    ax.set_ylim([0, r_max])
    # r_tick_width刻みにtickを入れる
    ax.set_yticks([r_tick_width * i for i in range(int(math.ceil(r_max/r_tick_width)))])
    
    for i in range(len(angle_list)-1):
        # 扇形の閉曲線を作る
        theta = np.array(
            [angle_list[i]*np.pi/180, angle_list[i]*np.pi/180] + 
            [theta*np.pi/180 for theta in np.linspace(angle_list[i], angle_list[i+1], num=int(angle_list[i+1]-angle_list[i]))] +
            [angle_list[i+1]*np.pi/180, angle_list[i+1]*np.pi/180]
        )
        r = np.array(
            [0, angle_dist[angle_list[i]]] + 
            [angle_dist[angle_list[i]] for theta in np.linspace(angle_list[i], angle_list[i+1], num=int(angle_list[i+1]-angle_list[i]))] + 
            [angle_dist[angle_list[i]], 0]
        )
        # 作った扇形をプロット
        ax.plot(theta, r, color="#333333", linewidth=1)
        # 扇の内側を塗りつぶす
        ax.fill(theta, r, "#aaaaaa")
    return ax

# ここからグラフを作成
# 1行2列のグラフの土台を作成
fig, axes = plt.subplots(1, 2, subplot_kw=dict(projection="polar"))
# グラフのサイズ指定
fig.set_size_inches(12, 5)

# df_Aからangular_distributionとangle_listを計算する
angle_dist_A, angle_list_A = create_angular_frequency_distribution_table(df_A, 9)
# 1行1列目のグラフにプロット
axes[0] = plot_angular_distribution(axes[0], angle_dist_A, angle_list_A, 22, 10)

# df_Bからangular_distributionとangle_listを計算する
angle_dist_B, angle_list_B = create_angular_frequency_distribution_table(df_B, 9)
# 1行2列目のグラフにプロット
axes[1] = plot_angular_distribution(axes[1], angle_dist_B, angle_list_B, 22, 10)

for ax, name in zip(axes, ["Sample A", "Sample B"]):
    # タイトルをフォントサイズ14で"Sample B"とし、位置調整
    ax.set_title(name, y=0.85, fontsize=14)
    # xlabelをフォントサイズ14で"Frequency"に
    ax.set_xlabel("Frequency", fontsize=14)
    # xlabelの位置調整
    ax.xaxis.set_label_coords(0.5, 0.15)
fig.tight_layout()

# saveするかどうかのboolean
save = False
# 保存先のpathとファイル名を指定
save_path = ".//"
save_file_name = "20181024 angle_distribution_plot_ex"
if save==True:
    plt.savefig(save_path + save_file_name + ".png")
    plt.savefig(save_path + save_file_name + ".svg")

# グラフを見る
plt.show()
# グラフの情報を捨てる
plt.close()

github.com

結果

f:id:itsuna:20181024234514p:plain