手搓開源一個數據擬合曲線(需要配置環境)(求指點!)

lz是化工專業,經常要處理數據,於是用python和copilot搓了一個擬合曲線的程序出來因爲只是半吊子還希望有大佬能指點指點

使用方法:安裝matplotlib等環境,在該py文件根目錄創建txt文檔,內容格式參考如下

實驗標題: 

x軸名稱:

y軸名稱:

x軸數據:

y軸數據:

(支持繪製單一曲線和多條曲線)

以下是py文件代碼:

import numpy as np

from scipy.optimize import curve_fit

import matplotlib.pyplot as plt

from matplotlib.backend_bases import MouseEvent

from matplotlib import rcParams

import re  # 引入正則表達式模塊

 

# 定義鼠標點擊事件處理函數

def on_click(event: MouseEvent):

    if event.inaxes:  # 確保點擊在圖像區域內

        x_click = event.xdata  # 獲取點擊的 x 座標

        y_click = None

 

        try:

            # 使用最佳擬合模型的結果

            x_fit, y_fit, label = best_fit

            if min(x_fit) <= x_click <= max(x_fit):  # 確保 x_click 在擬合曲線範圍內

                y_click = np.interp(x_click, x_fit, y_fit)  # 插值計算 y 值

        except Exception as e:

            print(f"鼠標點擊查詢失敗: {e}")

            return

 

        if y_click is not None:

            print(f"點擊座標: x = {x_click:.4f}, y = {y_click:.4f}")

            # 在圖像上標記點擊點

            plt.scatter([x_click], [y_click], color='green', label=f'查詢點: ({x_click:.4f}, {y_click:.4f})')

            plt.legend()

            plt.draw()  # 更新圖像

        else:

            print("點擊的 x 座標不在最佳擬合曲線範圍內。")

 

# 輸入 x,輸出擬合曲線的 y 值

def query_y_from_x(x_input, fit_choice, x_data, y_data):

    try:

        x_fit, y_fit, label = perform_fitting(fit_choice, x_data, y_data)

        if min(x_fit) <= x_input <= max(x_fit):

            y_output = np.interp(x_input, x_fit, y_fit)  # 插值計算 y 值

            print(f"擬合模型: {label}")

            print(f"輸入 x = {x_input:.4f}, 輸出 y = {y_output:.4f}")

        else:

            print("輸入的 x 超出了擬合曲線的範圍。")

    except Exception as e:

        print(f"查詢失敗: {e}")

 

# 輸入 y,輸出擬合曲線的 x 值

def query_x_from_y(y_input, fit_choice, x_data, y_data):

    try:

        x_fit, y_fit, label = perform_fitting(fit_choice, x_data, y_data)

        # 查找 y_input 對應的 x 值

        if min(y_fit) <= y_input <= max(y_fit):

            x_output = np.interp(y_input, y_fit, x_fit)  # 插值計算 x 值

            print(f"擬合模型: {label}")

            print(f"輸入 y = {y_input:.4f}, 輸出 x = {x_output:.4f}")

        else:

            print("輸入的 y 超出了擬合曲線的範圍。")

    except Exception as e:

        print(f"查詢失敗: {e}")

 

# 設置支持中文的字體

rcParams['font.sans-serif'] = ['SimHei']

rcParams['axes.unicode_minus'] = False

 

# 讀取用戶指定的 txt 文件

file_name = input("請輸入數據文件的名稱(包含擴展名,如 data.txt):").strip()

 

# 從文件中讀取數據

with open(file_name, 'r', encoding='utf-8') as file:

    lines = file.readlines()

 

# 解析文件內容

plot_title = lines[0].split(':')[1].strip()  # 實驗標題

 

# 使用正則表達式提取 x 和 y 的標籤

x_label_match = re.search(r':(.+)', lines[1])

x_label = x_label_match.group(1).strip() if x_label_match else "未知 x 軸"

 

# 初始化存儲多組數據的列表

data_groups = []

current_group = {}

 

# 解析多組數據

