活动介绍

import pandas as pd import matplotlib.pyplot as plt import os import re from tkinter import Tk from tkinter.filedialog import askdirectory # 创建Tkinter窗口 root = Tk() root.withdraw() # 隐藏主窗口 # 选择文件夹 folder_path = askdirectory(title='选择文件夹') # 存储数据 data = [] # 遍历文件夹中的所有xlsx文件 for filename in os.listdir(folder_path): if filename.endswith('.xlsx'): # 使用正则表达式提取序号、温度和压力 match = re.search(r'(\d+)\s+DB.*?-(\d+)MPa.*?-(\d+)C', filename) if match: index = int(match.group(1)) # 文件名的第一个数字 pressure = float(match.group(2)) # 压力 temp = float(match.group(3)) # 温度 # 读取traces工作表 file_path = os.path.join(folder_path, filename) df = pd.read_excel(file_path, sheet_name='traces', header=None) # 不使用列名 # 提取Start Time和SampleInterval start_time = df.loc[5, 2] # 第3行第3列 sample_interval = df.loc[6, 2] # 第4行第2列 # 计算到达时间 p_wave_data = df.iloc[8:, 2].astype(float) # p波数据 s1_wave_data = df.iloc[8:, 3].astype(float) # s1波数据 s2_wave_data = df.iloc[8:, 4].astype(float) # s2波数据 # 计算到达时间 arrival_times = [start_time + i * sample_interval for i in range(len(p_wave_data))] # 存储数据 data.append((index, arrival_times, p_wave_data.tolist(), s1_wave_data.tolist(), s2_wave_data.tolist(), temp, pressure)) # 转换为DataFrame df_data = pd.DataFrame(data, columns=['Index', 'Arrival Times', 'P Wave', 'S1 Wave', 'S2 Wave', 'Temperature', 'Pressure']) # 打印每列的长度以调试 for col in df_data.columns: print(f"{col} 的长度: {len(df_data[col])}") # 确保数据类型正确 df_data['Arrival Times'] = df_data['Arrival Times'].apply(pd.to_numeric, errors='coerce') df_data['P Wave'] = df_data['P Wave'].apply(pd.to_numeric, errors='coerce') df_data['S1 Wave'] = df_data['S1 Wave'].apply(pd.to_numeric, errors='coerce') df_data['S2 Wave'] = df_data['S2 Wave'].apply(pd.to_numeric, errors='coerce') # 绘制第一个图:波形变面积图 plt.figure(figsize=(12, 6)) for index, row in df_data.iterrows(): plt.fill_between(row['Arrival Times'], row['P Wave'], alpha=0.5, label=f'File {row["Index"]} P Wave', color='blue') plt.fill_between(row['Arrival Times'], row['S1 Wave'], alpha=0.5, label=f'File {row["Index"]} S1 Wave', color='orange') plt.fill_between(row['Arrival Times'], row['S2 Wave'], alpha=0.5, label=f'File {row["Index"]} S2 Wave', color='green') plt.xlabel('测量时间') plt.ylabel('波形幅度') plt.title('波形变面积图') plt.legend() plt.show() # 绘制第二个图:温度与压力双纵轴图 fig, ax1 = plt.subplots(figsize=(12, 6)) # 左侧温度 ax1.set_xlabel('测量时间') ax1.set_ylabel('温度 (°C)', color='tab:blue') ax1.plot(df_data['Index'], df_data['Temperature'], color='tab:blue', label='温度') ax1.tick_params(axis='y', labelcolor='tab:blue') # 右侧压力 ax2 = ax1.twinx() ax2.set_ylabel('压力 (MPa)', color='tab:red') ax2.plot(df_data['Index'], df_data['Pressure'], color='tab:red', label='压力') ax2.tick_params(axis='y', labelcolor='tab:red') plt.title('温度与压力双纵轴图') plt.show() 修改这个代码,使得其以每个波的序号为横轴,按序号从小到大排列,以到达时间为纵轴,横轴向右变大,纵轴向下变大,然后将p波,s1波,s2波分别画到不同的图上

