ラベル Matplotlib の投稿を表示しています。 すべての投稿を表示
ラベル Matplotlib の投稿を表示しています。 すべての投稿を表示

2026-06-16

NavigationToolbar2QT のカスタマイズ 〜 Matplotlib 〜

Matplotlib は、Python と NumPy のためのプロットライブラリです。Tkinter、wxPython、Qt、GTK のような汎用 GUI ツールキットを使ったアプリケーションにプロットを埋め込むためのオブジェクト指向 API を提供しています。

Wikipedia より引用、翻訳

PySide6 の GUI で Matplotlib のチャートを扱うときに利用する、ズームや保存などの便利な機能を提供するナビゲーション・ツールバー NavigationToolbar2QT について、ユーザー定義のボタンを追加する方法(+α)をまとめました。

下記の環境で動作確認をしています。

Fedora Linux 44
KDE Plasma Desktop x86_64
Python 3.14.5
matplotlib 3.11.0
pyside6 6.11.1
seaborn 0.13.2

実行例

サンプルとして正規分布の乱数のヒストグラムを表示していますが、今回のテーマは、このチャートの下に表示されているナビゲーション・ツールバーのカスタマイズです。

ナビゲーション・ツールバーのカスタマイズ例として、「保存」ボタンの右側に「Qt」アイコン (SP_TitleBarMenuButton) を表示しています。

qt_matplotlib/toolbar.py の実行例

NavigationToolbar2QT を継承

NavigationToolbar2QT を継承して「Qt」アイコン (SP_TitleBarMenuButton)(ボタン)を追加しています。

from matplotlib.backends.backend_qtagg import (
    NavigationToolbar2QT as NavigationToolbar,
)

class MyNavToolbar(NavigationToolbar):
    def __init__(self, canvas: FigureCanvas):
        super().__init__(canvas)
        user_action = QAction("User", self)
        icon = self.style().standardIcon(
            QStyle.StandardPixmap.SP_TitleBarMenuButton
        )
        user_action.setIcon(icon)
        user_action.triggered.connect(self.on_user_action)

        actions = self.actions()

        n = len(actions)
        self.insertAction(actions[n - 1], user_action)

    def on_user_action(self):
        # Zoomモードなら解除
        if self._actions["zoom"].isChecked():
            self._actions["zoom"].trigger()
        # Panモードなら解除
        if self._actions["pan"].isChecked():
            self._actions["pan"].trigger()

        print("User button clicked")

「Qt」アイコン (SP_TitleBarMenuButton)(ボタン)をクリックすると、on_user_action メソッドが実行されて文字列が標準出力されますが、その他の機能として、ズームボタン self._actions["zoom"] あるいはパン(移動)ボタン self._actions["pan"] が選択状態であれば解除するようにしています。

サンプル・コード

サンプルコード qt_matplotlib_toolbar.py を示しました。

qt_matplotlib/toolbar.py

参考サイト

  1. Embedding in Qt — Matplotlib documentation

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2025-09-16

Ta-Lib を Python で利用する

TA-Lib, Technical Analysis Library は 2001 年にリリースされ、20 年以上経った今でも広く利用されている著名なアルゴリズムを提供しています。コードは安定しており、長年にわたる検証を経ています。200 以上のテクニカル指標をサポートしており、API は C/C++ で記述されており Python ラッパー (wrapper) も提供されています。TA-Lib は BSD License (BSD-2-Clause license) の元で配布されているオープンソースのライブラリです。

以前は Linux 上で TA-Lib の Python 用パッケージを pip でインストールしてもビルドが必要で、しかもエラーでビルドできませんでした。自力でエラーを解決できなかったので TA-Lib の利用を避けていました。しかし最近の TA-Lib のバージョンの Python 用パッケージでは難なくインストールできることが判りました。

そこで今更ですが TA-Lib の使い方をおぼえようと、Jupyter Lab 上でテクニカル指標のいくつかをプロットしてみたので、備忘録的に内容をまとめました。

下記の OS 環境で動作確認をしています。

