LoginSignup
0
1

More than 3 years have passed since last update.

matplotlibにおけるデザインとデータの分離

Posted at

概要

課題

私はPythonからmatplotlibを使用してグラフを描くことが多いです。
Pythonアプリケーションでデータを生成、整形、matplotlibによるグラフ出力、のような流れで行います。

このとき、以下のような課題を抱えていました。

  • グラフのデザイン統一されていない。
  • 同じような記述をそれぞれのアプリケーションで行っている。
  • デザインコード(Pythonファイルの記述)視覚的言語的に結びついていない。

原因

データを可視化するために記述するコードと、デザインを管理するために記述するコードが
1つのアプリケーション内に書かれているからだと考えました。

対応

デザインを管理するためのコードを設定ファイルとして分離してみました。

詳細

課題として記載した3つのうち、グラフのデザインが統一されていないというのは、
縦軸や横軸に割り当てたラベルの大きさ凡例プロットされる点等が統一されていないということです。

これは2番目の課題とも関連するのですが、同じような記述を別のPythonファイルで行っているからでした。

統一されておらず、その場その場で過去のPythonファイルを参照して、コピペをしていたことが問題でした。

どうしてコピペを繰り返してしまうのか、それは私が求めているデザインコード
結びついていない、少なくとも直感的ではない、からだと思います。これは3つ目の課題に繋がります。

これら3つの課題の原因は、データデザイン分離されていないからだと、私は考えました。

そうであるのならば、話は単純でデザイン設定ファイルへ分離すればよいのです。

デザインを設定ファイルに言葉で表現

以下の表のようにデザインと言葉を対応させてみました。

各項目の意味は、

  • デザインの分類
    • 設定ファイルのパラメータ
      • パラメータの意味
      • 対応するmatplotlibのコード

です。

  • Size(大きさを設定する)

    • figure_x
      • 図の横方向の大きさ
      • pyplot.figure(figsize=(figure_x, figure_y))
    • figure_y
      • 図の縦方向の大きさ
      • figure_xと同じ
    • font_title
      • タイトルのフォントの大きさ
      • pyplot.title(fontsize=font_title)
    • font_x_label
      • X軸のフォントの大きさ
      • pyplot.figure.add_subplot().ax.set_xlabel(fontsize=font_x_label)
    • font_y_label
      • Y軸のフォントの大きさ
      • pyplot.figure.add_subplot().ax.set_ylabel(fontsize=font_y_label)
    • font_tick
      • 軸のメモリのフォントの大きさ
      • pyplot.tick_params(labelsize=font_tick)
    • font_legend
      • 凡例のフォントの大きさ
      • pyplot.legend(fontsize=font_legend)
    • marker
      • データをプロットする際のマーカーの大きさ
      • pyplot.figure.add_subplot().ax.plot(markersize=marker)
  • Position(位置を設定する)

    • subplot
      • グラフを配置する場所
      • pyplot.figure.add_subplot(subplot)
    • legend_location
      • 凡例を配置するグラフ内の場所
      • pyplot.legend(loc=legend_location)

具体的な設定ファイルの表現方法

設定ファイルはいくつかの表現方法、jsonやyaml等を検討しました。

結果、Pythonに標準であるconfigparserを使うことにしました。

jsonやyamlでもよいとは思うのですが、階層的に表現できるよりも、直感的に使いやすいほうがいいと考えました。

configparserで設定ファイルを表現すると次のようになります。

config.ini
[Size]
figure_x=8
figure_y=8
font_title=20
font_x_label=18
font_y_label=18
font_tick=10
font_legend=15
marker=10

[Position]
subplot=111
legend_location=upper right

[Markers]
0=D
1=>
2=.
3=+
4=|

[Color]
0=red
1=blue
2=green
3=black
4=yellow

デザインの分類に当たる項目は[Size]のように角括弧で囲います。これはセクションと呼ばれます。
その下に設定ファイルのパラメータ=値を書いていきます。これはキーと呼ばれます。

上記の設定ファイルにはSizePositionの他に、Markers(プロットするマーカーの種類)Color(プロットされるマーカーや線の色)も表現されています。

設定ファイルに記載されたパラメータをPythonコードから使用

設定ファイルのパラメータへPythonアプリケーションがアクセスするには、次のようにコードを記述します。


import configparser


rule_file = configparser.ConfigParser()
rule_file.read("configファイルのパス", "UTF-8")

