見出し画像

#44 Pythonで始める回帰分析(単回帰)、チャレンジ AI×100業務(製造業)

今回は、過去の設計データや試験データをもとに性能を予測するような、設計者目線の回帰分析を取り上げ、Pythonで形にしてみました。


それでは、スタートです。
*執筆者は以下を参照してください。


使ったツール・準備したこと

  • Visual Studio Code環境下でのPython

  • Chat GPT 4o

やってみたこと|コード・ステップ紹介

実行した内容の簡単な流れ

ChatGPT 4oへの会話:

回帰分析のコードを生成してください。以下留意してください
・データはcsvファイルを読み込み、Y、Xを選択するGUIを追加
・回帰式、相関係数(R)の表示
・予測区間を赤点線で描画

入力したプロンプト


回帰直線、予測区間+回帰式、統計量が示されています。
上記のグラフはデフォルトでの設定ですが、軸の範囲は変更可能です。

グラフは画像でアクティブにはなりませんので、軸範囲の設定GUIにて横軸、縦軸の範囲を設定するようにしています。
赤点線は95%予測区間を示しており、グラフをマウスオーバーすれば、右下に x, y の数値が表示されているので、大よその数値は把握できます。

今後の展開・アイデア

次回取り組みたいこと

・多次数の回帰分析です。


■ 今回のコードです。

import pandas as pd
import numpy as np
import tkinter as tk
from tkinter import filedialog, simpledialog, ttk, messagebox
import matplotlib.pyplot as plt
from matplotlib.backend_bases import MouseEvent
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from scipy.stats import t
import os
import matplotlib
matplotlib.rcParams['font.family'] = 'MS Gothic'

# CSVファイル選択
root = tk.Tk()
root.withdraw()
file_path = filedialog.askopenfilename(title="CSVファイルを選択", filetypes=[("CSV files", "*.csv")])
if not file_path:
    raise Exception("ファイルが選択されていません。")

# CSV読み込み(全行対象でエラー時fallback)
try:
    df = pd.read_csv(file_path, encoding="cp932", engine='python')
except Exception:
    df = pd.read_csv(file_path, encoding="utf-8", engine='python')

# GUIでX, Y, 次数を選択
root = tk.Tk()
root.title("列と次数の選択")
root.geometry("600x250")

x_var = tk.StringVar()
y_var = tk.StringVar()
xlabel_var = tk.StringVar()
ylabel_var = tk.StringVar()
degree_var = tk.IntVar(value=2)

x_var.set(df.columns[0])
y_var.set(df.columns[1] if len(df.columns) > 1 else df.columns[0])
def update_labels(*args):
    print("X列:", x_var.get(), "→ X軸ラベルに反映")
    xlabel_var.set(x_var.get())
    ylabel_var.set(y_var.get())



def update_labels(*args):
    print("更新: X→", x_var.get(), "Y→", y_var.get())
    xlabel_var.set(x_var.get())
    ylabel_var.set(y_var.get())

x_var.trace_add('write', update_labels)
y_var.trace_add('write', update_labels)
update_labels()



tk.Label(root, text="X列を選択:").grid(row=0, column=0, sticky='w')
ttk.Combobox(root, textvariable=x_var, values=list(df.columns), state="readonly", width=50).grid(row=0, column=1)
tk.Label(root, text="Y列を選択:").grid(row=1, column=0, sticky='w')
ttk.Combobox(root, textvariable=y_var, values=list(df.columns), state="readonly", width=50).grid(row=1, column=1)
tk.Label(root, text="X軸ラベル:").grid(row=2, column=0, sticky='w')
tk.Entry(root, textvariable=xlabel_var, width=50).grid(row=2, column=1)
tk.Label(root, text="Y軸ラベル:").grid(row=3, column=0, sticky='w')
tk.Entry(root, textvariable=ylabel_var, width=50).grid(row=3, column=1)
tk.Label(root, text="回帰次数 (2~5):").grid(row=4, column=0, sticky='w')
ttk.Combobox(root, textvariable=degree_var, values=list(range(2, 6)), state="readonly", width=10).grid(row=4, column=1, sticky='w')

def submit(event=None):
    root.quit()
    root.destroy()

tk.Button(root, text="OK", command=submit).grid(row=5, column=1, pady=10, sticky='e')
root.bind('<Return>', submit)
root.mainloop()

# データ準備
x_col = x_var.get()
y_col = y_var.get()
xlabel = xlabel_var.get()
ylabel = ylabel_var.get()
df = df[[x_col, y_col]].dropna()
x = df[[x_col]].values
y = df[y_col].values
degree = degree_var.get()

