Extra Dates Created in range of dates for slider

Hello,

I’m having a really weird problem that I’m not sure how to solve. I’m building a COVID19 dashboard in Plotly Dash and I want to report the number of cumulative cases and cumulative deaths globally by date. I have two cards that display these numbers and I want them to update based on the date selected in my slider. Here’s an example of the code:

#Import packages
import pandas as pd
import numpy as np
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import time


who_data = pd.read_csv("https://covid19.who.int/WHO-COVID-19-global-data.csv")


who_data.rename(columns={'New_cases': 'New Cases', 'Cumulative_cases': 'Cumulative Cases', 'New_deaths': 'New Deaths','Cumulative_deaths': 'Cumulative Deaths'}, inplace=True)

who_data['Date_reported_string'] = who_data['Date_reported'].copy()
who_data['Date_reported'] = who_data['Date_reported'].str.replace('-', '').astype(int)


card1_body = dbc.CardBody([html.H4("Card title", className="card-title",id="card_num1"),
                  html.P("Cumulative Cases for Date", className="card-text",id="card_text1")
                 ],
                 style={'display': 'inline-block',
                        'text-align': 'center',
                        'color':'white',
                        'background-color': 'rgba(37, 150, 190)'})

card2_body = dbc.CardBody([html.H4("Card title", className="card-title",id="card_num2"),
                  html.P("Cumulative Deaths for Date", className="card-text",id="card_text2")
                 ],
                 style={'display': 'inline-block',
                        'text-align': 'center',
                        'color':'white',
                        'background-color': 'rgba(37, 150, 190)'})

card1 = dbc.Card(card1_body,outline=True)
card2 = dbc.Card(card2_body,outline=True)



app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = html.Div([
    dcc.Tabs([
        
        dcc.Tab(label='Spread',
                children=[
                    html.Div([
                       
                        dcc.Slider(id='slider',
                                         min = who_data['Date_reported'].min(),
                                         max = who_data['Date_reported'].max(),                                   
                                         value = who_data['Date_reported'].min()#,
                                        
#                                          marks={each : {"label": who_data['Date_reported_string'], 
#                                                         "style": {"transform": "rotate(45deg)"}} 
#                                                 for each, who_data['Date_reported_string']
#                                                 in enumerate(who_data['Date_reported'])},


                                       ),
                         dbc.Row(id="card_row",children=[dbc.Col(card1),
                                                         dbc.Col(card2)
                                                        ])
                    ])
        ])
    ])
])


@app.callback(
    Output('card_row','children'),
    Input('slider','value')
)

def update_cards(date_select):
    
    
    date_df = who_data[who_data['Date_reported']<=date_select]
    
    tot_cases = f"{date_df['New Cases'].sum():,.0f}"
    tot_deaths = f"{date_df['New Deaths'].sum():,.0f}"
    
    card1 = dbc.Card([
        dbc.CardBody([
            html.H4(tot_cases, className="card-title"),
            html.P(f"Cumulative Cases on {date_select}")

        ])
    ],
    style={'display': 'inline-block',
           'width': '50%',
           'text-align': 'center',
           'background-color': 'rgba(37, 150, 190)',
           'color':'white',
           'fontWeight': 'bold',
           'fontSize':20},
    outline=True)
    
    card2 = dbc.Card([
        dbc.CardBody([
            html.H4(tot_deaths, className="card-title"),
            html.P(f"Cumulative Deaths on {date_select}")
        ])
    ],
    style={'display': 'inline-block',
           'width': '50%',
           'text-align': 'center',
           'background-color': 'rgba(37, 150, 190)',
           'color':'white',
           'fontWeight': 'bold',
           'fontSize':20},
    outline=True)
    
    return (card1, card2)


app.run_server(host='0.0.0.0',port='8050')

If you try using the slider, it will filter the dataset appropriately, but since I converted the date to an integer value, the increments for which slider step through are not the same as moving through each unique value in who_data[‘Date_reported’]. Ideally, I would like to have the slider read dates as follows, “20200330, 20200331, 20200401,…” But instead, I’m getting, “20200330, 20200331, 20200332,…”. It will literally step the date value up by one. Not sure how this happened or even how to fix it - I’m not getting any errors.

Does anyone know how to fix this? Any help would be appreciated!

Looks like you had a couple of issues with your code. Here’s the fixed version with slight changes. You should be able to spot the changes pretty easily but if you are not clear, feel free to ask:

#Import packages
from datetime import date
from numpy.lib.function_base import median
import pandas as pd
import numpy as np
import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import time