时间: 2025-06-13 07:28:25 浏览: 31
<think>好的,我现在需要帮助用户修改他们的Python代码,以满足新的绘图需求。首先,我需要仔细理解用户的需求。用户希望将原来的波形变面积图改为以波的序号为横轴,按序号从小到大排列,到达时间为纵轴,并且纵轴向下变大。同时,P波、S1波、S2波需要分别画在不同的图上。 首先,我需要回顾用户提供的原始代码。原始代码遍历文件夹中的Excel文件,提取序号、温度、压力,以及波形数据,然后生成两个图:一个是波形变面积图,另一个是温度与压力的双纵轴图。现在需要修改第一个图的部分。 用户的新需求中,横轴是每个波的序号,即每个文件对应的Index列的值。纵轴是到达时间,但需要纵轴向下变大,也就是原点在上方,数值越往下越大。这可能需要调整坐标轴的方向。此外,每个波(P、S1、S2)需要分开画在不同的图上,而不是在同一张图上叠加。 首先,我需要确定如何获取每个波的到达时间。原来的代码中,arrival_times是一个列表,包含了每个数据点的时间。但用户现在希望以序号为横轴,到达时间作为纵轴,可能需要每个波的首达时间,即第一个到达的时间点?或者可能是指每个波的整个时间序列?需要进一步分析。 仔细阅读用户的问题,用户提到“以到达时间为纵轴”,可能是指每个波的到达时间,也就是每个文件中波的到达时间。例如,每个文件对应一个序号,每个序号对应一个到达时间,这可能指的是某个特征时间,比如波的初至时间。但原始代码中的arrival_times是每个数据点的时间数组,而用户可能希望每个波的到达时间是指该波的首达时间,即第一个非零或超过阈值的点的时间。 但原代码中的arrival_times是计算了每个数据点的时间,所以可能需要明确用户的需求。用户的问题可能描述不够准确,可能需要根据上下文推测。例如,每个文件对应的序号作为横坐标,纵坐标是该文件中波的到达时间,可能是指该波的某个特征时间,比如初至时间或者最大振幅时间。 但原代码中的arrival_times是每个数据点的时间序列,所以如果用户希望以序号为横轴,每个序号对应一个到达时间点,那么可能需要从每个波的arrival_times中提取一个特定的时间点,比如第一个到达的时间,或者最大振幅的时间。否则,无法将每个序号对应一个纵坐标值。 这时,可能需要进一步确认,但用户的问题可能希望将每个序号对应整个时间序列的到达时间,即横轴是序号,纵轴是到达时间,每个序号对应多条时间序列的到达时间,这似乎不太合理。或者,可能用户希望将每个波的到达时间作为纵轴,每个数据点对应一个序号,即每个波的每个采样点都对应一个序号?这也不太清楚。 或者,可能用户希望将每个文件的序号作为横轴,而纵轴是该文件中某个波的到达时间,例如,P波的到达时间是该文件中P波的初至时间,S1和S2同理。这样,每个序号对应三个点(P、S1、S2的到达时间),分别画在不同的图上。 此时,可能需要用户进一步澄清,但由于用户可能无法立即回应,我需要根据代码和问题描述进行合理推测。 原代码中,每个文件处理后会生成arrival_times,这是一个列表,包含了每个数据点的时间。而P Wave、S1 Wave、S2 Wave的数据是对应的振幅值。在原来的变面积图中,fill_between是在时间轴和振幅之间填充区域。 现在用户希望以序号为横轴,每个序号对应一个文件,纵轴是到达时间,即每个序号对应一个时间值,或者多个时间值?需要明确。可能用户希望每个序号对应一个到达时间,即每个文件中波的到达时间,比如该文件中P波的到达时间,S1波的到达时间等,作为纵轴的值,而横轴是文件的序号,按从小到大的顺序排列。 如果是这样的话,那么需要从每个文件的波形数据中提取出到达时间。例如,检测每个波(P、S1、S2)的初至时间,即振幅超过某个阈值的第一个时间点。这可能需要添加一个函数来计算每个波的初至时间。 但原代码中并没有计算初至时间,而是直接使用整个arrival_times数组。因此,用户可能需要的是将每个文件的序号作为横轴,而纵轴是该文件中各个波的到达时间(比如初至时间),并且每个波单独画在不同的图上,三个图分别显示P、S1、S2波的到达时间随序号的变化。 因此,修改后的代码需要做以下步骤: 1. 对每个文件,提取P波、S1波、S2波的初至时间。 2. 将每个文件的序号作为横坐标,按从小到大排序。 3. 纵坐标为初至时间,并且纵轴向下增大(即原点在上,数值越往下越大)。 4. 分别绘制三个图,每个图对应一个波(P、S1、S2),显示序号与初至时间的关系。 因此,首先需要确定如何提取初至时间。这可能需要设置一个阈值,当波的振幅超过该阈值时,视为到达。例如,找到第一个超过阈值的点的arrival_time。 但是,原始数据中的振幅可能正负都有,所以可能需要取绝对值。或者根据数据的特性调整阈值。 假设用户希望提取每个波的初至时间,这里需要添加一个函数来检测初至时间。例如: def find_first_arrival(data, threshold): for i, value in enumerate(data): if abs(value) > threshold: return i return len(data) - 1 # 如果未找到,返回最后一个索引 然后,根据采样间隔计算出实际的到达时间。例如,找到索引后,乘以sample_interval,加上start_time。 但每个文件的start_time和sample_interval可能不同,需要根据每个文件的情况处理。 在原始代码中,每个文件的start_time和sample_interval是从Excel文件的特定位置读取的,因此每个文件可能有不同的start_time和sample_interval。因此,在计算到达时间时,需要根据每个文件的具体参数。 接下来,将每个文件的序号作为横轴,按从小到大排序。当前的df_data是按读取文件的顺序排列的,可能序号并不连续或有序。因此,需要先对df_data按Index列进行排序。 然后,在每个波的图中,横轴是序号,纵轴是到达时间,并且纵轴向下增大。这需要设置坐标轴的y轴反转,可以使用plt.gca().invert_yaxis()或者ax.invert_yaxis()。 此外,每个波需要单独的图,因此需要创建三个图,分别绘制P、S1、S2的到达时间。 现在,修改后的代码可能包括以下步骤: 1. 对每个文件提取P、S1、S2波的初至时间。 2. 将数据按Index排序。 3. 创建三个图,每个图对应一个波,横轴是序号,纵轴是到达时间,纵轴向下。 但用户的问题中提到“以到达时间为纵轴”,而原代码中的arrival_times是时间数组,所以可能需要确定每个波的到达时间是一个单一值(如初至时间)还是一个数组。根据用户的需求,可能是指每个波的到达时间数组,但以序号为横轴的话,可能需要将每个序号对应多个到达时间点,这似乎不太可能。因此,更可能的是每个文件对应一个到达时间点,即初至时间,因此每个序号对应三个点(P、S1、S2的初至时间)。 因此,需要修改代码,提取每个波的初至时间,并存储到DataFrame中。 因此,修改后的步骤: 1. 对每个文件处理时,计算P、S1、S2波的初至时间。 2. 存储这些初至时间到DataFrame中。 3. 按序号排序。 4. 创建三个图,每个图显示序号与对应波的初至时间,纵轴反转。 现在,需要确定如何计算初至时间。例如,假设阈值为某个值,比如0.1(可能需要根据数据调整),当波的振幅绝对值超过阈值时,视为到达。 在代码中,可以添加一个函数来计算初至时间: def calculate_arrival_time(wave_data, start_time, sample_interval, threshold=0.1): # wave_data是波的振幅列表 for i, amp in enumerate(wave_data): if abs(amp) > threshold: arrival_time = start_time + i * sample_interval return arrival_time # 如果没有超过阈值,返回None或最后一个时间点 return start_time + (len(wave_data)-1)*sample_interval 然后在处理每个文件时,计算每个波的初至时间: p_arrival = calculate_arrival_time(p_wave_data, start_time, sample_interval) s1_arrival = calculate_arrival_time(s1_wave_data, start_time, sample_interval) s2_arrival = calculate_arrival_time(s2_wave_data, start_time, sample_interval) 并将这些时间存储到data中: data.append((index, p_arrival, s1_arrival, s2_arrival, temp, pressure)) 然后,DataFrame的列可能包括'Index', 'P Arrival', 'S1 Arrival', 'S2 Arrival', 'Temperature', 'Pressure'。 接下来,按Index排序: df_data = df_data.sort_values('Index') 然后,分别绘制三个图: plt.figure() plt.scatter(df_data['Index'], df_data['P Arrival']) plt.gca().invert_yaxis() plt.xlabel('Index') plt.ylabel('Arrival Time') plt.title('P Wave Arrival Time') plt.show() 同理,S1和S2波。 但用户希望将到达时间作为纵轴,并且纵轴向下变大,即数值大的在上,小的在下,这需要反转y轴。 现在,回到用户的原始代码,原来的arrival_times是每个数据点的时间数组,但用户现在可能希望每个序号对应每个波的到达时间(比如初至时间),所以需要修改数据提取部分,将每个波的到达时间存储为单个值,而不是整个时间序列。 因此,总结修改步骤: 1. 修改数据提取部分,计算每个波的初至时间,并保存。 2. 按Index排序数据。 3. 分别绘制三个图,每个图对应一个波的初至时间,横轴是Index,纵轴是到达时间,并反转y轴。 现在,需要检查原代码中的数据处理是否正确。例如,原代码中的start_time和sample_interval是否正确提取?在原始代码中: start_time = df.loc[5, 2] # 第3行第3列?因为pandas的行列从0开始,所以.loc[5,2]是第六行第三列? 这可能需要确认Excel文件的结构,但用户可能已经确认这部分是正确的。 接下来,关于波形数据,原代码提取的是从第八行开始的数据: p_wave_data = df.iloc[8:, 2].astype(float) # 第9行第三列开始? 这可能正确,但需要确保。 现在,修改后的代码需要添加初至时间的计算,并存储这些时间。然后,按序号排序,绘制三个图。 因此,修改后的代码大致如下: 在循环处理每个文件的部分: ... # 计算到达时间(初至时间) # 假设阈值为0.1,根据实际数据调整 threshold = 0.1 # 提取波形数据 p_wave = df.iloc[8:, 2].astype(float).values s1_wave = df.iloc[8:, 3].astype(float).values s2_wave = df.iloc[8:, 4].astype(float).values # 计算每个波的初至时间 def get_first_arrival(wave_data, start, interval, threshold): for i, val in enumerate(wave_data): if abs(val) > threshold: return start + i * interval return start + (len(wave_data)-1)*interval # 未找到则返回最后时间 p_arrival = get_first_arrival(p_wave, start_time, sample_interval, threshold) s1_arrival = get_first_arrival(s1_wave, start_time, sample_interval, threshold) s2_arrival = get_first_arrival(s2_wave, start_time, sample_interval, threshold) # 存储数据 data.append((index, p_arrival, s1_arrival, s2_arrival, temp, pressure)) 然后,创建DataFrame: df_data = pd.DataFrame(data, columns=['Index', 'P Arrival', 'S1 Arrival', 'S2 Arrival', 'Temperature', 'Pressure']) 然后,按Index排序: df_data = df_data.sort_values('Index') 然后,绘制三个图: plt.figure(figsize=(10,6)) plt.scatter(df_data['Index'], df_data['P Arrival'], label='P Wave', color='blue') plt.gca().invert_yaxis() # 反转纵轴 plt.xlabel('序号') plt.ylabel('到达时间') plt.title('P波到达时间') plt.legend() plt.show() 同理,S1和S2波。 但用户原来的代码中的波形变面积图可能不再需要,而是替换为这三个图。因此,需要注释掉原来的绘图代码,替换为新的。 此外,用户可能希望每个波的到达时间以序号为横轴,并且横轴向右增大,纵轴向下增大。这已经通过排序和反转y轴实现。 最后,确保所有波的数据都被正确提取,并且阈值设置合适。如果阈值设置不当,可能导致初至时间提取错误。因此,可能需要用户根据实际数据调整阈值,或者在代码中添加参数输入。 综上,最终的修改后的代码将包括提取每个波的初至时间,按序号排序,分别绘制三个图,并反转纵轴。</think>以下是修改后的代码实现,主要改动包括: 1. 增加初至时间提取逻辑 2. 按序号排序数据 3. 分别绘制三波独立图形 4. 坐标轴方向调整 ```python import pandas as pd import matplotlib.pyplot as plt import os import re from tkinter import Tk from tkinter.filedialog import askdirectory # 阈值参数(根据实际数据调整) ARRIVAL_THRESHOLD = 0.1 # 初至检测阈值 # 创建Tkinter窗口 root = Tk() root.withdraw() # 选择文件夹 folder_path = askdirectory(title='选择文件夹') # 存储数据 data = [] def detect_first_arrival(wave_data, start_time, interval): """检测初至时间,返回第一个超过阈值的绝对值的对应时间""" for i, value in enumerate(wave_data): if abs(value) > ARRIVAL_THRESHOLD: return start_time + i * interval return start_time + len(wave_data)*interval # 默认返回结束时间 # 处理文件 for filename in os.listdir(folder_path): if filename.endswith('.xlsx'): match = re.search(r'(\d+)\s+DB.*?-(\d+)MPa.*?-(\d+)C', filename) if match: index = int(match.group(1)) pressure = float(match.group(2)) temp = float(match.group(3)) file_path = os.path.join(folder_path, filename) df = pd.read_excel(file_path, sheet_name='traces', header=None) # 提取时间参数 start_time = df.loc[5, 2] sample_interval = df.loc[6, 2] # 提取波形数据 p_wave = df.iloc[8:, 2].astype(float).values s1_wave = df.iloc[8:, 3].astype(float).values s2_wave = df.iloc[8:, 4].astype(float).values # 计算初至时间 p_arrival = detect_first_arrival(p_wave, start_time, sample_interval) s1_arrival = detect_first_arrival(s1_wave, start_time, sample_interval) s2_arrival = detect_first_arrival(s2_wave, start_time, sample_interval) data.append((index, p_arrival, s1_arrival, s2_arrival, temp, pressure)) # 创建DataFrame并排序 df_data = pd.DataFrame(data, columns=['Index', 'P_Arrival', 'S1_Arrival', 'S2_Arrival', 'Temperature', 'Pressure']) df_data = df_data.sort_values('Index') # 按序号排序 # 绘制三个独立波形图 def plot_wave(wave_type, color): plt.figure(figsize=(10, 6)) plt.scatter(df_data['Index'], df_data[f'{wave_type}_Arrival'], color=color, s=50, edgecolors='k') ax = plt.gca() ax.invert_yaxis() # 纵轴向下增大 plt.grid(True, linestyle='--', alpha=0.7) plt.xlabel('样本序号', fontsize=12) plt.ylabel('到达时间 (s)', fontsize=12) plt.title(f'{wave_type}波到达时间分布', fontsize=14) plt.xticks(df_data['Index']) # 显示所有序号 plt.tight_layout() plt.show() # 分别绘制三波图形 plot_wave('P', 'royalblue') plot_wave('S1', 'darkorange') plot_wave('S2', 'forestgreen') # 温度压力图保持不变(根据需求保留) ``` **代码改进说明:** 1. **初至时间检测**: - 新增`detect_first_arrival()`函数,使用绝对值阈值法检测初至时间 - 可调整`ARRIVAL_THRESHOLD`参数适应不同数据特性 - 返回第一个超过阈值的采样点对应的时间值 2. **数据结构优化**: - 每个样本仅存储三个初至时间(不再存储完整波形数据) - 添加明确的列名`P_Arrival`, `S1_Arrival`, `S2_Arrival` 3. **可视化增强**: - 独立绘图函数`plot_wave()`实现标准化绘图 - 添加网格线、调整点样式、强制显示所有序号标签 - 纵轴反转实现"向下增大"的效果 - 三波分别使用不同主题色绘制 4. **交互性提升**: - 自动按序号排序确保横轴正序 - 保留温度压力双轴图(按需启用) **效果示意图:** - 横轴:样本序号(从左到右递增) - 纵轴:到达时间(从上到下递增) - 散点位置:每个样本对应波的初至时间 - 三波分别显示在独立窗口中,方便对比分析 **注意事项:** 1. 需要根据实际数据调整`ARRIVAL_THRESHOLD`值 2. 可通过修改`plt.scatter()`参数调整点的大小、颜色、样式 3. 如需添加趋势线,可在`plot_wave()`中添加多项式拟合代码 4. 异常值处理可扩展`detect_first_arrival()`函数的逻辑 如果需要进一步分析三波到达时间的相关性,可以添加联合分布图或时差分析功能。
阅读全文

