Search code examples
pythonjupyter-notebookipywidgetsbqplot

Interactive Covid Plot with Multiple iPyWidgets Dropdowns


I'm attempting to put together a Jupyter Notebook that allows a user to input a state in a dropdown, which would change the result in the second dropdown to only show cities from that state. Once the city is selected, a button could be pressed to refresh the graph (I realize there's you can use manual with interact, but was unable to get this to function properly) and show the resultant data (covid cases over time). I've had some success making the widgets, but I can't get the data to plot correctly.

This is my first time using widgets in Jupyter and I'm a bit lost what with interact, interactivity, display, and observe (not to mention the deprecated on_trait_change). Here's what I have so far...

from ipywidgets import interact, widgets
from bqplot import pyplot as plt
import pandas as pd

#make example DF
date_list = ['2020-02-01','2020-02-01','2020-02-01','2020-02-01','2020-02-02','2020-02-02','2020-02-02','2020-02-02']
state_list = ['CA','NY','CA','NY','CA','NY','CA','NY']
city_list = ['San Fran','NYC','LA','Albany','San Fran','NYC','LA','Albany']
cases_list = [0,0,0,0,2,6,4,8]

df = pd.DataFrame(index=date_list)
df['state'] = state_list
df['city'] = city_list
df['cases'] = cases_list

#getting unique state and city list sorted in alphabetical order
state_unique = df['state'].unique()
state_unique.sort()

state = widgets.Dropdown(
    options=['All'] + list(state_unique),
    value='CA', #indicates default starting value
    description='State:', #this is the label for the dropdown
)

city = widgets.Dropdown(
    description='City:',
)


# function that updates 'city' dropdown depending on value in 'state' dropdown
def list_cities(x):
    if state.value == 'All':
        city.options = ['']
        return city_list
    else:
        temp = ['All'] + sorted(df.loc[df['state'].eq(state.value),'city'].unique())
        city.options = temp

state.observe(list_cities, names = 'value') #the names part tells the observe the change name to look for, this is only looking for value changes



b_refresh = widgets.Button(
    description='Refresh',
    icon='fa-refresh',
    button_style='warning',
    layout=widgets.Layout(width='100px')
)

# plotting function
def set_plot(x_data,y_data):
    plt.plot(x_data, y_data)
    plt.show()

x = list(df.index.unique())
y = df.loc[\
    (df['city']==city.value)&\
    (df['state']==state.value),'cases']

b_refresh.on_click(set_plot(x, y))

display (state,city,b_refresh)

Solution

  • from ipywidgets.widgets import Dropdown, Button
    from ipywidgets.widgets import Layout, HBox, VBox
    import bqplot as bq
    import pandas as pd
    
    #make example DF
    date_list = ['2020-02-01','2020-02-01','2020-02-01','2020-02-01','2020-02-07','2020-02-07','2020-02-07','2020-02-07']
    state_list = ['CA','NY','CA','NY','CA','NY','CA','NY']
    city_list = ['San Fran','NYC','LA','Albany','San Fran','NYC','LA','Albany']
    cases_list = [0,10,0,12,2,6,4,8]
    
    df = pd.DataFrame(index=date_list)
    df['state'] = state_list
    df['city'] = city_list
    df['cases'] = cases_list
    
    df.index = pd.to_datetime(df.index)
    df.rename_axis("date", axis='index', inplace=True)
    
    #getting unique state list sorted in alphabetical order
    state_unique = df['state'].unique()
    state_unique.sort()
    
    #make graphical parts - 2 dropdowns and refresh button
    state = Dropdown(
        options=['All'] + list(state_unique),
        value='All', #indicates default starting value
        description='State:', #this is the label for the dropdown
        layout=Layout(width='200px',
        margin = '0px 0px 0px 0px')
    )
    
    city = Dropdown(
        description='City:',
        layout=Layout(width='200px',
        margin = '0px 0px 0px 0px')
    )
    
    b_refresh = Button(
        description='Refresh',
        icon='fa-refresh',
        button_style='warning',
        layout=Layout(width='100px',
                  margin = '0px 0px 0px 60px')
    )
    
    #make graph parts
    x = list(df.index.unique())
    y = list(df.groupby('date')['cases'].sum())
    
    xs = bq.DateScale()
    ys = bq.LinearScale(min=0)
    
    line = bq.Lines(
        x=x, 
        y=y, 
        scales={'x': xs, 'y': ys}
        )
    
    x_ax = bq.Axis(
        scale=xs, 
        label='Dates', 
        grid_lines='solid', 
        tick_format='%m-%d', 
        tick_rotate=-0, 
        label_location ='middle',
        label_offset = "40px"
    )
    
    y_ax = bq.Axis(
        scale=ys, 
        orientation='vertical', 
        tick_format=',d', 
        label='FillInLater', 
        grid_lines='solid',
        label_offset = "60px"
    )
    
    # function that updates 'city' dropdown depending on value in 'state' dropdown
    def list_cities(x):
        if state.value == 'All':
            global y
            city.options = ['']
            y = list(df.groupby('date')['cases'].sum())
        else:
            temp = ['All'] + sorted(df.loc[df['state'].eq(state.value),'city'].unique())
            city.options = temp
            update_city(x)
    
    
    # function that updates graph's y values depending on value in 'city' dropdown
    def update_city(x):
        global y
        if city.value == 'All':
            matching_cities = df.loc[(df['state']==state.value),'cases'].to_frame(name='cases')
            y = list(matching_cities.groupby('date')['cases'].sum())       
        else:
            y = list(df.loc[\
            (df['city']==city.value)&\
            (df['state']==state.value),'cases'])
    
    # update plot function
    def set_plot(b):
        global y
        line.y = [y]
    
    
    #assign widget actions to functions
    state.observe(list_cities, names = 'value') #the names part tells .observe the change name to look for, this is only looking for value changes
    city.observe(update_city, names = 'value')        
    b_refresh.on_click(set_plot)
    
    #display all
    fig = bq.Figure(
        layout=Layout(width='95%', height='400px', border ='solid 1px gray'),
        axes=[x_ax, y_ax],
        marks=[line],
        fig_margin=dict(top=10, bottom=80, left=80, right=20),
        animation_duration=500
    )
    
    box = HBox(
        children=(state, city, b_refresh),
        layout=Layout(margin = '10px 0px 10px 0px')
    )
    
    vert_box  = VBox(
        children=(box, fig),
        layout=Layout(border='solid 1px gray', margin = '0 0 0 0')
    )
    
    display (vert_box)