for line in lines[2:]:

    line = line.strip()  # 去除行首和行尾的空格

    if not line:  # 跳過空行

        continue

    if line.startswith("y軸名稱"):

        if current_group:  # 如果當前組有數據,保存到 data_groups

            if "x_data" not in current_group or "y_data" not in current_group:

                raise ValueError("數據組缺少 x軸數據 或 y軸數據,請檢查文件格式。")

            data_groups.append(current_group)

        current_group = {"y_label": line.split(':')[1].strip()}

    elif line.startswith("x軸數據"):

        current_group["x_data"] = np.array(list(map(float, line.split(':')[1].strip().split())))

    elif line.startswith("y軸數據"):

        current_group["y_data"] = np.array(list(map(float, line.split(':')[1].strip().split())))

    else:

        print(f"警告:無法識別的行格式:{line},請檢查文件內容。")

 

# 保存最後一組數據

if current_group:

    if "x_data" not in current_group or "y_data" not in current_group:

        raise ValueError("數據組缺少 x軸數據 或 y軸數據,請檢查文件格式。")

    data_groups.append(current_group)

 

if not data_groups:

    raise ValueError("未找到有效的數據組,請檢查文件格式。")

 

# 如果只有一組數據,直接處理

if len(data_groups) == 1:

    group = data_groups[0]

    x_data = group["x_data"]

    y_data = group["y_data"]

    y_label = group["y_label"]

 

# 提供選擇方式選項

print("請選擇操作模式:")

print("1. 用戶選擇擬合方式")

print("2. 計算機自動選擇擬合方式")

mode_choice = input("請輸入選項編號(1 或 2):")

 

# 定義擬合模型

 

# 線性擬合

def linear_model(x, a, b):

    return a * x + b

 

# 二次擬合

def quadratic_model(x, a, b, c):

    return a * x**2 + b * x + c

 

# 指數擬合模型

def exponential_model(x, a, b):

    return a * np.exp(b * x)

 

# 對數擬合模型

def logarithmic_model(x, a, b):

    return a * np.log(b * x)

 

# 冪函數擬合模型

def power_model(x, a, b):

    return a * x**b

 

# 傅里葉級數擬合

def fourier_model(x, *params):

    n = len(params) // 2

    a = params[:n]

    b = params[n:]

    result = a[0] + sum(a[i] * np.cos((i + 1) * x) + b[i] * np.sin((i + 1) * x) for i in range(n - 1))

    return result

 

# 三角函數擬合模型(正弦形式)

def sine_model(x, a, b, c):

    return a * np.sin(b * x + c)

 

# 高階多項式擬合

def high_order_polynomial(x, *coeffs):

    return sum(c * x**i for i, c in enumerate(coeffs))

 

# 拉普拉斯平滑擬合(示例:簡單的平滑處理)

def laplace_smoothing(x, y, alpha=0.1):

    smoothed_y = np.zeros_like(y)

    smoothed_y[0] = y[0]

    for i in range(1, len(y)):

        smoothed_y[i] = alpha * y[i] + (1 - alpha) * smoothed_y[i - 1]

    return smoothed_y

 

# 計算 R^2(決定係數)

def calculate_r2(y_true, y_pred):

    ss_res = np.sum((y_true - y_pred) ** 2)

    ss_tot = np.sum((y_true - np.mean(y_true)) ** 2)

    return 1 - (ss_res / ss_tot)

 

# 封裝擬合邏輯爲模塊