相关推荐

怎么样把import tkinter as tk import csv from tkinter import filedialog root = tk.Tk() root.title("数据科学基础") root.geometry("800x600") #修改字体 font = ("楷体", 16) root.option_add("*Font", font) #修改背景颜色 root.configure(bg="pink") def import_csv_data(): global file_path file_path = filedialog.askopenfilename() # 读取CSV文件并显示在Text控件上 data = pd.read_csv(file_path) # 获取前5行数据 top_5 = data.head() # 将前5行数据插入到Text控件 #txt_data.delete('1.0'.tk.END) txt_data.insert(tk.END, top_5) #创建导入按钮和文本框 btn_import = tk.Button(root,text="导入CSV文件",command=import_csv_data) btn_import.pack() txt_data = tk.Text(root) txt_data.pack() root.mainloop()怎么样把这段代码和import pandas as pd import matplotlib.pyplot as plt from tkinter import * from tkinter import filedialog from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg # 创建 Tkinter 窗口 root = Tk() # 设置窗口标题 root.title("CSV文件分析") # 创建标签 label = Label(root, text="请选择要导入的CSV文件:") label.pack() # 创建按钮 button = Button(root, text="选择文件") # 创建事件处理函数 def choose_file(): # 弹出文件选择对话框 file_path = filedialog.askopenfilename() # 读取CSV文件 df = pd.read_csv(file_path) # 创建标签 label2 = Label(root, text="请选择要显示的图像:") label2.pack() # 创建按钮 button1 = Button(root, text="散点图") button1.pack() button2 = Button(root, text="折线图") button2.pack() button3 = Button(root, text="柱状图") button3.pack() # 创建图形容器 fig_container = Frame(root) fig_container.pack() # 创建事件处理函数 def show_scatter(): # 获取数据 x = df.iloc[:, 0] y = df.iloc[:, 1] # 绘制散点图 fig = plt.figure(figsize=(4, 4)) plt.scatter(x, y) # 将图形显示在容器中 canvas = FigureCanvasTkAgg(fig, master=fig_container) canvas.draw() canvas.get_tk_widget().pack() def show_line(): # 获取数据 x = df.iloc[:, 0] y = df.iloc[:, 1] # 绘制折线图 fig = plt.figure(figsize=(4, 4)) plt.plot(x, y) # 将图形显示在容器中 canvas = FigureCanvasTkAgg(fig, master=fig_container) canvas.draw() canvas.get_tk_widget().pack() def show_bar(): # 获取数据 x = df.iloc[:, 0] y = df.iloc[:, 1] # 绘制柱状图 fig = plt.figure(figsize=(4, 4)) plt.bar(x, y) # 将图形显示在容器中 canvas = FigureCanvasTkAgg(fig, master=fig_container) canvas.draw() canvas.get_tk_widget().pack() # 绑定事件处理函数 button1.config(command=show_scatter) button2.config(command=show_line) button3.config(command=show_bar) # 绑定事件处理函数 button.config(command=choose_file) button.pack() # 运行窗口 root.mainloop()这段代码结合起来一起实现

import tkinter as tk import pandas as pd import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import os class ExcelPlotter(tk.Frame): def init(self, master=None): super().init(master) self.master = master self.master.title("图方便") self.file_label = tk.Label(master=self, text="Excel File Path:") self.file_label.grid(row=0, column=0, sticky="w") self.file_entry = tk.Entry(master=self) self.file_entry.grid(row=0, column=1, columnspan=2, sticky="we") self.file_button = tk.Button(master=self, text="Open", command=self.open_file) self.file_button.grid(row=0, column=3, sticky="e") self.plot_button = tk.Button(master=self, text="Plot", command=self.plot_data) self.plot_button.grid(row=1, column=2, sticky="we") self.name_label = tk.Label(master=self, text="Out Image Name:") self.name_label.grid(row=2, column=0, sticky="w") self.name_entry = tk.Entry(master=self) self.name_entry.grid(row=2, column=1, columnspan=2, sticky="we") self.save_button = tk.Button(master=self, text="Save", command=self.save_image) self.save_button.grid(row=2, column=3, sticky="e") self.figure = plt.figure(figsize=(5, 4), dpi=150) self.canvas = FigureCanvasTkAgg(self.figure, master=self) self.canvas.get_tk_widget().grid(row=4, column=0, columnspan=4, sticky="we") self.pack() def open_file(self): file_path = tk.filedialog.askopenfilename(filetypes=[("Excel Files", "*.xls")]) self.file_entry.delete(0, tk.END) self.file_entry.insert(tk.END, file_path) def plot_data(self): file_path = self.file_entry.get() if os.path.exists(file_path): data = pd.read_excel(file_path) plt.plot(data['波长(nm)'], data['吸光度'], 'k') plt.xlim(300, 1000) plt.xlabel('Wavelength(nm)', fontsize=16) plt.ylabel('Abs.', fontsize=16) plt.gcf().subplots_adjust(left=0.13, top=0.91, bottom=0.16) plt.savefig('Last Fig', dpi=1000) plt.show() def save_image(self): if self.figure: file_path = tk.filedialog.asksaveasfilename(defaultextension=".png") if file_path: self.figure.savefig(file_path) root = tk.Tk() app = ExcelPlotter(master=root) app.mainloop()帮我增加一个删除当前图像的功能

