本当のゴールシーク

訳あって、複数行を一発でゴールシークするプログラムを自作することに。

Excelのオンプレ版ならVBAでできる。だけど、ケチって無料のLibreOfficeを使っていて、GoalSeekのAPIが死んでいるようで使えない。

それに、業務のかなりの部分がGoogleのSpread Sheetに移行してしまっている。

で、どうしたらいいか色々考えてchatGPTに相談しながら、試行錯誤すること丸一日。

LibreOfficeでもGoogleSSでもExcel365でも、要はなんでもcsvに吐き出せば、各行の数式をそれぞれゴールシークして解答を出すPythonのプログラムを作りました。

数式を別途、Pythonの形式でセルに文字列で入れておくのが汎用化の味噌です。

と、ここまで書いて、ふと気になってchatGPTに聞いてみました。

「もしかして、プログラムを書かなくてもchatGPTにcsvを渡して『全部の行をゴールシークして』と言えば応えてくれるのでしょうか?」

「はい、もちろんです」

最初に、言って欲しかった。本当のゴールはそこでした。

import csv
import tkinter as tk
from tkinter import filedialog, messagebox
from sympy import symbols, sympify, nsolve
from pathlib import Path

# 数式と変数リストからSymPy式を構築
def parse_expr(expr_str, varnames):
    syms = {name: symbols(name) for name in varnames}
    expr = sympify(expr_str, locals=syms)
    return expr, syms

# GoalSeek処理(nsolveでtargetに一致する変数値を求める)
def goal_seek(expr, var_symbol, fixed_dict, target, init_guess=1.0):
    substituted_expr = expr.subs(fixed_dict)
    return float(nsolve(substituted_expr - target, var_symbol, init_guess))

# CSVを読み込み、計算して出力用データを作成
def process_csv(file_path):
    with open(file_path, newline='', encoding='utf-8') as infile:
        reader = csv.DictReader(infile)
        headers = reader.fieldnames
        varnames = [h for h in headers if h not in ("formula", "target", "var")]

        result_rows = []
        for row in reader:
            try:
                expr_str = row["formula"]
                target = float(row["target"])
                solve_var = row["var"]

                # Noneチェックとstrip()の安全な併用
                fixed_vals = {
                    k: float(row[k])
                    for k in varnames
                    if k != solve_var and row.get(k) is not None and str(row[k]).strip() != ""
                }

                expr, syms = parse_expr(expr_str, varnames)
                result = goal_seek(expr, syms[solve_var], {syms[k]: v for k, v in fixed_vals.items()}, target)
                row["solved_value"] = result
            except Exception as e:
                row["solved_value"] = f"ERR: {e}"
            result_rows.append(row)

        return headers + ["solved_value"], result_rows

# ファイル選択から処理までのメイン関数
def main():
    root = tk.Tk()
    root.withdraw()  # GUI非表示

    file_path = filedialog.askopenfilename(
        title="CSVファイルを選択",
        filetypes=[("CSV files", "*.csv")]
    )
    if not file_path:
        messagebox.showwarning("中止", "ファイルが選択されませんでした。")
        return

    try:
        headers, rows = process_csv(file_path)
        out_path = str(Path(file_path).with_name("output.csv"))
        with open(out_path, "w", newline='', encoding='utf-8') as outfile:
            writer = csv.DictWriter(outfile, fieldnames=headers)
            writer.writeheader()
            for row in rows:
                writer.writerow(row)
        messagebox.showinfo("完了", f"処理が完了しました:\n{out_path}")
    except Exception as e:
        messagebox.showerror("エラー", str(e))

if __name__ == "__main__":
    main()

お問い合わせ
Contact

TOPへ戻る