PythonのMatplotlibのグラフに領域を指示して最大値と最小値を取得する

PythonのMatplotlibで描画したグラフに対して、マウスでここからここまでという領域を指定して、その領域内のグラフの最大値と最小値を取得してみます。

目次

  1. マウスで指定した範囲の値を取得する方法
  2. グラフの生成
  3. グラフを表示して最大値と最小値を取得する

マウスで指定した範囲の値を取得する方法

Matplotlibのイベントを利用してマウスの座標を取得し、その座標からグラフの最大値と最小値を計算します。

グラフの生成

今回は、グラフの元になるデータはCSVファイルから読み込むこととします。

そのテスト用のCSVファイルを作るコードは下記です。

import csv
import numpy as np

x = np.arange(0, 10, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)
out = np.concatenate([[x],[y1], [y2]]).transpose().tolist()
out = [list(map(str, i)) for i in out]

with open('test.csv', mode='wt', newline='', encoding='utf-8') as f:
    csv_writer = csv.writer(f)
    csv_writer.writerows(out)

シンプルなものですが、ndarrayをそのままCSVモジュールで書き出そうとするとうまくいきません。ndarrayのデータ型を文字列に変更しなければなりません。

そこで、このコードではndarrayからリストに変換して、mapメソッドで各要素を文字列にキャストしています。

グラフを表示して最大値と最小値を取得する

では、グラフを表示して最大値と最小値を取得してみます。

import csv
import numpy as np
import matplotlib.pyplot as plt

class LineBuilder:
    def __init__(self, line, x, y1, y2, line2):
        self.line = line
        self.xs = list(line.get_xdata())
        self.ys = list(line.get_ydata())
        self.x = x
        self.y1 = y1
        self.y2 = y2
        self.line2 = line2
        self.xs2 = list(line2.get_xdata())
        self.ys2 = list(line2.get_ydata())
        self.cid = line.figure.canvas.mpl_connect('button_press_event', self)
        self.cid = line.figure.canvas.mpl_connect('button_release_event', self)

    def __call__(self, event):
        if event.inaxes!=self.line.axes: return
        lim = ax.get_ylim()
        if event.name == 'button_press_event':
            self.xs.append(event.xdata)
            self.ys.append(lim[0])
            self.xs.append(event.xdata)
            self.ys.append(lim[1])
            self.line.set_data(self.xs, self.ys)
            self.line.figure.canvas.draw()
        else:
            self.xs.append(None)
            self.ys.append(None)
            self.xs.append(event.xdata)
            self.ys.append(lim[0])
            self.xs.append(event.xdata)
            self.ys.append(lim[1])
            self.line.set_data(self.xs, self.ys)
            self.line.figure.canvas.draw()
            x_selected = []
            y1_selected = []
            y2_selected = []
            x_min = min(self.xs[-4], self.xs[-1])
            x_max = max(self.xs[-4], self.xs[-1])
            for i in range(len(x)):
                if self.x[i] >= x_min and self.x[i] <= x_max:
                    x_selected.append(self.x[i])
                    y1_selected.append(self.y1[i])
                    y2_selected.append(self.y2[i])
            if len(x_selected) > 1:
                y1_max_idx = np.argmax(y1_selected)
                y2_max_idx = np.argmax(y2_selected)
                y1_min_idx = np.argmin(y1_selected)
                y2_min_idx = np.argmin(y2_selected)
                print('Y1 min: ', x_selected[y1_min_idx], y1_selected[y1_min_idx])
                print('Y1 max: ', x_selected[y1_max_idx], y1_selected[y1_max_idx])
                print('Y2 min: ', x_selected[y2_min_idx], y2_selected[y2_min_idx])
                print('Y2 max: ', x_selected[y2_max_idx], y2_selected[y2_max_idx])
                self.xs2 = [x_selected[y1_min_idx]-0.2, x_selected[y1_min_idx]+0.2, None, x_selected[y1_min_idx], x_selected[y1_min_idx]]
                self.ys2 = [y1_selected[y1_min_idx], y1_selected[y1_min_idx], None, y1_selected[y1_min_idx]-0.2, y1_selected[y1_min_idx]+0.2]
                self.xs2.extend([None, x_selected[y1_max_idx]-0.2, x_selected[y1_max_idx]+0.2, None, x_selected[y1_max_idx], x_selected[y1_max_idx]])
                self.ys2.extend([None, y1_selected[y1_max_idx], y1_selected[y1_max_idx], None, y1_selected[y1_max_idx]-0.2, y1_selected[y1_max_idx]+0.2])
                self.xs2.extend([None, x_selected[y2_min_idx]-0.2, x_selected[y2_min_idx]+0.2, None, x_selected[y2_min_idx], x_selected[y2_min_idx]])
                self.ys2.extend([None, y2_selected[y2_min_idx], y2_selected[y2_min_idx], None, y2_selected[y2_min_idx]-0.2, y2_selected[y2_min_idx]+0.2])
                self.xs2.extend([None, x_selected[y2_max_idx]-0.2, x_selected[y2_max_idx]+0.2, None, x_selected[y2_max_idx], x_selected[y2_max_idx]])
                self.ys2.extend([None, y2_selected[y2_max_idx], y2_selected[y2_max_idx], None, y2_selected[y2_max_idx]-0.2, y2_selected[y2_max_idx]+0.2])
                self.line2.set_data(self.xs2, self.ys2)
                self.line2.figure.canvas.draw()
            else:
                self.xs = []
                self.ys = []
                self.line.set_data(self.xs, self.ys)
                self.line.figure.canvas.draw()
                self.xs2 = []
                self.ys2 = []
                self.line2.set_data(self.xs2, self.ys2)
                self.line2.figure.canvas.draw()
            self.xs = []
            self.ys = []
            self.xs2 = []
            self.ys2 = []

# ファイル読み込み
fn = 'test.csv'
with open(fn, mode='r', newline='') as f_in:
    reader = csv.reader(f_in)
    data_array = [row for row in reader]

# 要素を文字列からfloatに変換
data_array = [list(map(float, i)) for i in data_array]

# グラフ描画
np_array = np.array(data_array).transpose()
x = np_array[0]
y1 = np_array[1]
y2 = np_array[2]

fig, ax = plt.subplots()
ax.plot(x, y1)
ax.plot(x, y2)

line, = ax.plot([], [])
line2, = ax.plot([], [])
linebuilder = LineBuilder(line, x, y1, y2, line2)

plt.show()

マウスの左ボタンを押したときに領域選択を開始し、マウスの左ボタンを離したときに領域選択を終了します。

選択した領域内の2つのグラフのそれぞれの最大値と最小値に印を付けて、その座標を標準出力に表示します。

実行するとこのようにグラフの最大値と最小値が得られます。

実行画面

公開日

広告