Marco_CH
Marco_CH

Reputation: 3294

Python Dash Update Dataframe every 60 Minutes

I have a script which is collecting Data every hour and visualize this with Plotly Dash.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd

df = pd.read_csv("/home/pi/backup/data/data.csv").sort_values(by="Number")
df["Datetime"] = df["Datetime"].str.replace("T", " ").str.replace("Z", "")
df["Datetime"] = pd.to_datetime(df["Datetime"], format="%Y-%m-%d %H:%M:%S")
df["Datetime"] = df["Datetime"].dt.floor("H")
grouped = df.groupby(["Datetime", "Number", "Shop"]).min().reset_index()

app = dash.Dash(__name__)

tabs_styles = {
    'height': '30px'
}
tab_style = {
    'borderBottom': '1px solid #d6d6d6',
    'padding': '6px',
    'fontWeight': 'bold'
}
tab_selected_style = {
    'borderTop': '1px solid #d6d6d6',
    'borderBottom': '1px solid #d6d6d6',
    'backgroundColor': '#119DFF',
    'color': 'white',
    'padding': '6px'
}

fig = px.line(grouped, x="Datetime", y="Price", hover_name="Price",
                     facet_col='Number', color="Shop", facet_col_wrap=5,
                    width=1900, height=850)
fig.update_yaxes(matches=None, title=None)
fig.update_xaxes(title=None)
fig.update_traces(line=dict(width=1))
fig.update_layout(transition_duration=500, hovermode="x unified")

app.layout = html.Div([
    dcc.Tabs([
         dcc.Tab(label ="Overview", children=[
            dcc.Graph(
                id='example-graph',
                figure=fig
            )
        ], style=tab_style, selected_style=tab_selected_style),
        dcc.Tab(label = "Detail", children=[
            dcc.Dropdown(id='Detail_Input', options=[
                {'label': i, 'value': i} for i in df.sort_values(by=["Number"])["Number"].unique()
                ], multi=False, value=df["Number"].min()),
        dcc.Graph(id="Detail_Graph"),
        ], style=tab_style, selected_style=tab_selected_style)
    ], style=tabs_styles)
])

@app.callback(
    Output("Detail_Graph", "figure"),
    [Input("Detail_Input", "value")])
def update_figure(input):
    fig = px.line(grouped[grouped["Number"] == input], x="Datetime", y="Price", color="Shop", hover_name="Price",
                  width=1900, height=850)
    fig.update_layout(transition_duration=500, hovermode="x unified")
    return fig

if __name__ == '__main__':
    app.run_server(debug=True,port=8050,host="0.0.0.0")

