Dash Choosing an ID and then Make Plot with Multiple Sliders

Question:

I have a dataset which is similar to below one. Please note that there are multiple values for a single 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'])

I would like to use Dash so that I can firstly choose the ID, and then make a plot of that chosen ID. Besides, I made a slider to choose the time interval between measurements in terms of minutes. This slider should work for Morning and Night values separately. So, I have already implemented a slider which works for all day and night times. I would like to have two sliders, one for Morning values from 06:00 until 17:59, and other one called Night from 18 until 05:59.

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
df = pd.read_csv(df,sep=',',parse_dates=['DATE_TIME','DATE'], 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)


How can I implement such two sliders? One slider should work for Night values of TIME_OF_DAY column and another one for Morning values of TIME_OF_DAY column. I look at Dash website, but there is no such tool available.

Asked By: dspractician

||

Answers:

The solution below only requires vey minor modifications to your example code. It essentially filters the original dataframe twice (once for TIME_OF_DAY=’Night’, and once for TIME_OF_DAY=’Morning’) and concatenates them before plotting.

I’ve also modified the bins in the to_day_period function to only produce two labels that match the request in the text ("Morning values from 06:00 until 17:59, and […] Night from 18 until 05:59."), but the dash code can similarily be used for more categories.

    bins = ['0', '06:00:00', '18:00:00', '24:00:00']
    labels = ['Night', 'Morning', 'Night']

Dash code

app = Dash(__name__)

app.layout = html.Div([
    html.H4('Interactive Scatter Plot with ABPM dataset'),
    html.P("Select ID:"),
    dcc.Dropdown(df.ID.unique(), id='pandas-dropdown-1'),  # for choosing ID,
    html.P("Filter by time interval during nighttime (18:00-6:00):"),
    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.P("Filter by time interval during daytime (6:00-18:00):"),
    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]
    ),
    dcc.Graph(id="scatter-plot", style={'width': '130vh', 'height': '80vh'}),
    html.Div(id='dd-output-container')
])


@app.callback(
    Output("scatter-plot", "figure"),
    Input("pandas-dropdown-1", "value"),
    Input("range-slider-night", "value"),
    Input("range-slider-morning", "value"),
    prevent_initial_call=True)

def update_lineplot(value, slider_range_night, slider_range_morning):
    low_night, high_night = slider_range_night
    low_morning, high_morning = slider_range_morning
    df_night = df.query("ID == @value & TIME_OF_DAY == 'Night' & TIMEINTERVAL >= @low_night & TIMEINTERVAL < @high_night").copy()
    df_morning = df.query("ID == @value & TIME_OF_DAY == 'Morning' & TIMEINTERVAL >= @low_morning & TIMEINTERVAL < @high_morning").copy()
    df1 = pd.concat([df_night, df_morning], axis=0).sort_values(['TIME'])

    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 no_update

app.run_server(debug=True, use_reloader=False)

Application

In the screenshot below, all the "Morning"/"Daytime" observations have been filtered for TIMEINTERVAL, while the "Night" observations remain unaffected:

enter image description here

Answered By: pholzm
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.