import streamlit as st import plotly.express as px import plotly.graph_objects as go import pandas as pd import numpy as np from datetime import datetime, timedelta from app.services.extruder_service import ExtruderService from app.services.main_process_service import MainProcessService from app.services.data_processing_service import DataProcessingService # 稳态识别类定义 class SteadyStateDetector: def __init__(self): self.data_processor = DataProcessingService() def preprocess_data(self, df, weight_col='metered_weight', window_size=20): if df is None or df.empty: return df df_processed = df.copy() df_processed[weight_col] = df_processed[weight_col].ffill().bfill() df_processed['smoothed_weight'] = df_processed[weight_col] df_processed['rolling_std'] = df_processed[weight_col].rolling(window=window_size, min_periods=1).std() df_processed['rolling_mean'] = df_processed[weight_col].rolling(window=window_size, min_periods=1).mean() return df_processed def detect_steady_state(self, df, weight_col='smoothed_weight', window_size=20, std_threshold=0.5, duration_threshold=60): if df is None or df.empty: return df, [] df['time'] = pd.to_datetime(df['time']) df['time_diff'] = df['time'].diff().dt.total_seconds().fillna(0) df['is_steady'] = 0 df['window_std'] = df['smoothed_weight'].rolling(window=window_size, min_periods=5).std() df['window_mean'] = df['smoothed_weight'].rolling(window=window_size, min_periods=5).mean() df['fluctuation_range'] = (df['window_std'] / df['window_mean']) * 100 df['fluctuation_range'] = df['fluctuation_range'].fillna(0) df.loc[(df['fluctuation_range'] < std_threshold) & (df['smoothed_weight'] >= 0.1), 'is_steady'] = 1 steady_segments = [] current_segment = {} for i, row in df.iterrows(): if row['is_steady'] == 1: if not current_segment: current_segment = { 'start_time': row['time'], 'start_idx': i, 'weights': [row['smoothed_weight']] } else: current_segment['weights'].append(row['smoothed_weight']) else: if current_segment: current_segment['end_time'] = df.loc[i-1, 'time'] if i > 0 else df.loc[i, 'time'] current_segment['end_idx'] = i-1 duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds() if duration >= duration_threshold: weights_array = np.array(current_segment['weights']) current_segment['duration'] = duration current_segment['mean_weight'] = np.mean(weights_array) current_segment['std_weight'] = np.std(weights_array) current_segment['min_weight'] = np.min(weights_array) current_segment['max_weight'] = np.max(weights_array) current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100 confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50 confidence = max(50, min(100, confidence)) current_segment['confidence'] = confidence steady_segments.append(current_segment) current_segment = {} if current_segment: current_segment['end_time'] = df['time'].iloc[-1] current_segment['end_idx'] = len(df) - 1 duration = (current_segment['end_time'] - current_segment['start_time']).total_seconds() if duration >= duration_threshold: weights_array = np.array(current_segment['weights']) current_segment['duration'] = duration current_segment['mean_weight'] = np.mean(weights_array) current_segment['std_weight'] = np.std(weights_array) current_segment['min_weight'] = np.min(weights_array) current_segment['max_weight'] = np.max(weights_array) current_segment['fluctuation_range'] = (current_segment['std_weight'] / current_segment['mean_weight']) * 100 confidence = 100 - (current_segment['fluctuation_range'] / std_threshold) * 50 confidence = max(50, min(100, confidence)) current_segment['confidence'] = confidence steady_segments.append(current_segment) for segment in steady_segments: df.loc[segment['start_idx']:segment['end_idx'], 'is_steady'] = 1 return df, steady_segments def get_steady_state_metrics(self, steady_segments): if not steady_segments: return {} avg_duration = np.mean([seg['duration'] for seg in steady_segments]) avg_fluctuation = np.mean([seg['fluctuation_range'] for seg in steady_segments]) avg_confidence = np.mean([seg['confidence'] for seg in steady_segments]) total_steady_duration = sum([seg['duration'] for seg in steady_segments]) return { 'total_steady_segments': len(steady_segments), 'average_steady_duration': avg_duration, 'average_fluctuation_range': avg_fluctuation, 'average_confidence': avg_confidence, 'total_steady_duration': total_steady_duration } def show_metered_weight_correlation(): # 初始化服务 extruder_service = ExtruderService() main_process_service = MainProcessService() # 页面标题 st.title("米重相关性分析") # 初始化服务 extruder_service = ExtruderService() main_process_service = MainProcessService() steady_state_detector = SteadyStateDetector() # 初始化会话状态用于日期同步 if 'mc_start_date' not in st.session_state: st.session_state['mc_start_date'] = datetime.now().date() - timedelta(days=7) if 'mc_end_date' not in st.session_state: st.session_state['mc_end_date'] = datetime.now().date() if 'mc_quick_select' not in st.session_state: st.session_state['mc_quick_select'] = "最近7天" if 'mc_time_offset' not in st.session_state: st.session_state['mc_time_offset'] = 0.0 # 初始化稳态识别相关参数 if 'mc_ss_window_size' not in st.session_state: st.session_state['mc_ss_window_size'] = 20 if 'mc_ss_std_threshold' not in st.session_state: st.session_state['mc_ss_std_threshold'] = 1.5 if 'mc_ss_duration_threshold' not in st.session_state: st.session_state['mc_ss_duration_threshold'] = 60 if 'mc_use_steady_only' not in st.session_state: st.session_state['mc_use_steady_only'] = False # 定义回调函数 def update_dates(qs): st.session_state['mc_quick_select'] = qs today = datetime.now().date() if qs == "今天": st.session_state['mc_start_date'] = today st.session_state['mc_end_date'] = today elif qs == "最近3天": st.session_state['mc_start_date'] = today - timedelta(days=3) st.session_state['mc_end_date'] = today elif qs == "最近7天": st.session_state['mc_start_date'] = today - timedelta(days=7) st.session_state['mc_end_date'] = today elif qs == "最近30天": st.session_state['mc_start_date'] = today - timedelta(days=30) st.session_state['mc_end_date'] = today def on_date_change(): st.session_state['mc_quick_select'] = "自定义" # 查询条件区域 with st.expander("🔍 查询配置", expanded=True): # 添加自定义 CSS 实现响应式换行 st.markdown(""" """, unsafe_allow_html=True) # 创建布局 cols = st.columns([1, 1, 1, 1, 1, 1.5, 1.5, 1]) options = ["今天", "最近3天", "最近7天", "最近30天", "自定义"] for i, option in enumerate(options): with cols[i]: # 根据当前选择状态决定按钮类型 button_type = "primary" if st.session_state['mc_quick_select'] == option else "secondary" if st.button(option, key=f"btn_mc_{option}", width='stretch', type=button_type): update_dates(option) st.rerun() with cols[5]: start_date = st.date_input( "开始日期", label_visibility="collapsed", key="mc_start_date", on_change=on_date_change ) with cols[6]: end_date = st.date_input( "结束日期", label_visibility="collapsed", key="mc_end_date", on_change=on_date_change ) with cols[7]: query_button = st.button("🚀 开始分析", key="mc_query", width='stretch') # 数据对齐调整 st.markdown("---") offset_cols = st.columns([2, 4, 2]) with offset_cols[0]: st.write("⏱️ **数据对齐调整**") with offset_cols[1]: time_offset = st.slider( "时间偏移 (分钟)", min_value=0.0, max_value=5.0, value=st.session_state['mc_time_offset'], step=0.1, help="调整主流程和温度数据的时间偏移,使其与挤出机米重数据对齐。" ) st.session_state['mc_time_offset'] = time_offset with offset_cols[2]: st.write(f"当前偏移: {time_offset} 分钟") # 稳态参数配置 st.markdown("---") steady_state_cols = st.columns(4) with steady_state_cols[0]: st.write("⚙️ **稳态参数配置**") window_size = st.slider( "滑动窗口大小 (秒)", min_value=5, max_value=60, value=st.session_state['mc_ss_window_size'], step=5, key="mc_ss_window_size", help="用于平滑数据和计算统计特征的滑动窗口大小" ) with steady_state_cols[1]: st.write("📏 **波动阈值配置**") std_threshold = st.slider( "标准差阈值", min_value=0.1, max_value=2.0, value=st.session_state['mc_ss_std_threshold'], step=0.1, key="mc_ss_std_threshold", help="米重波动的标准差阈值,低于此值视为稳态" ) with steady_state_cols[2]: st.write("⏱️ **持续时间配置**") duration_threshold = st.slider( "稳态持续时间 (秒)", min_value=30, max_value=300, value=st.session_state['mc_ss_duration_threshold'], step=10, key="mc_ss_duration_threshold", help="稳态持续的最小时间,低于此值不视为稳态段" ) with steady_state_cols[3]: st.write("🎯 **分析数据选择**") use_steady_only = st.checkbox( "仅使用稳态数据", value=st.session_state['mc_use_steady_only'], key="mc_use_steady_only", help="勾选后,相关性分析仅使用识别出的稳态数据" ) # 转换为datetime对象 start_dt = datetime.combine(start_date, datetime.min.time()) end_dt = datetime.combine(end_date, datetime.max.time()) # 查询处理 - 仅获取数据并缓存到会话状态 if query_button: with st.spinner("正在获取数据..."): # 1. 获取完整的挤出机数据(包含所有时间点的螺杆转速和机头压力) df_extruder_full = extruder_service.get_extruder_data(start_dt, end_dt) # 2. 获取主流程控制数据 df_main_speed = main_process_service.get_cutting_setting_data(start_dt, end_dt) df_temp = main_process_service.get_temperature_control_data(start_dt, end_dt) # 检查是否有数据 has_data = any([ df_extruder_full is not None and not df_extruder_full.empty, df_main_speed is not None and not df_main_speed.empty, df_temp is not None and not df_temp.empty ]) if not has_data: st.warning("所选时间段内未找到任何数据,请尝试调整查询条件。") # 清除缓存数据 for key in ['cached_extruder_full', 'cached_main_speed', 'cached_temp', 'last_query_start', 'last_query_end']: if key in st.session_state: del st.session_state[key] return # 缓存数据到会话状态 st.session_state['cached_extruder_full'] = df_extruder_full st.session_state['cached_main_speed'] = df_main_speed st.session_state['cached_temp'] = df_temp st.session_state['last_query_start'] = start_dt st.session_state['last_query_end'] = end_dt # 数据处理和图表渲染 - 每次应用重新运行时执行(包括调整时间偏移时) if all(key in st.session_state for key in ['cached_extruder_full', 'cached_main_speed', 'cached_temp']): with st.spinner("正在分析数据相关性..."): # 获取缓存数据 df_extruder_full = st.session_state['cached_extruder_full'] df_main_speed = st.session_state['cached_main_speed'] df_temp = st.session_state['cached_temp'] # 获取当前时间偏移量 offset_delta = timedelta(minutes=st.session_state['mc_time_offset']) # 处理数据 if df_extruder_full is not None and not df_extruder_full.empty: # 过滤机头压力大于2的值 df_extruder_filtered = df_extruder_full[df_extruder_full['head_pressure'] <= 2] # 为米重数据创建偏移后的时间列(只对米重数据进行时间偏移) df_extruder_filtered['weight_time'] = df_extruder_filtered['time'] - offset_delta else: df_extruder_filtered = None # 检查是否有数据 has_data = any([ df_extruder_filtered is not None and not df_extruder_filtered.empty, df_main_speed is not None and not df_main_speed.empty, df_temp is not None and not df_temp.empty ]) if not has_data: st.warning("所选时间段内未找到任何数据,请尝试调整查询条件。") return # 数据整合与预处理 def integrate_data(df_extruder_filtered, df_main_speed, df_temp): # 确保挤出机数据存在 if df_extruder_filtered is None or df_extruder_filtered.empty: return None # 创建只包含米重和偏移时间的主数据集 df_weight = df_extruder_filtered[['weight_time', 'metered_weight']].copy() df_weight.rename(columns={'weight_time': 'time'}, inplace=True) # 将weight_time重命名为time作为基准时间 # 创建包含螺杆转速和原始时间的完整数据集 # 注意:这里使用完整的螺杆转速数据,而不仅仅是与米重对应的数据点 df_screw = df_extruder_filtered[['time', 'screw_speed_actual']].copy() # 创建包含机头压力和原始时间的完整数据集 # 注意:这里使用完整的机头压力数据,而不仅仅是与米重对应的数据点 df_pressure = df_extruder_filtered[['time', 'head_pressure']].copy() # 使用偏移后的米重时间整合螺杆转速数据 # 关键:使用merge_asof根据偏移后的米重时间查找最接近的螺杆转速数据 df_merged = pd.merge_asof( df_weight.sort_values('time'), df_screw.sort_values('time'), on='time', direction='nearest', tolerance=pd.Timedelta('1min') ) # 使用偏移后的米重时间整合机头压力数据 # 关键:使用merge_asof根据偏移后的米重时间查找最接近的机头压力数据 df_merged = pd.merge_asof( df_merged.sort_values('time'), df_pressure.sort_values('time'), on='time', direction='nearest', tolerance=pd.Timedelta('1min') ) # 整合主流程数据 if df_main_speed is not None and not df_main_speed.empty: df_main_speed = df_main_speed[['time', 'process_main_speed']] df_merged = pd.merge_asof( df_merged.sort_values('time'), df_main_speed.sort_values('time'), on='time', direction='nearest', tolerance=pd.Timedelta('1min') ) # 整合温度数据 if df_temp is not None and not df_temp.empty: temp_cols = ['time', 'nakata_extruder_screw_display_temp', 'nakata_extruder_rear_barrel_display_temp', 'nakata_extruder_front_barrel_display_temp', 'nakata_extruder_head_display_temp'] df_temp_subset = df_temp[temp_cols].copy() df_merged = pd.merge_asof( df_merged.sort_values('time'), df_temp_subset.sort_values('time'), on='time', direction='nearest', tolerance=pd.Timedelta('1min') ) # 重命名列以提高可读性 df_merged.rename(columns={ 'screw_speed_actual': '螺杆转速', 'head_pressure': '机头压力', 'process_main_speed': '流程主速', 'nakata_extruder_screw_display_temp': '螺杆温度', 'nakata_extruder_rear_barrel_display_temp': '后机筒温度', 'nakata_extruder_front_barrel_display_temp': '前机筒温度', 'nakata_extruder_head_display_temp': '机头温度' }, inplace=True) # 清理数据 df_merged.dropna(subset=['metered_weight'], inplace=True) return df_merged # 执行稳态识别 df_extruder_steady, steady_segments = None, [] if df_extruder_filtered is not None and not df_extruder_filtered.empty: # 数据预处理 df_processed = steady_state_detector.preprocess_data( df_extruder_filtered, weight_col='metered_weight', window_size=st.session_state['mc_ss_window_size'] ) # 稳态检测 df_extruder_steady, steady_segments = steady_state_detector.detect_steady_state( df_processed, weight_col='smoothed_weight', window_size=st.session_state['mc_ss_window_size'], std_threshold=st.session_state['mc_ss_std_threshold'], duration_threshold=st.session_state['mc_ss_duration_threshold'] ) # 将稳态标记添加到df_extruder_filtered中,以便在趋势图中使用 df_extruder_filtered = df_extruder_filtered.merge( df_extruder_steady[['time', 'is_steady', 'smoothed_weight', 'fluctuation_range']], on='time', how='left' ) # 执行数据整合 df_analysis = integrate_data(df_extruder_filtered, df_main_speed, df_temp) if df_analysis is None or df_analysis.empty: st.warning("数据整合失败,请检查数据质量或调整时间范围。") return # 重命名米重列 df_analysis.rename(columns={'metered_weight': '米重'}, inplace=True) # 如果选择仅使用稳态数据,过滤掉非稳态数据 if st.session_state['mc_use_steady_only']: # 确保df_analysis包含is_steady列 if 'is_steady' not in df_analysis.columns: # 将稳态标记合并到分析数据中 df_analysis = df_analysis.merge( df_extruder_steady[['time', 'is_steady']], on='time', how='left' ) # 过滤稳态数据 df_analysis = df_analysis[df_analysis['is_steady'] == 1] if df_analysis.empty: st.warning("未找到稳态数据,请调整稳态参数或取消'仅使用稳态数据'选项。") return # 缓存稳态数据到会话状态 st.session_state['cached_steady_segments'] = steady_segments st.session_state['cached_extruder_steady'] = df_extruder_steady # --- 原始数据趋势图 --- st.subheader("📈 原始数据趋势图") # 创建趋势图 fig_trend = go.Figure() # 添加米重数据(使用偏移后的时间) if df_extruder_filtered is not None and not df_extruder_filtered.empty: fig_trend.add_trace(go.Scatter( x=df_extruder_filtered['weight_time'], # 使用偏移后的时间 y=df_extruder_filtered['metered_weight'], name='米重 (Kg/m) [已偏移]', mode='lines', line=dict(color='blue', width=2) )) # 添加螺杆转速(使用原始时间) fig_trend.add_trace(go.Scatter( x=df_extruder_filtered['time'], # 使用原始时间 y=df_extruder_filtered['screw_speed_actual'], name='螺杆转速 (RPM)', mode='lines', line=dict(color='green', width=1.5), yaxis='y2' )) # 添加机头压力(使用原始时间,已过滤大于2的值) fig_trend.add_trace(go.Scatter( x=df_extruder_filtered['time'], # 使用原始时间 y=df_extruder_filtered['head_pressure'], name='机头压力 (≤2)', mode='lines', line=dict(color='orange', width=1.5), yaxis='y3' )) # 添加流程主速 if df_main_speed is not None and not df_main_speed.empty: fig_trend.add_trace(go.Scatter( x=df_main_speed['time'], y=df_main_speed['process_main_speed'], name='流程主速 (M/Min)', mode='lines', line=dict(color='red', width=1.5), yaxis='y4' )) # 添加温度数据 if df_temp is not None and not df_temp.empty: # 螺杆温度 fig_trend.add_trace(go.Scatter( x=df_temp['time'], y=df_temp['nakata_extruder_screw_display_temp'], name='螺杆温度 (°C)', mode='lines', line=dict(color='purple', width=1), yaxis='y5' )) # 后机筒温度 fig_trend.add_trace(go.Scatter( x=df_temp['time'], y=df_temp['nakata_extruder_rear_barrel_display_temp'], name='后机筒温度 (°C)', mode='lines', line=dict(color='pink', width=1), yaxis='y5' )) # 前机筒温度 fig_trend.add_trace(go.Scatter( x=df_temp['time'], y=df_temp['nakata_extruder_front_barrel_display_temp'], name='前机筒温度 (°C)', mode='lines', line=dict(color='brown', width=1), yaxis='y5' )) # 机头温度 fig_trend.add_trace(go.Scatter( x=df_temp['time'], y=df_temp['nakata_extruder_head_display_temp'], name='机头温度 (°C)', mode='lines', line=dict(color='gray', width=1), yaxis='y5' )) # 添加稳态区域标记 for segment in steady_segments: # 获取米重数据的y轴范围,用于确定矩形高度 y_min = df_extruder_filtered['metered_weight'].min() * 0.95 y_max = df_extruder_filtered['metered_weight'].max() * 1.05 fig_trend.add_shape( type="rect", x0=segment['start_time'], y0=y_min, x1=segment['end_time'], y1=y_max, fillcolor="rgba(0, 255, 0, 0.2)", line=dict(color="rgba(0, 200, 0, 0.5)", width=1), name="稳态区域", layer="below" ) # 配置趋势图布局 fig_trend.update_layout( title=f'原始数据趋势 (米重向前偏移 {st.session_state["mc_time_offset"]} 分钟)', xaxis=dict( title='时间', rangeslider=dict(visible=True), type='date' ), yaxis=dict( title='米重 (Kg/m)', title_font=dict(color='blue'), tickfont=dict(color='blue') ), yaxis2=dict( title='螺杆转速 (RPM)', title_font=dict(color='green'), tickfont=dict(color='green'), overlaying='y', side='right' ), yaxis3=dict( title='机头压力', title_font=dict(color='orange'), tickfont=dict(color='orange'), overlaying='y', side='right', anchor='free', position=0.85 ), yaxis4=dict( title='流程主速 (M/Min)', title_font=dict(color='red'), tickfont=dict(color='red'), overlaying='y', side='right', anchor='free', position=0.75 ), yaxis5=dict( title='温度 (°C)', title_font=dict(color='purple'), tickfont=dict(color='purple'), overlaying='y', side='left', anchor='free', position=0.15 ), legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ), height=600, margin=dict(l=100, r=200, t=100, b=100), hovermode='x unified', dragmode='select', ) # 显示趋势图 selection = st.plotly_chart(fig_trend, width='stretch', config={'scrollZoom': True}, on_select='rerun' ) # 稳态统计指标 st.subheader("📊 稳态识别统计") steady_metrics = steady_state_detector.get_steady_state_metrics(steady_segments) metrics_cols = st.columns(5) with metrics_cols[0]: st.metric( "稳态段总数", steady_metrics.get('total_steady_segments', 0), help="识别到的稳态段数量" ) with metrics_cols[1]: st.metric( "平均稳态时长", f"{steady_metrics.get('average_steady_duration', 0):.2f} 秒", help="所有稳态段的平均持续时间" ) with metrics_cols[2]: st.metric( "平均波动范围", f"{steady_metrics.get('average_fluctuation_range', 0):.2f}%", help="稳态段内米重的平均波动范围(相对于均值的百分比)" ) with metrics_cols[3]: st.metric( "平均置信度", f"{steady_metrics.get('average_confidence', 0):.1f}%", help="稳态识别结果的平均置信度" ) with metrics_cols[4]: st.metric( "总稳态时长", f"{steady_metrics.get('total_steady_duration', 0)/60:.2f} 分钟", help="所有稳态段的总持续时间" ) # --- 稳态数据趋势图 --- st.subheader("📊 稳态数据趋势图") # 创建稳态数据趋势图 if df_extruder_steady is not None and not df_extruder_steady.empty: fig_steady = go.Figure() # 添加原始米重数据 fig_steady.add_trace(go.Scatter( x=df_extruder_steady['time'], y=df_extruder_steady['metered_weight'], name='原始米重', mode='lines', opacity=0.6, line=dict(color='lightblue', width=1) )) # 添加平滑后的米重数据 fig_steady.add_trace(go.Scatter( x=df_extruder_steady['time'], y=df_extruder_steady['smoothed_weight'], name='平滑米重', mode='lines', line=dict(color='blue', width=2) )) # 添加波动范围(作为面积图) fig_steady.add_trace(go.Scatter( x=df_extruder_steady['time'], y=df_extruder_steady['metered_weight'] + df_extruder_steady['rolling_std'], name='波动上限', mode='lines', line=dict(color='rgba(255,0,0,0)'), showlegend=True )) fig_steady.add_trace(go.Scatter( x=df_extruder_steady['time'], y=df_extruder_steady['metered_weight'] - df_extruder_steady['rolling_std'], name='波动下限', mode='lines', line=dict(color='rgba(255,0,0,0)'), fill='tonexty', fillcolor='rgba(255,0,0,0.1)' )) # 添加稳态标记 fig_steady.add_trace(go.Scatter( x=df_extruder_steady['time'], y=df_extruder_steady['is_steady'] * (df_extruder_steady['metered_weight'].max() * 1.1), name='稳态标记', mode='lines', line=dict(color='green', width=1, dash='dash') )) # 配置稳态数据趋势图布局 fig_steady.update_layout( title="稳态数据趋势分析", xaxis=dict( title='时间', rangeslider=dict(visible=True), type='date' ), yaxis=dict( title='米重 (Kg/m)', title_font=dict(color='blue'), tickfont=dict(color='blue') ), yaxis2=dict( title='稳态标记', title_font=dict(color='green'), tickfont=dict(color='green'), overlaying='y', side='right', range=[-0.1, 1.1], showgrid=False ), legend=dict( orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1 ), height=500, margin=dict(l=100, r=100, t=100, b=100), hovermode='x unified' ) # 显示稳态数据趋势图 st.plotly_chart(fig_steady, width='stretch', config={'scrollZoom': True}) # --- 稳态参数相关性趋势图 --- st.subheader("📈 稳态参数相关性趋势图") if df_analysis is not None and not df_analysis.empty: # 创建稳态参数相关性趋势图 fig_steady_corr = go.Figure() # 选择相关系数最高的前3个参数 if '米重' in df_analysis.columns: # 计算各参数与米重的相关系数 corr_with_weight = df_analysis.corr()[['米重']].sort_values('米重', ascending=False) top_params = corr_with_weight.index[1:4] # 排除米重本身,取前3个 # 添加米重数据 fig_steady_corr.add_trace(go.Scatter( x=df_analysis['time'], y=df_analysis['米重'], name='米重', mode='lines', line=dict(color='blue', width=2), yaxis='y' )) # 添加相关参数数据 colors = ['red', 'green', 'orange'] for i, param in enumerate(top_params): if param in df_analysis.columns: fig_steady_corr.add_trace(go.Scatter( x=df_analysis['time'], y=df_analysis[param], name=param, mode='lines', line=dict(color=colors[i], width=1.5), yaxis=f'y{i+2}' )) # 配置图表布局 layout = { 'title': f'米重与相关参数趋势(前3个相关参数)', 'xaxis': { 'title': '时间', 'rangeslider': dict(visible=True), 'type': 'date' }, 'yaxis': { 'title': '米重 (Kg/m)', 'title_font': dict(color='blue'), 'tickfont': dict(color='blue') }, 'legend': { 'orientation': "h", 'yanchor': "bottom", 'y': 1.02, 'xanchor': "right", 'x': 1 }, 'height': 600, 'margin': dict(l=100, r=200, t=100, b=100), 'hovermode': 'x unified' } # 添加额外的y轴配置 for i, param in enumerate(top_params): layout[f'yaxis{i+2}'] = { 'title': param, 'title_font': dict(color=colors[i]), 'tickfont': dict(color=colors[i]), 'overlaying': 'y', 'side': 'right', 'anchor': 'free', 'position': 1 - (i+1)*0.15 } fig_steady_corr.update_layout(layout) st.plotly_chart(fig_steady_corr, width='stretch', config={'scrollZoom': True}) # 调试输出 # st.write("原始 selection 对象:", selection) # 定义分析列 analysis_cols = ['米重', '螺杆转速', '机头压力', '流程主速', '螺杆温度', '后机筒温度', '前机筒温度', '机头温度'] # 定义要分析的参数 params = [ ('螺杆转速', 'RPM'), ('机头压力', ''), ('流程主速', 'M/Min'), ('螺杆温度', '°C'), ('后机筒温度', '°C'), ('前机筒温度', '°C'), ('机头温度', '°C') ] # 正确提取 selected_data = None if selection.selection and selection.selection.box: boxs = selection.selection.box # 获取选中框的x轴范围 x_range = boxs[0]['x'][0], boxs[0]['x'][1] st.write("x轴范围:", x_range) # 过滤出在x轴范围内的数据 # 注意:这里需要使用df_analysis的time列进行过滤 # 首先需要确保df_analysis有time列 if 'time' in df_analysis.columns: selected_data = df_analysis[ (df_analysis['time'] >= x_range[0]) & (df_analysis['time'] <= x_range[1]) ].copy() # 使用copy()避免切片警告 st.write(f"选中范围内的数据点数量: {len(selected_data)}") # 显示可用的列名,帮助调试 st.write("可用列名:", list(selected_data.columns)) else: st.warning("数据中缺少time列,无法进行范围过滤") else: st.info("请使用矩形框选工具选择时间范围(已自动启用选择模式)") # 添加细节分析按钮 if selected_data is not None and not selected_data.empty: if st.button("🔍 细节分析"): st.subheader("📊 框选范围细节分析") # 计算选中范围内的相关系数矩阵 selected_corr_matrix = selected_data[analysis_cols].corr() # 创建选中范围的热力图 selected_fig_heatmap = px.imshow( selected_corr_matrix, text_auto=True, aspect="auto", title="框选范围参数相关性矩阵", color_continuous_scale=["#0000FF", "#FFFFFF", "#FF0000"], color_continuous_midpoint=0, labels=dict(color="相关系数") ) # 自定义布局 selected_fig_heatmap.update_layout( height=400, margin=dict(l=80, r=80, t=80, b=80), xaxis=dict(tickangle=-45), yaxis=dict(tickangle=0) ) # 显示选中范围的热力图 st.plotly_chart(selected_fig_heatmap, width='stretch') # 显示选中范围的参数与米重散点图 st.subheader("📈 框选范围参数与米重散点图") # 创建选中范围的散点图 for i in range(0, len(params), 2): row_cols = st.columns(2) for j in range(2): if i + j < len(params): param_name, unit = params[i + j] with row_cols[j]: if param_name in selected_data.columns: # 计算相关系数(添加错误处理) try: # 过滤掉NaN值 valid_data = selected_data[[param_name, '米重']].dropna() if len(valid_data) >= 2: # 至少需要2个数据点 corr_coef = np.corrcoef(valid_data['米重'], valid_data[param_name])[0, 1] else: corr_coef = None except Exception as e: corr_coef = None # 创建散点图 fig_scatter = px.scatter( selected_data, x=param_name, y='米重', title=f"{param_name} vs 米重(框选范围)", labels={param_name: f"{param_name} ({unit})" if unit else param_name, '米重': '米重 (Kg/m)'} ) # 添加趋势线(添加错误处理) try: # 过滤掉NaN值 valid_data = selected_data[[param_name, '米重']].dropna() if len(valid_data) >= 2: # 至少需要2个数据点 trend_line = np.poly1d(np.polyfit(valid_data[param_name], valid_data['米重'], 1))(valid_data[param_name]) fig_scatter.add_trace(go.Scatter( x=valid_data[param_name], y=trend_line, mode='lines', name='趋势线', line=dict(color='red', width=2) )) except Exception as e: # 如果趋势线计算失败,跳过添加趋势线 pass # 添加相关系数注释(添加错误处理) if corr_coef is not None: fig_scatter.add_annotation( x=0.05, y=0.95, xref='paper', yref='paper', text=f"相关系数: {corr_coef:.4f}", showarrow=False, font=dict(size=12, color="black"), bgcolor="white", bordercolor="black", borderwidth=1 ) else: fig_scatter.add_annotation( x=0.05, y=0.95, xref='paper', yref='paper', text="相关系数: 无法计算", showarrow=False, font=dict(size=12, color="black"), bgcolor="white", bordercolor="black", borderwidth=1 ) # 显示散点图 st.plotly_chart(fig_scatter, use_container_width=True) else: st.warning(f"数据中缺少 {param_name} 列") # 显示选中范围的数据摘要 st.subheader("📊 框选范围数据摘要") selected_summary_cols = st.columns(4) with selected_summary_cols[0]: if '米重' in selected_data.columns: st.metric("平均米重", f"{selected_data['米重'].mean():.2f} Kg/m") with selected_summary_cols[1]: if '螺杆转速' in selected_data.columns: st.metric("平均螺杆转速", f"{selected_data['螺杆转速'].mean():.2f} RPM") with selected_summary_cols[2]: if '流程主速' in selected_data.columns: st.metric("平均流程主速", f"{selected_data['流程主速'].mean():.2f} M/Min") with selected_summary_cols[3]: if '机头压力' in selected_data.columns: st.metric("平均机头压力", f"{selected_data['机头压力'].mean():.2f}") # 显示选中范围的数据预览 st.subheader("🔍 框选范围数据预览") st.dataframe(selected_data[analysis_cols].head(10), use_container_width=True) # --- 相关性矩阵热力图 --- st.subheader("📊 相关性矩阵热力图") # 重命名米重列 df_analysis.rename(columns={'metered_weight': '米重'}, inplace=True) # 计算相关系数矩阵 corr_matrix = df_analysis[analysis_cols].corr() # 创建热力图 fig_heatmap = px.imshow( corr_matrix, text_auto=True, aspect="auto", title="参数相关性矩阵", color_continuous_scale=["#0000FF", "#FFFFFF", "#FF0000"], color_continuous_midpoint=0, labels=dict(color="相关系数") ) # 自定义布局 fig_heatmap.update_layout( height=500, margin=dict(l=100, r=100, t=100, b=100), xaxis=dict(tickangle=-45), yaxis=dict(tickangle=0) ) # 显示热力图 st.plotly_chart(fig_heatmap, width='stretch') # --- 参数与米重散点图 --- st.subheader("📈 参数与米重散点图") # 创建散点图 for i in range(0, len(params), 2): row_cols = st.columns(2) for j in range(2): if i + j < len(params): param_name, unit = params[i + j] with row_cols[j]: if param_name in df_analysis.columns: # 计算相关系数(添加错误处理) try: # 过滤掉NaN值 valid_data = df_analysis[[param_name, '米重']].dropna() if len(valid_data) >= 2: # 至少需要2个数据点 corr_coef = np.corrcoef(valid_data['米重'], valid_data[param_name])[0, 1] else: corr_coef = None except Exception as e: corr_coef = None # 创建散点图 fig_scatter = px.scatter( df_analysis, x=param_name, y='米重', title=f"{param_name} vs 米重", labels={param_name: f"{param_name} ({unit})" if unit else param_name, '米重': '米重 (Kg/m)'} ) # 添加趋势线(添加错误处理) try: # 过滤掉NaN值 valid_data = df_analysis[[param_name, '米重']].dropna() if len(valid_data) >= 2: # 至少需要2个数据点 trend_line = np.poly1d(np.polyfit(valid_data[param_name], valid_data['米重'], 1))(valid_data[param_name]) fig_scatter.add_trace(go.Scatter( x=valid_data[param_name], y=trend_line, mode='lines', name='趋势线', line=dict(color='red', width=2) )) except Exception as e: # 如果趋势线计算失败,跳过添加趋势线 pass # 添加相关系数注释(添加错误处理) if corr_coef is not None: fig_scatter.add_annotation( x=0.05, y=0.95, xref='paper', yref='paper', text=f"相关系数: {corr_coef:.4f}", showarrow=False, font=dict(size=12, color="black"), bgcolor="white", bordercolor="black", borderwidth=1 ) else: fig_scatter.add_annotation( x=0.05, y=0.95, xref='paper', yref='paper', text="相关系数: 无法计算", showarrow=False, font=dict(size=12, color="black"), bgcolor="white", bordercolor="black", borderwidth=1 ) # 显示散点图 st.plotly_chart(fig_scatter, use_container_width=True) else: st.warning(f"数据中缺少 {param_name} 列") # --- 相关性统计表格 --- st.subheader("📋 相关性统计") # 计算每个参数与米重的相关系数(添加错误处理) corr_stats = [] for param_name, _ in params: if param_name in df_analysis.columns: try: # 过滤掉NaN值 valid_data = df_analysis[[param_name, '米重']].dropna() if len(valid_data) >= 2: # 至少需要2个数据点 corr_coef = np.corrcoef(valid_data['米重'], valid_data[param_name])[0, 1] corr_stats.append({ '参数': param_name, '相关系数': corr_coef, '相关程度': '强' if abs(corr_coef) > 0.7 else '中等' if abs(corr_coef) > 0.3 else '弱' }) else: corr_stats.append({ '参数': param_name, '相关系数': None, '相关程度': '无法计算' }) except Exception as e: corr_stats.append({ '参数': param_name, '相关系数': None, '相关程度': '无法计算' }) # 创建统计表格 corr_df = pd.DataFrame(corr_stats) # 按相关系数绝对值排序(处理None值) try: # 计算相关系数绝对值,对于None值使用-1(这样会排在最后) corr_df['相关系数绝对值'] = corr_df['相关系数'].apply(lambda x: abs(x) if x is not None else -1) corr_df.sort_values('相关系数绝对值', ascending=False, inplace=True) corr_df.drop('相关系数绝对值', axis=1, inplace=True) except Exception as e: # 如果排序失败,保持原始顺序 pass # 显示表格 st.dataframe(corr_df, use_container_width=True) # --- 数据摘要 --- # st.subheader("📊 数据摘要") # summary_cols = st.columns(4) # with summary_cols[0]: # if '米重' in df_analysis.columns: # st.metric("平均米重", f"{df_analysis['米重'].mean():.2f} Kg/m") # with summary_cols[1]: # if '螺杆转速' in df_analysis.columns: # st.metric("平均螺杆转速", f"{df_analysis['螺杆转速'].mean():.2f} RPM") # with summary_cols[2]: # if '流程主速' in df_analysis.columns: # st.metric("平均流程主速", f"{df_analysis['流程主速'].mean():.2f} M/Min") # with summary_cols[3]: # if '机头压力' in df_analysis.columns: # st.metric("平均机头压力", f"{df_analysis['机头压力'].mean():.2f}") # --- 数据预览 --- st.subheader("🔍 数据预览") st.dataframe(df_analysis[analysis_cols].head(20), use_container_width=True) else: # 提示用户点击开始分析按钮 st.info("请选择时间范围并点击'开始分析'按钮获取数据。")