Interactive Covid Plot with Multiple iPyWidgets Dropdowns

1.4k views Asked by At

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)
1

There are 1 answers

0
fakedane On BEST ANSWER
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)