def perform_fitting(fit_choice, x_data, y_data):

    """

    根據擬合選擇進行擬合,並返回擬合結果。

   

    參數:

        fit_choice (str): 用戶選擇的擬合方式

        x_data (array-like): 橫座標數據

        y_data (array-like): 縱座標數據

   

    返回:

        x_fit (array-like): 擬合曲線的橫座標

        y_fit (array-like): 擬合曲線的縱座標

        label (str): 擬合曲線的標籤

    """

    if fit_choice == "1":

        params, covariance = curve_fit(linear_model, x_data, y_data)

        a, b = params

        x_fit = np.linspace(min(x_data), max(x_data), 100)

        y_fit = linear_model(x_fit, a, b)

        label = f'線性擬合: y = {a:.4f}x + {b:.4f}'

    elif fit_choice == "2":

        params, covariance = curve_fit(quadratic_model, x_data, y_data)

        a, b, c = params

        x_fit = np.linspace(min(x_data), max(x_data), 100)

        y_fit = quadratic_model(x_fit, a, b, c)

        label = f'二次函數擬合: y = {a:.4f}x^2 + {b:.4f}x + {c:.4f}'

    elif fit_choice == "3":

        params, covariance = curve_fit(exponential_model, x_data, y_data)

        a, b = params

        x_fit = np.linspace(min(x_data), max(x_data), 100)

        y_fit = exponential_model(x_fit, a, b)

        label = f'指數擬合: y = {a:.4f} * exp({b:.4f} * x)'

    elif fit_choice == "4":  # 對數擬合

        params, covariance = curve_fit(logarithmic_model, x_data, y_data)

        a, b = params

        x_fit = np.linspace(min(x_data), max(x_data), 100)

        y_fit = logarithmic_model(x_fit, a, b)

        label = f'對數擬合: y = {a:.4f} * log({b:.4f} * x)'

    elif fit_choice == "5":  # 冪函數擬合

        params, covariance = curve_fit(power_model, x_data, y_data)

        a, b = params

        x_fit = np.linspace(min(x_data), max(x_data), 100)

        y_fit = power_model(x_fit, a, b)

        label = f'冪函數擬合: y = {a:.4f} * x^{b:.4f}'

    elif fit_choice == "6":

        params, covariance = curve_fit(fourier_model, x_data, y_data, p0=[1] * 6)

        x_fit = np.linspace(min(x_data), max(x_data), 100)

        y_fit = fourier_model(x_fit, *params)

        label = "傅里葉級數擬合"

    elif fit_choice == "7":  # 三角函數擬合

        params, covariance = curve_fit(sine_model, x_data, y_data, p0=[1, 1, 0])

        a, b, c = params

        x_fit = np.linspace(min(x_data), max(x_data), 100)

        y_fit = sine_model(x_fit, a, b, c)

        label = f'三角函數擬合: y = {a:.4f} * sin({b:.4f} * x + {c:.4f})'

    elif fit_choice == "8":

        # 動態選擇多項式階數

        degree = min(len(x_data) - 1, 5)  # 階數不能超過數據點數 - 1

        params = np.polyfit(x_data, y_data, deg=degree)

        poly = np.poly1d(params)

        x_fit = np.linspace(min(x_data), max(x_data), 100)

        y_fit = poly(x_fit)

        # 構造擬合函數的表達式

        terms = [f"{coef:.4f}x^{i}" if i > 0 else f"{coef:.4f}" 

                 for i, coef in enumerate(params[::-1])]

        equation = " + ".join(terms).replace("x^1", "x")

        label = f"高階多項式擬合: y = {equation}"

    elif fit_choice == "9":  # 拉普拉斯平滑擬合

        y_fit = laplace_smoothing(x_data, y_data)

        x_fit = x_data  # 平滑擬合不改變 x 數據

        label = '拉普拉斯平滑擬合'

    else:

        raise ValueError("該擬合方法尚未實現或不支持。")

   

    return x_fit, y_fit, label

 

# 用戶選擇擬合方式

