Interface utilisateur à programmation schématisée avec Plotly Dash
Par: Jeffery Zhang, Statistique Canada
Introduction
Les scientifiques des données créent souvent des modèles qui sont mis en œuvre en R ou en Python. Si ces modèles sont destinés à la production, ils devront être accessibles aux utilisateurs non spécialisés.
Pour rendre les modèles de données accessibles aux utilisateurs non spécialisés dans la phase de production, l'un des principaux problèmes réside dans les aléas de la création d'interfaces utilisateurs accessibles. Bien qu'il soit acceptable qu'un prototype de recherche soit exécuté à partir d'une ligne de commande, toutes les complexités que ce type d'interface présente peuvent grandement décourager les utilisateurs non spécialisés.
La plupart des scientifiques des données manquent d'expérience dans la conception d'interfaces utilisateurs, et la plupart des projets ne disposent pas du budget nécessaire pour l'embauche d'un développeur spécialiste des interfaces utilisateurs. Dans le présent article, nous présentons un outil qui permet aux personnes qui ne sont pas des spécialistes de ce type d'interface de créer rapidement une interface utilisateur satisfaisante en langage Python.
En quoi consiste Plotly Dash?
Plotly est une bibliothèque de visualisation des données à code source ouvert. Dash est un cadre à programmation schématisée pour la conception d'applications de données à code source ouvert qui s'appuie sur Plotly. Plotly Dash offre une solution au problème des interfaces utilisateurs de données. Avec Plotly Dash, les scientifiques des données qui ne sont pas spécialisés dans les interfaces utilisateurs peuvent en quelques jours en concevoir une qui sera satisfaisante pour une application de données. Dans la plupart des projets, un investissement de deux à cinq jours de travail supplémentaires pour la conception d'une interface utilisateur graphique interactive en vaut la peine.
Comment fonctionne Plotly Dash?
Plotly et Dash peuvent être considérés comme des langages dédiés. Plotly est un langage dédié permettant de décrire des graphiques. L'objet central de Plotly est une figure
, qui décrit tous les aspects d'un graphique tels que les axes, ainsi que les composants graphiques comme les barres, les lignes ou les secteurs. Nous utilisons Plotly pour créer les objets de la figure
et avons ensuite recours à l'un des moteurs de rendu disponibles pour le rendre sur un dispositif de sortie cible, tel qu'un navigateur Web.
Dash fournit deux langages dédiés et un moteur de rendu Web pour les objets Figure de Plotly.
Le premier langage dédié de Dash sert à décrire la structure d'une interface utilisateur Web. Il comprend des composants pour les éléments HTML tels que div et p, ainsi que des contrôles d'interface utilisateur tels que Slider
et DropDown
. L'un des éléments clés du langage dédié Web de Dash est le composant Graph, qui nous permet d'intégrer une figure
Plotly dans l'interface utilisateur Web de Dash.
Voici un exemple d'une application Dash simple.
From dash import Dash, html, dcc, callback, Output, Input
import plotly.express as px
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv')
app = Dash(__name__)
app.layout = html.Div([
html.H1(children='Title of Dash App', style={'textAlign':'center'}),
dcc.Dropdown(df.country.unique(), 'Canada', id='dropdown-selection'),
dcc.Graph(id='graph-content')
])
if __name__ == '__main__':
app.run_server(debug=True)
Voici à quoi cela ressemble dans un navigateur Web.
Le deuxième langage dédié de Dash permet de décrire des flux de données réactifs. Cela nous permet d'ajouter de l'interactivité à l'application de données en décrivant de quelle façon les données passent des composants d'entrée de l'utilisateur au modèle de données, puis reviennent à l'interface utilisateur.
L'ajout du code suivant à l'exemple ci-dessus crée un flux de données réactif entre le composant d'entrée dropdown-selection
, la fonction update_graph
et le graphique de sortie. Dès que la valeur du composant d'entrée dropdown-selection
change, la fonction update_graph
est lancée avec la nouvelle valeur de dropdown-selection
, et la valeur de retour de update-graph est envoyée à la propriété figure
de l'objet graph-content. Cette opération met à jour le graphique en fonction de la sélection de l'utilisateur dans le composant déroulant.
@callback(
Output('graph-content', 'figure'),
Input('dropdown-selection', 'value')
)
def update_graph(value):
dff = df[df.country==value]
return px.line(dff, x='year', y='pop')
Fonctions utiles de Dash
Vous trouverez ci-dessous quelques scénarios courants d'applications de données; nous indiquons également de quelle façon les fonctions de Dash prennent en charge ces scénarios.
Longs délais de traitement
Il arrive que l'exécution d'un modèle de données prenne beaucoup de temps. Par conséquent, il est judicieux de fournir à l'utilisateur une rétroaction au cours de ce processus afin de l'informer que le modèle de données est en cours d'exécution et que l'application n'est pas tombée en panne. Il serait encore plus utile de fournir une mise à jour de l'état d'avancement afin que l'utilisateur sache approximativement la part du travail qui a été accomplie et celle qui reste à faire.
Nous pouvons également nous rendre compte que nous avons commis une erreur lors de la définition des paramètres d'un travail à longue exécution; dans ce cas, nous voudrions annuler le travail en cours et le recommencer après y avoir apporté des corrections. La fonction de Dash permettant la mise en œuvre de ces scénarios s'appelle « Background callbacks » (en anglais seulement).
Voici un exemple d'une application Dash simple qui présente un travail à longue exécution et montre la barre de progression et le bouton d'annulation.
Rappels multiples
Normalement, la valeur d'une sortie est déterminée de manière unique par un rappel. Si plusieurs rappels mettent à jour la même sortie, nous serons confrontés à un scénario dans lequel la sortie aura plusieurs valeurs en même temps et nous ne saurons pas laquelle est la bonne.
Cependant, nous pourrons parfois prendre le risque de lier plusieurs rappels à la même sortie pour simplifier les choses. Dash nous permet de le faire en indiquant expressément que nous autorisons les sorties multiples. Cette fonction s'active lorsque nous fixons la valeur du paramètre allow_duplicate
de Output
à True
. Voici un exemple :
app.layout = html.Div([
html.Button('Draw Graph', id='draw-2'),
html.Button('Reset Graph', id='reset-2'),
dcc.Graph(id='duplicate-output-graph')
])
@app.callback(
Output('duplicate-output-graph', 'figure', allow_duplicate=True),
Input('draw-2', 'n_clicks'),
prevent_initial_call=True
)
def draw_graph(n_clicks):
df = px.data.iris()
return px.scatter(df, x=df.columns[0], y=df.columns[1])
@app.callback(
Output('duplicate-output-graph', 'figure'),
Input('reset-2', 'n_clicks'),
)
def reset_graph(input):
return go.Figure()
app.run_server(debug=True)
Dans ce cas, nous disposons de deux boutons pour mettre à jour un graphique : Draw (dessiner) et Reset (réinitialiser). Le graphique sera mis à jour par le dernier bouton utilisé. Bien que cela soit pratique, la conception d'une interface utilisateur de cette manière comporte un risque. Dans un ordinateur pourvu d'un seul pointeur de souris, on peut supposer qu'un seul clic de bouton est possible à un moment donné. Par contre, dans le cas d'un écran tactile multipoint comme celui d'un téléphone intelligent ou d'une tablette, il est possible de cliquer sur deux boutons en même temps. En général, dès que nous autorisons des rappels multiples, la sortie devient potentiellement indéterminée, ce qui peut entraîner certains bogues très difficiles à reproduire.
Cette fonctionnalité est à la fois pratique et potentiellement dangereuse. Par conséquent, son utilisation est à vos risques et périls!
Composants personnalisés
Parfois, l'ensemble des composants fournis avec Dash n'est pas suffisant. L'interface utilisateur Web de Dash est créée avec React; Dash fournit un outil pratique pour intégrer des composants React personnalisés dans Dash. Cet article ne traite pas en détail de React ni de l'intégration Dash-React. Cependant, vous pouvez en savoir plus à ce sujet en consultant la page « Build your own components » (en anglais seulement).
Affichage des erreurs
Durant les calculs, il arrive qu'une erreur se produise en raison de problèmes liés aux données, au code ou à une erreur de l'utilisateur. Au lieu d'interrompre l'application, nous pourrions vouloir afficher l'erreur pour l'utilisateur et lui fournir quelques renseignements sur ce qu'il est possible de faire pour la corriger.
Deux fonctions de Dash sont utilisées pour ce scénario : multiple outputs
et dash.no_update
.
La fonction multiple outputs
autorise les rappels et retourne plusieurs sorties sous la forme d'un uplet.
Quant à la fonction dash.no_update
, elle prend une valeur et peut la retourner dans un emplacement de sortie pour indiquer qu'il n'y a pas de changement dans cette sortie.
Voici un exemple qui utilise ces deux fonctions pour mettre en œuvre l'affichage d'une erreur :
@app.callback(
Output('out', 'text'),
Output('err', 'text'),
Input('num', 'value')
)
def validate_num(num):
if validate(num):
return "OK", ""
else:
return dash.no_update, "Error"
Mises à jour partielles
Les calculs de rappel Dash étant effectués sur le serveur, il faut, pour afficher les résultats à l'intention du client, rassembler toutes les valeurs de retour du rappel et les lui envoyer à chaque mise à jour.
Ces mises à jour concernent parfois des objets de figure
très volumineux, qui consomment beaucoup de bande passante et ralentissent le processus de mise à jour. Cela aura une incidence négative sur l'expérience d'utilisateur. Une manière simple de réaliser des mises à jour par rappel consiste à effectuer des mises à jour monolithiques sur de grandes structures de données telles que des figures, même si seule une petite partie, comme le titre, a été modifiée.
Pour optimiser l'utilisation de la bande passante et améliorer l'expérience d'utilisateur, Dash dispose d'une fonction appelée « Partial Update » (mise à jour partielle). Cette fonctionnalité introduit un nouveau type de valeur de retour pour les rappels appelé Patch. Patch désigne les sous-composants d'une structure de données plus large qui doivent être mis à jour, ce qui nous permet d'éviter d'envoyer une structure de données entière dans l'ensemble du réseau lorsque seule une partie de celle-ci doit être mise à jour.
Voici un exemple de mise à jour partielle qui ne sert à modifier que la couleur de la police du titre de la figure, au lieu de la figure entière :
From dash import Dash, html, dcc, Input, Output, Patch
import plotly.express as px
import random
app = Dash(__name__)
df = px.data.iris()
fig = px.scatter(
df, x="sepal_length", y="sepal_width", color="species", title="Updating Title Color"
)
app.layout = html.Div(
[
html.Button("Update Graph Color", id="update-color-button-2"),
dcc.Graph(figure=fig, id="my-fig"),
]
)
@app.callback(Output("my-fig", "figure"), Input("update-color-button-2", "n_clicks"))
def my_callback(n_clicks):
# Defining a new random color
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
new_color = f"rgb({red}, {green}, {blue})"
# Creating a Patch object
patched_figure = Patch()
patched_figure["layout"]["title"]["font"]["color"] = new_color
return patched_figure
if __name__ == "__main__":
app.run_server(debug=True)
Interface utilisateur dynamique et filtrage de rappels
Parfois, il n'est pas possible de définir statiquement le flux de données. Si, par exemple, nous voulons créer une pile de filtres qui permet à l'utilisateur d'ajouter des filtres de façon flexible, nous ne saurons pas à l'avance quels filtres ce dernier ajoutera. C'est statiquement impossible de définir des flux de données comportant des composants d'entrée que l'utilisateur ajoute au moment de l'exécution.
Voici un exemple de pile dynamique de filtres à laquelle l'utilisateur peut en ajouter de nouveaux en cliquant sur le bouton ADD FILTER
. L'utilisateur peut ensuite sélectionner la valeur du filtre à l'aide de la liste déroulante qui s'ajoute dynamiquement.
Dash prend en charge ce scénario en nous permettant de lier des rappels à des sources de données de manière dynamique grâce à un mécanisme de filtrage.
Le code suivant met en œuvre l'interface utilisateur ci-dessus :
From dash import Dash, dcc, html, Input, Output, ALL, Patch
app = Dash(__name__)
app.layout = html.Div(
[
html.Button("Add Filter", id="add-filter-btn", n_clicks=0),
html.Div(id="dropdown-container-div", children=[]),
html.Div(id="dropdown-container-output-div"),
]
)
@app.callback(
Output("dropdown-container-div", "children"), Input("add-filter-btn", "n_clicks")
)
def display_dropdowns(n_clicks):
patched_children = Patch()
new_dropdown = dcc.Dropdown(
["NYC", "MTL", "LA", "TOKYO"],
id={"type": "city-filter-dropdown", "index": n_clicks},
)
patched_children.append(new_dropdown)
return patched_children
@app.callback(
Output("dropdown-container-output-div", "children"),
Input({"type": "city-filter-dropdown", "index": ALL}, "value"),
)
def display_output(values):
return html.Div(
[html.Div(f"Dropdown {i + 1} = {value}") for (i, value) in enumerate(values)]
)
if __name__ == "__main__":
app.run_server(debug=True)
Au lieu de définir les composants DropDown
de manière statique, nous créons dropdown-container-div
, où seront stockés tous les composants DropDown
que l'utilisateur créera. Si nous créons les composants DropDown
dans display_dropdowns
, chaque nouveau composant DropDown
sera doté d'un id
(identifiant). En règle générale, cette valeur id
aurait la forme d'une chaîne de caractères; cependant, pour activer le filtrage des rappels, Dash permet également que id
soit un dictionnaire. Il peut s'agir d'un dictionnaire arbitraire, de sorte que les clés de l'exemple ci-dessus ne sont pas des valeurs spéciales. Si id
est un dictionnaire, nous pouvons définir des filtres détaillés dont l'appariement est effectué avec chaque clé du dictionnaire.
Dans l'exemple ci-dessus, lorsque l'utilisateur ajoute de nouveaux composants DropDown
, les identifiants (id
) des composants dynamiques DropDown
sont marqués par des identifiants séquentiels comme les suivants :
- '{"type": "city-filter-dropdown", "index": 1}
- '{"type": "city-filter-dropdown", "index": 2}
- '{"type": "city-filter-dropdown", "index": 3}
Ensuite, dans les métadonnées du rappel display_output
, nous définissons son entrée comme Input({"type" : « city-filter-dropdown », « index" : ALL}, « value »)
, qui s'apparie alors à tous les composants dont l'id
a un type égal à city-filter-dropdown
. En indiquant "index": ALL
, nous précisons que l'appariement s'applique à toutes les valeurs de l'indice (index
).
Outre ALL
, Dash prend également en charge d'autres critères de filtrage tels que MATCH
et ALLSMALLER
. Pour en savoir davantage sur cette fonctionnalité, consultez la page « Pattern Matching Callbacks » (en anglais seulement).
Exemples
Voici quelques exemples d'applications créées avec Dash :
D'autres exemples figurent à la page « Dash Enterprise App Gallery » (en anglais seulement).
Conclusion
Une bonne interface utilisateur peut ajouter de la valeur aux projets en rendant les produits livrables plus présentables et utilisables. Pour les systèmes de production qui seront utilisés pendant longtemps, l'investissement préalable dans l'interface utilisateur peut se révéler rentable au fil du temps en réduisant la courbe d'apprentissage, en diminuant la confusion chez les utilisateurs et en améliorant leur productivité. Plotly Dash contribue à réduire considérablement le coût de conception d'interfaces utilisateurs pour les applications de données, ce qui peut augmenter le rendement sur l'investissement dans la conception de telles interfaces.
Rencontre avec le scientifique des données
Si vous avez des questions à propos de mon article ou si vous souhaitez en discuter davantage, je vous invite à une Rencontre avec le scientifique des données, un événement au cours duquel les auteurs rencontrent les lecteurs, présentent leur sujet et discutent de leurs résultats.
Jeudi, le 15 juin
De 13 00 h à 16 00 h, HE
MS Teams – le lien sera fourni aux participants par courriel
Inscrivez-vous à la présentation Rencontre avec le scientifique des données.
À bientôt!
Abonnez-vous au bulletin d'information du Réseau de la science des données pour la fonction publique fédérale pour rester au fait des dernières nouvelles de la science des données.
Références
- Plotly: Low-Code Data App Development (en anglais seulement)
- Rappels en arrière-plan : Plotly - Background Callbacks (en anglais seulement)
- Composants personnalisés : Plotly - Build Your Own Components (en anglais seulement)
- Rappels de filtrage : Plotly - Pattern-Matching Callbacks (en anglais seulement)
- Date de modification :