Data Science, Machine Learning und KI
Kontakt
Content Hub
Blog Post

Wie Du ein Dashboard In Python baust – Plotly Dash Step-by-Step Tutorial

  • Expert:innen Alexander Blaufuss
  • Datum 26. März 2020
  • Thema FrontendPythonTutorial
  • Format Blog
  • Kategorie Technology
Wie Du ein Dashboard In Python baust – Plotly Dash Step-by-Step Tutorial

Aus meiner Erfahrung bei STATWORX weiß ich, dass der beste Weg zum Lernen das Probieren ist – mit etwas Hilfe von einem Freund! In diesem Artikel werde ich euch einen hands-on Guide zum Bauen eines Dashboards in Python geben. Als Framework verwenden wir Dash, mit dem Ziel ein einfaches Dashboard mit einer Dropdown-Liste und zwei reaktiven Graphen zu erstellen.

dash-app-final

Entwickelt als eine Open-Source-Bibliothek von Plotly, baut das Python Framework Dash auf Flask, Plotly.js und React.js auf. Das Framework ermöglicht die Erstellung von interaktiven Webapplikationen in purem Python und eignet sich besonders für das Teilen von Datenanalysen.

Solltest du Interesse an der Erstellung von interaktiven Grafiken mit Python haben, kann ich den Blog meines Kollegen Markus, Plotly – An Interactive Charting Library, sehr empfehlen.

Ein grundlegendes Verständnis von HTML und CSS ist für die Erstellung von Webapplikationen empfohlen. Um diesem Blog folgen zu können, werden wir alle notwendigen externen Ressourcen zur Verfügung stellen und die Rollen von HTML und CSS in einem Dash(board) erläutern.

Der Source Code ist auf GitHub verfügbar.

Vorraussetzungen

Das Projekt besteht aus einem Stylesheet style.css, den Beispieldaten stockdata2.csv und der eigentlichen Dash-Applikation app.py.

Laden des Stylesheet

Die Verwendung eines Stylesheets ist für die Funktionalitäten des Dashboards nicht notwendig. Damit das Dashboard jedoch so aussieht, wie in unseren Beispielen, kann die Datei style.css von unserem STATWORX GitHub heruntergeladen werden. Das verwendete Stylesheet is eine leicht veränderte Version des Stylesheets der Dash Uber Rides Demo. Dash lädt automatisch .css-Dateien, die sich im Unterordner assets befinden.

dashapp
    |--assets
        |-- style.css
    |--data
        |-- stockdata2.csv
    |-- app.py

Die Dokumentation zu externen Ressourcen (u.a. Stylesheets) kann unter folgendem Link gefunden werden: https://dash.plot.ly/external-resources

Laden der Daten

Für unser Dashboard verwenden wir den Datensatz stockdata2.csv. Der Datensatz hat folgende Struktur:

date stock value change
2007-01-03 MSFT 23.95070 -0.1667
2007-01-03 IBM 80.51796 1.0691
2007-01-03 SBUX 16.14967 0.1134
import pandas as pd

# Load data
df = pd.read_csv('data/stockdata2.csv', index_col=0, parse_dates=True)
df.index = pd.to_datetime(df['Date'])

Erste Schritte – Wie man eine Dash-App startet

Nach der Installation von Dash (Anleitung kann hier gefunden werden) können wir die App starten. Die folgenden Zeilen importieren die benötigten Pakete dash und dash_html_components. Ohne eine Definition eines Layouts der App, kann die Applikation nicht gestartet werden. Eine leere html.Div genügt, um die App zu starten.

import dash
import dash_html_components as html

Falls du bereits Erfahrung mit dem WSGI Web Applikation Framework Flask hast, werden dir die nächsten Schritte sehr bekannt sein. Da Dash auf Flask basiert, wird durch die folgenden Zeilen eine Flask App gestartet.

# Initialise the app
app = dash.Dash(__name__)

# Define the app
app.layout = html.Div()
# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)

Wie eine .css-Datei das Layout beeinflusst

Das Modul dash_html_components beinhaltet verschiedene HTML-Komponenten. Mehr Informationen zu den Komponenten unter: https://dash.plot.ly/dash-html-components

HTML-Komponenten können über das children-Attribut verschachtelt werden.