Fedora Linux Workstation 42 x86_64
Python 3.13.7
JupyterLab 4.4.7
matplotlib 3.10.6
mplfinance 0.12.10b0
numpy 2.3.3
pandas 2.3.2
ta-lib 0.6.7
yfinance 0.2.65

サンプル

ライブラリをインポート

最初に利用するライブラリをまとめてインポートします。

import matplotlib.font_manager as fm
import matplotlib.pyplot as plt
import mplfinance as mpf
import numpy as np
import pandas as pd
import yfinance as yf
from talib import BBANDS, MACD, MFI, MOM, OBV, RSI, SAR, STOCH

yfinance で日経平均株価指数の過去データを取得

サンプルとして、今年の 1 月から半年間の日足データを取得します。

symbol = "^N225"
ticker = yf.Ticker(symbol)
df = ticker.history(start="2025-01-01", end="2025-07-01", interval="1d")

サンプル期間より少し古いデータからも取得しておきます。これでテクニカル指標を算出して、サンプルの期間の最初から指標がプロットされるようにします。

df2 = ticker.history(start="2024-10-01", end="2025-07-01", interval="1d")

mplfinance でチャートを作成

サンプル期間の日足データをローソク足チャートと出来高の棒グラフを並べてプロットしました。

fig = plt.figure(figsize=(8, 4))
ax = dict()
n = 2
gs = fig.add_gridspec(
    n, 1, wspace=0.0, hspace=0.0, height_ratios=[3 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex="col")):
    ax[i] = axis
    ax[i].grid()

mpf.plot(
    df,
    type="candle",
    style="default",
    datetime_format="%m/%d",
    xrotation=0,
    ax=ax[0],
    volume=ax[1],
)
ax[0].set_title(f"{ticker.info['longName']} ({symbol})")

plt.tight_layout()
# plt.savefig("screenshots/n225_default.png")
plt.show()

Bollinger bands

過去 20 日間の移動平均、移動標準偏差 +3σ, +2σ, +1σ, mean, -1σ, -2σ, -3σ でボリンジャーバンドを作成しました。

fig, ax = plt.subplots(figsize=(8, 3))

# BBANDS - Bollinger Bands
# upperband, middleband, lowerband = BBANDS(real, timeperiod=5, nbdevup=2, nbdevdn=2, matype=0)
period = 20
mv_upper_1, mv_mean, mv_lower_1 = BBANDS(df2["Close"], period, 1, 1)
mv_upper_2, _, mv_lower_2 = BBANDS(df2["Close"], period, 2, 2)
mv_upper_3, _, mv_lower_3 = BBANDS(df2["Close"], period, 3, 3)

apds = [
    mpf.make_addplot(
        mv_upper_3[df.index],
        width=1,
        color="C0",
        linestyle="dotted",
        label="+3σ",
        ax=ax,
    ),
    mpf.make_addplot(
        mv_upper_2[df.index],
        width=0.9,
        color="C1",
        linestyle="dashdot",
        label="+2σ",
        ax=ax,
    ),
    mpf.make_addplot(
        mv_upper_1[df.index],
        width=0.75,
        color="C2",
        linestyle="dashed",
        label="+1σ",
        ax=ax,
    ),
    mpf.make_addplot(
        mv_mean[df.index],
        width=1,
        color="C3",
        linestyle="solid",
        label="Mean",
        ax=ax,
    ),
    mpf.make_addplot(
        mv_lower_1[df.index],
        width=0.75,
        color="C4",
        linestyle="dashed",
        label="-1σ",
        ax=ax,
    ),
    mpf.make_addplot(
        mv_lower_2[df.index],
        width=0.9,
        color="C5",
        linestyle="dashdot",
        label="-2σ",
        ax=ax,
    ),
    mpf.make_addplot(
        mv_lower_3[df.index],
        width=1,
        color="C6",
        linestyle="dotted",
        label="-3σ",
        ax=ax,
    ),
]
mpf.plot(
    df,
    type="candle",
    style="default",
    addplot=apds,
    datetime_format="%m/%d",
    xrotation=0,
    update_width_config=dict(candle_linewidth=0.75),
    ax=ax,
)
ax.grid()
ax.legend(fontsize=7)
ax.set_title(
    f"{ticker.info['longName']} ({symbol})\nwith Bollinger Bands (period={period}days)"
)

