|
|
import sys
|
|
|
import numpy as np
|
|
|
import data as gl_data
|
|
|
import tkinter as tk
|
|
|
from tkinter import *
|
|
|
import tkinter.filedialog # 注意次数要将文件对话框导入
|
|
|
import re
|
|
|
import inspect
|
|
|
|
|
|
from X1 import *
|
|
|
from X2 import *
|
|
|
from X3 import *
|
|
|
|
|
|
|
|
|
import function
|
|
|
import input
|
|
|
|
|
|
global Q_root,F_root
|
|
|
global root_window
|
|
|
global label1,label2,label3
|
|
|
|
|
|
|
|
|
def window():
|
|
|
global root_window
|
|
|
global label1, label2, label3
|
|
|
root_window = Tk()
|
|
|
root_window.title('函数拟合')
|
|
|
root_window.geometry('900x600') # 设置窗口大小:宽x高,注,此处不能为 "*",必须使用 "x"
|
|
|
# 设置主窗口的背景颜色,颜色值可以是英文单词,或者颜色值的16进制数,除此之外还可以使用Tk内置的颜色常量
|
|
|
root_window["background"] = "white"
|
|
|
root_window.resizable(0, 0) # 防止用户调整尺寸
|
|
|
label1 = tk.Label(root_window, text="样本数据\n集文件", font=('Times', 8), bg="white",
|
|
|
width=13, height=3, # 设置标签内容区大小
|
|
|
padx=0, pady=0, borderwidth=0, )
|
|
|
label1.place(x=10, y=4) # 设置填充区距离、边框宽度和其样式(凹陷式)
|
|
|
label3 = tk.Label(root_window, text="", font=('Times', 8), bg="white", fg="black",
|
|
|
width=39, height=2, padx=0, pady=0, borderwidth=0, relief="ridge", highlightcolor="blue")
|
|
|
label3.place(x=122, y=10)
|
|
|
# 使用按钮控件调用函数
|
|
|
tk.Button(root_window, text="装载", relief=RAISED, command=lambda: askfile(label3)).place(x=370, y=12)
|
|
|
label2 = tk.Label(root_window, text="拟合曲线类型", font=('Times', 12), bg="white",
|
|
|
width=20, height=3, # 设置标签内容区大小
|
|
|
padx=0, pady=0, borderwidth=0, )
|
|
|
# 设置填充区距离、边框宽度和其样式(凹陷式)
|
|
|
label2.place(x=450, y=4)
|
|
|
gl_data.Canvas2 = tk.Canvas(root_window, bg='white', width=450, height=330)
|
|
|
gl_data.Canvas2.place(x=4, y=60)
|
|
|
|
|
|
|
|
|
# 定义一个处理文件的相关函数
|
|
|
def askfile(label3):
|
|
|
# 从本地选择一个文件,并返回文件的路径
|
|
|
filename = tkinter.filedialog.askopenfilename()
|
|
|
if filename != '':#若选中了一个文件,则对文件进行读取
|
|
|
label3.config(text=filename)#显示文件路径
|
|
|
read_sample_data(filename)#将文件读取并存到sampleData中
|
|
|
# print_sample_data(filename)#将文件读取并逐行输出
|
|
|
selfdata_show(gl_data.SampleData, gl_data.LOW, gl_data.HIGH)
|
|
|
else:#若未选择文件,则显示为空
|
|
|
label3.config(text='')
|
|
|
|
|
|
def print_sample_data(file_path):#打开对应文件
|
|
|
with open(file_path, 'r') as file:
|
|
|
for line in file:#逐行读取文件
|
|
|
line = line.strip('\n')#移除换行符
|
|
|
sx, sy = line.split(' ')#以空格分割x,y
|
|
|
print(f'sx: {float(sx)}, sy: {float(sy)}')#将sx、sy转换为浮点数并打印
|
|
|
|
|
|
|
|
|
def read_sample_data(file_path):
|
|
|
x, y = [], []#初始化x,y
|
|
|
with open(file_path, 'r') as file:
|
|
|
for line in file:#逐行读文件
|
|
|
line = line.strip('\n')#移除换行符
|
|
|
sx, sy = line.split(' ')#以空格分割x,y
|
|
|
x.append(float(sx))#将sx转换为浮点数并加入数组
|
|
|
y.append(float(sy))#将sy转换为浮点数并加入数组
|
|
|
gl_data.SampleData = np.array(list(zip(x, y)))#x,y数组转为array并赋值给全局变量
|
|
|
# x=np.array(x) # 列表转数组
|
|
|
# y=np.array(y)
|
|
|
# gl_data.SampleData = np.array(list(zip(x, y)))
|
|
|
|
|
|
|
|
|
# #################################拟合优度R^2的计算######################################
|
|
|
def __sst(y_no_fitting):
|
|
|
"""
|
|
|
计算SST(total sum of squares) 总平方和
|
|
|
:param y_no_predicted: List[int] or array[int] 待拟合的y
|
|
|
:return: 总平方和SST
|
|
|
"""
|
|
|
y_mean = sum(y_no_fitting) / len(y_no_fitting)
|
|
|
s_list =[(y - y_mean)**2 for y in y_no_fitting]
|
|
|
sst = sum(s_list)
|
|
|
return sst
|
|
|
|
|
|
|
|
|
def __ssr(y_fitting, y_no_fitting):
|
|
|
"""
|
|
|
计算SSR(regression sum of squares) 回归平方和
|
|
|
:param y_fitting: List[int] or array[int] 拟合好的y值
|
|
|
:param y_no_fitting: List[int] or array[int] 待拟合y值
|
|
|
:return: 回归平方和SSR
|
|
|
"""
|
|
|
y_mean = sum(y_no_fitting) / len(y_no_fitting)
|
|
|
s_list =[(y - y_mean)**2 for y in y_fitting]
|
|
|
ssr = sum(s_list)
|
|
|
return ssr
|
|
|
|
|
|
|
|
|
def __sse(y_fitting, y_no_fitting):
|
|
|
"""
|
|
|
计算SSE(error sum of squares) 残差平方和
|
|
|
:param y_fitting: List[int] or array[int] 拟合好的y值
|
|
|
:param y_no_fitting: List[int] or array[int] 待拟合y值
|
|
|
:return: 残差平方和SSE
|
|
|
"""
|
|
|
s_list = [(y_fitting[i] - y_no_fitting[i])**2 for i in range(len(y_fitting))]
|
|
|
sse = sum(s_list)
|
|
|
return sse
|
|
|
|
|
|
|
|
|
def goodness_of_fit(y_fitting, y_no_fitting):
|
|
|
"""
|
|
|
计算拟合优度R^2
|
|
|
:param y_fitting: List[int] or array[int] 拟合好的y值
|
|
|
:param y_no_fitting: List[int] or array[int] 待拟合y值
|
|
|
:return: 拟合优度R^2
|
|
|
"""
|
|
|
sse = __sse(y_fitting, y_no_fitting)
|
|
|
sst = __sst(y_no_fitting)
|
|
|
rr = 1 - sse /sst
|
|
|
return rr
|
|
|
|
|
|
|
|
|
# 简化版本的goodness_of_fit
|
|
|
def goodness_of_fit_easy(y_fitting, y_no_fitting):
|
|
|
"""
|
|
|
计算拟合优度R^2
|
|
|
:param y_fitting: List[int] or array[int] 拟合好的y值
|
|
|
:param y_no_fitting: List[int] or array[int] 待拟合y值
|
|
|
:return: 拟合优度R^2
|
|
|
"""
|
|
|
y_mean = sum(y_no_fitting) / len(y_no_fitting)
|
|
|
# 计算SST
|
|
|
sst = sum((y - y_mean) ** 2 for y in y_no_fitting)
|
|
|
# 计算SSE
|
|
|
sse = sum((y_fitting[i] - y_no_fitting[i]) ** 2 for i in range(len(y_fitting)))
|
|
|
# 计算R^2
|
|
|
r_squared = 1 - sse / sst
|
|
|
return r_squared
|
|
|
|
|
|
|
|
|
# 由于之前的函数修改了,这里需要对四个按钮的函数进行编程
|
|
|
def BF_generate_plot_dada(low, high):
|
|
|
num_samples = 25 # 设置样本点数量
|
|
|
sampleData = random_points(num_samples, low, high) # 生成随机样本数据25个
|
|
|
generate_and_plot_sample_data(sampleData, low, high)
|
|
|
|
|
|
|
|
|
def BF_plot_data(sampleData,low,high):
|
|
|
selfdata_show(sampleData,low,high)
|
|
|
|
|
|
|
|
|
def BF_plot_data_and_curve(curveData, sampleData,low,high):
|
|
|
# curveData = compute_curveData2(low,high,1,theta,m, x.mean(), x.std())
|
|
|
draw_dots_and_line(curveData,sampleData,low,high)
|
|
|
|
|
|
|
|
|
# # X4版本
|
|
|
def BF_fit_X4(xian_index, sampleData):
|
|
|
# gl_data.yvals_pow = []
|
|
|
|
|
|
print(xian_index)
|
|
|
|
|
|
cur = function.Fun[xian_index] # 装载正在选择的函数
|
|
|
func = cur.get_fun()#获取当前函数func
|
|
|
|
|
|
sx = sampleData[:,0]
|
|
|
sy = sampleData[:,1]
|
|
|
|
|
|
popt, pcov = curve_fit(func, sx, sy)# 用curve_fit来对点进行拟合
|
|
|
yvals_pow = func(sx, *popt)
|
|
|
|
|
|
# 这里直接套用会没有CurveData
|
|
|
x_values = np.arange(gl_data.LOW, gl_data.HIGH, 1)
|
|
|
y_values = func(x_values, *popt)
|
|
|
gl_data.CurveData = np.column_stack((x_values, y_values))
|
|
|
|
|
|
rr = goodness_of_fit(yvals_pow, sy)# 计算本次拟合的R*R值,用于表示拟合优度
|
|
|
|
|
|
# 输出拟合后的各个参数值
|
|
|
ans = '\n函数系数:F(x) = '
|
|
|
for i in range(cur.variable):
|
|
|
if i == 4:
|
|
|
ans += '\n'
|
|
|
if i != 0:
|
|
|
ans += ', '
|
|
|
ans += chr(ord('a') + i) + '=' + '{:.2e}'.format(popt[i]) # str(round(gl_data.popt[i], 3))
|
|
|
gl_data.Out = '函数形式:' + cur.name + ' '
|
|
|
gl_data.Out += cur.demo
|
|
|
gl_data.Out += ans
|
|
|
gl_data.Out += '\n拟合优度(R\u00b2):' + str(round(rr, 5))
|
|
|
show_fit_X4()# 显示函数信息到输出框
|
|
|
|
|
|
# # X4版本
|
|
|
# 这里是梯度下降法更新后的新的按钮函数
|
|
|
def BF_fit_X4_testnew(xian_index, sampleData):
|
|
|
# # print(xian_index)
|
|
|
#
|
|
|
# cur = function.Fun[xian_index] # 装载正在选择的函数
|
|
|
# func = cur.get_fun()#获取当前函数func
|
|
|
#
|
|
|
# sx = sampleData[:,0]
|
|
|
# sy = sampleData[:,1]
|
|
|
#
|
|
|
# popt, pcov = gradient_descent_method_X5(func, sx, sy)# 用curve_fit来对点进行拟合
|
|
|
#
|
|
|
# x_normalized = (sx - np.mean(sx)) / np.std(sx)
|
|
|
# yvals_pow = func(x_normalized, *popt)
|
|
|
#
|
|
|
# # print("yvals_pow", yvals_pow)
|
|
|
# # print("sy", sy)
|
|
|
#
|
|
|
# # 这里直接套用会没有CurveData
|
|
|
# x_values = np.arange(gl_data.LOW, gl_data.HIGH, 1)
|
|
|
# x_values_normalized = (x_values - np.mean(sx)) / np.std(sx)
|
|
|
# y_values = func(x_values_normalized, *popt)
|
|
|
# gl_data.CurveData = np.column_stack((x_values, y_values))
|
|
|
#
|
|
|
# rr = goodness_of_fit(yvals_pow, sy)# 计算本次拟合的R*R值,用于表示拟合优度
|
|
|
# # 输出拟合后的各个参数值
|
|
|
# ans = '\n函数系数:F(x) = '
|
|
|
# for i in range(cur.variable):
|
|
|
# if i == 4:
|
|
|
# ans += '\n'
|
|
|
# if i != 0:
|
|
|
# ans += ', '
|
|
|
# ans += chr(ord('a') + i) + '=' + '{:.2e}'.format(popt[i]) # str(round(gl_data.popt[i], 3))
|
|
|
# gl_data.Out = '函数形式:' + cur.name + ' '
|
|
|
# gl_data.Out += cur.demo
|
|
|
# gl_data.Out += ans
|
|
|
# gl_data.Out += '\n拟合优度(R\u00b2):' + str(round(rr, 5))
|
|
|
|
|
|
fit_X4(xian_index, sampleData)
|
|
|
show_fit_X4()# 显示函数信息到输出框
|
|
|
|
|
|
|
|
|
# 编程4.18 -----------------------------------------
|
|
|
def fitting(letters,result):
|
|
|
code_str = '''
|
|
|
def func({}):
|
|
|
return {}
|
|
|
'''.format(letters,result)
|
|
|
print("code_str",code_str)
|
|
|
return code_str
|
|
|
|
|
|
|
|
|
def create_func(code_str):
|
|
|
# 创建一个空的命名空间
|
|
|
namespace = {}
|
|
|
# 使用exec函数执行字符串代码,并指定命名空间为locals
|
|
|
exec(code_str, globals(), namespace)
|
|
|
# 返回命名空间中的函数对象
|
|
|
return namespace['func']
|
|
|
|
|
|
# 这里由于拓展的各种函数,因此使用的curve_fit
|
|
|
def BF_fit(root_window, screen_size, xian_index, sampleData):
|
|
|
fit_XX(xian_index, sampleData)
|
|
|
show_fit(root_window, screen_size) # 显示函数信息到输出框,包括上述信息
|
|
|
|
|
|
# 这里是因为自己的梯度下降法可以使用了,所以整一个新的,同时按照X4中的工作,将BF_Fit修改为简单的函数调用形式,而不是一大片
|
|
|
def BF_fit_X5_new(root_window, screen_size, xian_index, sampleData):
|
|
|
fit_X5(xian_index, sampleData)
|
|
|
show_fit(root_window, screen_size) # 显示函数信息到输出框,包括上述信息
|
|
|
|
|
|
# 编程4.18 END-----------------------------------------
|
|
|
|
|
|
|
|
|
def buttons():
|
|
|
# 随机生成样本点并显示
|
|
|
b_dot = tk.Button(root_window, text="生成\n数据集", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
|
|
|
command=lambda: BF_generate_plot_dada(gl_data.LOW, gl_data.HIGH))
|
|
|
b_dot.place(x=455, y=410)
|
|
|
# 显示当前样本点
|
|
|
b_show = tk.Button(root_window, text="显示\n数据集", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
|
|
|
command=lambda: BF_plot_data(gl_data.SampleData, gl_data.LOW, gl_data.HIGH))
|
|
|
b_show.place(x=510, y=410)
|
|
|
# 显示数据集与曲线
|
|
|
b_line = tk.Button(root_window, text="显示数据\n集与曲线", relief=RAISED, bd=4, bg="white", font=("微软雅黑", 10),
|
|
|
command=lambda: BF_plot_data_and_curve(gl_data.CurveData, gl_data.SampleData, gl_data.LOW, gl_data.HIGH))
|
|
|
b_line.place(x=565, y=410)
|
|
|
# 手动输入数据集
|
|
|
b_input = tk.Button(root_window, text="手动输入数据集", relief=RAISED, bd=4, bg="white", pady=7, font=("微软雅黑", 13),
|
|
|
command=lambda: input.input_num(root_window))
|
|
|
b_input.place(x=633, y=410)
|
|
|
# 拟合并输出拟合结果
|
|
|
b_fit = tk.Button(root_window, text="拟合", relief=RAISED, bd=4, bg="white", pady=7, font=("微软雅黑", 13),
|
|
|
command=lambda: BF_fit_X4_testnew(gl_data.Xian_index, gl_data.SampleData))
|
|
|
b_fit.place(x=771, y=410)
|
|
|
|
|
|
|
|
|
# 这里是X4版本的代码,X5修改了界面
|
|
|
def show_fit_X4():
|
|
|
L = tk.Label(root_window, text='结果输出:', bg='white', font=("微软雅黑", 16)
|
|
|
, anchor=W)
|
|
|
L.place(x=20, y=480, width=100, height=30)
|
|
|
ans = tk.Label(root_window, text=gl_data.Out, font=("微软雅黑", 14)
|
|
|
, anchor=W, justify='left')
|
|
|
ans.place(x=120, y=480, width=760, height=100)
|
|
|
print(gl_data.Out)
|
|
|
|
|
|
|
|
|
def show_fit(root_window, screen_size):
|
|
|
sout = str(gl_data.Out)
|
|
|
ans = tk.Label(root_window, text=sout, font=("微软雅黑", 14)
|
|
|
, anchor=W, justify='left')
|
|
|
ans.place(x=int(120*screen_size), y=int(480*screen_size), width=int(760*screen_size), height=100)
|
|
|
print(sout)
|
|
|
|
|
|
|
|
|
# 拟合按钮所对应的函数
|
|
|
def fit_data(xian_index, sampleData):
|
|
|
# gl_data.yvals_pow = []
|
|
|
cur = function.Fun[xian_index] # 装载正在选择的函数
|
|
|
func = cur.get_fun()#获取当前函数func
|
|
|
|
|
|
sx, sy = sampleData[:, 0], sampleData[:, 1]
|
|
|
|
|
|
# 使用 inspect.signature 获取函数的签名信息
|
|
|
sig = inspect.signature(func)
|
|
|
# 获取所有参数的名称
|
|
|
params = sig.parameters
|
|
|
print(params)
|
|
|
|
|
|
popt, pcov = curve_fit(func, sx, sy)# 用curve_fit来对点进行拟合
|
|
|
yvals_pow = func(sx, *popt)
|
|
|
|
|
|
rr = goodness_of_fit(yvals_pow, sy)# 计算本次拟合的R*R值,用于表示拟合优度
|
|
|
# 输出拟合后的各个参数值
|
|
|
ans = '\n函数系数:'
|
|
|
for i in range(cur.variable):
|
|
|
if i == 4:
|
|
|
ans += '\n'
|
|
|
if i != 0:
|
|
|
ans += ', '
|
|
|
ans += chr(ord('a') + i) + '=' + '{:.2e}'.format(popt[i]) # str(round(gl_data.popt[i], 3))
|
|
|
gl_data.Out = '函数形式:' + cur.name + ' '
|
|
|
gl_data.Out += cur.demo
|
|
|
gl_data.Out += ans
|
|
|
gl_data.Out += '\n拟合优度(R\u00b2):' + str(round(rr, 5))
|
|
|
show_fit_X4() # 显示函数信息到输出框
|
|
|
|
|
|
|
|
|
def change_Q_X4(no):
|
|
|
gl_data.Quadrant = no #更改全局变量的象限显示
|
|
|
if no:#若为一象限,则修改显示下限为0
|
|
|
gl_data.LOW = 0
|
|
|
else:#若为四象限,则修改显示下限为-gl_data.maxV
|
|
|
gl_data.LOW = -gl_data.MAXV
|
|
|
q_button_X4()#更新象限显示面板
|
|
|
|
|
|
|
|
|
def q_button_X4():
|
|
|
r = 7.5
|
|
|
rr = 2.5
|
|
|
for widget in Q_root.winfo_children():
|
|
|
widget.destroy()
|
|
|
q_cv = tk.Canvas(Q_root, width=400, height=50)
|
|
|
q_cv.place(x=0, y=0)
|
|
|
l = tk.Label(Q_root, text='坐标轴', bd=0, font=("微软雅黑", 16)
|
|
|
, anchor=W)
|
|
|
l.place(x=20, y=0, width=80, height=50,)
|
|
|
# 四象限按钮
|
|
|
b1 = tk.Button(Q_root, text='四象限', bd=0, font=("微软雅黑", 16)
|
|
|
, command=lambda: change_Q(0), anchor=W)
|
|
|
b1.place(x=170, y=0, width=80, height=50,)
|
|
|
# 一象限按钮
|
|
|
b2 = tk.Button(Q_root, text='一象限', bd=0, font=("微软雅黑", 16)
|
|
|
, command=lambda: change_Q(1), anchor=W)
|
|
|
b2.place(x=320, y=0, width=80, height=50,)
|
|
|
# 绘制标记框
|
|
|
q_cv.create_oval(140 - r, 25 - r, 140 + r, 25 + r
|
|
|
, fill="white", width=1, outline="black")
|
|
|
q_cv.create_oval(290 - r, 25 - r, 290 + r, 25 + r
|
|
|
, fill="white", width=1, outline="black")
|
|
|
# 根据当前的象限选择值来填充标记框
|
|
|
if gl_data.Quadrant == 0:
|
|
|
q_cv.create_oval(140 - rr, 25 - rr, 140 + rr, 25 + rr
|
|
|
, fill="black", width=1, outline="black")
|
|
|
else:
|
|
|
q_cv.create_oval(290 - rr, 25 - rr, 290 + rr, 25 + rr
|
|
|
, fill="black", width=1, outline="black")
|
|
|
|
|
|
|
|
|
def change_f_X4(no):
|
|
|
gl_data.Xian_index = no#设置全局函数编号为该函数
|
|
|
f_button_X4()#重新绘制函数显示界面
|
|
|
|
|
|
|
|
|
def f_button_X4():
|
|
|
r = 7.5#设置按钮大小
|
|
|
rr = 2.5#设置按钮大小
|
|
|
for widget in F_root.winfo_children():
|
|
|
widget.destroy()#清空原有按钮
|
|
|
f_cv = tk.Canvas(F_root, width=400, height=330)#新建画布
|
|
|
f_cv.place(x=0, y=0)#放置画布
|
|
|
f_cv.create_rectangle(2, 2, 398, 328, fill='white', outline="#0099ff")#设置画布边框及底色
|
|
|
cur = 0#函数计数
|
|
|
|
|
|
for fun in function.Fit_type_library:#遍历函数库
|
|
|
f = function.Fit_type_library.get(fun)#获取函数具体信息
|
|
|
if function.Show[f[0]] == 1:# 如果show为1则显示
|
|
|
f_cv.create_oval(20 - r, 20 + 15 + cur * 30 - r, 20 + r, 20 + 15 + cur * 30 + r
|
|
|
, fill="white", width=1, outline="black")# 绘制标记框
|
|
|
if f[0] == gl_data.Xian_index:# 若选择为当前函数则标记
|
|
|
f_cv.create_oval(20 - rr, 20 + 15 + cur * 30 - rr, 20 + rr, 20 + 15 + cur * 30 + rr
|
|
|
, fill="black", width=1, outline="black")
|
|
|
# 绘制切换按钮,单击则将当前使用函数切换为该函数
|
|
|
button = tk.Button(F_root, text=f[2] + ' ' + f[1], bd=0, bg="white", font=("微软雅黑", 12)
|
|
|
, command=lambda x=f[0]: change_f_X4(x), anchor=W)
|
|
|
button.place(x=40, y=20 + cur * 30, width=300, height=30)
|
|
|
cur += 1#计数加一
|
|
|
|
|
|
|
|
|
def close_window():
|
|
|
sys.exit()
|
|
|
|
|
|
|
|
|
def main():
|
|
|
global Q_root, F_root, root_window
|
|
|
gl_data.X = []
|
|
|
gl_data.X = []
|
|
|
window()
|
|
|
function.f_read()
|
|
|
# 函数选择相关界面
|
|
|
F_root = tk.Frame(root_window, width=400, height=330, bg='white', )
|
|
|
F_root.place(x=490, y=60)
|
|
|
# 象限选择相关界面
|
|
|
Q_root = tk.Frame(root_window, width=400, height=50, bg='white', )
|
|
|
Q_root.place(x=20, y=410)
|
|
|
buttons()
|
|
|
f_button_X4()
|
|
|
q_button_X4()
|
|
|
show_fit_X4()
|
|
|
root_window.protocol("WM_DELETE_WINDOW", close_window)
|
|
|
root_window.mainloop()
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
main()
|
|
|
|
|
|
# X4除了没有使用自己定义的方法以外基本差不多了
|
|
|
|
|
|
|