You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

478 lines
19 KiB

5 months ago
import mpl_toolkits.axisartist as axisartist
from scipy.optimize import curve_fit
from pandas import DataFrame
import matplotlib.pyplot as plt
import numpy as np
import pylab as mpl
from tkinter import *
import pandas as pd
import tkinter
import sys
import tkinter as tk
from tkinter import filedialog
import re
import function
import data as gl_data
from X1 import *
from X2 import *
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 解决matplotlib中文不显示问题
plt.rcParams['axes.unicode_minus'] = False # 解决matplotlib负数坐标显示问题
# 创建一个二维数组sampleData用于存储样本数据
sampleData = None
global Q_root,F_root
global root_window
global label1,label2,label3
# 编程4.6 -----------------------------------------
# 定义一个处理文件的相关函数
def askfile():
# 选择需要加载的数据文件,并返回文件的路径
filename = tkinter.filedialog.askopenfilename()
if filename != '': # 若选中了一个文件,则对文件进行读取
label3.config(text=filename) # 显示文件路径,其中label3为对应的按钮
read_sample_data(filename) # 将文件读取并存到sampleData中
# print_sample_data(filename)# 将文件读取并逐行输出
selfdata_show(gl_data.SampleData[:,0], gl_data.SampleData[:,1], 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(' ') # 以空格分割xy
print(f'sx: {float(sx)}, sy: {float(sy)}') # 将sx、sy转换为浮点数并打印
def read_sample_data(file_path):
x, y = [], [] # 初始化xy
with open(file_path, 'r') as file:
for line in file: # 逐行读文件
line = line.strip('\n') # 移除换行符
sx, sy = line.split(' ') # 以空格分割xy
x.append(float(sx)) # 将sx转换为浮点数并加入数组
y.append(float(sy)) # 将sy转换为浮点数并加入数组
# xy数组转为array并赋值给全局变量sampleData
x = np.array(x) # 列表转数组
y = np.array(y)
gl_data.SampleData = np.array(list(zip(x, y)))
# 编程4.6 END-----------------------------------------
# 编程4.7 -----------------------------------------
def generate_and_plot_sample_data(sampleData, Low, High):
sx,sy = sampleData[:, 0],sampleData[:, 1]
draw_axis(Low, High) # 绘制坐标轴
plt.scatter(sx, sy, color='red') # 绘制样本数据点
plt.savefig(r"dot.png", facecolor='w', bbox_inches='tight') # 保存到本地
plt.close() # 清除内存
set_phtot(1) # 显示到主界面 4.7示例时没有编程该函数
# if __name__ == '__main__':
# num_samples = 25 #设置样本点数量
# sampleData = random_points(num_samples, -1000, 1000) # 生成随机样本数据25个
# generate_and_plot_sample_data(sampleData, gl_data.LOW, gl_data.HIGH)
# 编程4.7 END-----------------------------------------
# 编程4.8 -----------------------------------------
def draw_dots_and_line(curveData, sampleData, low, high):
x_fit, y_fit = curveData[:, 0], curveData[:, 1]
x, y = sampleData[:, 0], sampleData[:, 1]
draw_axis(low, high) # 画xy坐标轴
positive_mask = y >= 0.0 # 给样本点分类
negative_mask = y < 0.0 # 给样本点分类
positive_colors = ['red' if xx >= 0.0 else 'blue' for xx in x] # 给样本点分类
negative_colors = ['green' if xx >= 0.0 else 'purple' for xx in x] # 给样本点分类
# 创建图形对象并绘制拟合曲线和样本点
ax = draw_axis(low,high,step=250)
# 根据样本点类型决定样本点颜色
ax.scatter(x[positive_mask], y[positive_mask], color=np.array(positive_colors)[positive_mask], lw=1)
ax.scatter(x[negative_mask], y[negative_mask], color=np.array(negative_colors)[negative_mask], lw=1)
plt.plot(x_fit, y_fit, color='blue', label='Fitted Curve')# 绘制拟合曲线
# plt.savefig(r"dot5.png", facecolor='w') # 保存到本地
# plt.legend()
# plt.show()
# X5修改后展示到主界面中
plt.legend(loc='center left', bbox_to_anchor=(1, 0.9), fontsize=9)
plt.savefig(r"line.png", facecolor='w', bbox_inches='tight')# 将图片保存到本地
plt.close()# 清除内存
set_phtot(2)# 将图片显示到程序中
# if __name__ == '__main__':
# gl_data.SampleData = random_points(25,-1000, 1000)
# # print("gl_data.SampleData",gl_data.SampleData)
# draw_dots_and_line(gl_data.LOW, gl_data.HIGH, gl_data.SampleData)
# if __name__ == '__main__':
# LimitNum = 1000
# # sampleData = random_points(20,0, 1000)
# # x, y = sampleData[:, 0], sampleData[:, 1]
# # 这里为了便于观察使用设计好的数据
# x = np.array([0, 100, 200, 300, 400, 500, 600, 700, 800, 900])
# y = np.array([10, 20, 10, 50, 80, 130, 210, 340, 550, 890])
# sampleData = np.array(list(zip(x, y))) # 将两个一维数组拼接成二维数组
# m = 3 # 使用三次函数拟合
# theta, covariance_matrix = least_square_method(m, sampleData)
# curveData = compute_curveData2(0,1000,1,theta,m, x.mean(), x.std())
# draw_dots_and_line(curveData,sampleData,0,1000)
# 编程4.8 END-----------------------------------------
# 编程4.9 -----------------------------------------
def input_num(root_tk):
global top
top = tk.Toplevel(root_tk)
top.geometry("300x50")
top.title('坐标点个数')
label1 = Label(top, text="坐标点个数")
label1.grid(row=0) # 这里的side可以赋值为LEFT RTGHT TOP BOTTOM
num1 = IntVar()
entry1 = Entry(top, textvariable=num1)
num1.set(0)
entry1.grid(row=0, column=1)
Label(top, text=" ").grid(row=0, column=3)
Button(top, text="确定", command=lambda: input_data(root_tk, int(entry1.get()))).grid(row=0, column=3)
top.mainloop()
def add_sample_data():
global sample_x, sample_y, sample_data
global entry_x,entry_y,label_status, numx
try:
x = float(entry_x.get())
y = float(entry_y.get())
except:
label_status.config(text="输入不合法")
return
entry_x.delete(0, tk.END)
entry_y.delete(0, tk.END)
if min(x, y) < gl_data.LOW or max(x, y) > gl_data.HIGH:
label_status.config(text="输入超过范围")
return
elif len(sample_data) < numx:
label_status.config(text="点对已添加")
sample_data.append((x, y))
sample_x.append(x)
sample_y.append(y)
else:
label_status.config(text="已达到最大数量")
def check_sample_data():
global label_status,numx,sample_x,sample_y,sample_data
if len(sample_data) == numx:
label_status.config(text="已达到最大数量")
gl_data.X = np.array(sample_x)
gl_data.Y = np.array(sample_y)
print('已添加', sample_data)
sys.exit()
else:
label_status.config(text="还需输入{}个点对".format(numx - len(sample_data)))
print(sample_data)
def input_data(root_tk, num):
global top
global sample_x,sample_y,sample_data
global numx
numx = num
sample_x = []
sample_y = []
sample_data = []
top.destroy()
top = tk.Toplevel(root_tk)
top.geometry("300x200")
top.title('坐标')
global entry_x, entry_y, label_status
label_x = tk.Label(top, text="X 值:")
label_x.pack()
entry_x = tk.Entry(top)
entry_x.pack()
label_y = tk.Label(top, text="Y 值:")
label_y.pack()
entry_y = tk.Entry(top)
entry_y.pack()
button_add = tk.Button(top, text="添加", command=add_sample_data)
button_add.pack()
button_check = tk.Button(top, text="检查", command=check_sample_data)
button_check.pack()
label_status = tk.Label(top, text="")
label_status.pack()
top.mainloop()
# 编程4.9 END-----------------------------------------
# 编程4.10 -----------------------------------------
def error_func(func, sampleData, theta): # 定义误差函数
sx, sy = sampleData[:, 0], sampleData[:, 1]
y_pred = func(theta, sx) # 使用func函数和参数theta计算预测值
error = np.sum((sy - y_pred) ** 2) # 计算误差平方和
ss_tot = np.sum((sy - np.mean(sy)) ** 2) # 计算总平方和
r_squared = 1 - (error / ss_tot) # 计算决定系数(R^2)
return error, r_squared
# 这里是处理了标准化的error_func
def error_func2(func, sampleData, theta): # 定义误差函数
sx, sy = sampleData[:, 0], sampleData[:, 1]
x_normalized = (sx - sx.mean()) / sx.std()
# 使用func函数和参数theta计算预测值
y_pred = func(theta, x_normalized)
# 计算误差平方和
error = np.sum((sy - y_pred) ** 2)
# 计算总平方和
ss_tot = np.sum((sy - np.mean(sy)) ** 2)
# 计算决定系数(R^2)
r_squared = 1 - (error / ss_tot)
return error, r_squared
# fit函数中修改使用方法为梯度下降法并输出拟合结果
def fit(sampleData, m):
theta, covariance_matrix = least_square_method(m, sampleData) # 计算拟合结果
# 计算拟合误差
if m == 1: func = linear_function
if m == 2: func = quadratic_function # 此处以m=2为例省略其他函数
if m == 3: func = qubic_function
if m == 4: func = quartic_function
error,r_squared = error_func2(func, sampleData, theta)
fit_function_name = func.__name__ # 打印拟合函数的形式、系数、误差和优度
print("拟合函数形式:{}".format(fit_function_name))
print("标准化拟合系数:{}".format(theta))
print("误差:{:.4f}".format(error))
print("拟合优度R^2{:.4f}".format(r_squared))
return theta, covariance_matrix
# if __name__ == '__main__':
# # sampleData = random_points(20,0, 1000)
# # x = sampleData[:,0]
# # y = sampleData[:,1]
# # 这里为了便于观察使用设计好的数据
# x = np.array([0, 100, 200, 300, 400, 500, 600, 700, 800, 900])
# y = np.array([10, 20, 10, 50, 80, 130, 210, 340, 550, 890])
# sampleData = np.array(list(zip(x, y))) # 将两个一维数组拼接成二维数组
# m = 2 # 假设选择使用二次函数拟合
# theta, covariance_matrix = fit(sampleData, m)
# ------fit_X4前提函数-------
# 为了适应扩展的函数,需要根据用户输入的函数类型构造函数
def fitting(letters,result):
code_str = '''
def func({}):
return {}
'''.format(letters,result)
return code_str
# 构造用户自定义的函数
def create_func(code_str):
# 创建一个空的命名空间
namespace = {}
# 使用exec函数执行字符串代码并指定命名空间为locals
exec(code_str, globals(), namespace)
# 返回命名空间中的函数对象
return namespace['func']
# 简化版本的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
# ------fit_X4前提函数-------
# 这里是为了后续的fit能够直接用到按钮中而不是BF_Fit写一大堆
def fit_X4(xian_index, sampleData):
sx, sy = sampleData[:, 0], sampleData[:, 1]
cur = function.Fun[xian_index] # 装载正在选择的函数
func = cur.get_fun() # 获取当前函数func
popt, pcov = gradient_descent_method_X5(func, sx, sy) # 用curve_fit来对数据进行拟合
x_normalized = (sx - np.mean(sx)) / np.std(sx) # 标准化处理的x
y_pred = func(x_normalized, *popt) # 获取y的预测值
gl_data.CurveData = compute_curveData_X5_2(func, popt, sampleData) # 计算拟合曲线的数据
rr = goodness_of_fit_easy(y_pred, 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))
# 编程4.17-----------------------------------------
# 选择
def fit_X4(xian_index, sampleData):
sx, sy = sampleData[:, 0], sampleData[:, 1]
cur = function.Fun[xian_index] # 装载正在选择的函数
func = cur.get_fun() # 获取当前函数func
# params_count = func.__code__.co_argcount - 2 # 计算参数数量
# if params_count <= 3 and len(sampleData) <= 20 : # 当参数和样本点数量较少时
# popt, pcov = least_square_method(func, sx, sy) # 用最小二乘法来对数据进行拟合
# else: # 当参数和样本点数量较多时
# popt, pcov = gradient_descent_method_X5(func, sx, sy) # 用梯度下降法来对数据进行拟合
popt, pcov = gradient_descent_method_X5(func, sx, sy)
x_normalized = (sx - np.mean(sx)) / np.std(sx) # 标准化处理的x
y_pred = func(x_normalized, *popt) # 获取y的预测值
gl_data.CurveData = compute_curveData_X5_2(func, popt, sampleData) # 计算拟合曲线的数据
rr = goodness_of_fit_easy(y_pred, 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])
gl_data.Out = '函数形式:' + cur.name + ' '
gl_data.Out += cur.demo
gl_data.Out += ans
gl_data.Out += '\n拟合优度(R\u00b2)' + str(round(rr, 5))
# 编程4.17 END-----------------------------------------
# 这里是为了后续的fit能够直接用到按钮中而不是BF_Fit写一大堆
def fit_X5(xian_index, sampleData):
sx, sy = sampleData[:, 0], sampleData[:, 1]
letters, result = gl_data.FITT_SAVE['variable'][xian_index], gl_data.FITT_SAVE['function'][xian_index]
code_str = fitting(letters, result) # 获取当前选择函数的表达式
func = create_func(code_str) # 构造当前函数的func方法
popt, pcov = gradient_descent_method_X5(func, sx, sy) # 用curve_fit来对数据进行拟合
x_normalized = (sx - np.mean(sx)) / np.std(sx)
y_pred = func(x_normalized, *popt) # 获取y的预测值
gl_data.CurveData = compute_curveData_X5_2(func, popt, sampleData) # 计算拟合曲线的数据
rr = goodness_of_fit_easy(y_pred, sy) # 计算本次拟合的R*R值用于表示拟合优度
# 输出拟合后的各个参数值
ans = '\n函数系数F(x) = '
letter = str(letters[2:]).split(',')
pattern = re.compile('|'.join(letter))
s = pattern.sub('{:.2g}', gl_data.FITT_SAVE['demo'][xian_index])
print(pattern)
print(gl_data.FITT_SAVE['demo'][xian_index])
print("s:", s)
ans += str(s.format(*popt))
gl_data.Out = '函数形式:' + gl_data.FITT_SAVE['name'][xian_index] + ' '
gl_data.Out += gl_data.FITT_SAVE['demo'][xian_index]
gl_data.Out += ans
gl_data.Out += '\n拟合优度(R\u00b2)' + str(round(rr, 5))
# 这里是使用curve_fit实现的实际使用的方式
def fit_XX(xian_index, sampleData):
sx, sy = sampleData[:, 0], sampleData[:, 1]
letters, result = gl_data.FITT_SAVE['variable'][xian_index], gl_data.FITT_SAVE['function'][xian_index]
code_str = fitting(letters, result) # 获取当前选择函数的表达式
func = create_func(code_str) # 构造当前函数的func方法
popt, pcov = curve_fit(func, sx, sy) # 用curve_fit来对数据进行拟合
y_pred = func(sx, *popt) # 获取y的预测值
gl_data.CurveData = compute_curveData_X5(func, popt) # 计算拟合曲线的数据
rr = goodness_of_fit_easy(y_pred, sy) # 计算本次拟合的R*R值用于表示拟合优度
# 输出拟合后的各个参数值
ans = '\n函数系数F(x) = '
letter = str(letters[2:]).split(',')
pattern = re.compile('|'.join(letter))
s = pattern.sub('{:.2g}', gl_data.FITT_SAVE['demo'][xian_index])
print(pattern)
print(gl_data.FITT_SAVE['demo'][xian_index])
print("s:", s)
ans += str(s.format(*popt))
gl_data.Out = '函数形式:' + gl_data.FITT_SAVE['name'][xian_index] + ' '
gl_data.Out += gl_data.FITT_SAVE['demo'][xian_index]
gl_data.Out += ans
gl_data.Out += '\n拟合优度(R\u00b2)' + str(round(rr, 5))
# 编程4.10 END-----------------------------------------
# 编程4.11 -----------------------------------------
def change_Q(no):
gl_data.Quadrant = no #更改全局变量的象限显示
if no: #若为一象限则修改显示下限为0
gl_data.LOW = 0
else: #若为四象限,则修改显示下限为-gl_data.MAXV
gl_data.LOW = -gl_data.MAXV
q_button_X3() #更新象限显示面板
def q_button_X3():
r = 7.5
rr = 2.5
for widget in Q_root.winfo_children():
widget.destroy()
q_cv = tk.Canvas(Q_root, width=450, height=500)
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")
if __name__ == '__main__':
# 象限选择相关界面
Q_root = tk.Tk()
Q_root.geometry("500x550") # 设置窗口的大小和位置
q_button_X3()
Q_root.mainloop()
# 编程4.11 END-----------------------------------------