La page d'actualisation de Python Dash ne met pas à jour les données source
J'ai écrit une application de tableau de bord de base qui extrait les données d'un csv et les affiche sur un graphique. Vous pouvez ensuite basculer les valeurs sur l'application et les mises à jour du graphique.
Cependant, lorsque j'ajoute de nouvelles données au csv (une fois par jour), l'application ne met pas à jour les données lors de l'actualisation de la page.
Le correctif est normalement que vous définissez votre en app.layout
tant que fonction, comme indiqué ici ( faites défiler les mises à jour lors du chargement de la page). Vous verrez dans mon code ci-dessous que j'ai fait cela.
Voici mon code:
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)
Les choses que j'ai essayées
- Suppression de l'initiale
df = pd.read_csv(path)
avant ledef layout_function():
. Cela entraîne une erreur. - Création d'un bouton de rappel pour actualiser les données à l'aide de ce code:
@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')
Cela ne produit pas d'erreur, mais le bouton n'actualise pas non plus les données.
- Définition
df
dans leupdate_graph
callback. Cela met à jour les données chaque fois que vous basculez quelque chose, ce qui n'est pas possible (mes données réelles sont> 10 ^ 6 lignes, donc je ne veux pas les lire à chaque fois que l'utilisateur change une valeur de bascule)
En bref, je pense que la définition app.layout = layout_function
devrait faire fonctionner cela, mais ce n'est pas le cas. Qu'est-ce que je manque / ne vois pas?
Appréciez toute aide.
Réponses
TLDR; Je suggère que vous chargiez simplement les données à partir du rappel. Si le temps de chargement est trop long, vous pouvez changer le format (par exemple en plume ) et / ou réduire la taille des données via un prétraitement. Si ce n'est toujours pas assez rapide, l'étape suivante consiste à stocker les données dans un cache en mémoire côté serveur tel que Redis .
Puisque vous réaffectez df
et df2
dans le layout_function
, ces variables sont considérées comme locales en Python , et vous ne modifiez donc pas les variables df
et df2
de la portée globale. Bien que vous puissiez obtenir ce comportement en utilisant le mot - clé global , l'utilisation de variables globales est déconseillée dans Dash .
L'approche standard dans Dash serait de charger les données dans un callback (ou dans le layout_function
) et de les stocker dans un Store
objet (ou de manière équivalente, un hidden Div
). La structure serait quelque chose comme
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)
...
Cependant, cette approche sera généralement beaucoup plus lente que la simple lecture des données du disque à l'intérieur du rappel (ce qui semble être ce que vous essayez d'éviter) car elle entraîne le transfert des données entre le serveur et le client.