Now I want to update the Dataframe "df" every hour. Alternatively I could check if the file "/home/pi/backup/data/data.csv" is updated and if yes, refresh the data. Found some ideas on google or stackoverflow, but was not able to adapt it to my script (I'm very new to Dash... coming from R Shiny).

Upvotes: 0

Views: 4832

Answers (2)

Marco_CH
Marco_CH

Reputation: 3294

Now I found a solution that works for me. Don't know if its best practice. So, if someone has an improvement I'd be grateful.

Nevertheless, it's just some MB of data. Performance shouldn't become a problem.

import dash
import dash_auth
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd


df = pd.read_csv("/home/pi/backup/lego/log.csv")

app = dash.Dash(__name__)
auth = dash_auth.BasicAuth(
    app,
    VALID_USERNAME_PASSWORD_PAIRS
)

tabs_styles = {
    'height': '30px'
}
tab_style = {
    'borderBottom': '1px solid #d6d6d6',
    'padding': '6px',
    'fontWeight': 'bold'
}
tab_selected_style = {
    'borderTop': '1px solid #d6d6d6',
    'borderBottom': '1px solid #d6d6d6',
    'backgroundColor': '#119DFF',
    'color': 'white',
    'padding': '6px'
}

app.layout = html.Div([
    dcc.Tabs([
         dcc.Tab(label ="Overview", children=[
            dcc.Interval(id="interval", interval=60000), dcc.Graph(id="output")
        ], style=tab_style, selected_style=tab_selected_style),
        dcc.Tab(label = "Detail", children=[
            dcc.Dropdown(id='Detail_Input', options=[
                {'label': i, 'value': i} for i in df.sort_values(by=["Number"])["Number"].unique()
                ], multi=False, value=df["Number"].min()),
        dcc.Graph(id="Detail_Graph",
                config={
                    'displayModeBar': False
                }),
        ], style=tab_style, selected_style=tab_selected_style)
    ], style=tabs_styles)
])
@app.callback(Output("output", "figure"), [Input("interval", "n_intervals")])
def display_time(n):
    df = pd.read_csv("/home/pi/backup/lego/log.csv")
    df["Datetime"] = df["Datetime"].str.replace("T", " ").str.replace("Z", "")
    df["Datetime"] = pd.to_datetime(df["Datetime"], format="%Y-%m-%d %H:%M:%S")
    df["Datetime"] = df["Datetime"].dt.floor("H")
    grouped = df.groupby(["Datetime", "Number", "Shop"]).min().reset_index().sort_values(by=["Number", "Datetime"])
    fig = px.line(grouped, x="Datetime", y="Price", hover_name="Price",
                  facet_col='Number', color="Shop", facet_col_wrap=6,
                  width=1900, height=850)
    return fig
@app.callback(
    Output("Detail_Graph", "figure"),
    [Input("Detail_Input", "value")])
def update_figure(input):
    df = pd.read_csv("/home/pi/backup/lego/log.csv")
    df["Datetime"] = df["Datetime"].str.replace("T", " ").str.replace("Z", "")
    df["Datetime"] = pd.to_datetime(df["Datetime"], format="%Y-%m-%d %H:%M:%S")
    df["Datetime"] = df["Datetime"].dt.floor("H")
    grouped = df.groupby(["Datetime", "Number", "Shop"]).min().reset_index().sort_values(by=["Number", "Datetime"])
    fig = px.line(grouped[grouped["Number"] == input], x="Datetime", y="Price", color="Shop", hover_name="Price",
                  width=1900, height=850)
    fig.update_layout(transition_duration=500, hovermode="x unified")
    return fig

if __name__ == '__main__':
    app.run_server(debug=True,port=8050,host="0.0.0.0")

Upvotes: 0

emher
emher

Reputation: 6024

I would suggest the following approach,

  • Create one callback (A) that updates the data. While you could do the update manually (i.e. checking the file time stamp and doing updates as needed), it would probably be easier to use a server side cache (such as Flask-Caching) with a timeout instead.

  • Create a second callback (B) that draws the graphs when the data in callback A changes.

If you want live updates (i.e. without refreshing the page), you should use an Interval component to trigger callback A. Here is a small example using the dash_extensions==0.0.28 package,

import dash_html_components as html
import dash_core_components as dcc

from datetime import datetime
from dash_extensions.enrich import Dash, Trigger, Output, Input, ServersideOutput, FileSystemStore

# Create server side store to hold the data.
fss = FileSystemStore(cache_dir="some_dir", default_timeout=10)  # timeout in seconds, i.e. yours should be 3600
# Create an example app.
app = Dash(__name__)
app.layout = html.Div([
    html.Div(id="log"),  # logging of data, a mock replacement of your graph
    dcc.Store(id="store"),  # store that holds the data reference
    dcc.Interval(id="trigger", interval=1000),  # trigger to invoke data refresh attempt, defaults to once per second
])


@app.callback(ServersideOutput("store", "data", session_check=False, backend=fss),
              Trigger("trigger", "n_intervals"), memoize=True)
def update_data():
    return datetime.now()  # put your update logic here


@app.callback(Output("log", "children"), Input("store", "data"))
def show_data(data):
    return f"Data were collected at {data}, current time is {datetime.now()}"  # put your graph drawing code here


if __name__ == "__main__":
    app.run_server()

Disclaimer: I am the author of dash_extensions.

Upvotes: 5

Related Questions