import tkinter as tk import csv import pandas as pd import matplotlib.pyplot as plt from tkinter import filedialog from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg root = tk.Tk() root.title("数据科学基础") root.geometry("800x600") font = ("楷体", 16) root.option_add("*Font", font) root.configure(bg="pink") def import_csv_data(): global file_path, df, txt_data file_path = filedialog.askopenfilename() df = pd.read_csv(file_path) top_5 = df.head() txt_data.delete('1.0', tk.END) txt_data.insert(tk.END, top_5) label2 = tk.Label(root, text="请选择要显示的图像:") label2.pack(side="left") button1 = tk.Button(root, text="散点图") button1.pack(side="left") button2 = tk.Button(root, text="折线图") button2.pack(side="left") button3 = tk.Button(root, text="柱状图") button3.pack(side="left") fig_container = tk.Frame(root) fig_container.pack() def show_figure(): x = df.iloc[:, 0] y = df.iloc[:, 1] if plt.fignum_exists(1): plt.clf() # 清空画布 if button1["state"] == "normal": plt.scatter(x, y) elif button2["state"] == "normal": plt.plot(x, y) elif button3["state"] == "normal": plt.bar(x, y) canvas = FigureCanvasTkAgg(plt.gcf(), master=fig_container) canvas.draw() canvas.get_tk_widget().pack() button1.config(command=lambda: (button1.config(state="disabled"), button2.config(state="normal"), button3.config(state="normal"), show_figure())) button2.config(command=lambda: (button2.config(state="disabled"), button1.config(state="normal"), button3.config(state="normal"), show_figure())) button3.config(command=lambda: (button3.config(state="disabled"), button1.config(state="normal"), button2.config(state="normal"), show_figure())) btn_import = tk.Button(root,text="导入CSV文件",command=import_csv_data) btn_import.pack() txt_data = tk.Text(root) txt_data.pack() btn_show_figure = tk.Button(root, text="显示图像", command=lambda: (button1.config(state="normal"), button2.config(state="normal"), button3.config(state="normal"))) btn_show_figure.pack() root.mainloop()如何修改代码实现对界面右侧实现滑动上下拉动界面的功能

# This is a sample Python script. import tkinter as tk from tkinter import ttk, filedialog import serial import pandas as pd #from serial.tools import list_ports from matplotlib import pyplot as plt # Press Shift+F10 to execute it or replace it with your code. # Press Double Shift to search everywhere for classes, files, tool windows, actions, and settings. #def print_hi(name): # Use a breakpoint in the code line below to debug your script. # print(f'Hi, {name}') # Press Ctrl+F8 to toggle the breakpoint. class SerialAssistant: def __init__(self): self.window = tk.Tk() self.window.title("智能串口助手 v1.0") # 串口参数配置区 self.port_label = ttk.Label(self.window, text="选择端口:") self.port_combo = ttk.Combobox(self.window) # 数据接收显示区 self.data_text = tk.Text(self.window, height=15) # print(f'Hi_1') # 控制按钮区 self.btn_connect = ttk.Button(self.window, text="连接", command=self.connect_serial) self.btn_save = ttk.Button(self.window, text="保存数据", command=self.save_data) def connect_serial(self): if not self.serial_connected: try: self.ser = serial.Serial( port=self.port_combo.get(), baudrate=115200, timeout=1 ) self.start_receive_thread() except Exception as e: print(f"连接失败: {str(e)}") else: self.ser.close() def start_receive_thread(self): import threading self.receive_thread = threading.Thread(target=self.read_serial) self.receive_thread.daemon = True self.receive_thread.start() def read_serial(self): self.data_buffer = [] while self.serial_connected: try: data = self.ser.readline().decode().strip() if data: self.data_text.insert(tk.END, data + "\n") self.data_buffer.append([pd.Timestamp.now(), data]) except Exception as e: print(f"接收错误: {str(e)}") def save_data(self): file_types = [("CSV文件", "*.csv"), ("文本文件", "*.txt")] file_path = filedialog.asksaveasfilename(filetypes=file_types) if not file_path: return df = pd.DataFrame(self.data_buffer, columns=["时间戳", "数据"]) if file_path.endswith('.csv'): df.to_csv(file_path, index=False) else: df.to_csv(file_path, sep='\t', index=False) def data_processing(self, method='plot'): df = pd.DataFrame(self.data_buffer, columns=["时间戳", "数据"]) # 示例处理功能 if method == 'plot': plt.figure(figsize=(10, 6)) df['数据'] = pd.to_numeric(df['数据'], errors='coerce') df.plot(x='时间戳', y='数据') plt.title("实时数据趋势图") plt.show() elif method == 'stats': stats = df['数据'].describe() print(stats.to_markdown()) # Press the green button in the gutter to run the script. if __name__ == '__main__': print(f'Hi') # Press Ctrl+F8 to toggle the breakpoint. app = SerialAssistant() # 必须显式创建实例 # app.run() # 如果存在启动方法需要调用 # app = ADCVisualizer() #app.root.mainloop() # See PyCharm help at https://www.jetbrains.com/help/pycharm/为什么pycharm会对这个代码报错