app.layout = html.Div(children=[
                      html.Div(className='row',  # Define the row element
                               children=[
                                  html.Div(className='four columns div-user-controls'),  # Define the left element
                                  html.Div(className='eight columns div-for-charts bg-grey')  # Define the right element
                                  ])
                                ])

Das erste html.Div() hat ein untergeordnetes Element (eng. child). Das Element is ein weiteres html.Div() mit dem Namen (className) row, welches der Container für unseren Inhalt sein wird. Die weiteren untergeordneten Elemente sind four-columns div-user-controls und eight columns div-for-charts bg-grey.

Der Stil der div-Komponenten stammt aus unserer style.css-Datei.

Wir starten damit, dass wir der App weitere Informationen zur Verfügung stellen, wie Titel und Beschreibung. Zum Element four columns div-user-controls fügen wir die Komponenten H2 für die Überschrift und P als Paragraph hinzu.

children = [
    html.H2('Dash - STOCK PRICES'),
    html.P('''Visualising time series with Plotly - Dash'''),
    html.P('''Pick one or more stocks from the dropdown below.''')
]

Wechsle ins Terminal und starte die App mit: python app.py

dash-app-first-layout

Die Basics des App-Layouts

Ein weiteres Feature von Flask (und somit Dash) ist das hot-reloading. Das Feature macht es möglich, eine Änderung im Code ohne einen Neustart der App sehen zu können.

Zusätzlich wird durch debug=True ein Feld rechts in der unteren Ecke der App angezeigt, worin wir auf Fehlermeldungen und den Callback Graph zugriff haben. Wir werden im letzten Abschnitt des Artikels auf Callback Graph zurück kommen, nachdem wir interaktive Funktionalitäten implementiert haben.

dash-app-layout

Erstellen von Grafiken in Dash – Wie man eine Plotly-Grafik anzeigt

Nachdem wir die grundlegenden Container für unsere App erstellt haben, erstellen wir jetzt einen Plotly-Graphen. Die Komponente dcc.Graph aus den dash_core_components nutzt das gleiche figure-Argument wie das plotly.py Paket. Dash übersetzt jeden Aspekt des Charts zu einem Schlüssel-Wert-Paar, welches von der darunterliegenden JavaScript-Bibliothek Plotly.js verarbeitet wird.

Im folgenden Abschnitt verwenden wir die Expressversion von plotly.py und das Paket Dash Core Components. Nach der Installation von Dash sollte plotly in der Entwicklungsumgebung bereits verfügbar sein.

import dash_core_components as dcc
import plotly.express as px

Dash Core Components beinhaltet eine Kollektion von nützlichen und einfach zu bedienenden Komponenten, welche zur Interaktivität und Funktionalität des Dashboard beitragen.

Plotly Express ist die Expressversion von plotly.py, welche die Erstellung von Plotly-Grafiken vereinfacht, mit der Einschränkung dass es die Flexibilität verringert.

Um einen Plot in der rechten Seite unserer App zu zeichnen, wird ein dcc.Graph() als ein Unterelement dem html.Div() mit dem Namen eight columns div-for-charts bg-grey hinzugefügt. Die Komponente dcc.Graph() kann für jede von plotly-gestützte Visualisierung genutzt werden. In unserem Fall wird die figure von px.line() aus dem Paket plotly.express erstellt. Das Layout der Grafik ändern wir durch die Methode update_layout(). Um den Hintergrund der Grafik transparent zu machen, setzen wir die Farbe auf rgba(0, 0, 0, 0). Ohne den Hintergrund transparent zu setzen, würde eine große weiße Box in der Mitte unserer App stehen. Da dcc.Graph() lediglich die Grafik anzeigt, können wir nach der Erstellung die Eigenschaften der Grafik nicht ohne weiteres ändern.

dcc.Graph(id='timeseries',
          config={'displayModeBar': False},
          animate=True,
          figure=px.line(df,
                         x='Date',
                         y='value',
                         color='stock',
                         template='plotly_dark').update_layout(
                                   {'plot_bgcolor': 'rgba(0, 0, 0, 0)',
                                    'paper_bgcolor': 'rgba(0, 0, 0, 0)'})
                                    )

Nachdem Dash die Applikation neu geladen hat (hot-reloading), sieht die App folgendermaßen aus:

dash-app-with-plot

Erstellen einer Dropdown-Liste