if mode_choice == "1":

    print("請選擇擬合方式:")

    print("1. 線性擬合 (y = ax + b)")

    print("2. 二次擬合 (y = ax^2 + bx + c)")

    print("3. 指數擬合 (y = a * exp(b * x))")

    print("4. 對數擬合 (y = a * log(b * x))")

    print("5. 冪函數擬合 (y = a * x^b)")

    print("6. 傅里葉擬合")

    print("7. 三角函數擬合 (y = a * sin(b * x + c))")

    print("8. 高階多項式擬合")

    print("9. 拉普拉斯平滑擬合")

    fit_choice = input("請輸入擬合方式編號:")

   

    # 繪製多條擬合曲線

    plt.ion()  # 開啓交互模式

    fig, ax = plt.subplots()  # 創建繪圖窗口

 

    # 遍歷每組數據並繪製擬合曲線

    for i, group in enumerate(data_groups):

        x_data = group["x_data"]

        y_data = group["y_data"]

        y_label = group["y_label"]

 

        # 執行擬合

        x_fit, y_fit, label = perform_fitting(fit_choice, x_data, y_data)

 

        # 計算 R²

        r2 = calculate_r2(y_data, np.interp(x_data, x_fit, y_fit))

        print(f"數據組 {i + 1} - 擬合模型: {label},R^2 = {r2:.4f}")

 

        # 繪製數據點和擬合曲線

        ax.scatter(x_data, y_data, label=f'數據組 {i + 1}: {y_label}', alpha=0.7)

        ax.plot(x_fit, y_fit, label=f'擬合組 {i + 1}: {label} (R^2={r2:.4f})')

 

    # 設置圖例和標題

    ax.set_xlabel(x_label)

    ax.set_ylabel("y軸")

    ax.set_title(plot_title)

    ax.legend()

    ax.grid()

    plt.show(block=False)  # 非阻塞顯示圖像

 

    # 循環詢問用戶是否需要查詢

    while True:

        print("\n查詢選項:")

        print("1. 輸入 x 查詢擬合曲線的 y 值")

        print("2. 輸入 y 查詢擬合曲線的 x 值")

        print("3. 退出查詢")

        query_choice = input("請輸入選項編號(1, 2 或 3):")

   

        if query_choice in ["1", "2"]:

            # 顯示所有擬合曲線供用戶選擇

            print("\n可用擬合曲線:")

            for i, fit in enumerate(all_fits):

                print(f"{i + 1}. {fit['label']} (R^2={fit['r2']:.4f})")

            curve_choice = int(input("請選擇擬合曲線編號:")) - 1

 

            if 0 <= curve_choice < len(all_fits):

                selected_fit = all_fits[curve_choice]

                x_fit = selected_fit["x_fit"]

                y_fit = selected_fit["y_fit"]

                label = selected_fit["label"]

 

                if query_choice == "1":

                    x_input = float(input("請輸入 x 的值:"))

                    if min(x_fit) <= x_input <= max(x_fit):

                        y_output = np.interp(x_input, x_fit, y_fit)  # 插值計算 y 值

                        print(f"擬合模型: {label}")

                        print(f"輸入 x = {x_input:.4f}, 輸出 y = {y_output:.4f}")

                    else:

                        print("輸入的 x 超出了擬合曲線的範圍。")

                elif query_choice == "2":

                    y_input = float(input("請輸入 y 的值:"))

                    if min(y_fit) <= y_input <= max(y_fit):

                        x_output = np.interp(y_input, y_fit, x_fit)  # 插值計算 x 值

                        print(f"擬合模型: {label}")

                        print(f"輸入 y = {y_input:.4f}, 輸出 x = {x_output:.4f}")

                    else:

                        print("輸入的 y 超出了擬合曲線的範圍。")

            else:

                print("無效的曲線編號,請重新選擇。")

        elif query_choice == "3":

            print("退出查詢。")

            break

        else:

            print("無效的選項,請重新輸入。")

 

    plt.ioff()  # 關閉交互模式

    plt.close(fig)  # 關閉圖像窗口

 

# 自動選擇擬合方式