import tkinter as tk from tkinter import ttk, filedialog, messagebox import pandas as pd import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from sklearn.preprocessing import MinMaxScaler import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense,Layer,Dropout from tensorflow.keras.optimizers import Adam from tensorflow.keras.callbacks import EarlyStopping,ModelCheckpoint,ReduceLROnPlateau import os plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体 plt.rcParams['axes.unicode_minus'] = False class Attention(Layer): def __init__(self, **kwargs): super(Attention, self).__init__(**kwargs) def build(self, input_shape): self.W = self.add_weight(name='attention_weight', shape=(input_shape[-1], 1), initializer='random_normal', trainable=True) self.b = self.add_weight(name='attention_bias', shape=(input_shape[1], 1), initializer='zeros', trainable=True) super(Attention, self).build(input_shape) def call(self, x): e = tf.tanh(tf.matmul(x, self.W) + self.b) a = tf.nn.softmax(e, axis=1) output = x * a return tf.reduce_sum(output, axis=1) class DamSeepageModel: def __init__(self, root): self.root = root self.root.title("大坝渗流预测模型") self.root.geometry("1200x800") # 初始化数据 self.train_df = None self.test_df = None self.model = None self.scaler = MinMaxScaler(feature_range=(0, 1)) # 创建主界面 self.create_widgets() def create_widgets(self): # 创建主框架 main_frame = ttk.Frame(self.root, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 左侧控制面板 control_frame = ttk.LabelFrame(main_frame, text="模型控制", padding=10) control_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5) # 文件选择部分 file_frame = ttk.LabelFrame(control_frame, text="数据文件", padding=10) file_frame.pack(fill=tk.X, pady=5) # 训练集选择 ttk.Label(file_frame, text="训练集:").grid(row=0, column=0, sticky=tk.W, pady=5) self.train_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.train_file_var, width=30, state='readonly').grid(row=0, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("train")).grid(row=0, column=2) # 测试集选择 ttk.Label(file_frame, text="测试集:").grid(row=1, column=0, sticky=tk.W, pady=5) self.test_file_var = tk.StringVar() ttk.Entry(file_frame, textvariable=self.test_file_var, width=30, state='readonly').grid(row=1, column=1, padx=5) ttk.Button(file_frame, text="选择文件", command=lambda: self.select_file("test")).grid(row=1, column=2) # 参数设置部分 param_frame = ttk.LabelFrame(control_frame, text="模型参数", padding=10) param_frame.pack(fill=tk.X, pady=10) ttk.Label(param_frame, text="LSTM层数:").grid(row=4, column=0, sticky=tk.W, pady=5) self.lstm_layers_var = tk.IntVar(value=2) ttk.Spinbox(param_frame, from_=1, to=5, increment=1, textvariable=self.lstm_layers_var, width=10).grid(row=4, column=1, padx=5) ttk.Label(param_frame, text="Dropout率:").grid(row=5, column=0, sticky=tk.W, pady=5) self.dropout_rate_var = tk.DoubleVar(value=0.2) ttk.Spinbox(param_frame, from_=0.0, to=0.5, increment=0.05, format="%.2f", textvariable=self.dropout_rate_var, width=10).grid(row=5, column=1, padx=5) # 时间窗口大小 ttk.Label(param_frame, text="时间窗口大小:").grid(row=0, column=0, sticky=tk.W, pady=5) self.window_size_var = tk.IntVar(value=60) ttk.Spinbox(param_frame, from_=10, to=200, increment=5, textvariable=self.window_size_var, width=10).grid(row=0, column=1, padx=5) # LSTM单元数量 ttk.Label(param_frame, text="LSTM单元数:").grid(row=1, column=0, sticky=tk.W, pady=5) self.lstm_units_var = tk.IntVar(value=50) ttk.Spinbox(param_frame, from_=10, to=200, increment=10, textvariable=self.lstm_units_var, width=10).grid(row=1, column=1, padx=5) # 训练轮次 ttk.Label(param_frame, text="训练轮次:").grid(row=2, column=0, sticky=tk.W, pady=5) self.epochs_var = tk.IntVar(value=100) ttk.Spinbox(param_frame, from_=10, to=500, increment=10, textvariable=self.epochs_var, width=10).grid(row=2, column=1, padx=5) # 批处理大小 ttk.Label(param_frame, text="批处理大小:").grid(row=3, column=0, sticky=tk.W, pady=5) self.batch_size_var = tk.IntVar(value=32) ttk.Spinbox(param_frame, from_=16, to=128, increment=16, textvariable=self.batch_size_var, width=10).grid(row=3, column=1, padx=5) # 控制按钮 btn_frame = ttk.Frame(control_frame) btn_frame.pack(fill=tk.X, pady=10) ttk.Button(btn_frame, text="训练模型", command=self.train_model).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="预测结果", command=self.predict).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="保存结果", command=self.save_results).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="重置", command=self.reset).pack(side=tk.RIGHT, padx=5) # 状态栏 self.status_var = tk.StringVar(value="就绪") status_bar = ttk.Label(control_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(fill=tk.X, side=tk.BOTTOM) # 右侧结果显示区域 result_frame = ttk.Frame(main_frame) result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建标签页 self.notebook = ttk.Notebook(result_frame) self.notebook.pack(fill=tk.BOTH, expand=True) # 损失曲线标签页 self.loss_frame = ttk.Frame(self.notebook) self.notebook.add(self.loss_frame, text="训练损失") # 预测结果标签页 self.prediction_frame = ttk.Frame(self.notebook) self.notebook.add(self.prediction_frame, text="预测结果") # 初始化绘图区域 self.fig, self.ax = plt.subplots(figsize=(10, 6)) self.canvas = FigureCanvasTkAgg(self.fig, master=self.prediction_frame) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) self.loss_fig, self.loss_ax = plt.subplots(figsize=(10, 4)) self.loss_canvas = FigureCanvasTkAgg(self.loss_fig, master=self.loss_frame) self.loss_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # 文件选择 def select_file(self, file_type): """选择Excel文件""" file_path = filedialog.askopenfilename( title=f"选择{file_type}集Excel文件", filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")] ) if file_path: try: # 读取Excel文件 df = pd.read_excel(file_path) # 时间特征列 time_features = ['year', 'month', 'day'] missing_time_features = [feat for feat in time_features if feat not in df.columns] if '水位' not in df.columns: messagebox.showerror("列名错误", "Excel文件必须包含'水位'列") return if missing_time_features: messagebox.showerror("列名错误", f"Excel文件缺少预处理后的时间特征列: {', '.join(missing_time_features)}\n" "请确保已使用预处理功能添加这些列") return # 创建完整的时间戳列 # 处理可能缺失的小时、分钟、秒数据 if 'hour' in df.columns and 'minute' in df.columns and 'second' in df.columns: df['datetime'] = pd.to_datetime( df[['year', 'month', 'day', 'hour', 'minute', 'second']] ) elif 'hour' in df.columns and 'minute' in df.columns: df['datetime'] = pd.to_datetime( df[['year', 'month', 'day', 'hour', 'minute']].assign(second=0) ) else: df['datetime'] = pd.to_datetime(df[['year', 'month', 'day']]) # 设置时间索引 df = df.set_index('datetime') # 保存数据 if file_type == "train": self.train_df = df self.train_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载训练集: {len(self.train_df)}条数据") else: self.test_df = df self.test_file_var.set(os.path.basename(file_path)) self.status_var.set(f"已加载测试集: {len(self.test_df)}条数据") except Exception as e: messagebox.showerror("文件错误", f"读取文件失败: {str(e)}") def create_dataset(self, data, window_size): """创建时间窗口数据集""" X, y = [], [] for i in range(len(data) - window_size): X.append(data[i:(i + window_size), 0]) y.append(data[i + window_size, 0]) return np.array(X), np.array(y) def train_model(self): """训练LSTM模型""" if self.train_df is None: messagebox.showwarning("警告", "请先选择训练集文件") return try: self.status_var.set("正在预处理数据...") self.root.update() # 数据预处理 train_scaled = self.scaler.fit_transform(self.train_df[['水位']]) # 创建时间窗口数据集 window_size = self.window_size_var.get() X_train, y_train = self.create_dataset(train_scaled, window_size) # 调整LSTM输入格式 X_train = np.reshape(X_train, (X_train.shape[0], X_train.shape[1], 1)) # 构建LSTM模型 self.model = Sequential() self.model.add(LSTM( self.lstm_units_var.get(), return_sequences=True, input_shape=(window_size, 1) )) self.model.add(Dropout(self.dropout_rate_var.get())) # 添加Dropout # 添加额外的LSTM层 for _ in range(self.lstm_layers_var.get() - 1): self.model.add(LSTM(self.lstm_units_var.get(), return_sequences=True)) self.model.add(Dropout(self.dropout_rate_var.get())) # 添加Dropout self.model.add(Attention()) self.model.add(Dense(1)) self.model.compile( optimizer=Adam(learning_rate=0.001), # 优化器 loss='binary_crossentropy', # 损失函数 metrics=['accuracy'] # 评估指标 ) # 添加更完善的回调机制 early_stopping = EarlyStopping( monitor='val_loss', patience=30, # 增加耐心值 min_delta=0.0001, restore_best_weights=True, verbose=1 ) model_checkpoint = ModelCheckpoint( 'best_model.h5', monitor='val_loss', save_best_only=True, verbose=1 ) reduce_lr = ReduceLROnPlateau( monitor='val_loss', factor=0.2, # 更激进的学习率衰减 patience=10, min_lr=1e-6, verbose=1 ) # 训练模型(添加更多回调) history = self.model.fit( X_train, y_train, epochs=self.epochs_var.get(), batch_size=self.batch_size_var.get(), validation_split=0.2, callbacks=[early_stopping, model_checkpoint, reduce_lr], verbose=0 ) # 绘制损失曲线 self.loss_ax.clear() self.loss_ax.plot(history.history['loss'], label='训练损失') self.loss_ax.plot(history.history['val_loss'], label='验证损失') self.loss_ax.set_title('模型训练损失') self.loss_ax.set_xlabel('轮次') self.loss_ax.set_ylabel('损失',rotation=0) self.loss_ax.legend() self.loss_ax.grid(True) self.loss_canvas.draw() # 根据早停情况更新状态信息 if early_stopping.stopped_epoch > 0: stopped_epoch = early_stopping.stopped_epoch best_epoch = early_stopping.best_epoch final_loss = history.history['loss'][-1] best_loss = min(history.history['val_loss']) self.status_var.set( f"训练在{stopped_epoch + 1}轮提前终止 | " f"最佳模型在第{best_epoch + 1}轮 | " f"最终损失: {final_loss:.6f} | " f"最佳验证损失: {best_loss:.6f}" ) messagebox.showinfo( "训练完成", f"模型训练提前终止!\n" f"最佳模型在第{best_epoch + 1}轮\n" f"最佳验证损失: {best_loss:.6f}" ) else: final_loss = history.history['loss'][-1] self.status_var.set(f"模型训练完成 | 最终损失: {final_loss:.6f}") messagebox.showinfo("训练完成", "模型训练成功完成!") except Exception as e: messagebox.showerror("训练错误", f"模型训练失败:\n{str(e)}") self.status_var.set("训练失败") def predict(self): """使用模型进行预测""" if self.model is None: messagebox.showwarning("警告", "请先训练模型") return if self.test_df is None: messagebox.showwarning("警告", "请先选择测试集文件") return try: self.status_var.set("正在生成预测...") self.root.update() # 预处理测试数据 test_scaled = self.scaler.transform(self.test_df[['水位']]) # 创建测试集时间窗口 window_size = self.window_size_var.get() X_test, y_test = self.create_dataset(test_scaled, window_size) X_test = np.reshape(X_test, (X_test.shape[0], X_test.shape[1], 1)) # 进行预测 test_predict = self.model.predict(X_test) # 反归一化 test_predict = self.scaler.inverse_transform(test_predict) y_test_orig = self.scaler.inverse_transform([y_test]).T # 创建时间索引 test_time = self.test_df.index[window_size:window_size + len(test_predict)] # 绘制结果 self.ax.clear() self.ax.plot(self.train_df.index, self.train_df['水位'], 'b-', label='训练集数据') self.ax.plot(test_time, self.test_df['水位'][window_size:window_size + len(test_predict)], 'g-', label='测试集数据') self.ax.plot(test_time, test_predict, 'r--', label='模型预测') # 添加分隔线 split_point = test_time[0] self.ax.axvline(x=split_point, color='k', linestyle='--', alpha=0.5) self.ax.text(split_point, self.ax.get_ylim()[0] * 0.9, ' 训练/测试分界', rotation=90) self.ax.set_title('大坝渗流水位预测结果') self.ax.set_xlabel('时间') self.ax.set_ylabel('测压管水位',rotation=0) self.ax.legend() self.ax.grid(True) self.ax.tick_params(axis='x', rotation=45) self.fig.tight_layout() self.canvas.draw() self.status_var.set("预测完成,结果已显示") except Exception as e: messagebox.showerror("预测错误", f"预测失败:\n{str(e)}") self.status_var.set("预测失败") def save_results(self): """保存预测结果""" if not hasattr(self, 'test_predict') or self.test_predict is None: messagebox.showwarning("警告", "请先生成预测结果") return save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")] ) if not save_path: return try: # 创建包含预测结果的DataFrame window_size = self.window_size_var.get() test_time = self.test_df.index[window_size:window_size + len(self.test_predict)] result_df = pd.DataFrame({ '时间': test_time, '实际水位': self.test_df['水位'][window_size:window_size + len(self.test_predict)].values, '预测水位': self.test_predict.flatten() }) # 保存到Excel result_df.to_excel(save_path, index=False) # 保存图表 chart_path = os.path.splitext(save_path)[0] + "_chart.png" self.fig.savefig(chart_path, dpi=300) self.status_var.set(f"结果已保存至: {os.path.basename(save_path)}") messagebox.showinfo("保存成功", f"预测结果和图表已保存至:\n{save_path}\n{chart_path}") except Exception as e: messagebox.showerror("保存错误", f"保存结果失败:\n{str(e)}") def reset(self): """重置程序状态""" self.train_df = None self.test_df = None self.model = None self.train_file_var.set("") self.test_file_var.set("") self.ax.clear() self.loss_ax.clear() self.canvas.draw() self.loss_canvas.draw() self.data_text.delete(1.0, tk.END) self.status_var.set("已重置,请选择新数据") messagebox.showinfo("重置", "程序已重置,可以开始新的分析") if __name__ == "__main__": root = tk.Tk() app = DamSeepageModel(root) root.mainloop() 为什么我给它进行了很多改进,比如引入了注意力机制,改善了早停回调,结果拟合情况更差了