hogehoge = rule_file["セクション名"]["キー名"]

注意点として、読み込んだ値は文字列になります。

実際の使用例

以下のコードは、渡されたデータデザインの設定ファイルをもとに折れ線グラフを作成します。

make_line_graph.py
""" 折れ線グラフ作成関数

渡されたデータを使用して折れ線グラフを描画し、画像データとして保存する。

"""


import configparser
import matplotlib.pyplot as plt


def make_line_graph(data, config="config.ini"):
    """折れ線グラフ描画

    渡されたデータを使用して折れ線グラフを作成する。
    デザインは別のconfigファイルから読み取る。

    Args:
        data(dict): プロットするデータが格納されている
        config(str): configファイルの名前

    Returns:
        bool: Trueなら作成完了、Falseなら作成失敗

    Note:
        引数のdataに含めるべきkeyとvalueについて以下に記載する。

        key         : value
        ------------------------
        title(str): グラフのタイトル名
        label(list): 凡例の説明
        x_data(list): x軸のデータ
        y_data(list): y軸のデータ
        x_ticks(list): x軸のメモリに表示する値
        y_ticks(list): y軸のメモリに表示する値
        x_label(str): x軸の名前
        y_label(str): y軸の名前
        save_dir(str): 保存ファイルパス
        save_name(str): 保存ファイル名
        file_type(str): 保存ファイル形式
    """

    rule_file = configparser.ConfigParser()
    rule_file.read("./conf/{0}".format(config), "UTF-8")

    fig = plt.figure(figsize=(int(rule_file["Size"]["figure_x"]), int(rule_file["Size"]["figure_y"])))

    ax = fig.add_subplot(int(rule_file["Position"]["subplot"]))
    ax.set_xlabel(data["x_label"], fontsize=int(rule_file["Size"]["font_x_label"]))
    ax.set_ylabel(data["y_label"], fontsize=int(rule_file["Size"]["font_y_label"]))

    for index in range(len(data["x_data"])):
        ax.plot(data["x_data"][index],
                data["y_data"][index],
                label=data["label"][index],
                color=rule_file["Color"][str(index)],
                marker=rule_file["Markers"][str(index)],
                markersize=int(rule_file["Size"]["marker"]))

    plt.title(data["title"], fontsize=int(rule_file["Size"]["font_title"]))

    if "x_ticks" in data.keys():
        plt.xticks(data["x_ticks"][0], data["x_ticks"][1])

    if "y_ticks" in data.keys():
        plt.yticks(data["y_ticks"][0], data["y_ticks"][1])

    plt.tick_params(labelsize=int(rule_file["Size"]["font_tick"]))
    plt.legend(fontsize=rule_file["Size"]["font_legend"], loc=rule_file["Position"]["legend_location"])
    plt.savefig("".join([data["save_dir"], "/", data["save_name"], ".", data["file_type"]]))

データを渡すPythonファイルは以下のような感じになります。

main.py

from make_line_graph import make_line_graph

data = {
    "title": "hogehoge",
    "label": ["A", "B"],
    "x_data": [x_data1, x_data2],
    "y_data": [y_data1, y_data2],
    "x_ticks": [x_ticks1, x_ticks2],
    "y_ticks": [y_ticks1, y_ticks2],
    "x_label": "hogehoge",
    "y_label": "hogehoge",
    "save_dir": "保存したいフォルダのパス",
    "save_name": "保存したいファイル名",
    "file_type": "拡張子",
}

make_line_graph(data, config="config.ini")

使ってみた感想

良かった点

デザインが変えやすくなりました。
特に、各種フォントサイズはプロットするデータやラベルに入れる文字数によって変わります。

また設定ファイルを複製してカスタマイズすることで、グラフデザインを変えたくなったときのPythonファイル変更量が少なくなりました。
読み込む設定ファイル名だけ変えればよし。

グラフデザインと設定ファイルが結びついているので、どのデザインがどのコードと対応しているのか忘れても大丈夫です。

悪かった点

どこまで汎用性をもたせるかが難しいです。

作ったmake_line_graph.pyは折れ線グラフ作成関数ですが、似たようなPythonファイルが増えるとよろしくないのでできる限り汎用的にしました。
ただこれではうまくグラフが描画出来ず、それに対応するために別の折れ線グラフ作成関数が乱立すると、振り出しに戻ってしまいそうです・・・。

汎用性を考えればきりがないのかなとも思ったりしてますが・・・。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1