La page d'actualisation de Python Dash ne met pas à jour les données source

Aug 20 2020

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.layouttant 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

  1. Suppression de l'initiale df = pd.read_csv(path)avant le def layout_function():. Cela entraîne une erreur.
  2. 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.

  1. Définition dfdans le update_graphcallback. 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_functiondevrait 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

3 emher Aug 21 2020 at 06:03

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 dfet df2dans le layout_function, ces variables sont considérées comme locales en Python , et vous ne modifiez donc pas les variables dfet df2de 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 Storeobjet (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.