La pagina di aggiornamento di Python Dash non aggiorna i dati di origine
Ho scritto un'app di base per trattini plottamente che estrae i dati da un CSV e li visualizza su un grafico. È quindi possibile alternare i valori sull'app e gli aggiornamenti del grafico.
Tuttavia, quando aggiungo nuovi dati al csv (fatto una volta al giorno) l'app non aggiorna i dati all'aggiornamento della pagina.
La correzione è normalmente che definisci la tua app.layout
come funzione, come descritto qui (scorri verso il basso fino agli aggiornamenti al caricamento della pagina). Vedrai nel mio codice qui sotto che l'ho fatto.
Ecco il mio codice:
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)
Cose che ho provato
- Rimuovendo l'iniziale
df = pd.read_csv(path)
prima del filedef layout_function():
. Ciò si traduce in un errore. - Creazione di un pulsante di richiamata per aggiornare i dati utilizzando questo codice:
@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')
Questo non produce un errore, ma neanche il pulsante aggiorna i dati.
- Definizione
df
all'interno delupdate_graph
callback. Questo aggiorna i dati ogni volta che si attiva qualcosa, il che non è praticabile (i miei dati reali sono> 10 ^ 6 righe, quindi non voglio leggerli ogni volta che l'utente cambia un valore di attivazione)
In breve, penso che la definizione app.layout = layout_function
dovrebbe funzionare, ma non è così. Cosa mi manca / non vedo?
Apprezzo qualsiasi aiuto.
Risposte
TLDR; Suggerirei di caricare semplicemente i dati dall'interno della richiamata. Se il tempo di caricamento è troppo lungo, è possibile modificare il formato (ad es. In piuma ) e / o ridurre la dimensione dei dati tramite la pre-elaborazione. Se questo non è ancora abbastanza veloce, il passaggio successivo sarebbe memorizzare i dati in una cache in memoria lato server come Redis .
Dal momento che stai riassegnando df
e df2
in layout_function
, queste variabili sono considerate locali in Python , e quindi non stai modificando le variabili df
e df2
dall'ambito globale. Sebbene tu possa ottenere questo comportamento utilizzando la parola chiave globale , l'uso di variabili globali è sconsigliato in Dash .
L'approccio standard in Dash sarebbe caricare i dati in un callback (o nel layout_function
) e memorizzarli in un Store
oggetto (o, equivalentemente, in un nascosto Div
). La struttura sarebbe qualcosa di simile
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)
...
Tuttavia, questo approccio sarà in genere molto più lento della semplice lettura dei dati dal disco all'interno del callback (che sembra essere ciò che si sta cercando di evitare) poiché si traduce nel trasferimento dei dati tra il server e il client.