Eine weitere Komponente ist dcc.Dropdown(), welche genutzt wird – du kannst es dir vielleicht denken –, um eine Dropdown-Liste zu erstellen. Die verfügbaren Optionen in einem Dropdown werden entweder durch eine (Python) Liste gegeben oder innerhalb einer Funktion definiert.

Für unsere Dropdown-Liste erstellen wir eine Funktion, welche eine Liste von Dictionaries ausgibt. Die Liste besteht aus den Dictionaries mit den Schlüsseln label und value. Hierdurch werden die Optionen unserer Dropdown-Liste definiert. Der Wert von label ist der angezeigte Text in der App und der Wert von value kann innerhalb der App als Input für Funktionen verwendet werden. Solltest du beispielsweise den vollen Namen des Unternehmens anzeigen, kann der Wert von label auf Microsoft gesetzt werden. Der Einfachheit halber wählen wir für label und value die gleichen Werte.

Füge die folgende Funktion zum Skript hinzu, bevor du das Layout der App definierst:

# Creates a list of dictionaries, which have the keys 'label' and 'value'.
def get_options(list_stocks):
    dict_list = []
    for i in list_stocks:
        dict_list.append({'label': i, 'value': i})

    return dict_list

Nach unserer Funktion get_option, können wir das Element dcc.Dropdown() von den Dash Core Components zu unserer App hinzufügen. Füge html.Div() als untergeordenetes Element zur Liste der Elemente in four columns div-user-controls hinzu und setze das Argument className=div-for-dropdown. html.Div() hat ein Unterelement, dcc.Dropdown().

Wir möchten die Möglichkeit habe, nicht nur einen einzelnen Wert, sondern mehrere Werte zur gleichen Zeit auszuwählen und setzen daher das Argument multi=True. Da die App jedoch nicht leer erscheinen soll, setzen wir zudem einen initialen Wert für value.

 html.Div(className='div-for-dropdown',
          children=[
              dcc.Dropdown(id='stockselector',
                           options=get_options(df['stock'].unique()),
                           multi=True,
                           value=[df['stock'].sort_values()[0]],
                           style={'backgroundColor': '#1E1E1E'},
                           className='stockselector')
                    ],
          style={'color': '#1E1E1E'})

Die id und options Argumente des dcc.Dropdown() sind wichtig für den nächsten Abschnitt. Um unterschiedliche Styles der Dropdown-Liste auszuprobieren, folge diesem Link.

Mit Callbacks arbeiten

Wie man der App interaktive Funktionalitäten hinzufügt

Callbacks (eng. für Rückruffunktion) sind für die Interaktivität in einem Dashboard verantwortlich. Sie nehmen Inputs entgegen, z.B. die ausgewählten Optionen einer Dropdown-Liste und leiten die Werte zu einer Funktion weiter. Der Output der Funktion wird durch den Callback an ein definiertes Element geleitet. Wir werden im nächsten Schritt eine Funktion schreiben, welche eine Grafik basierend auf den Namen von Unternehmen ausgibt. In unserer Implementierung wird die erstellte Grafik an eine dcc.Graph()-Komponente geleitet.

Momenten hat die Auswahl der Dropdown-Liste noch keinen Einfluss auf die angezeigte Grafik. Um diese Funktionalität zu implementieren werden wir einen Callback nutzen. Der Callback übernimmt die Kommunikation zwischen der Dropdown-Liste mit der ID id='stockselector' und dem Graphen 'timeseries'. Zur Vorbereitung entfernen wir die zuvor erstellte Grafik, da wir die figure dynamisch erstellen möchten.

In unserer App möchten wir zwei Graphen haben, weshalb wir eine weitere dcc.Graph()-Komponente erstellen und mit dem Argument id='change' identifizierbar machen.

  • Entfernen der figure von der Komponente dcc.Graph(id='timeseries')
  • Hinzufügen der Komponente dcc.Graph(id='change')
  • Beide Komponenten sollten Unterelemente von eight columns div-for-charts bg-grey sein.
dcc.Graph(id='timeseries', config={'displayModeBar': False})
dcc.Graph(id='change', config={'displayModeBar': False})