plt.tight_layout()
# plt.savefig("screenshots/n225_talib_bbands.png")
plt.show()

Parabolic SAR

AF step=0.02, max=0.2 で Parabolic SAR をプロットしました。上昇下降トレンドの情報が無いので、灰色の丸点でプロットしました。

fig, ax = plt.subplots(figsize=(8, 3))

# SAR - Parabolic SAR
# real = SAR(high, low, acceleration=0, maximum=0)
af_step = 0.02
af_max = 0.2
sar = SAR(df2["High"], df2["Low"], af_step, af_max)

apds = [
    mpf.make_addplot(
        sar[df.index],
        type="scatter",
        marker='o',
        markersize=3,
        color="darkgray",
        ax=ax,
    ),
]
mpf.plot(
    df,
    type="candle",
    style="default",
    addplot=apds,
    datetime_format="%m/%d",
    xrotation=0,
    update_width_config=dict(candle_linewidth=0.75),
    ax=ax,
)
ax.grid()
ax.set_title(
    f"{ticker.info['longName']} ({symbol})\nwith Parabolic SAR (AF step={af_step}, max={af_max})"
)

plt.tight_layout()
# plt.savefig("screenshots/n225_talib_sar.png")
plt.show()

Momentum

過去 10 日間のデータでモメンタムを算出しました。

fig = plt.figure(figsize=(8, 4))
ax = dict()
n = 2
gs = fig.add_gridspec(
    n, 1, wspace=0.0, hspace=0.0, height_ratios=[2 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex="col")):
    ax[i] = axis
    ax[i].grid()

# MOM - Momentum
# real = MOM(real, timeperiod=10)
period = 10
mom = MOM(df2["Close"], period)
apds = [
    mpf.make_addplot(
        mom[df.index],
        width=1,
        color="C0",
        linestyle="solid",
        ax=ax[1],
    ),
]
mpf.plot(
    df,
    type="candle",
    style="default",
    addplot=apds,
    datetime_format="%m/%d",
    xrotation=0,
    ax=ax[0],
)
ax[1].set_ylabel("Momentum")
ax[0].set_title(
    f"{ticker.info['longName']} ({symbol})\nwith Momentum (period={period}days)"
)

plt.tight_layout()
# plt.savefig("screenshots/n225_talib_mom.png")
plt.show()

RSI, Relative Strength Index

過去 14 日間のデータで RSI を算出しました。

fig = plt.figure(figsize=(8, 4))
ax = dict()
n = 2
gs = fig.add_gridspec(
    n, 1, wspace=0.0, hspace=0.0, height_ratios=[2 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex="col")):
    ax[i] = axis
    ax[i].grid()

# RSI - Relative Strength Index
# real = RSI(real, timeperiod=14)
period = 14
rsi = RSI(df2["Close"], period)
apds = [
    mpf.make_addplot(
        rsi[df.index],
        width=1,
        color="C0",
        linestyle="solid",
        ax=ax[1],
    ),
]
mpf.plot(
    df,
    type="candle",
    style="default",
    addplot=apds,
    datetime_format="%m/%d",
    xrotation=0,
    ax=ax[0],
)
ax[0].set_title(f"{ticker.info['longName']} ({symbol})\nwith RSI (period={period}days)")
ax[1].set_ylabel("RSI")
ax[1].set_ylim(0, 100)
ax[1].axhline(30, color="black", linewidth=0.5)
ax[1].axhline(70, color="black", linewidth=0.5)

plt.tight_layout()
# plt.savefig("screenshots/n225_talib_rsi.png")
plt.show()

Stochastic oscillator

スローストキャスティクスをデフォルトのパラメータのままでプロットしています。

fig = plt.figure(figsize=(8, 4))
ax = dict()
n = 2
gs = fig.add_gridspec(
    n, 1, wspace=0.0, hspace=0.0, height_ratios=[2 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex="col")):
    ax[i] = axis
    ax[i].grid()

