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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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-----------------------------------------