La página de actualización de Python Dash no actualiza los datos de origen
He escrito una aplicación básica de plotly dash que extrae datos de un csv y los muestra en un gráfico. Luego puede alternar valores en la aplicación y las actualizaciones del gráfico.
Sin embargo, cuando agrego nuevos datos al csv (hecho una vez al día), la aplicación no actualiza los datos al actualizar la página.
La solución normalmente es que usted define su app.layout
como una función, como se describe aquí (desplácese hacia abajo para ver las actualizaciones al cargar la página). Verá en mi código a continuación que lo he hecho.
Aquí está mi código:
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import numpy as np
import pandas as pd
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
path = 'https://raw.githubusercontent.com/tbuckworth/Public/master/CSVTest.csv'
df = pd.read_csv(path)
df2 = df[(df.Map==df.Map)]
def layout_function():
df = pd.read_csv(path)
df2 = df[(df.Map==df.Map)]
available_strats = np.append('ALL',pd.unique(df2.Map.sort_values()))
classes1 = pd.unique(df2["class"].sort_values())
metrics1 = pd.unique(df2.metric.sort_values())
return html.Div([
html.Div([
dcc.Dropdown(
id="Strategy",
options=[{"label":i,"value":i} for i in available_strats],
value=list(available_strats[0:1]),
multi=True
),
dcc.Dropdown(
id="Class1",
options=[{"label":i,"value":i} for i in classes1],
value=classes1[0]
),
dcc.Dropdown(
id="Metric",
options=[{"label":i,"value":i} for i in metrics1],
value=metrics1[0]
)],
style={"width":"20%","display":"block"}),
html.Hr(),
dcc.Graph(id='Risk-Report')
])
app.layout = layout_function
@app.callback(
Output("Risk-Report","figure"),
[Input("Strategy","value"),
Input("Class1","value"),
Input("Metric","value"),
])
def update_graph(selected_strat,selected_class,selected_metric):
if 'ALL' in selected_strat:
df3 = df2[(df2["class"]==selected_class)&(df2.metric==selected_metric)]
else:
df3 = df2[(df2.Map.isin(selected_strat))&(df2["class"]==selected_class)&(df2.metric==selected_metric)]
df4 = df3.pivot_table(index=["Fund","Date","metric","class"],values="value",aggfunc="sum").reset_index()
traces = []
for i in df4.Fund.unique():
df_by_fund = df4[df4["Fund"] == i]
traces.append(dict(
x=df_by_fund["Date"],
y=df_by_fund["value"],
mode="lines",
name=i
))
if selected_class=='USD':
tick_format=None
else:
tick_format='.2%'
return {
'data': traces,
'layout': dict(
xaxis={'type': 'date', 'title': 'Date'},
yaxis={'title': 'Values','tickformat':tick_format},
margin={'l': 40, 'b': 40, 't': 10, 'r': 10},
legend={'x': 0, 'y': 1},
hovermode='closest'
)
}
if __name__ == '__main__':
app.run_server(debug=True)
Cosas que he probado
- Quitando la inicial
df = pd.read_csv(path)
antes deldef layout_function():
. Esto resulta en un error. - Creando un botón de devolución de llamada para actualizar los datos usando este código:
@app.callback(
Output('Output-1','children'),
[Input('reload_button','n_clicks')]
)
def update_data(nclicks):
if nclicks == 0:
raise PreventUpdate
else:
df = pd.read_csv(path)
df2 = df[(df.Map==df.Map)]
return('Data refreshed. Click to refresh again')
Esto no produce un error, pero el botón tampoco actualiza los datos.
- Definición
df
dentro de laupdate_graph
devolución de llamada. Esto actualiza los datos cada vez que cambia algo, lo cual no es factible (mis datos reales son> 10 ^ 6 filas, por lo que no quiero leerlos cada vez que el usuario cambia un valor de alternancia)
En resumen, creo que definir app.layout = layout_function
debería hacer que esto funcione, pero no es así. ¿Qué me estoy perdiendo / no viendo?
Agradezco cualquier ayuda.
Respuestas
TLDR; Sugeriría que simplemente cargue los datos desde dentro de la devolución de llamada. Si el tiempo de carga es demasiado largo, puede cambiar el formato (por ejemplo, a difuminar ) y / o reducir el tamaño de los datos mediante el procesamiento previo. Si esto aún no es lo suficientemente rápido, el siguiente paso sería almacenar los datos en un caché en memoria del lado del servidor, como Redis .
Dado que está reasignando df
y df2
en layout_function
, estas variables se consideran locales en Python y, por lo tanto, no está modificando las variables df
y df2
del alcance global. Si bien puede lograr este comportamiento utilizando la palabra clave global , se desaconseja el uso de variables globales en Dash .
El enfoque estándar en Dash sería cargar los datos en una devolución de llamada (o en el layout_function
) y almacenarlos en un Store
objeto (o equivalentemente, un oculto Div
). La estructura sería algo así como
import pandas as pd
import dash_core_components as dcc
from dash.dependencies import Output, Input
app.layout = html.Div([
...
dcc.Store(id="store"), html.Div(id="trigger")
])
@app.callback(Output('store','data'), [Input('trigger','children')], prevent_initial_call=False)
def update_data(children):
df = pd.read_csv(path)
return df.to_json()
@app.callback(Output("Risk-Report","figure"), [Input(...)], [State('store', 'data')])
def update_graph(..., data):
if data is None:
raise PreventUpdate
df = pd.read_json(data)
...
Sin embargo, este enfoque generalmente será mucho más lento que simplemente leer los datos del disco dentro de la devolución de llamada (que parece ser lo que está tratando de evitar), ya que da como resultado que los datos se transfieran entre el servidor y el cliente.