
1 Introduction
Ce tutoriel explore Shiny pour Python, un framework puissant pour transformer vos analyses de données en applications web interactives, sans avoir besoin de connaître le HTML, le CSS ou le JavaScript. Nous irons au-delà des bases pour explorer le cœur de Shiny : la programmation réactive et la construction d’applications structurées.
Public Cible : Développeurs et analystes de données à l’aise avec Pandas qui souhaitent construire des outils interactifs, maintenables et performants.
Objectifs :
- Maîtriser le modèle de programmation réactive de Shiny.
- Utiliser les calculs réactifs (
@reactive.calc) pour optimiser votre application. - ✨ Valider les entrées et prévenir les erreurs avec
req(). - Structurer une application avec une mise en page professionnelle et des graphiques interactifs.
- ✨ Organiser votre code avec les Modules Shiny pour des applications plus larges.
1.1 ✨ Avant de Commencer : Lancer votre Application
Une fois que vous avez écrit votre code dans un fichier app.py, comment le voir en action ? Ouvrez un terminal dans le dossier de votre projet et lancez cette commande :
shiny run app.py --reloadshiny rundémarre le serveur de développement.--reloadredémarre automatiquement l’application à chaque fois que vous sauvegardez votre fichierapp.py, ce qui est indispensable pour un développement rapide.
2 Partie 1 : Le Cœur de Shiny - UI, Serveur et Réactivité
Une application Shiny (app.py) a deux composantes principales :
ui(User Interface) : L’agencement visuel de l’application. Il est construit avec des widgets d’entrée (input_...) et de sortie (output_...).server(Logique Serveur) : La “salle des machines” où la magie opère. Elle prend les valeurs desinput, effectue des calculs et renvoie les résultats auxoutput.
Le lien entre les deux est la réactivité : Shiny construit un “graphe” de dépendances. Quand un input change, Shiny recalcule intelligemment et uniquement les outputs qui en dépendent directement ou indirectement.
# app.py
from shiny import App, render, ui
# 1. L'interface utilisateur (UI) avec un slider et une sortie texte
app_ui = ui.page_fluid(
ui.input_slider("n", "Nombre d'observations", min=0, max=100, value=20),
ui.output_text("txt"),
)
# 2. La logique serveur
def server(input, output, session):
@output
@render.text
def txt():
# input.n() crée une dépendance réactive au slider "n"
return f"La valeur sélectionnée est {input.n()}"
# 3. L'application
app = App(app_ui, server)3 Partie 2 : Maîtriser le Flux Réactif
Pour des applications complexes, il est crucial de bien gérer les calculs.
3.1 Optimiser avec @reactive.calc
Imaginez un filtrage de données coûteux utilisé par un graphique ET un tableau. Sans précaution, le calcul serait fait deux fois. La solution est le décorateur @reactive.calc qui met en cache son résultat. Il n’est ré-exécuté que si ses dépendances (inputs ou autres calculs réactifs) changent.
3.2 ✨ Prévenir les erreurs avec req()
Que se passe-t-il si un input est vide au démarrage ? Le code peut planter. shiny.reactive.req() permet de “geler” un calcul tant que les entrées requises ne sont pas valides ou disponibles. C’est une pratique essentielle pour des applications robustes.
from shiny import App, reactive, render, ui, req
import pandas as pd
from plotnine import ggplot, aes, geom_point
df_iris = pd.read_csv("[https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv](https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv)")
app_ui = ui.page_fluid(
ui.input_select("species", "Espèce", choices=[""] + df_iris["species"].unique().tolist()),
ui.output_plot("plot"),
ui.output_data_frame("table")
)
def server(input, output, session):
@reactive.calc
def filtered_data():
# Le calcul ne se poursuit que si une espèce est sélectionnée
req(input.species())
return df_iris[df_iris["species"] == input.species()]
@output
@render.plot
def plot():
return (ggplot(filtered_data(), aes("sepal_length", "sepal_width")) + geom_point())
@output
@render.data_frame
def table():
return render.DataGrid(filtered_data())
app = App(app_ui, server)3.3 Contrôler l’Exécution avec @reactive.event
Pour déclencher un calcul uniquement sur un événement (ex: un clic de bouton), utilisez @reactive.event.
# Dans la fonction server:
@output
@render.plot
@reactive.event(input.update_button) # Ne s'exécute que sur clic
def plot():
# ...
# Dans l'UI, on ajoute le bouton: ui.input_action_button("update_button", "Mettre à jour")4 Partie 3 : Construire un Tableau de Bord Interactif
Shiny fournit des composants de mise en page pour créer des interfaces professionnelles.
ui.layout_sidebar: Crée un panneau latéral pour les contrôles.ui.navset_card_tab: Crée des onglets.ui.card: Encadre le contenu.ui.value_box: Affiche des indicateurs clés.
4.1 ✨ Graphiques Interactifs (Plotly, Altair, etc.)
Shiny s’intègre parfaitement avec les bibliothèques de graphiques interactifs. Il suffit d’utiliser le décorateur de rendu approprié (@render.plotly, @render.altair_chart, etc.) ou le plus générique @render.ui.
# Dans la UI
# ui.output_ui("interactive_plot")
# Dans le serveur
import plotly.express as px
@output
@render.plot(alt="Graphique interactif Plotly")
def interactive_plot():
# On utilise les données filtrées du @reactive.calc précédent
df = filtered_data()
return px.scatter(df, x="sepal_length", y="sepal_width")5 ✨ Partie 4 : Structurer une Application Complexe avec les Modules
Quand une application grandit, le fichier app.py peut devenir ingérable. Les Modules Shiny sont la solution. Un module est une pièce autonome de l’application (UI + serveur) qui peut être réutilisée.
Avantages :
- Organisation : Sépare la logique en morceaux gérables.
- Réutilisabilité : Utilisez le même module à plusieurs endroits.
- Isolation : Pas de conflit de noms d’ID entre les modules et l’application principale.
# Dans un fichier séparé, ex: my_module.py
from shiny import module, ui
@module.ui
def my_module_ui():
# Le namespace ns() garantit des ID uniques
ns = module.ns
return ui.div(
ui.input_slider(ns("n"), "Slider du module", 1, 100, 50),
ui.output_text(ns("txt"))
)
@module.server
def my_module_server(input, output, session):
@output
@render.text
def txt():
return f"Valeur du module : {input.n()}"
# Dans votre app.py principal
# from my_module import my_module_ui, my_module_server
# ...
# app_ui = ui.page_fluid(my_module_ui("id_unique_du_module"))
# def server(input, output, session):
# my_module_server("id_unique_du_module")6 Partie 5 : Partager votre Application
Une fois votre application prête, vous voulez la partager. Les deux options principales sont :
- shinyapps.io : Un service d’hébergement de Posit, facile à utiliser et avec une offre gratuite généreuse pour démarrer.
- Posit Connect : Une solution professionnelle pour les entreprises, pour déployer des contenus sécurisés.
Le processus implique généralement de créer un fichier requirements.txt et d’utiliser le package rsconnect-python :
# 1. Geler les dépendances
pip freeze > requirements.txt
# 2. Déployer (après configuration initiale)
rsconnect deploy shiny . --name mon-dashboard --title "Mon Dashboard"7 Conclusion
Vous avez maintenant les clés pour construire des applications Shiny pour Python robustes, performantes et maintenables. En maîtrisant le triptyque input -> reactive -> output, en validant vos entrées avec req() et en structurant votre code avec des modules, vous pouvez transformer n’importe quelle analyse statique en un outil interactif puissant.
Objectif : Construire un tableau de bord complet pour explorer le jeu de données penguins. L’utilisateur pourra filtrer les données via des contrôles et visualiser les résultats dans des graphiques et des tableaux.
Instructions :
- Créez un fichier
app.py. - Copiez-collez le code ci-dessous.
- Lancez l’application avec
shiny run app.py --reload. - Analysez le code pour bien comprendre comment chaque partie (UI, serveur, réactivité) fonctionne.
Ce code peut nécessiter des ajustement du fat des mise à jour de certaines librairies!
# app.py
import seaborn as sns
import plotly.express as px
from faicons import icon_svg
from shiny import App, reactive, render, req, ui
# 1. Chargement et préparation des données
df = sns.load_dataset("penguins")
df.dropna(inplace=True)
# 2. Définition de l'Interface Utilisateur (UI)
app_ui = ui.page_sidebar(
ui.sidebar(
ui.input_slider("mass", "Masse (g)", 2000, 7000, 3500),
ui.input_checkbox_group(
"species",
"Espèces",
{s: s for s in df["species"].unique()},
selected=df["species"].unique()[0],
),
title="Contrôles de l'Explorateur",
),
ui.layout_columns(
ui.value_box("Nombre de manchots", ui.output_text("penguin_count"), showcase=icon_svg("kiwi-bird")),
ui.value_box("Longueur moyenne du bec", ui.output_text("avg_bill_length"), showcase=icon_svg("ruler-horizontal")),
ui.value_box("Profondeur moyenne du bec", ui.output_text("avg_bill_depth"), showcase=icon_svg("ruler-vertical")),
),
ui.navset_card_tab(
ui.nav("Analyse Visuelle", ui.output_plot("scatter_plot")),
ui.nav("Données Filtrées", ui.output_data_frame("data_table")),
),
title="Explorateur des Manchots",
)
# 3. Définition de la logique Serveur
def server(input, output, session):
@reactive.calc
def filtered_data():
req(input.species()) # Ne pas continuer si aucune espèce n'est cochée
# Filtrage basé sur les inputs
filtered = df[df["species"].isin(input.species())]
filtered = filtered[filtered["body_mass_g"] > input.mass()]
return filtered
@output
@render.text
def penguin_count():
return str(len(filtered_data()))
@output
@render.text
def avg_bill_length():
return f"{filtered_data()['bill_length_mm'].mean():.2f} mm"
@output
@render.text
def avg_bill_depth():
return f"{filtered_data()['bill_depth_mm'].mean():.2f} mm"
@output
@render.plot
def scatter_plot():
return px.scatter(
filtered_data(),
x="bill_length_mm",
y="bill_depth_mm",
color="species",
title="Longueur vs. Profondeur du Bec",
labels={"bill_length_mm": "Longueur du bec (mm)", "bill_depth_mm": "Profondeur du bec (mm)"}
)
@output
@render.data_frame
def data_table():
return render.DataGrid(filtered_data())
# 4. Création de l'application
app = App(app_ui, server)7.1 Points Clés à Observer
@reactive.calc: La fonctionfiltered_data()est le cœur de l’application. Elle est mise en cache et ne se recalcule que siinput.mass()ouinput.species()changent. Tous les outputs dépendent d’elle, ce qui rend le code efficace et facile à lire.req(input.species()): Empêche l’application de planter si l’utilisateur décoche toutes les cases.- Mise en page : L’utilisation de
page_sidebar,layout_columns,value_boxetnavset_card_tabpermet de créer une interface structurée et professionnelle. - Graphique Interactif : Le nuage de points est généré avec Plotly, ce qui le rend interactif (zoom, survol) sans effort supplémentaire.