Callbacks erhöhen die Interaktivität deiner Anwendung. Sie können Eingaben von Komponenten entgegennehmen, z. B. bestimmte Aktien, die über ein Dropdown-Menü ausgewählt werden, diese Eingaben an eine Funktion weitergeben und die von der Funktion zurückgegebenen Werte an Komponenten zurückgeben.

In unserer Implementierung wird ein Callback ausgelöst, sobald der Nutzer eine Aktie im Dropdown auswählt. Der Callback nimmt den Wert des dcc.Dropdown() entgegen (Input) und leitet den Wert an die Funktionen update_timeseries() und update_change() weiter. Die Funktionen filtern die Daten und erstellen eine auf den Inputs basierende Grafik. Der Callback leitet anschließend den Output der Funktionen (Grafik) an die als Output spezifizierten Elemente weiter.

Der Callback ist als Decorator für eine Funktion implementiert. Mehrere Inputs und Outputs sind möglich. Wir werden jedoch bei einem Input und einem Output bleiben. Wir importieren die Objekte dash.dependencies import Input, Output.

Füge die folgende Zeile zu deinemSkript hinzu.

from dash.dependencies import Input, Output

Input() und Output() nehmen die id einer Komponente (bspw. dcc.Graph(id='timeseries') hat die id 'timeseries') sowie die Eigenschaft einer Komponente (hier figure) als Argument entgegen.

Beispiel Callback:

# Update Time Series
@app.callback(Output('id of output component', 'property of output component'),
              [Input('id of input component', 'property of input component')])
def arbitrary_function(value_of_first_input):
    '''
    The property of the input component is passed to the function as value_of_first_input.
    The functions return value is passed to the property of the output component.
    '''
    return arbitrary_output

Wenn wir durch unseren stockselector eine Zeitreihe für einen oder mehrere Aktien anzeigen möchten, benötigen wir eine Funktion. Der value unseres Inputs ist die Liste von ausgewählten Unternehmen aus der Dropdown-Liste.

Implementieren von Callbacks

Die Funktion zeichnet die Linien (eng. traces) der Plotly-Grafik, basierend auf den übergebenen Aktiennamen werden die Linien gezeichnet und zusammen als eine figure ausgegeben, welche von dcc.Graph() angezeigt werden kann. Die Inputs für unsere Funktion werden in der Reihenfolge übergeben, in welcher Sie im Callback gesetzt wurden. (Bemerkung: Seit Dash 2.0 muss dies nicht immer der Fall sein.)

Update der Grafik (figure) für die time series:

@app.callback(Output('timeseries', 'figure'),
              [Input('stockselector', 'value')])
def update_timeseries(selected_dropdown_value):
    ''' Draw traces of the feature 'value' based one the currently selected stocks '''
    # STEP 1
    trace = []  
    df_sub = df
    # STEP 2
    # Draw and append traces for each stock
    for stock in selected_dropdown_value:   
        trace.append(go.Scatter(x=df_sub[df_sub['stock'] == stock].index,
                                 y=df_sub[df_sub['stock'] == stock]['value'],
                                 mode='lines',
                                 opacity=0.7,
                                 name=stock,
                                 textposition='bottom center'))  
    # STEP 3
    traces = [trace]
    data = [val for sublist in traces for val in sublist]
    # Define Figure
    # STEP 4
    figure = {'data': data,
              'layout': go.Layout(
                  colorway=["#5E0DAC", '#FF4F00', '#375CB1', '#FF7400', '#FFF400', '#FF0056'],
                  template='plotly_dark',
                  paper_bgcolor='rgba(0, 0, 0, 0)',
                  plot_bgcolor='rgba(0, 0, 0, 0)',
                  margin={'b': 15},
                  hovermode='x',
                  autosize=True,
                  title={'text': 'Stock Prices', 'font': {'color': 'white'}, 'x': 0.5},
                  xaxis={'range': [df_sub.index.min(), df_sub.index.max()]},
              ),

              }

    return figure

STEP 1

  • Ein trace wird für jede Aktie gezeichnet. Erstellen einer leeren list für jeden trace der Plotly-Grafik.

STEP 2

In einem for-loop, wird ein trace für die Plotly-Grafik von go.Scatter erstellt. (go stammt aus dem Import import plotly.graph_objects as go)

  • Iteriere über die selektierten Aktien im Drodown, zeichne eintrace und hänge dastrace an die Liste aus Step 3 an.

STEP 3

  • Ebne (eng. flatten) die Liste.