# STOCH - Stochastic
# slowk, slowd = STOCH(high, low, close, fastk_period=5, slowk_period=3, slowk_matype=0, slowd_period=3, slowd_matype=0)
slowk, slowd = STOCH(df2["High"], df2["Low"], df2["Close"])

apds = [
    mpf.make_addplot(
        slowk[df.index],
        width=1,
        color="C0",
        linestyle="solid",
        label="Slow%K",
        ax=ax[1],
    ),
    mpf.make_addplot(
        slowd[df.index],
        width=1,
        color="C1",
        linestyle="solid",
        label="Slow%D",
        ax=ax[1],
    ),
]
mpf.plot(
    df,
    type="candle",
    style="default",
    addplot=apds,
    datetime_format="%m/%d",
    xrotation=0,
    ax=ax[0],
)
ax[0].set_title(f"{ticker.info['longName']} ({symbol})\nwith Stochastic oscillator")
ax[1].set_ylabel("Stochastic")
ax[1].set_ylim(0, 100)
ax[1].axhline(20, color="black", linewidth=0.5)
ax[1].axhline(80, color="black", linewidth=0.5)
ax[1].legend(fontsize=7)

plt.tight_layout()
# plt.savefig("screenshots/n225_talib_stoch.png")
plt.show()

MACD, Moving Average Convergence Divergence

MACD もデフォルトのパラメータでプロットしています。MACD のヒストグラムは正負で色を変えたかったのですが、すぐに出来なかったので単色にしてしまいました。

fig = plt.figure(figsize=(8, 4))
ax = dict()
n = 2
gs = fig.add_gridspec(
    n, 1, wspace=0.0, hspace=0.0, height_ratios=[2 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex="col")):
    ax[i] = axis
    ax[i].grid()

# MACD - Moving Average Convergence/Divergence
# macd, macdsignal, macdhist = MACD(real, fastperiod=12, slowperiod=26, signalperiod=9)
period_fast = 12
period_slow = 26
period_signal = 9
macd, signal, macdhist = MACD(df2["Close"], period_fast, period_slow, period_signal)

apds = [
    mpf.make_addplot(
        macd[df.index],
        width=1,
        color="C0",
        linestyle="solid",
        label="MACD",
        ax=ax[1],
    ),
    mpf.make_addplot(
        signal[df.index],
        width=1,
        color="C1",
        linestyle="solid",
        label="Signal",
        ax=ax[1],
    ),
    mpf.make_addplot(
        macdhist[df.index],
        type="bar",
        color="C2",
        ax=ax[1],
    ),
]
mpf.plot(
    df,
    type="candle",
    style="default",
    addplot=apds,
    datetime_format="%m/%d",
    xrotation=0,
    ax=ax[0],
)
ax[0].set_title(
    f"{ticker.info['longName']} ({symbol})\nwith MACD [{period_fast}, {period_slow}, {period_signal}]"
)
ax[1].set_ylabel("MACD")
ax[1].legend(fontsize=7)

plt.tight_layout()
# plt.savefig("screenshots/n225_talib_macd.png")
plt.show()

OBV, On Balance Volume

OBV は終値と出来高から算出する指標です。

fig = plt.figure(figsize=(8, 5))
ax = dict()
n = 3
gs = fig.add_gridspec(
    n, 1, wspace=0.0, hspace=0.0, height_ratios=[2 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex="col")):
    ax[i] = axis
    ax[i].grid()

# OBV - On Balance Volume
# real = OBV(close, volume)
obv = OBV(df2["Close"], df2["Volume"])

apds = [
    mpf.make_addplot(
        obv[df.index],
        width=1,
        color="C0",
        linestyle="solid",
        ax=ax[2],
    ),
]
mpf.plot(
    df,
    type="candle",
    style="default",
    addplot=apds,
    datetime_format="%m/%d",
    xrotation=0,
    ax=ax[0],
    volume=ax[1],
)
ax[0].set_title(f"{ticker.info['longName']} ({symbol})\nwith OBV")
ax[2].set_ylabel("OBV")

plt.tight_layout()
# plt.savefig("screenshots/n225_talib_obv.png")
plt.show()

