基于Dash实现ID选择及分时段双滑块过滤绘图功能
问题描述
我有一个单ID对应多条测量记录的数据集,构造代码如下:
import pandas as pd import numpy as np import random df = pd.DataFrame({'DATE_TIME':pd.date_range('2022-11-01', '2022-11-05 23:00:00',freq='20min'), 'SBP':[random.uniform(110, 160) for n in range(358)], 'DBP':[random.uniform(60, 100) for n in range(358)], 'ID':[random.randrange(1, 3) for n in range(358)], 'TIMEINTERVAL':[random.randrange(1, 200) for n in range(358)]}) df['VISIT'] = df['DATE_TIME'].dt.day df['MODE'] = np.select([df['VISIT']==1, df['VISIT'].isin([2,3])], ['CKD', 'Dialysis'], 'Late TPL') df['TIME'] = df['DATE_TIME'].dt.time df['TIME'] = df['TIME'].astype('str') def to_day_period(s): bins = ['0', '06:00:00', '13:00:00', '18:00:00', '23:00:00', '24:00:00'] labels = ['Night', 'Morning', 'Afternoon', 'Evening', 'Night'] return pd.cut( pd.to_timedelta(s), bins=list(map(pd.Timedelta, bins)), labels=labels, right=False, ordered=False ) df['TIME_OF_DAY'] = to_day_period(df['TIME'])
我已经用Dash实现了ID选择功能,以及单个RangeSlider对全时段TIMEINTERVAL的过滤绘图,现有代码如下:
from dash import Dash, html, dcc, Input, Output import pandas as pd import os import plotly.express as px # FUNCTION TO CHOOSE A SINGLE PATIENT def choose_patient(dataframe_name, id_number): return dataframe_name[dataframe_name['ID']==id_number] # FUNCTION TO CHOOSE A SINGLE PATIENT WITH A SINGLE VISIT def choose_patient_visit(dataframe_name, id_number,visit_number): return dataframe_name[(dataframe_name['ID']==id_number) & (dataframe_name['VISIT']==visit_number)] # READING THE DATA # 注意:原代码中pd.read_csv(df)有误,需替换为实际文件路径 df = pd.read_csv("your_data.csv",sep=',',parse_dates=['DATE_TIME'], infer_datetime_format=True) # ---------------------------------------------------- dash example ---------------------------------------------------- app = Dash(__name__) app.layout = html.Div([ html.H4('Interactive Scatter Plot'), dcc.Graph(id="scatter-plot",style={'width': '130vh', 'height': '80vh'}), html.P("Filter by time interval:"), dcc.Dropdown(df.ID.unique(), id='pandas-dropdown-1'), # for choosing ID, dcc.RangeSlider( id='range-slider', min=0, max=600, step=10, marks={0: '0', 50: '50', 100: '100', 150: '150', 200: '200', 250: '250', 300: '300', 350: '350', 400: '400', 450: '450', 500: '500', 550: '550', 600: '600'}, value=[0, 600] ), html.Div(id='dd-output-container') ]) @app.callback( Output("scatter-plot", "figure"), Input("pandas-dropdown-1", "value"), Input("range-slider", "value"), prevent_initial_call=True) def update_lineplot(value, slider_range): low, high = slider_range df1 = df.query("ID == @value & TIMEINTERVAL >= @low & TIMEINTERVAL < @high").copy() if df1.shape[0] != 0: fig = px.line(df1, x="DATE_TIME", y=["SBP", "DBP"], hover_data=['TIMEINTERVAL'], facet_col='VISIT', facet_col_wrap=2, symbol='MODE', facet_row_spacing=0.1, facet_col_spacing=0.09) fig.update_xaxes(matches=None, showticklabels=True) return fig else: return dash.no_update app.run_server(debug=True, use_reloader=False)
现在需要新增两个RangeSlider,分别针对TIME_OF_DAY为**Morning(06:00-17:59)和Night(18:00-05:59)**的数据进行TIMEINTERVAL范围过滤,请问如何实现?
解决方案
要实现分时段的TIMEINTERVAL过滤,需要从布局和回调逻辑两方面修改:
1. 修改Dash布局,新增两个RangeSlider
在原布局中添加针对Morning和Night的RangeSlider,每个滑块对应明确的时段说明,参数和原滑块保持一致:
app.layout = html.Div([ html.H4('Interactive Scatter Plot'), dcc.Graph(id="scatter-plot",style={'width': '130vh', 'height': '80vh'}), html.P("选择患者ID:"), dcc.Dropdown(df.ID.unique(), id='pandas-dropdown-1'), html.P("全时段TIMEINTERVAL过滤:"), dcc.RangeSlider( id='range-slider-all', min=0, max=600, step=10, marks={0: '0', 50: '50', 100: '100', 150: '150', 200: '200', 250: '250', 300: '300', 350: '350', 400: '400', 450: '450', 500: '500', 550: '550', 600: '600'}, value=[0, 600] ), html.P("Morning时段(06:00-17:59)TIMEINTERVAL过滤:"), dcc.RangeSlider( id='range-slider-morning', min=0, max=600, step=10, marks={0: '0', 50: '50', 100: '100', 150: '150', 200: '200', 250: '250', 300: '300', 350: '350', 400: '400', 450: '450', 500: '500', 550: '550', 600: '600'}, value=[0, 600] ), html.P("Night时段(18:00-05:59)TIMEINTERVAL过滤:"), dcc.RangeSlider( id='range-slider-night', min=0, max=600, step=10, marks={0: '0', 50: '50', 100: '100', 150: '150', 200: '200', 250: '250', 300: '300', 350: '350', 400: '400', 450: '450', 500: '500', 550: '550', 600: '600'}, value=[0, 600] ), html.Div(id='dd-output-container') ])
2. 修改回调函数,接收新输入并调整过滤逻辑
更新回调的Input参数,对不同时段的记录应用对应的滑块范围:
@app.callback( Output("scatter-plot", "figure"), Input("pandas-dropdown-1", "value"), Input("range-slider-all", "value"), Input("range-slider-morning", "value"), Input("range-slider-night", "value"), prevent_initial_call=True) def update_lineplot(patient_id, slider_all, slider_morning, slider_night): # 先筛选当前患者的数据 patient_df = df[df['ID'] == patient_id].copy() # Morning时段过滤 morning_low, morning_high = slider_morning morning_mask = (patient_df['TIME_OF_DAY'] == 'Morning') & \ (patient_df['TIMEINTERVAL'] >= morning_low) & \ (patient_df['TIMEINTERVAL'] < morning_high) # Night时段过滤 night_low, night_high = slider_night night_mask = (patient_df['TIME_OF_DAY'] == 'Night') & \ (patient_df['TIMEINTERVAL'] >= night_low) & \ (patient_df['TIMEINTERVAL'] < night_high) # 其他时段(Afternoon/Evening)使用全时段滑块 all_low, all_high = slider_all other_mask = (~patient_df['TIME_OF_DAY'].isin(['Morning', 'Night'])) & \ (patient_df['TIMEINTERVAL'] >= all_low) & \ (patient_df['TIMEINTERVAL'] < all_high) # 合并所有符合条件的记录 filtered_df = patient_df[morning_mask | night_mask | other_mask] if filtered_df.shape[0] != 0: fig = px.line(filtered_df, x="DATE_TIME", y=["SBP", "DBP"], hover_data=['TIMEINTERVAL', 'TIME_OF_DAY'], facet_col='VISIT', facet_col_wrap=2, symbol='MODE', color='TIME_OF_DAY', # 用颜色区分时段,更直观 facet_row_spacing=0.1, facet_col_spacing=0.09) fig.update_xaxes(matches=None, showticklabels=True) return fig else: return dash.no_update
3. 完整修正后的代码
from dash import Dash, html, dcc, Input, Output, no_update import pandas as pd import numpy as np import random import plotly.express as px # ---------------------- 构造数据集(如果是读取CSV则注释这部分) ---------------------- df = pd.DataFrame({'DATE_TIME':pd.date_range('2022-11-01', '2022-11-05 23:00:00',freq='20min'), 'SBP':[random.uniform(110, 160) for n in range(358)], 'DBP':[random.uniform(60, 100) for n in range(358)], 'ID':[random.randrange(1, 3) for n in range(358)], 'TIMEINTERVAL':[random.randrange(1, 200) for n in range(358)]}) df['VISIT'] = df['DATE_TIME'].dt.day df['MODE'] = np.select([df['VISIT']==1, df['VISIT'].isin([2,3])], ['CKD', 'Dialysis'], 'Late TPL') df['TIME'] = df['DATE_TIME'].dt.time df['TIME'] = df['TIME'].astype('str') def to_day_period(s): bins = ['0', '06:00:00', '13:00:00', '18:00:00', '23:00:00', '24:00:00'] labels = ['Night', 'Morning', 'Afternoon', 'Evening', 'Night'] return pd.cut( pd.to_timedelta(s), bins=list(map(pd.Timedelta, bins)), labels=labels, right=False, ordered=False ) df['TIME_OF_DAY'] = to_day_period(df['TIME']) # ---------------------- 数据集构造结束 ---------------------- # READING THE DATA(如果用CSV则取消注释) # df = pd.read_csv("your_data.csv",sep=',',parse_dates=['DATE_TIME'], infer_datetime_format=True) app = Dash(__name__) app.layout = html.Div([ html.H4('Interactive Scatter Plot'), dcc.Graph(id="scatter-plot",style={'width': '130vh', 'height': '80vh'}), html.P("选择患者ID:"), dcc.Dropdown(df.ID.unique(), id='pandas-dropdown-1'), html.P("全时段TIMEINTERVAL过滤:"), dcc.RangeSlider( id='range-slider-all', min=0, max=600, step=10, marks={0: '0', 50: '50', 100: '100', 150: '150', 200: '200', 250: '250', 300: '300', 350: '350', 400: '400', 450: '450', 500: '500', 550: '550', 600: '600'}, value=[0, 600] ), html.P("Morning时段(06:00-17:59)TIMEINTERVAL过滤:"), dcc.RangeSlider( id='range-slider-morning', min=0, max=600, step=10, marks={0: '0', 50: '50', 100: '100', 150: '150', 200: '200', 250: '250', 300: '300', 350: '350', 400: '400', 450: '450', 500: '500', 550: '550', 600: '600'}, value=[0, 600] ), html.P("Night时段(18:00-05:59)TIMEINTERVAL过滤:"), dcc.RangeSlider( id='range-slider-night', min=0, max=600, step=10, marks={0: '0', 50: '50', 100: '100', 150: '150', 200: '200', 250: '250', 300: '300', 350: '350', 400: '400', 450: '450', 500: '500', 550: '550', 600: '600'}, value=[0, 600] ), html.Div(id='dd-output-container') ]) @app.callback( Output("scatter-plot", "figure"), Input("pandas-dropdown-1", "value"), Input("range-slider-all", "value"), Input("range-slider-morning", "value"), Input("range-slider-night", "value"), prevent_initial_call=True) def update_lineplot(patient_id, slider_all, slider_morning, slider_night): patient_df = df[df['ID'] == patient_id].copy() # Morning时段过滤 morning_low, morning_high = slider_morning morning_mask = (patient_df['TIME_OF_DAY'] == 'Morning') & \ (patient_df['TIMEINTERVAL'] >= morning_low) & \ (patient_df['TIMEINTERVAL'] < morning_high) # Night时段过滤 night_low, night_high = slider_night night_mask = (patient_df['TIME_OF_DAY'] == 'Night') & \ (patient_df['TIMEINTERVAL'] >= night_low) & \ (patient_df['TIMEINTERVAL'] < night_high) # 其他时段用全时段滑块 all_low, all_high = slider_all other_mask = (~patient_df['TIME_OF_DAY'].isin(['Morning', 'Night'])) & \ (patient_df['TIMEINTERVAL'] >= all_low) & \ (patient_df['TIMEINTERVAL'] < all_high) filtered_df = patient_df[morning_mask | night_mask | other_mask] if filtered_df.shape[0] != 0: fig = px.line(filtered_df, x="DATE_TIME", y
相关产品推荐
相关产品推荐