who_data = pd.read_csv("https://covid19.who.int/WHO-COVID-19-global-data.csv")


who_data.rename(columns={'New_cases': 'New Cases', 'Cumulative_cases': 'Cumulative Cases', 'New_deaths': 'New Deaths','Cumulative_deaths': 'Cumulative Deaths'}, inplace=True)

who_data['Date_reported_string'] = who_data['Date_reported'].copy()
#who_data['Date_reported'] = who_data['Date_reported'].str.replace('-', '').astype(date)
slider_options = dict((d_key, d_val) for d_key, d_val in enumerate(sorted(who_data['Date_reported_string'].unique())))

card1_body = dbc.CardBody([html.H4("Card title", className="card-title",id="card_num1"),
                  html.P("Cumulative Cases for Date", className="card-text",id="card_text1")
                 ],
                 style={'display': 'inline-block',
                        'text-align': 'center',
                        'color':'white',
                        'background-color': 'rgba(37, 150, 190)'})

card2_body = dbc.CardBody([html.H4("Card title", className="card-title",id="card_num2"),
                  html.P("Cumulative Deaths for Date", className="card-text",id="card_text2")
                 ],
                 style={'display': 'inline-block',
                        'text-align': 'center',
                        'color':'white',
                        'background-color': 'rgba(37, 150, 190)'})

card1 = dbc.Card(card1_body,outline=True)
card2 = dbc.Card(card2_body,outline=True)



app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = html.Div([
    dcc.Tabs([
        
        dcc.Tab(label='Spread',
                children=[
                    html.Div([
                       
                        dcc.Slider(id='slider',
                                    #marks = slider_options,
                                        step = 1,
                                        min = min(slider_options.keys()),
                                         max = max(slider_options.keys()),                                   
                                         value = min(slider_options.keys())#,
                                        
#                                          marks={each : {"label": who_data['Date_reported_string'], 
#                                                         "style": {"transform": "rotate(45deg)"}} 
#                                                 for each, who_data['Date_reported_string']
#                                                 in enumerate(who_data['Date_reported'])},


                                       ),
                         dbc.Row(id="card_row",children=[dbc.Col(card1),
                                                         dbc.Col(card2)
                                                        ])
                    ])
        ])
    ])
])


@app.callback(
    Output('card_row','children'),
    Input('slider','value')
)

def update_cards(date_select_index):

    date_select = slider_options.get(date_select_index, "1100-01-01")
    date_df = who_data[who_data['Date_reported']<=date_select]
    
    tot_cases = f"{date_df['New Cases'].sum():,.0f}"
    tot_deaths = f"{date_df['New Deaths'].sum():,.0f}"
    
    card1 = dbc.Card([
        dbc.CardBody([
            html.H4(tot_cases, className="card-title"),
            html.P(f"Cumulative Cases on {date_select}")

        ])
    ],
    style={'display': 'inline-block',
           'width': '50%',
           'text-align': 'center',
           'background-color': 'rgba(37, 150, 190)',
           'color':'white',
           'fontWeight': 'bold',
           'fontSize':20},
    outline=True)
    
    card2 = dbc.Card([
        dbc.CardBody([
            html.H4(tot_deaths, className="card-title"),
            html.P(f"Cumulative Deaths on {date_select}")
        ])
    ],
    style={'display': 'inline-block',
           'width': '50%',
           'text-align': 'center',
           'background-color': 'rgba(37, 150, 190)',
           'color':'white',
           'fontWeight': 'bold',
           'fontSize':20},
    outline=True)
    
    return (card1, card2)


app.run_server(port=8050)

Thanks so much! This works beautifully! Can you explain the slider_options and the date_select vs. date_select_index?

Sure. As you’d already figured out, the way Slider control works is that it takes a numerical min/max range and an optional step size to plot itself. When the user drags it to a new position, the new numerical value indicates the new position of the slider.

Keeping that in mind, I took all unique dates from your dataframe, sorted them ascending and then converted them into a dictionary called “slider_options”. The keys are auto-incrementing integers and the corresponding values are the dates on that position (from the sorted unique dates list). That’s what I use to plot the slider: 0 - 2020-01-03, 1 - 2020-01-04 and so on.

Finally, when you drag the slider, I get the new integer based position value as “date_select_index”, use it as a “lookup” into the “slider_options” dictionary to get the corresponding date value which then becomes a filter for your dataframe inside the callback function.

I hope this makes sense :slight_smile:

Thank you! That does make sense! Appreciate the response!