I am new to Python and have limited coding experience, so any input and advice is deeply appreciated.
I have created a dynamic choropleth map which includes a scatter_geo plot that overlays the relevant areas.
I am trying create a hover callback so that when I hover over one of these points, a dataframe appears that is indexed according to the point id (the first column in the defined dataframe). Essentially, it is a choropleth map equivalent of this example: https://plotly.com/python/v3/cars-exploration/ but without using FigureWidget.
I keep getting stuck on the hover callback function; no dataframe displays when I hover. Below is the code I have so far.
license_df1 = pd.read_excel(lic, "Primary Holdings by License", dtype = "str").fillna('')
license_df2 = pd.read_excel(lic, "Secondary Holdings by License", dtype = "str").fillna('')
### CREATE PLOTTING FEATURES ###
app = dash.Dash(__name__, suppress_callback_exceptions = True)
app.css.config.serve_locally = True
app.scripts.config.serve_locally = True
app.layout = html.Div([
html.P("Spectrum Band:"), # Create Toggle Items between spectrum bands
dcc.RadioItems(id = "Band", options=[{'value': x, 'label':x} for x in df1_band], value = df1_band[0]),
dcc.Graph(id = "choropleth"),
dash_table.DataTable(id = "table")])
#app.callback(
Output("choropleth", "figure"),
[Input("Band", "value")])
def build_graph(value):
if value == '600 MHz':
df1_600 = df1[(df1["Band"] == "600 MHz")]
fig1 = px.choropleth(df1_600, geojson = PEAs, featureidkey = "properties.PEA_Num",
locations = 'PEA # ', hover_data = {'PEA # ': False}, scope = "usa")
# Overlay Geographic Scatter Plot for interactive functionality
fig1b = px.scatter_geo(df1_600, geojson = PEAs, featureidkey = "properties.PEA_Num",
locations = 'PEA # ', hover_name = 'Market', scope = "usa")
fig1.add_trace(fig1b.data[0])
fig1.update_traces(showlegend = False)
fig1.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
return fig1
elif value == '3.7 GHz':
df1_3700 = df1[(df1["Band"] == "3.7 GHz")]
fig2 = px.choropleth(df1_3700, geojson = PEAs, featureidkey = "properties.PEA_Num",
locations = 'PEA # ', hover_data = {'PEA # ': False}, scope = "usa")
# Overlay Geographic Scatter Plot for interactive functionality
fig2b = px.scatter_geo(df1_3700, geojson = PEAs, featureidkey = "properties.PEA_Num",
locations = 'PEA # ', hover_name = 'Market', scope = "usa")
fig2.add_trace(fig2b.data[0])
fig2.update_traces(showlegend = False)
fig2.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
return fig2
#app.callback(
Output("table", "data"),
[Input("fig1", "hover_data")]) # WHERE I AM HAVING TROUBLE
def disp_license1(hover_data):
table_vals = license_df1.iloc[points.point_inds[0]].to_frame().to_html()
return table_vals
app.run_server(debug = True)
Related
I'm trying to generate a Dash app which displays historical and forecasted housing prices. I've got the forecasted data stored in a different dataframe from the historical prices, and I'd like to plot them both on the same graph in Dash, and have the graph get updated via callback when the user picks a different city from a dropdown menu. I would like both traces of the graph to update when a value is selected in the dropdown. I've tried various things but can only get one trace from one dataframe plotted for the graph in my callback:
# --- import libraries ---
import dash
import dash_table
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
from dash.dependencies import Output, Input
# --- load data ---
df_h = pd.read_csv('df_h.csv')
df_arima = pd.read_csv('df_arima.csv')
options = [] #each column in the df_h dataframe is an option for the dropdown menu
for column in df_h.columns:
options.append({'label': '{}'.format(column, column), 'value': column})
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
# --- initialize the app ---
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
# --- layout the dashboard ---
app.layout = html.Div(
children = [
html.Div([
html.Label('Select a feature from drop-down to plot'),
dcc.Dropdown(
id = 'city-dropdown',
options = options,
value = 'Denver, CO',
multi = False,
clearable = True,
searchable = True,
placeholder = 'Choose a City...'),
html.Div(id = 'forecast-container',
style = {'padding': '50px'}),
]),
],
)
# --- dropdown callback ---
#app.callback(
Output('forecast-container', 'children'),
Input('city-dropdown', 'value'))
def forecast_graph(value):
dff = df_h[['Date', value]] #'value' is identical between the two dataframes. references
dfa = df_arima[df_arima['City'] == value] # a col in dff and row values in dfa
return [
dcc.Graph(
id = 'forecast-graph',
figure = px.line(
data_frame = dff,
x = 'Date',
y = value).update_layout(
showlegend = False,
template = 'xgridoff',
yaxis = {'title': 'Median Home Price ($USD)'},
xaxis = {'title': 'Year'},
title = {'text': 'Median Home Price vs. Year for {}'.format(value),
'font': {'size': 24}, 'x': 0.5, 'xanchor': 'center'}
),
)
]
I was able to accomplish this in Plotly but can't figure out how to do it in Dash. This is what I want in Dash:
Plotly graph I am trying to reproduce in callback, that is linked to a dropdown menu
This is all I can get to work in Dash:
Only one dataframe plots in Dash
This is the code that works in plotly graph objects:
from statsmodels.tsa.arima_model import ARIMA
df_ml = pd.read_csv('df_ml.csv')
# --- data cleaning ---
df_pred = df_ml[df_ml['RegionName'] == city]
df_pred = df_pred.transpose().reset_index().drop([0])
df_pred.columns = ['Date', 'MedianHomePrice_USD']
df_pred['MedianHomePrice_USD'] = df_pred['MedianHomePrice_USD'].astype('int')
df_pred['Date'] = pd.to_datetime(df_pred['Date'])
df_pred['Date'] = df_pred['Date'].dt.strftime('%Y-%m')
df_model = df_pred.set_index('Date')
model_data = df_model['MedianHomePrice_USD']
def house_price_forecast(model_data, forecast_steps, city):
#--- ARIMA Model (autoregressive integrated moving average) ---
model = ARIMA(model_data, order = (2,1,2), freq = 'MS')
res = model.fit()
forecast = res.forecast(forecast_steps)
forecast_mean = forecast[0]
forecast_se = forecast[1]
forecast_ci = forecast[2]
df_forecast = pd.DataFrame()
df_forecast['Mean'] = forecast_mean.T
df_forecast['Lower_ci'], df_forecast['Upper_ci'] = forecast_ci.T
df_forecast['Date'] = pd.date_range(start = '2021-02', periods = forecast_steps, freq = 'MS')
df_forecast['Date'] = df_forecast['Date'].dt.strftime('%Y-%m')
df_forecast.index = df_forecast['Date']
fig = go.Figure()
fig.add_trace(go.Scatter(x = df_pred['Date'], y = df_pred['MedianHomePrice_USD'],
mode = 'lines', name = 'Median Home Price ($USD)',
line_color = 'rgba(49, 131, 189, 0.75)', line_width = 2))
fig.add_trace(go.Scatter(x = df_forecast.index, y = df_forecast['Mean'],
mode = 'lines', line_color = '#e6550d',
name = 'Forecast mean'))
fig.add_trace(go.Scatter(x = df_forecast.index, y = df_forecast['Upper_ci'],
mode = 'lines', line_color = '#e0e0e0', fill = 'tonexty',
fillcolor = 'rgba(225,225,225, 0.3)',
name = 'Upper 95% confidence interval'))
fig.add_trace(go.Scatter(x = df_forecast.index, y = df_forecast['Lower_ci'],
mode = 'lines', line_color = '#e0e0e0', fill = 'tonexty',
fillcolor = 'rgba(225,225,225, 0.3)',
name = 'Lower 95% confidence interval'))
fig.update_layout(title = 'Median Home Price in {}, {} - {} (Predicted)'.format(
city, model_data.idxmin()[:-3], df_forecast_mean.idxmax()[:-3]),
xaxis_title = 'Year', yaxis_title = 'Median Home Price ($USD)',
template = 'xgridoff')
fig.show()
house_price_forecast(model_data, 24, 'Denver, CO') #24 month prediction
Perhaps a more succinct way of asking this question: How do I add a trace to an existing Dash graph, with data from a different dataframe, and both traces get updated when the user selects a value from a single dropdown?
Figured it out...
Don't use the syntax I used above in your callback. Put the px.line call inside a variable(fig, in this case), and then use fig.add_scatter to add data from a different dataframe to the graph. Both parts of the graph will update from the callback.
Also, fig.add_scatter doesn't have a dataframe argument, so use df.column or df[column] (ex. 'dfa.Date' below)
# --- import libraries ---
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.express as px
from dash.dependencies import Output, Input
# --- load data ---
df_h = pd.read_csv('df_h.csv')
df_h['Date'] = pd.to_datetime(df_h['Date'])
df_arima = pd.read_csv('df_arima.csv')
df_arima['Date'] = pd.to_datetime(df_arima['Date'])
df_arima['Date'] = df_arima['Date'].dt.strftime('%Y-%m')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
# --- initialize the app ---
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
dcc.Graph(id = 'forecast-container')
]
)
# --- dropdown callback ---
#app.callback(
Output('forecast-container', 'figure'),
Input('city-dropdown', 'value'))
def update_figure(selected_city):
dff = df_h[['Date', selected_city]]
# dff[selected_city] = dff[selected_city].round(0)
dfa = df_arima[df_arima['City'] == selected_city]
fig = px.line(dff, x = 'Date', y = selected_city,
hover_data = {selected_city: ':$,f'})
fig.add_scatter(x = dfa.Date, y = dfa.Mean,
line_color = 'orange', name = 'Forecast Mean')
fig.add_scatter(x = dfa.Date, y = dfa.Lower_ci,
fill = 'tonexty', fillcolor = 'rgba(225,225,225, 0.3)',
marker = {'color': 'rgba(225,225,225, 0.9)'},
name = 'Lower 95% Confidence Interval')
fig.add_scatter(x = dfa.Date, y = dfa.Upper_ci,
fill = 'tonexty', fillcolor = 'rgba(225,225,225, 0.3)',
marker = {'color': 'rgba(225,225,225, 0.9)'},
name = 'Upper 95% Confidence Interval')
fig.update_layout(template = 'xgridoff',
yaxis = {'title': 'Median Home Price ($USD)'},
xaxis = {'title': 'Year'},
title = {'text': 'Median Home Price vs. Year for {}'.format(selected_city),
'font': {'size': 24}, 'x': 0.5, 'xanchor': 'center'})
return fig
if __name__ == '__main__':
app.run_server(debug = True)
I am pretty new to dash and I have tried to read as much as I can to understand what the issue might be. In a nutshell I have a single datepicker which is an input to the DataTable and Graph callback. The graph callback is working fine so it is just the DataTable which is causing problems. I also tried the single input to multiple output callback but didnt work. My code is as below:
app = JupyterDash()
folder = os.getcwd()
portfolio_returns_table = pd.read_csv(Path(folder, 'portfolioreturns_maria.csv',parse_dates=[0]))
portfolio_returns_table = portfolio_returns_table.set_index('Unnamed: 0')
name_portfolioID_table = pd.read_csv(Path(folder, 'name_portfolioID.csv'))
#Calculate portfolio cumulative returns
df_cumret = (portfolio_returns_table+1).cumprod().round(5)
df_cumret.index = pd.to_datetime(df_cumret.index)
app.layout = html.Div(html.Div([dcc.DatePickerSingle(
id='my-date-picker-single',
min_date_allowed=dt.date(df_cumret.index.min()),
max_date_allowed=dt.date(df_cumret.index.max()),
initial_visible_month=dt.date(df_cumret.index.max()),
date = dt.date(df_cumret.index.max())
,display_format = 'Y-MM-DD',clearable = True),
html.Div(id='output-container-date-picker-single'),
html.Div(dash_table.DataTable(id = 'data_table',
data = {},
fixed_rows={'headers': True},
style_cell = {'textAlign': 'left'},
style_table={'height': 400})),
html.Div(dcc.Graph('my_graph'))
]))
#app.callback([Output('data_table','data'),Output('data_table','columns')],
[Input('my-date-picker-
single','date')])
def update_leader_table(date):
#Get data for the selected date and transpose
df_T = df_cumret.loc[[date]].T
#Sort the table to reveal the top leaders
df_Top = df_T.sort_values(df_T.columns[0], ascending=False)[:10]
#Convert the index to an interger
df_Top.index = df_Top.index.astype(int)
#Generate the leaderboard to given date
df_leader = pd.merge(df_Top,name_portfolioID_table,
left_index=True,right_index=True, how = 'left')
#Create the col rank
df_leader['Rank'] = range(1,len(df_leader)+1)
df_leader.columns = ['Cum Return', 'Investor','Rank']
df_leader.reset_index(drop = True, inplace = True)
data = df_leader.to_dict('records')
columns= [{'id': c, 'name': c, "selectable": True} for c in
df_leader.columns]
return (data,columns)
#callback to link calendar to graph
#app.callback(Output('my_graph','figure'),[Input('my-date-picker-single','date')])
def update_graph(date):
#date filter
df_T = df_cumret.loc[:date].T
#Sort the table to reveal the top leaders & filter for leaderboard
df_Top = df_T.sort_values(df_T.columns[-1], ascending=False)[:10]
#Transpose to have date as index
df_top_graph = df_Top.T
#set the columns as an Int
df_top_graph.columns = df_top_graph.columns.astype(int)
#Rename columns
df_top_graph.rename(columns=dict(zip(name_portfolioID_table.index,
name_portfolioID_table.name)),
inplace=True)
#Generate graph
fig = px.line(df_top_graph, x = df_top_graph.index, y =
df_top_graph.columns, title='ETF LEADERBOARD PERFORMANCE: '+date, labels=
{'Unnamed: 0':'Date','value':'Cumulative Returns'})
fig.update_layout(hovermode = 'x unified')
fig.update_traces(hovertemplate='Return: %{y} <br>Date: %{x}')
fig.update_layout(legend_title_text = 'Investor')
return fig
if __name__ == '__main__':
app.run_server(mode = 'inline',debug=True, port = 65398)
I am following the dash tutorials and totally confused about how dash call upon functions. Following the second tute on this page https://dash.plotly.com/basic-callbacks. The same example is shown below. I am totally clueless where the update_figure function is even called but the graph is still plotted within the dashboard (i.e there is no mention of update_figure() function anywhere within the app.layout or app.callback).
So any ideas on how the function is passed on?
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')
# initialize
app = dash.Dash()
app.layout = html.Div([
dcc.Graph(id = 'graph-with-slider'),
dcc.Slider(
id = 'year-slider',
min = df['year'].min(),
max = df['year'].max(),
value = df['year'].min(),
marks = {str(year) : str(year) for year in df['year'].unique()},
step = None
)
])
#app.callback(
Output('graph-with-slider','figure'),
[Input('year-slider','value')]
)
def update_figure(selected_year):
filtered_df = df[df.year == selected_year]
fig = px.scatter(filtered_df, x = 'gdpPercap', y = 'lifeExp', size = 'pop', color = 'continent', hover_name = 'country', log_x = True, size_max = 55)
fig.update_layout(transition_duration = 500)
return fig
if __name__ == '__main__':
app.run_server()
Dash does that under the hood using the #app.callback decorator. The function will be called whenever the Inputs change, and Dash will take the returned value from the callback to update the outputs.
I am using Bokeh 1.0.1. I am unable to update the data source in the Update method i.e src.data.update(new_src.data) doesn't seem to work. Below is the full code.
def modify_doc(doc):
def create_dataset(df, resample='D'):
# Resample the data
src = df.resample(resample).mean()
# Reset index for hovering
src.reset_index(inplace=True)
return ColumnDataSource(src)
def create_plot(src):
# Blank plot with correct labels
p = figure(plot_width=700, plot_height=300, x_axis_type="datetime",
title = 'Variation of Pollution',
x_axis_label = 'Time', y_axis_label = 'Pollution (µg/m³)')
p.line(source=src, x='Date & Time', y='pm2.5', line_width=2,
color='firebrick', line_alpha=0.5, legend='Actual')
hover = HoverTool(tooltips=[('Pollution', '#{pm2.5} µg/m³'),
('Air Temp', '#{Air Temp.} °C'),
('Temp', '(#{Min. Temp.}{0.2f}, #{Max. Temp.}{0.2f}) °C'),
('Dew Pt.', '#{Dew Pt.} °C'),
('Rainfall', '#Rainfall mm'),
('Wind Dir.', '#{Wind Dir.} °'),
('Wind Speed', '#{Wind Speed} km/hr'),
('Relative humidity', '(#{Min. RH}{0.2f}, #{Max. RH}{0.2f}) %')],
mode='vline')
p.add_tools(hover)
p.legend.click_policy = 'hide'
return p
# Update function takes three default parameters
def update(attr, old, new):
# Resampling list
re_list = ['D', 'W', 'M', 'A']
# Make a new dataset based on the selected carriers and the
# make_dataset function defined earlier
new_src = create_dataset(df,
resample = re_list[resample_button_group.active])
# Update the source used the quad glpyhs
src.data.update(new_src.data)
resample_button_group = RadioButtonGroup(labels=["Day", "Week", "Month", "Year"], active=1)
resample_button_group.on_change('active', update)
controls = WidgetBox(resample_button_group)
# Initial Plot
src = create_dataset(df)
p = create_plot(src.data)
layout = row(controls, p)
doc.add_root(layout)
# Set up an application
handler = FunctionHandler(modify_doc)
app = Application(handler)
You should be able to update the line glyph directly.
First, modify your plotting code to assign a name to the line glyph:
pm_line = p.line(
source=src,
x='Date & Time',
y='pm2.5',
line_width=2,
color='firebrick',
line_alpha=0.5,
legend='Actual',
name='pm_line' # Add this!
)
Then in your update function, replace your existing update line with the following:
pm_line = p.select_one({'name':'pm_line'})
pm_line.data_source.data = new_src.data
I wanted to make a choropleth world map, which shows the hits(number of searches) of a word, on a World map.
Following is the code:
import plotly
import plotly.offline
import pandas as pd
df = pd.read_excel('F:\\Intern\\csir\\1yr\\news\\region_2016_2017.xlsx')
df = df.query('keyword==["addiction"]')
scl = [[0.0, 'rgb(242,240,247)'],[0.2, 'rgb(218,218,235)'],[0.4, 'rgb(188,189,220)'],\
[0.6, 'rgb(158,154,200)'],[0.8, 'rgb(117,107,177)'],[1.0, 'rgb(84,39,143)']]
data = [dict(
type='choropleth',
colorscale=scl,
locations = df['location'],
z = df['hits'].astype(int),
locationmode = "country names",
autocolorscale = False,
reversescale = False,
marker = dict(
line = dict (
color = 'rgb(180,180,180)',
width = 0.5)),
colorbar = dict(
autotick = False,
title = 'Hits'),)]
layout = dict(
title = 'Addiction keyword 1yr analysis',
geo = dict(
showframe = False,
showcoastlines = False,
projection = dict(
type = 'Mercator'
)
)
)
fig = dict(data = data,layout = layout)
plotly.offline.plot(fig,validate=False,filename = 'd3-world-map.html')
And the plotted map is:
As one can see clearly, many countries are missing. This may be due to the fact that many countries didn't have entries which explicitly stated that they have zero hits.
I don't want to explicitly do that with my data. Is there any other way out of this? So that we can see all of the countries.
Data set can be found here.
Note that the dataset that I've linked is an .csv file whereas the file used in the program is an .xlsx version of the file.
You need to turn on country outlines under layout...
"geo":{
"countriescolor": "#444444",
"showcountries": true
},