MFI, Money Flow Index

MFI も、株価と出来高から算出する指標です。

fig = plt.figure(figsize=(8, 5))
ax = dict()
n = 3
gs = fig.add_gridspec(
    n, 1, wspace=0.0, hspace=0.0, height_ratios=[2 if i == 0 else 1 for i in range(n)]
)
for i, axis in enumerate(gs.subplots(sharex="col")):
    ax[i] = axis
    ax[i].grid()

# MFI - Money Flow Index
# NOTE: The MFI function has an unstable period.
# real = MFI(high, low, close, volume, timeperiod=14)
period = 14
mfi = MFI(df2["High"], df2["Low"], df2["Close"], df2["Volume"], period)

apds = [
    mpf.make_addplot(
        mfi[df.index],
        width=1,
        color="C0",
        linestyle="solid",
        ax=ax[2],
    ),
]
mpf.plot(
    df,
    type="candle",
    style="default",
    addplot=apds,
    datetime_format="%m/%d",
    xrotation=0,
    ax=ax[0],
    volume=ax[1],
)
ax[0].set_title(f"{ticker.info['longName']} ({symbol})\nwith MFI (period={period}days)")
ax[2].axhline(20, color="black", linewidth=0.5)
ax[2].axhline(80, color="black", linewidth=0.5)
ax[2].set_ylabel("MFI")
ax[2].set_ylim(0, 100)

plt.tight_layout()
# plt.savefig("screenshots/n225_talib_mfi.png")
plt.show()

参考サイト

  1. TA-Lib - Technical Analysis Library
  2. TA-Lib/ta-lib: TA-Lib (Core C Library)
  3. TA-Lib/ta-lib-python: Python wrapper for TA-Lib
  4. TA-Lib · PyPI

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2025-05-28

【備忘録】Matplotlib で保存するプロットのデフォルト名

Matplotlib は、Python と NumPy のためのプロットライブラリです。Tkinter、wxPython、Qt、GTK のような汎用 GUI ツールキットを使ったアプリケーションにプロットを埋め込むためのオブジェクト指向 API を提供しています。

Wikipedia より引用、翻訳

JupyterLab 上では、Matplotlib でプロットしたチャートを保存するには、plt.savefig(filename) を使うのですが、そうでないときは、以下のようにチャート (figure) の下にナビゲータバーが表示されているので、右端のフロッピーディスクを模した保存用ボタンをクリックして画像として保存します。

保存用のダイアログに表示されたデフォルトのファイル名は Figure_1 になっています。

このデフォルトのファイル名を変更したい

些細なことのようですが、このデフォルトのファイル名 Figure_1 を変えられないかどうか調べたところ、Stack Overflow [1] に該当するやりとりを見つけました。保存するファイルのデフォルトのファイル名を変更するサンプルを紹介します。

下記の OS 環境で動作確認をしています。

Fedora Linux 42 Workstation x86_64
Python 3.13.3
matplotlib 3.10.3
numpy 2.2.6

サンプル・コードを以下の通りです。

sample_mpl_save.py
# This is based on the following sample:
# https://matplotlib.org/stable/users/getting_started/
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x)

fig, ax = plt.subplots()
ax.plot(x, y)

# デフォルトのファイル名を Untitled に設定
c = fig.canvas
ext = c.get_default_filetype()
c.get_default_filename = lambda: f"Untitled.{ext}"

plt.show()

一手間加えるだけで、デフォルトのファイル名を Figure_1 から Untitled へ変更できました。

ちなみに、PySide6 に Matplotlib のチャートを埋め込んで Matplotlib のナビゲーションツールバー (NavigationToolbar2QT) を利用してチャートを画像形式で保存する時には、デフォルトのファイル名が image.png になります。さすがに GUI アプリではデフォルトの保存ファイル名を自由に設定したいので、同じような考え方でファイル名を変更しています。

参考サイト

  1. python - How to change default filename from Matplotlib NavigationToolbar in a PyQt5 application? - Stack Overflow [2017-01-16]

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2025-05-20

Matplotlib と QChart