帮我优化一下代码 import matplotlib.pyplot as plt from matplotlib.offsetbox import OffsetImage, AnnotationBbox import pandas as pd import tkinter as tk from tkinter import filedialog import csv import numpy as np filepath = filedialog.askopenfilename() readData = pd.read_csv(filepath, encoding = 'gb2312') # 读取csv数据 print(readData) xdata = readData.iloc[:, 2].tolist() # 获取dataFrame中的第3列,并将此转换为list ydata = readData.iloc[:, 3].tolist() # 获取dataFrame中的第4列,并将此转换为list Color_map = { '0x0': 'r', '0x10': 'b', '0x20': 'pink', '0x30': 'm', '0x40': 'm', '0x50': 'm', '0x60': 'g', '0x70': 'orange', '0x80': 'orange', '0x90': 'm', '0xa0': 'b', '0xb0': 'g', '0xc0': 'g', '0xd0': 'orange', '0xe0': 'orange', '0xf0': 'orange', } plt.ion() fig = plt.figure(num = "蓝牙钥匙连接状态", figsize= (10.8,10.8),frameon= True) gs = fig.add_gridspec(1, 1) ax = fig.add_subplot(gs[0, 0]) colors = readData.iloc[:, 1].map(Color_map) plt.title("Connecting Status For Bluetooth Key") #plt.rcParams['figure.figsize']=(15, 15) ax.axis('equal') a,b = (0.,0.) r = [5,10] for r1 in r: theta = np.arange(0,r1*np.pi,0.05) ax.plot(a+r1*np.cos(theta),b+r1*np.sin(theta),linestyle='-.',c = 'darkgrey') ax.spines['bottom'].set_position(('data', 0)) ax.spines['left'].set_position(('data', 0)) ax.spines['right'].set_position(('data', 0)) ax.spines['top'].set_position(('data', 0)) arr_img = plt.imread('D:\\2022\\测试工作\\蓝牙钥匙测试\\定位\\室内定位(v3.6.21).rar-1656500746516.室内定位(v3.6.21)\\车型图2.png') imagebox = OffsetImage(arr_img, zoom=0.3) ab = AnnotationBbox(imagebox, [0, 0],xybox=(0, 0),pad=0) ax.add_artist(ab) ticks = np.arange(-10,10,2) plt.xticks(ticks) plt.yticks(ticks) #plt.figure(figsize=(15,15)) plt.scatter(xdata, ydata, s=150, edgecolors = None, linewidths=0, alpha=0.3,c = colors) # 画散点图,*:r表示点用*表示,颜色为红色 plt.legend() plt.ioff() plt.show() # 画图