# 多項式回帰
poly = PolynomialFeatures(degree=degree, include_bias=False)
x_poly = poly.fit_transform(x)
model = LinearRegression()
model.fit(x_poly, y)
y_pred = model.predict(x_poly)
r2 = r2_score(y, y_pred)

# 式の生成
coef_str = " + ".join([f"{c:.3f}x^{i+1}" for i, c in enumerate(model.coef_)])
eq = f"Y = {model.intercept_:.3f} + {coef_str}"

# 予測区間
x_range = np.linspace(x.min(), x.max(), 100).reshape(-1, 1)
x_range_poly = poly.transform(x_range)
y_fit = model.predict(x_range_poly)

# 残差と予測誤差(簡易版)
n = len(x)
p = x_poly.shape[1]
se = np.sqrt(np.sum((y - y_pred)**2) / (n - p - 1))
mean_x = np.mean(x)
y_std_error = se * np.sqrt(1 + 1/n + ((x_range - mean_x)**2 / np.sum((x - mean_x)**2)))
t_val = t.ppf(0.975, df=n - p - 1)
y_lower = y_fit - t_val * y_std_error.flatten()
y_upper = y_fit + t_val * y_std_error.flatten()

# 再描画関数
active_fig = None
x_min_var = tk.StringVar()
x_max_var = tk.StringVar()
y_min_var = tk.StringVar()
y_max_var = tk.StringVar()

def redraw_plot(xlim=None, ylim=None):
    global active_fig
    if active_fig:
        plt.close(active_fig)
    active_fig = plt.figure(figsize=(10, 6))
    plt.scatter(x, y, color='black', s=10)
    plt.plot(x_range, y_fit, color='blue', label=f'{degree}次回帰')
    plt.plot(x_range, y_lower, 'r--', label='予測区間(95%)')
    plt.plot(x_range, y_upper, 'r--')
    plt.title("多項式回帰分析", fontsize=16)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    plt.text(0.05, 0.95, eq, transform=plt.gca().transAxes)
    plt.text(0.05, 0.90, f"R² = {r2:.3f}", transform=plt.gca().transAxes)
    plt.text(0.05, 0.85, f"S = {se:.4f}", transform=plt.gca().transAxes)
    plt.grid(True)
    plt.legend()
    if xlim:
        plt.xlim(xlim)
    if ylim:
        plt.ylim(ylim)
    plt.tight_layout()
    plt.show()

def open_axis_gui():
    gui = tk.Toplevel()
    gui.title("軸範囲の設定")
    gui.geometry("500x200")

    tk.Label(gui, text="X軸 最小値:").grid(row=0, column=0)
    tk.Entry(gui, textvariable=x_min_var, width=30).grid(row=0, column=1)
    tk.Label(gui, text="X軸 最大値:").grid(row=1, column=0)
    tk.Entry(gui, textvariable=x_max_var, width=30).grid(row=1, column=1)
    tk.Label(gui, text="Y軸 最小値:").grid(row=2, column=0)
    tk.Entry(gui, textvariable=y_min_var, width=30).grid(row=2, column=1)
    tk.Label(gui, text="Y軸 最大値:").grid(row=3, column=0)
    tk.Entry(gui, textvariable=y_max_var, width=30).grid(row=3, column=1)

    def apply_and_redraw():
        try:
            current_xlim = plt.gca().get_xlim()
            current_ylim = plt.gca().get_ylim()
            xlim = (
                float(x_min_var.get()) if x_min_var.get() else current_xlim[0],
                float(x_max_var.get()) if x_max_var.get() else current_xlim[1]
            )
            ylim = (
                float(y_min_var.get()) if y_min_var.get() else current_ylim[0],
                float(y_max_var.get()) if y_max_var.get() else current_ylim[1]
            )
            redraw_plot(xlim, ylim)
        except ValueError:
            messagebox.showerror("エラー", "数値を正しく入力してください")

    redraw_button = tk.Button(gui, text="再描画", command=apply_and_redraw)
    redraw_button.grid(row=4, column=1, pady=10)
    gui.bind('<Return>', lambda event: apply_and_redraw())

open_axis_gui()
redraw_plot()

def on_click(event: MouseEvent):
    if event.inaxes:
        open_axis_gui()

fig = plt.figure()
cid = fig.canvas.mpl_connect('button_press_event', on_click)
plt.close(fig)


いいなと思ったら応援しよう!