STEP 4

Plotly-Grafiken sind Dictionaries mit den Schlüsseln data und layout. Der Wert von data ist unsere Liste mit den erstellten traces. Das layout wird mit go.Layout() definiert.

  • Füge die traces zu unserer figure (Grafik) hinzu
  • Definiere das Layout der figure

Diese gleichen Schritte werden für unsere zweite Grafik durchgeführt, mit dem Unterschied, dass die Daten auf der y-Achse auf change gesetzt werden.

Update der Grafik change:

@app.callback(Output('change', 'figure'),
              [Input('stockselector', 'value')])
def update_change(selected_dropdown_value):
    ''' Draw traces of the feature 'change' based one the currently selected stocks '''
    trace = []
    df_sub = df
    # Draw and append traces for each stock
    for stock in selected_dropdown_value:
        trace.append(go.Scatter(x=df_sub[df_sub['stock'] == stock].index,
                                 y=df_sub[df_sub['stock'] == stock]['change'],
                                 mode='lines',
                                 opacity=0.7,
                                 name=stock,
                                 textposition='bottom center'))
    traces = [trace]
    data = [val for sublist in traces for val in sublist]
    # Define Figure
    figure = {'data': data,
              'layout': go.Layout(
                  colorway=["#5E0DAC", '#FF4F00', '#375CB1', '#FF7400', '#FFF400', '#FF0056'],
                  template='plotly_dark',
                  paper_bgcolor='rgba(0, 0, 0, 0)',
                  plot_bgcolor='rgba(0, 0, 0, 0)',
                  margin={'t': 50},
                  height=250,
                  hovermode='x',
                  autosize=True,
                  title={'text': 'Daily Change', 'font': {'color': 'white'}, 'x': 0.5},
                  xaxis={'showticklabels': False, 'range': [df_sub.index.min(), df_sub.index.max()]},
              ),
              }

    return figure

Starte die App erneut. In der App können nun über die Dropdown-Liste die verschiedenen Aktien ausgewählt werden. Für jedes selektierte Element wird ein Line-Plot gezeichnet und angezeigt. Standardmäßig hat die Dropdown-Komponente einige Funktionalitäten, wie bspw. die Suchfunktion, weshalb die Selektion von einzelnen Elementen auch bei vielen Auswahlmöglichkeiten sehr einfach ist.

dash-app-final

Visualisieren von Callbacks – Callback Graph

Mit den implementierten Callbacks ist die App fertig. Werfen wir nun einen Blick auf den sogenannten Callback Graph. Wenn die App mit debug=True gestartet wird, erscheint in der unteren rechten Ecke ein Feld. Hierüber kann eine visuelle Repräsentation des Callback Graph angezeigt werden, welchen wir im Skript definiert haben. Der Graph zeigt, dass die Komponenten timeseries und change eine figure anzeigen, die auf den ausgewählten Werten im stockselector basieren.

Sollten Callbacks nicht so funktionieren, wie es geplant wurde, ist dieses Tool sehr hilfreich beim Debuggen.

dash-app-final-callback

Fazit

Fassen wir die wichtigsten Elemente von Dash zusammen. Um die App zu starten, benötigt man nur ein paar Zeilen Code. Ein grundlegendes Verständnis von HTML und CSS genügt, um den Stil eines Dashboards anzupassen. Interaktive Grafiken können ohne Probleme implementiert werden, da Dash für das Arbeiten mit interaktiven Plotly-Grafiken entwickelt wurde. Über Callbacks, welche den Input von Nutzenden an Funktionen weiterleiten, werden unterschiedliche Komponenten miteinander verbunden.

Wenn dir der Blogartikel gefallen hat, kontaktiere mich gerne über LinkedIn oder per Email. Ich bin neugierig, welche weiteren Anwendungsmöglichkeiten das Framework bietet und freue mich auf Fragen zu Daten, Machine Learning und KI sowie den spannenden Lösungen, an denen wir bei STATWORX arbeiten.

Vielen Dank!

Alexander Blaufuss Alexander Blaufuss

Erfahre mehr!

Als eines der führenden Unternehmen im Bereich Data Science, Machine Learning und KI begleiten wir Sie in die datengetriebene Zukunft. Erfahren Sie mehr über statworx und darüber, was uns antreibt.
Über uns