import sys import io import os import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt import scapy.all as scapy from collections import defaultdict from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import pandas as pd import tkinter as tk from tkinter import filedialog, messagebox from scapy.layers.inet import IP, TCP, UDP, ICMP from scapy.layers.dns import DNS, DNSRR from datetime import datetime import time from sklearn.decomposition import PCA # 设置Matplotlib中文显示支持 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'WenQuanYi Micro Hei'] plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # 设置系统输出编码为UTF-8 sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') if os.name == 'nt': # Windows系统专用设置 os.system('chcp 65001') # 切换到UTF-8代码页 # ====================== # 文件选择对话框 # ====================== def select_files(): """创建文件选择对话框""" root = tk.Tk() root.withdraw() # 隐藏主窗口 # 选择PCAP文件 pcap_path = filedialog.askopenfilename( title="选择PCAP文件", filetypes=[("PCAP文件", "*.pcap"), ("PCAPNG文件", "*.pcapng"), ("所有文件", "*.*")] ) if not pcap_path: print("未选择PCAP文件,程序退出") sys.exit(0) # 选择输出目录 output_dir = filedialog.askdirectory(title="选择结果保存位置") if not output_dir: print("未选择输出目录,程序退出") sys.exit(0) return pcap_path, output_dir # ====================== # 1. PCAP文件解析与流重组 # ====================== print("请选择PCAP文件和输出位置...") pcap_path, output_dir = select_files() print(f"解析文件: {pcap_path}") print(f"保存位置: {output_dir}") # 新增:DNS解析缓存 dns_cache = {} # 流式解析大PCAP文件,避免内存溢出 def parse_large_pcap(pcap_path): flows = defaultdict(list) # 使用字典存储网络流 packet_count = 0 # 总包计数器 ip_packet_count = 0 # IP包计数器 global dns_cache # 使用全局DNS缓存 # 使用PcapReader流式读取,避免一次性加载大文件 with scapy.PcapReader(pcap_path) as pcap_reader: for pkt in pcap_reader: packet_count += 1 # 进度显示(每10000个包) if packet_count % 10000 == 0: print(f"已处理 {packet_count} 个包...") # 新增:DNS响应解析 - 修复索引越界问题 if DNS in pkt and pkt[DNS].qr == 1: # 只处理DNS响应 # 获取实际可用的回答记录数量 actual_ancount = len(pkt[DNS].an) if hasattr(pkt[DNS], 'an') else 0 # 使用实际数量而不是ancount字段 for i in range(min(pkt[DNS].ancount, actual_ancount)): rr = pkt[DNS].an[i] if isinstance(rr, DNSRR) and rr.type == 1: # A记录 try: ip = rr.rdata # 正确处理域名格式 domain = rr.rrname.decode('utf-8', errors='ignore').rstrip('.') # 添加到DNS缓存 if ip not in dns_cache: dns_cache[ip] = set() dns_cache[ip].add(domain) except Exception as e: # 忽略解析错误 pass # 仅处理IP层数据包 if IP in pkt: ip_packet_count += 1 ip_layer = pkt[IP] proto_num = ip_layer.proto # 获取协议号 # 初始化端口变量 src_port, dst_port = 0, 0 # 根据协议类型提取端口信息 if proto_num == 6 and TCP in pkt: # TCP协议 src_port = pkt[TCP].sport dst_port = pkt[TCP].dport elif proto_num == 17 and UDP in pkt: # UDP协议 src_port = pkt[UDP].sport dst_port = pkt[UDP].dport elif proto_num == 1 and ICMP in pkt: # ICMP协议 # 使用类型和代码代替端口 src_port = pkt[ICMP].type dst_port = pkt[ICMP].code # 创建五元组作为流的唯一标识 flow_key = (ip_layer.src, ip_layer.dst, src_port, dst_port, proto_num) flows[flow_key].append(pkt) # 将包添加到对应流 print(f"共处理 {packet_count} 个包,其中 {ip_packet_count} 个IP包") print(f"DNS缓存大小: {len(dns_cache)} 个IP到域名的映射") return flows # 解析PCAP文件 flows = parse_large_pcap(pcap_path) print(f"共识别出 {len(flows)} 个网络流") # ====================== # 2. 特征工程 # ====================== print("正在提取流量特征...") features = [] # 存储特征向量 valid_flows = [] # 存储有效流的标识 # 只初始化源时间列表 src_start_times = [] src_end_times = [] # 特征名称列表 feature_names = [ '包数量', '总字节数', '平均包间隔', '包间隔标准差', '平均包长', '包长标准差', '最小包长', '最大包长', '上行比例' ] min_packets = 5 # 流的最小包数阈值 for flow_key, packets in flows.items(): packet_count = len(packets) # 过滤包数不足的流 if packet_count < min_packets: continue # 1. 基础统计特征 total_bytes = sum(len(p) for p in packets) # 总字节数 # 2. 时序特征(包到达间隔) timestamps = np.array([float(p.time) for p in packets]) if packet_count > 1: iat = np.diff(timestamps) # 包间隔 mean_iat = np.mean(iat) # 平均间隔 std_iat = np.std(iat) if len(iat) > 1 else 0 # 间隔标准差 else: mean_iat = std_iat = 0 # 3. 包长特征 pkt_lengths = np.array([len(p) for p in packets]) mean_pkt_len = np.mean(pkt_lengths) # 平均包长 std_pkt_len = np.std(pkt_lengths) if packet_count > 1 else 0 # 包长标准差 min_pkt_len = np.min(pkt_lengths) # 最小包长 max_pkt_len = np.max(pkt_lengths) # 最大包长 # 4. 方向特征(上行比例) src_ip = flow_key[0] # 流源IP dst_ip = flow_key[1] # 流目的IP # 判断数据包方向(1=上行,0=下行) directions = np.array([1 if p[IP].src == src_ip else 0 for p in packets]) up_ratio = np.mean(directions) # 上行包比例 # 源IP发送的包的时间戳 src_pkt_times = [float(p.time) for p in packets if p[IP].src == src_ip] # 计算源的起始和终止时间 src_start = min(src_pkt_times) if src_pkt_times else np.nan src_end = max(src_pkt_times) if src_pkt_times else np.nan # 添加时间信息到列表 src_start_times.append(src_start) src_end_times.append(src_end) # 添加特征向量 features.append([ packet_count, total_bytes, mean_iat, std_iat, mean_pkt_len, std_pkt_len, min_pkt_len, max_pkt_len, up_ratio ]) valid_flows.append(flow_key) # 记录有效流 if not features: # 错误处理:无有效流 print("错误:没有提取到任何有效网络流") print("可能原因:") print("1. 所有流都少于5个包(尝试减小过滤阈值)") print("2. 文件格式不兼容(尝试用Wireshark打开验证)") print("3. 没有IP流量(检查网络捕获配置)") sys.exit(1) feature_matrix = np.array(features) print(f"提取了 {len(features)} 个有效流的 {len(feature_names)} 维特征") # ====================== # 3. 数据标准化 # ====================== print("正在进行数据标准化...") scaler = StandardScaler() scaled_features = scaler.fit_transform(feature_matrix) # Z-score标准化 # ====================== # 4. 肘部法则确定最佳K值 # ====================== print("使用肘部法则确定最佳聚类数...") n_samples = scaled_features.shape[0] # 样本数量 # 动态调整K值范围 if n_samples < 5: print(f"警告:样本数量较少({n_samples}),将使用简化聚类策略") k_range = range(1, min(11, n_samples + 1)) # K上限不超过样本数 else: k_range = range(1, 11) # 测试1-10个聚类 sse = [] # 存储SSE(误差平方和) kmeans_models = {} # 存储不同K值的模型 for k in k_range: # 跳过超过样本数的K值 if k > n_samples: print(f"跳过K={k}(超过样本数{n_samples})") continue # 训练K-means模型 kmeans = KMeans(n_clusters=k, n_init=10, random_state=42) kmeans.fit(scaled_features) sse.append(kmeans.inertia_) # 记录SSE kmeans_models[k] = kmeans # 存储模型 # 样本不足时的处理 if len(sse) < 2: optimal_k = min(2, n_samples) # 至少2类(不超过样本数) print(f"样本过少,直接设置K={optimal_k}") else: # 绘制肘部法则图 plt.figure(figsize=(10, 6)) plt.plot(list(k_range)[:len(sse)], sse, 'bo-') plt.xlabel('聚类数量 $ K$') plt.ylabel('SSE (误差平方和)') plt.title('肘部法则确定最佳聚类数') plt.grid(True) elbow_path = os.path.join(output_dir, 'elbow_method.png') plt.savefig(elbow_path, dpi=300) plt.close() print(f"肘部法则图已保存为 {elbow_path}") # 自动检测拐点(二阶差分最小值) if len(sse) >= 3: knee_point = np.argmin(np.diff(sse, 2)) + 2 # 计算二阶差分 optimal_k = max(2, min(10, knee_point)) # 确保K在2-10之间 else: optimal_k = max(2, min(3, n_samples)) # 样本少时选择2或3 print(f"自动检测的最佳聚类数: K = {optimal_k}") # ====================== # 5. K-means聚类 # ====================== print(f"正在进行K-means聚类 (K={optimal_k})...") # 调整K值不超过样本数 if optimal_k > n_samples: optimal_k = max(1, n_samples) print(f"调整聚类数为样本数: K = {optimal_k}") # 使用已有模型或新建模型 if optimal_k in kmeans_models: kmeans = kmeans_models[optimal_k] cluster_labels = kmeans.labels_ else: kmeans = KMeans(n_clusters=optimal_k, n_init=10, random_state=42) cluster_labels = kmeans.fit_predict(scaled_features) # 计算轮廓系数(K>1时) if optimal_k > 1: silhouette_avg = silhouette_score(scaled_features, cluster_labels) print(f"轮廓系数: {silhouette_avg:.4f} (接近1表示聚类效果良好)") else: print("聚类数为1,无需计算轮廓系数") # ====================== # 6. 结果分析与可视化(新增域名显示) # ====================== # 创建结果DataFrame result_df = pd.DataFrame({ '源IP': [flow[0] for flow in valid_flows], '目的IP': [flow[1] for flow in valid_flows], '协议': [f"TCP(6)" if flow[4] == 6 else f"UDP(17)" for flow in valid_flows], '聚类标签': cluster_labels, '源起始时间': src_start_times, '源终止时间': src_end_times, '包数量': feature_matrix[:, 0], '总字节数': feature_matrix[:, 1] }) # 新增:添加域名信息 def get_domains(ip): """获取IP对应的域名列表""" domains = dns_cache.get(ip, set()) return ", ".join(domains) if domains else "N/A" result_df['目的域名'] = result_df['目的IP'].apply(get_domains) # 新增:时间格式化函数 def format_timestamp(ts): """将时间戳格式化为可读字符串""" if np.isnan(ts): return "N/A" return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] # 保留毫秒 # 应用时间格式化 result_df['源起始时间'] = result_df['源起始时间'].apply(format_timestamp) result_df['源终止时间'] = result_df['源终止时间'].apply(format_timestamp) # 只保留需要的列 required_columns = ['源IP', '目的IP', '协议', '源起始时间', '源终止时间', '目的域名', '包数量', '总字节数'] result_df = result_df[required_columns] # 生成结果文件名(带时间戳) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_filename = f'cluster_result_{timestamp}.csv' output_path = os.path.join(output_dir, output_filename) try: result_df.to_csv(output_path, index=False, encoding='utf_8_sig') print(f"聚类结果已保存为 {output_path}") except Exception as e: print(f"保存结果文件失败: {str(e)}") # 尝试临时目录作为备选 temp_dir = os.path.join(os.environ.get('TEMP', ''), 'netflow_clustering') os.makedirs(temp_dir, exist_ok=True) temp_path = os.path.join(temp_dir, output_filename) result_df.to_csv(temp_path, index=False, encoding='utf_8_sig') print(f"聚类结果已保存为 {temp_path}") # 簇统计信息(使用聚类标签列) if '聚类标签' in result_df.columns: cluster_stats = result_df.groupby('聚类标签').agg({ '包数量': 'mean', '总字节数': 'mean' }).rename(columns={ '包数量': '平均包数量', '总字节数': '平均总字节数' }) print("\n各用户簇流量特征统计:") print(cluster_stats.round(2)) else: print("警告:结果中缺少聚类标签列,无法进行簇统计") # 使用PCA降维可视化 pca = PCA(n_components=2) # 降维到2D principal_components = pca.fit_transform(scaled_features) plt.figure(figsize=(12, 8)) scatter = plt.scatter( principal_components[:, 0], principal_components[:, 1], c=cluster_labels, # 按簇着色 cmap='viridis', alpha=0.6 ) # 标记簇中心 centers = pca.transform(kmeans.cluster_centers_) plt.scatter( centers[:, 0], centers[:, 1], c='red', s=200, alpha=0.9, marker='X', edgecolor='black' ) plt.colorbar(scatter, label='用户簇') plt.xlabel('主成分 1') plt.ylabel('主成分 2') plt.title(f'校园网用户流量聚类 (K={optimal_k})') plt.grid(alpha=0.3) # 可视化文件保存到输出目录 vis_filename = f'cluster_visualization_{timestamp}.png' vis_path = os.path.join(output_dir, vis_filename) plt.savefig(vis_path, dpi=300) plt.close() print(f"聚类可视化图已保存为 {vis_path}") # 最终输出 print("\n聚类完成! 结果文件已保存:") print(f"- {elbow_path}: 肘部法则图") print(f"- {output_path}: 详细聚类结果") print(f"- {vis_path}: 聚类可视化") 根据这段程序画出pcap分包流程

最新推荐

recommend-type

婚纱摄影公司网络推广人员工作绩效说明.docx

婚纱摄影公司网络推广人员工作绩效说明.docx
recommend-type

公路工程的项目管理分析.doc

公路工程的项目管理分析.doc
recommend-type