Matplotlib は、Python と NumPy のためのプロットライブラリです。Tkinter、wxPython、Qt、GTK のような汎用 GUI ツールキットを使ったアプリケーションにプロットを埋め込むためのオブジェクト指向 API を提供しています。

Wikipedia より引用、翻訳

最近は、何かにつけて Google Gemini(以下、単に Gemini と呼びます)に相談することが多くなり、Python などのコーディングについても相談しています。便利になったものです。

リアルタイムでデータを取得してトレンドチャートにするニーズに対して、PySide6 の QChart で実装しようとして Gemini にあれこれ相談しています。

Matplotlib についても同じようなリアルタイムなトレンドチャートができるか Gemini に聞いてみたところ、あっさりサンプルを示してくれました。確認したところ、厳密にはデータ追加の度に再描画をしているとのことですが、それでもこのやり方を探していたんだと(Matplotlib でリアルタイムにデータ点を追加することに苦戦していただけに)いままでの自分の調査力の拙さにがっかりしてしまいました。

PySide6 に埋め込んだ Matplotlib によるリアルタイムなトレンドチャート (by Google Gemini)

Gemini が出してくれたコードは、本ブログ記事の下の方 qt_matplotlib_rt.py に示しました。

Matplotlib でもできるのであれば、現在 PySide6 の QChart で実装しようとしているトレンドチャートを Matplotlib でやり直そうか…。結構な量のデータをリアルタイムで扱うことから、リソース消費やパフォーマンスを追求する必要があったので、Gemini に頼んで比較してもらいました。

以下が Gemini が出力してくれた「まとめ」の表に色など少し修飾を加えたものです。

PySide6 QChart と 埋め込んだ Matplotlib の比較 (by Google Gemini)
特徴 PySide6 QChart 埋め込んだ Matplotlib
処理速度 高速 (リアルタイムデータ更新に最適化) 比較的低速 (再描画のオーバーヘッド)
リソース効率 有利 (軽量、メモリ使用量少) 不利 (多機能、メモリ使用量多)
リアルタイム性 高い 比較的低い
汎用性 グラフの種類は Matplotlib より限定的 非常に高い (多様なグラフに対応)
カスタマイズ性 ある程度可能 非常に高い (細部までカスタマイズ可能)
統合性 Qt アプリケーションとの親和性が高い 外部ライブラリの埋め込み

Jupyter Lab 上でのチャートは Matplotlib が便利で、多少リソースを食うからと言っても変える気はありませんが、GUI アプリの特定用途向けのチャートでは GUI ライブラリの機能(この場合は QChart)を使った方が良さそうです。

便利なツールは躊躇せずにどんどん試してみるべきですが、Gemini に限らず、生成 AI は便利すぎて、依存度が高くなってしまっています。😅

qt_matplotlib_rt.py (by Google Gemini)

参考サイト

  1. Google Gemini

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加

2025-02-10

【備忘録】等高線図 (contour map)

Matplotlib は、Python と NumPy のためのプロットライブラリです。Tkinter、wxPython、Qt、GTK のような汎用 GUI ツールキットを使ったアプリケーションにプロットを埋め込むためのオブジェクト指向 API を提供しています。

Wikipedia より引用、翻訳

いくつものパラメータで、ある特性の最適点を探索するとき、興味がある2つのパラメータとその特性の関係を視覚化したい場合があります。(他のパラメータ値を固定した上で)2つのパラメータを変動させて、メッシュ状に総当たりのデータを用意できれば、Matplotlib の countour で等高線図を作成することができます。

数式からループを回して大きなメッシュ状のデータを用意して見た目に美しい等高線図を作成したことはあっても、生のデータから直接作成したことがありませんでした。どちらもやっていることは本質的には同じなのですが、あらためて少ないデータから等高線図を作ってみると、線形モデルを前提としなくとも、データの挙動をざっくり捉えるには使えそうだと思ったので、備忘録にまとめました。

下記の OS 環境で動作確認をしています。

Fedora Linux 41 Workstation x86_64
Python 3.13.2
jupyterlab 4.3.5
matplotlib 3.10.0
numpy 2.2.2
pandas 2.2.3

