
訳あって、複数行を一発でゴールシークするプログラムを自作することに。
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()