2025青海省道路路网矢量数据图层Shp数据最新版下载

2025青海省道路路网矢量数据图层,shp格式,包含多级道路分类属性,路名等属性,包含全省几十万条道路,坐标系为WGS1984坐标系统
recommend-type

项目管理机构配备情况-secret.doc

项目管理机构配备情况-secret.doc
recommend-type

常用HTML图标小集1:精选实用图标集合

资源下载链接为: https://pan.quark.cn/s/d9ef5828b597 这些HTML图标真是太美了,我打算好好收藏起来,以后说不定就能用上呢。
recommend-type

VC图像编程全面资料及程序汇总

【标题】:"精通VC图像编程资料全览" 【知识点】: VC即Visual C++,是微软公司推出的一个集成开发环境(IDE),专门用于C++语言的开发。VC图像编程涉及到如何在VC++开发环境中处理和操作图像。在VC图像编程中,开发者通常会使用到Windows API中的GDI(图形设备接口)或GDI+来进行图形绘制,以及DirectX中的Direct2D或DirectDraw进行更高级的图形处理。 1. GDI(图形设备接口): - GDI是Windows操作系统提供的一套应用程序接口,它允许应用程序通过设备无关的方式绘制图形。 - 在VC图像编程中,主要使用CDC类(设备上下文类)来调用GDI函数进行绘制,比如绘制线条、填充颜色、显示文本等。 - CDC类提供了很多函数,比如`MoveTo`、`LineTo`、`Rectangle`、`Ellipse`、`Polygon`等,用于绘制基本的图形。 - 对于图像处理,可以使用`StretchBlt`、`BitBlt`、`TransparentBlt`等函数进行图像的位块传输。 2. GDI+: - GDI+是GDI的后继技术,提供了更丰富的图形处理功能。 - GDI+通过使用`Graphics`类来提供图像的绘制、文本的渲染、图像的处理和颜色管理等功能。 - GDI+引入了对矢量图形、渐变色、复杂的文本格式和坐标空间等更高级的图形处理功能。 - `Image`类是GDI+中用于图像操作的基础类,通过它可以进行图像的加载、保存、旋转、缩放等操作。 3. DirectX: - DirectX是微软推出的一系列API集合,用于在Windows平台上进行高性能多媒体编程。 - DirectX中的Direct2D是用于硬件加速的二维图形API,专门用于UI元素和简单的图形渲染。 - DirectDraw主要用于硬件加速的位图操作,比如全屏游戏开发中的画面渲染。 4. 位图操作: - 在VC图像编程中,位图操作是一个重要的部分。需要了解如何加载、保存和处理位图(BMP)文件。 - 可以使用位图文件格式的解析,来访问位图的像素数据,进行像素级别的图像处理和修改。 5. 高级图像处理技术: - 包括图像滤镜、图像转换、图像压缩和解压缩技术。 - 需要掌握一些图像处理算法,比如卷积、FFT(快速傅里叶变换)、DCT(离散余弦变换)等。 - 了解图像的色彩空间转换,比如RGB到YUV的转换,这在视频处理中非常重要。 6. 图像库的使用: - 除了直接使用API进行图像处理之外,还可以使用开源的图像处理库,如OpenCV。 - OpenCV是一个跨平台的计算机视觉和机器学习软件库,它提供了很多高级的图像处理功能。 【压缩包子文件的文件名称列表】: VC++ 文件名称列表显示为"VC++",这表明文件包中可能包含了Visual C++的项目、源代码文件、动态链接库(DLLs)、编译器设置、链接器设置等。如果要使用这些文件进行VC图像编程,需要确保Visual C++开发环境已经安装并且配置好,同时需要安装好所有依赖的库和工具。 要开始VC图像编程,开发者需要具备C++编程基础,熟悉Windows编程概念,并且对图形学有一定的了解。掌握VC图像编程技巧,对于进行桌面应用程序开发、图像处理软件开发以及游戏开发等都是至关重要的。
recommend-type

Pokemmo响应速度翻倍:多线程处理的高级技巧

# 摘要 多线程处理是提高软件并发性能和响应速度的关键技术,本文从基础概念与优势出发,深入探讨了多线程的实现机制,包括线程模型、调度算法、编程关键技术、线程安全性最佳实践、死锁预防与处理等方面。通
recommend-type

人名列表滚动抽奖

<think>嗯,用户想实现一个滚动抽奖功能,需要前端动画效果和随机选择逻辑。首先,我得考虑前端怎么实现滚动效果。可能用CSS动画或者JavaScript的requestAnimationFrame?比如,使用CSS的@keyframes来控制位移,或者用JS动态更新样式。然后,随机选择算法,可能需要确保公平性,比如用Fisher-Yates洗牌算法,或者用Math.random()来生成随机索引。然后,用户可能需要平滑的滚动动画,比如先快速滚动,然后逐渐减速,最后停在选中的人名上。这可能需要设置定时器,逐步改变位置,或者使用CSS过渡效果。另外,还要考虑性能,避免页面卡顿,可能需要使用硬件加
recommend-type

一站式JSF开发环境:即解压即用JAR包

标题:“jsf开发完整JAR包”所指的知识点: 1. JSF全称JavaServer Faces,是Java EE(现EE4J)规范之一,用于简化Java Web应用中基于组件的用户界面构建。JSF提供了一种模型-视图-控制器(MVC)架构的实现,使得开发者可以将业务逻辑与页面表示分离。 2. “开发完整包”意味着这个JAR包包含了JSF开发所需的所有类库和资源文件。通常来说,一个完整的JSF包会包含核心的JSF库,以及一些可选的扩展库,例如PrimeFaces、RichFaces等,这些扩展库提供了额外的用户界面组件。 3. 在一个项目中使用JSF,开发者无需单独添加每个必要的JAR文件到项目的构建路径中。因为打包成一个完整的JAR包后,所有这些依赖都被整合在一起,极大地方便了开发者的部署工作。 4. “解压之后就可以直接导入工程中使用”表明这个JAR包是一个可执行的归档文件,可能是一个EAR包或者一个可直接部署的Java应用包。解压后,开发者只需将其内容导入到他们的IDE(如Eclipse或IntelliJ IDEA)中,或者将其放置在Web应用服务器的正确目录下,就可以立即进行开发。 描述中所指的知识点: 1. “解压之后就可以直接导入工程中使用”说明这个JAR包是预先配置好的,它可能包含了所有必要的配置文件,例如web.xml、faces-config.xml等,这些文件是JSF项目运行所必需的。 2. 直接使用意味着减少了开发者配置环境和处理依赖的时间,有助于提高开发效率。 标签“jsf jar包”所指的知识点: 1. 标签指明了JAR包的内容是专门针对JSF框架的。因此,这个JAR包包含了JSF规范所定义的API以及可能包含的具体实现,比如Mojarra或MyFaces。 2. “jar包”是一种Java平台的归档文件格式,用于聚合多个文件到一个文件中。在JSF开发中,JAR文件经常被用来打包和分发库或应用程序。 文件名称列表“jsf”所指的知识点: 1. “jsf”文件名可能意味着这是JSF开发的核心库,它应该包含了所有核心的JavaServer Faces类文件以及资源文件。 2. 如果是使用特定版本的JSF,例如“jsf-2.2.jar”,则表明文件内包含了对应版本的JSF实现。这种情况下,开发者必须确认他们所使用的Web服务器或应用程序服务器支持该版本的JSF。 3. 文件名称也可能是“jsf-components.jar”、“jsf-impl.jar”等,表明这个JAR包是JSF的一个子模块或特定功能组件。例如,“jsf-components.jar”可能包含了一系列用于在JSF应用中使用的自定义组件。 4. 对于开发者而言,了解文件名称中所蕴含的信息非常重要,因为这将决定他们需要下载哪些JAR包来满足特定项目的需求。 综合以上信息,开发者在使用JSF进行Java Web应用开发时,会通过一个预先配置好的JAR包来快速地搭建和启动项目。这样做不仅简化了项目初始化的过程,也使得开发者能够更加聚焦于业务逻辑的实现和界面设计,而不必深究底层框架配置的细节。
recommend-type

Pokemmo内存优化揭秘:专家教你如何降低50%资源消耗

# 摘要 本文综述了Pokemmo游戏的内存优化方法,从内存管理基础出发,探讨内存使用效率的影响因素,并介绍了性能监控与分析工具。在内存优化实践技巧章节中,详细讨论了代码层面的优化、数据结构和算法选择对内存效率的影响,并通过案例分析展示了实际的优化过程。针对Pokemmo游戏特点,分析了内存消耗特性并提出了特定优化技术。最后,本文展望了未来内存管理技术的发展方向,以及游戏开发中面临的新挑战,为Pokemmo及类似游戏提供了优化建议。 # 关键字 内存优化;内存管理;性能监控;数据结构;算法效率;游戏开发 参考资源链接:[Pokemmo必备资源包:四种ROM与汉化补丁](https://we