以下の作業は JupyterLab 上でおこなっています。

最初に必要なライブラリをインポートします。

import matplotlib.ticker as ticker
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

等高線図用データの準備

サンプルの CSV データを読み込みます。ファイル sample_contour.csv は下記からダウンロードできます。

sample_contour.csv
csvfile = 'sample_contour.csv'
df = pd.read_csv(csvfile)
df.head(10)

 

このデータは、X と Y それぞれのパラメータを一つずつ変化させて全ての組み合わせで Z を取得した総当たりのデータです。

X の値は下記のように 3 から 7 までの 5 つの整数です。

df['X'].unique()
array([3, 4, 5, 6, 7])

X のユニークな数 (5) を n_x に保存しておきます。

n_x = len(df['X'].unique())

Y の値は下記のように 0 から 5 までの 6 つの整数です。

df['Y'].unique()
array([0, 1, 2, 3, 4, 5])

Y のユニークな数 (6) を n_y に保存しておきます。

n_y = len(df['Y'].unique())

データフレームの列を Y, X の順番でソートします。

df.sort_values(['Y', 'X'], ignore_index=True, inplace=True)
df.head(10)

 

matplotlib の concour に渡す二次元配列 x, y, z を用意します。二次元配列は縦横のサイズが同じである必要があります。

データフレーム df から X, Y, Z 列を Series で取り出し、numpy の配列にした後、n_y 行 × n_x 列の二次元配列に整形します。

x = np.array(df['X']).reshape([n_y, n_x])
y = np.array(df['Y']).reshape([n_y, n_x])
z = np.array(df['Z']).reshape([n_y, n_x])
x
array([[3, 4, 5, 6, 7],
       [3, 4, 5, 6, 7],
       [3, 4, 5, 6, 7],
       [3, 4, 5, 6, 7],
       [3, 4, 5, 6, 7],
       [3, 4, 5, 6, 7]])
y
array([[0, 0, 0, 0, 0],
       [1, 1, 1, 1, 1],
       [2, 2, 2, 2, 2],
       [3, 3, 3, 3, 3],
       [4, 4, 4, 4, 4],
       [5, 5, 5, 5, 5]])
z
array([[ 900.,  550.,  450.,  150., -250.],
       [ 800.,  250.,  300.,  200., -100.],
       [ 150., -300., -150., -450., -450.],
       [ 400.,  -50.,  100., -200., -200.],
       [ 450.,    0.,  150., -150., -150.],
       [ 450.,    0.,  150., -150., -150.]])

等高線図のプロット

matplotlib の coucour で、二次元配列 x, y, z の等高線図をプロットします。

plt.rcParams['font.family'] = 'monospace'

fig = plt.figure(figsize=(6, 6))
ax = fig.add_subplot(111)

cont = ax.contour(
    x, y, z,
    linestyles='solid',
    linewidths=1,
)
cont.clabel(fmt='%.f', fontsize=12)

ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(1))

ax.xaxis.set_tick_params(labelsize=12)
ax.yaxis.set_tick_params(labelsize=12)

ax.set_xlabel('X', fontsize=14)
ax.set_ylabel('Y', fontsize=14)

ax.grid()

plt.tight_layout()
plt.show()

 

凸凹な等高線図ですが、これでもパラメータの変化にどのように依存するかの傾向を捉えることができます。

実用的には数あるパラメータから、JupyterLab 上であれこれパラメータを変えてプロットを作成するのは手間なので、PySide6 で GUI を用意して、使い勝手を良くしています。GUI を用意するのに時間がかかりますが、そこそこ使えるようにしてしまえば、あとは改善を重ねてどんどん使いやすくできます。

参考サイト

  1. matplotlib.pyplot.contour — Matplotlib documentation
  2. matplotlib.contour — Matplotlib documentation

 

ブログランキング・にほんブログ村へ bitWalk's - にほんブログ村 にほんブログ村 IT技術ブログ オープンソースへ
にほんブログ村

オープンソース - ブログ村ハッシュタグ
#オープンソース



このエントリーをはてなブックマークに追加