elif mode_choice == "2":

    models_with_dashed_lines = ["1", "2", "5"]  # 線性、二次、冪函數擬合

    best_r2 = -np.inf

    best_fit = None

    all_fits = []  # 用於存儲所有擬合曲線的結果

 

    # 詢問用戶是否繪製虛線

    draw_dashed_lines = input("是否繪製虛線表示其他擬合模型?(y/n):").strip().lower() == "y"

 

    plt.ion()  # 開啓交互模式

    fig, ax = plt.subplots()  # 創建繪圖窗口

    ax.scatter(x_data, y_data, label='數據', color='red')  # 繪製原始數據

 

    for fit_choice in models_with_dashed_lines + ["6", "7", "8", "9"]:

        try:

            x_fit, y_fit, label = perform_fitting(fit_choice, x_data, y_data)

            r2 = calculate_r2(y_data, np.interp(x_data, x_fit, y_fit))

 

            # 保存擬合結果

            all_fits.append({"fit_choice": fit_choice, "x_fit": x_fit, "y_fit": y_fit, "label": label, "r2": r2})

 

            # 更新最佳擬合模型

            if r2 > best_r2:

                best_r2 = r2

                best_fit = (x_fit, y_fit, label)

 

            # 根據用戶選擇決定是否繪製虛線

            if draw_dashed_lines and fit_choice in models_with_dashed_lines:

                ax.plot(x_fit, y_fit, linestyle='--', label=f'{label} (R^2={r2:.4f})', alpha=0.5)

        except Exception as e:

            print(f"擬合方式 {fit_choice} 出錯: {e}")

            continue

 

    if best_fit:

        x_fit, y_fit, label = best_fit

        print(f"最佳擬合模型: {label},R^2 = {best_r2:.4f}")

        ax.plot(x_fit, y_fit, label=f'{label} (R^2={best_r2:.4f})', color='blue', linewidth=2)

    else:

        print("未找到合適的擬合模型。")

 

    ax.set_xlabel(x_label)

    ax.set_ylabel(y_label)

    ax.set_title(plot_title)

    ax.legend()

    ax.grid()

 

    # 綁定鼠標點擊事件

    fig.canvas.mpl_connect('button_press_event', on_click)

 

    plt.show(block=False)  # 非阻塞顯示圖像

 

    # 循環詢問用戶是否需要查詢

    while True:

        print("\n查詢選項:")

        print("1. 輸入 x 查詢擬合曲線的 y 值")

        print("2. 輸入 y 查詢擬合曲線的 x 值")

        print("3. 退出查詢")

        query_choice = input("請輸入選項編號(1, 2 或 3):")

   

        if query_choice in ["1", "2"]:

            # 顯示所有擬合曲線供用戶選擇

            print("\n可用擬合曲線:")

            for i, fit in enumerate(all_fits):

                print(f"{i + 1}. {fit['label']} (R^2={fit['r2']:.4f})")

            curve_choice = int(input("請選擇擬合曲線編號:")) - 1

 

            if 0 <= curve_choice < len(all_fits):

                selected_fit = all_fits[curve_choice]

                x_fit = selected_fit["x_fit"]

                y_fit = selected_fit["y_fit"]

                label = selected_fit["label"]

 

                if query_choice == "1":

                    x_input = float(input("請輸入 x 的值:"))

                    if min(x_fit) <= x_input <= max(x_fit):

                        y_output = np.interp(x_input, x_fit, y_fit)  # 插值計算 y 值

                        print(f"擬合模型: {label}")

                        print(f"輸入 x = {x_input:.4f}, 輸出 y = {y_output:.4f}")

                    else:

                        print("輸入的 x 超出了擬合曲線的範圍。")

                elif query_choice == "2":

                    y_input = float(input("請輸入 y 的值:"))

                    if min(y_fit) <= y_input <= max(y_fit):

                        x_output = np.interp(y_input, y_fit, x_fit)  # 插值計算 x 值

                        print(f"擬合模型: {label}")

                        print(f"輸入 y = {y_input:.4f}, 輸出 x = {x_output:.4f}")

                    else:

                        print("輸入的 y 超出了擬合曲線的範圍。")

            else:

                print("無效的曲線編號,請重新選擇。")

        elif query_choice == "3":

            print("退出查詢。")

            break

        else:

            print("無效的選項,請重新輸入。")

 

    plt.ioff()  # 關閉交互模式

    plt.close(fig)  # 關閉圖像窗口

 

else:

    print("無效的選項編號,請重新運行程序並輸入 1 或 2。")

 

更多遊戲資訊請關註:電玩幫遊戲資訊專區

電玩幫圖文攻略 www.vgover.com