Python Dash-Aktualisierungsseite aktualisiert keine Quelldaten
Ich habe eine einfache Plotly-Dash-App geschrieben, die Daten aus einer CSV-Datei abruft und in einem Diagramm anzeigt. Sie können dann die Werte in der App umschalten und das Diagramm aktualisieren.
Wenn ich der CSV jedoch neue Daten hinzufüge (einmal täglich), aktualisiert die App die Daten beim Aktualisieren der Seite nicht.
Die Korrektur besteht normalerweise darin, dass Sie Ihre app.layoutFunktion wie hier beschrieben definieren (scrollen Sie nach unten, um beim Laden der Seite zu aktualisieren). Sie werden in meinem Code unten sehen, dass ich das getan habe.
Hier ist mein 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)
Dinge, die ich versucht habe
- Entfernen der Initiale
df = pd.read_csv(path)vor demdef layout_function():. Dies führt zu einem Fehler. - Erstellen einer Rückruftaste zum Aktualisieren der Daten mit diesem 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')
Dies erzeugt keinen Fehler, aber die Schaltfläche aktualisiert auch nicht die Daten.
- Definieren
dfinnerhalb desupdate_graphRückrufs. Dies aktualisiert die Daten jedes Mal, wenn Sie etwas umschalten, was nicht praktikabel ist (meine realen Daten sind> 10 ^ 6 Zeilen, daher möchte ich sie nicht jedes Mal lesen, wenn der Benutzer einen Umschaltwert ändert).
Kurz gesagt, ich denke, dass das Definieren app.layout = layout_functiondiese Arbeit machen sollte, aber es funktioniert nicht. Was vermisse ich / sehe ich nicht?
Schätzen Sie jede Hilfe.
Antworten
TLDR; Ich würde vorschlagen, dass Sie die Daten einfach aus dem Rückruf laden. Wenn die Ladezeit zu lang ist, können Sie das Format ändern (z. B. in Feder ) und / oder die Datengröße durch Vorverarbeitung reduzieren. Wenn dies immer noch nicht schnell genug ist, besteht der nächste Schritt darin, die Daten in einem serverseitigen In-Memory-Cache wie Redis zu speichern .
Da Sie dfund df2in neu zuweisen layout_function, werden diese Variablen in Python als lokal betrachtet , und Sie ändern daher nicht die Variablen dfund df2aus dem globalen Bereich. Während Sie dieses Verhalten mit dem globalen Schlüsselwort erreichen können , wird von der Verwendung globaler Variablen in Dash abgeraten .
Der Standardansatz in Dash besteht darin, die Daten in einen Rückruf (oder in den layout_function) zu laden und in einem StoreObjekt (oder gleichwertig in einem versteckten Div) zu speichern . Die Struktur wäre so etwas wie
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)
...
Dieser Ansatz ist jedoch in der Regel viel langsamer als das Lesen der Daten von der Festplatte innerhalb des Rückrufs (was Sie anscheinend vermeiden möchten), da die Daten zwischen Server und Client übertragen werden.