Un dashboard (ou tableau de bord) est une interface visuelle qui regroupe, organise et affiche les indicateurs clés de performance (KPI) et les données sous forme de graphiques, de tableaux et de visualisations interactives. On peut se demander si la capacité à créer des dashboards personnalisés ne représente pas un tournant majeur dans la façon dont nous communiquons nos résultats scientifiques ?
- Accessibilité : Rendre les analyses complexes compréhensibles pour des non-spécialistes
- Interactivité : Permettre l’exploration dynamique des données sans reprogrammation
- Reproductibilité : Standardiser les analyses et visualisations
- Collaboration : Faciliter le partage et la discussion autour des données
- Prise de décision : Synthétiser l’information pour orienter les stratégies
Ce document regroupe quatre approches complémentaires pour créer des dashboards :
| Framework | Type | Complexité | Usage principal |
|---|---|---|---|
| Streamlit | Web (Python) | Faible à moyenne | Prototypage rapide et dashboards interactifs pour DS et ML |
| Dash | Web (Python) | Moyenne | Dashboards data science, ML |
| Flask | Web (Python) | Élevée | Applications web sur mesure |
| Electron | Desktop | Élevée | Applications desktop multiplateformes |
Streamlit : Le framework de prototypage ultra-rapide
Streamlit est un framework Python open-source créé en 2019 qui révolutionne le développement de dashboards en adoptant une philosophie radicalement différente : “Transformez un script Python en application web en quelques minutes”.
Définition : Streamlit est un framework qui transforme automatiquement du code Python séquentiel en application web interactive, sans nécessiter de callbacks explicites ni de séparation frontend/backend.
Paradigme de programmation impérative
Contrairement à Dash qui utilise des callbacks déclaratifs, Streamlit suit un modèle de script réactif :
import streamlit as st
import pandas as pd
# Le script s'exécute du haut vers le bas à chaque interaction
st.title("Mon Dashboard")
# Widget qui crée automatiquement l'interactivité
valeur = st.slider("Sélectionnez une valeur", 0, 100, 50)
# Le code après est automatiquement ré-exécuté quand le slider change
st.write(f"Vous avez sélectionné : {valeur}")
resultat = valeur ** 2
st.write(f"Le carré est : {resultat}")
Propriété clé : À chaque interaction utilisateur, tout le script est ré-exécuté du début à la fin. C’est ce qu’on appelle le “rerun model”.
Composants et widgets
Affichage de contenu
Streamlit propose des fonctions simples pour afficher du contenu :
import streamlit as st
# Texte et titres
st.title("Titre principal")
st.header("En-tête")
st.subheader("Sous-en-tête")
st.text("Texte simple")
st.markdown("Texte **Markdown**")
st.latex(r"\frac{d}{dx} e^x = e^x")
# Code
st.code("print('Hello')", language='python')
# Métriques
st.metric(label="IC50", value="2.3 μM", delta="-0.5 μM")
Propriété importante : Toutes ces fonctions s’affichent dans l’ordre d’exécution du script.
Widgets interactifs
Les widgets retournent directement leur valeur sans callback :
# Slider
temperature = st.slider("Température", 0, 100, 60)
# Input texte
smiles = st.text_input("Entrez SMILES", "CCO")
# Selectbox (dropdown)
catalyseur = st.selectbox("Catalyseur", ["Pd", "Pt", "Ru"])
# Multiselect
series = st.multiselect("Séries", ["A", "B", "C"], default=["A"])
# Checkbox
afficher_details = st.checkbox("Afficher détails")
# Radio buttons
type_graph = st.radio("Type de graphique", ["Scatter", "Box", "Violin"])
# Date input
date = st.date_input("Date d'expérience")
# File uploader
fichier = st.file_uploader("Charger CSV", type=['csv'])
Définition : Chaque widget retourne immédiatement sa valeur courante, qui peut être utilisée dans la suite du script.
Affichage de données
import pandas as pd
import numpy as np
# DataFrame
df = pd.DataFrame(np.random.randn(10, 3), columns=['A', 'B', 'C'])
st.dataframe(df) # Table interactive (scrollable, triable)
st.table(df) # Table statique
# JSON
st.json({'key': 'value', 'array': [1, 2, 3]})
Graphiques
Streamlit intègre nativement plusieurs bibliothèques de visualisation :
import plotly.express as px
# Plotly (recommandé pour interactivité)
fig = px.scatter(df, x='MW', y='LogP', color='Activity')
st.plotly_chart(fig, use_container_width=True)
# Matplotlib
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
st.pyplot(fig)
# Graphiques natifs Streamlit (simples et rapides)
st.line_chart(df)
st.bar_chart(df)
st.area_chart(df)
Gestion de l’état avec session_state
Problème : Le modèle de rerun fait que toutes les variables sont réinitialisées à chaque interaction.
Solution : st.session_state permet de persister des données entre les reruns.
import streamlit as st
# Initialiser l'état si nécessaire
if 'compteur' not in st.session_state:
st.session_state.compteur = 0
# Bouton qui modifie l'état
if st.button("Incrémenter"):
st.session_state.compteur += 1
st.write(f"Compteur : {st.session_state.compteur}")
Propriétés de session_state :
- Dictionnaire persistant par session utilisateur
- Accessible partout dans le script
- Survit aux reruns
- Disparaît quand l’utilisateur ferme/rafraîchit
Usage typique : Stocker des résultats de calculs coûteux, état de formulaires, données filtrées.
Layout et organisation
Colonnes
col1, col2, col3 = st.columns(3)
with col1:
st.header("Colonne 1")
st.write("Contenu 1")
with col2:
st.header("Colonne 2")
st.write("Contenu 2")
with col3:
st.header("Colonne 3")
st.write("Contenu 3")
Propriété : Les colonnes peuvent avoir des largeurs relatives : st.columns([2, 1, 1])
Expander et conteneurs
# Expander (section collapsable)
with st.expander("Voir les détails"):
st.write("Contenu masqué par défaut")
st.dataframe(df)
# Container (groupement logique)
container = st.container()
container.write("Ce conteneur peut être rempli plus tard")
# Sidebar
st.sidebar.title("Filtres")
param = st.sidebar.slider("Paramètre", 0, 100)
Tabs et pages
# Tabs (onglets)
tab1, tab2, tab3 = st.tabs(["Espace chimique", "Activité", "SAR"])
with tab1:
st.header("Espace chimique")
st.plotly_chart(fig_space)
with tab2:
st.header("Analyse d'activité")
st.plotly_chart(fig_activity)
with tab3:
st.header("Structure-Activity Relationship")
st.plotly_chart(fig_sar)
Optimisation avec caching
Problème : Le rerun complet peut être coûteux si le script contient des calculs longs ou des chargements de données.
Solution : Les décorateurs de cache de Streamlit.
@st.cache_data
Utilisez pour cacher des opérations qui retournent des données (DataFrames, arrays, etc.) :
@st.cache_data
def charger_donnees():
"""Cette fonction ne s'exécute qu'une fois, puis le résultat est caché"""
df = pd.read_csv("large_dataset.csv")
return df
# Appel ultra-rapide après le premier chargement
df = charger_donnees()
Propriétés :
- Cache basé sur les arguments de la fonction
- Arguments différents → nouveau calcul
- Données mutables (copies créées automatiquement)
- TTL configurable :
@st.cache_data(ttl=3600)(1 heure)
@st.cache_resource
Utilisez pour cacher des objets non-sérialisables (connexions DB, modèles ML, etc.) :
@st.cache_resource
def charger_modele():
"""Charge le modèle une seule fois et le partage entre utilisateurs"""
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor()
model.load("model.pkl")
return model
model = charger_modele()
Différence clé :
cache_data: Crée une copie pour chaque utilisateurcache_resource: Partage la même instance entre tous les utilisateurs
Formulaires et boutons
Problème : Par défaut, chaque interaction déclenche un rerun, ce qui peut être gênant pour des formulaires multi-champs.
Solution : Les formulaires groupent plusieurs widgets et ne déclenchent un rerun que lors de la soumission.
with st.form("mon_formulaire"):
st.write("Calculer des descripteurs")
smiles = st.text_input("SMILES")
solvant = st.selectbox("Solvant", ["THF", "DCM", "Toluene"])
temperature = st.slider("Température (°C)", 0, 100, 60)
# Le bouton de soumission est OBLIGATOIRE dans un formulaire
submitted = st.form_submit_button("Calculer")
if submitted:
# Code exécuté uniquement à la soumission
st.write(f"Calcul pour {smiles} dans {solvant} à {temperature}°C")
# ... calculs ...
Propriété importante : Rien dans le formulaire ne déclenche de rerun sauf le bouton de soumission.
Application multi-pages
Pour des applications complexes, Streamlit permet une architecture multi-pages :
mon_app/
├── app.py # Page principale (obligatoire)
└── pages/
├── 1_📊_Espace_chimique.py
├── 2_🎯_Activite.py
└── 3_🔬_SAR_Analysis.py
Convention : Les fichiers dans pages/ deviennent automatiquement des pages accessibles via un menu latéral.
# app.py (page d'accueil)
import streamlit as st
st.title("Dashboard de Criblage Chimique")
st.write("Bienvenue ! Utilisez le menu pour naviguer.")
# pages/1_📊_Espace_chimique.py
import streamlit as st
st.title("Espace Chimique")
# ... contenu de la page ...
Propriétés :
- Navigation automatique créée par Streamlit
- Chaque page a son propre
session_statepartagé - Les émojis dans les noms de fichiers apparaissent dans le menu
Exemple du Dashboard de criblage
import streamlit as st
import pandas as pd
import plotly.express as px
import numpy as np
# Configuration de la page
st.set_page_config(
page_title="Chemical Screening",
page_icon="🧪",
layout="wide"
)
# Titre et description
st.title("🧪 Dashboard de Criblage Chimique")
st.markdown("Analyse interactive de 500 composés testés")
# Chargement des données (caché)
@st.cache_data
def load_data():
# Simulation de données
np.random.seed(42)
return pd.DataFrame({
'Compound_ID': [f'CPD_{i:04d}' for i in range(500)],
'MW': np.random.normal(350, 80, 500),
'LogP': np.random.normal(2.8, 1.3, 500),
'PSA': np.random.normal(75, 25, 500),
'IC50_uM': np.random.lognormal(0, 1.5, 500),
'Scaffold': np.random.choice(['Benzene', 'Pyridine', 'Indole'], 500)
})
df = load_data()
# Sidebar : Filtres
st.sidebar.header("🔍 Filtres")
scaffolds_selected = st.sidebar.multiselect(
"Scaffold",
options=df['Scaffold'].unique(),
default=df['Scaffold'].unique()
)
mw_range = st.sidebar.slider(
"Plage de MW",
float(df['MW'].min()),
float(df['MW'].max()),
(float(df['MW'].min()), float(df['MW'].max()))
)
ic50_max = st.sidebar.number_input(
"IC50 max (μM)",
min_value=0.0,
value=100.0,
step=10.0
)
# Filtrage des données
df_filtered = df[
(df['Scaffold'].isin(scaffolds_selected)) &
(df['MW'] >= mw_range[0]) &
(df['MW'] <= mw_range[1]) &
(df['IC50_uM'] <= ic50_max)
]
# Métriques en haut
col1, col2, col3, col4 = st.columns(4)
with col1:
st.metric("Composés filtrés", len(df_filtered))
with col2:
n_active = len(df_filtered[df_filtered['IC50_uM'] < 10])
st.metric("Hits (IC50 < 10 μM)", n_active)
with col3:
hit_rate = (n_active / len(df_filtered) * 100) if len(df_filtered) > 0 else 0
st.metric("Taux de succès", f"{hit_rate:.1f}%")
with col4:
median_ic50 = df_filtered['IC50_uM'].median()
st.metric("IC50 médian", f"{median_ic50:.2f} μM")
# Tabs pour organisation
tab1, tab2, tab3 = st.tabs(["📈 Espace chimique", "📊 Distribution", "📋 Données"])
with tab1:
st.subheader("Espace chimique (MW vs LogP)")
# Création du graphique
fig = px.scatter(
df_filtered,
x='MW',
y='LogP',
color='IC50_uM',
size='PSA',
hover_data=['Compound_ID', 'Scaffold'],
color_continuous_scale='Viridis',
title='Distribution des composés dans l\'espace chimique'
)
# Limites Lipinski
fig.add_hline(y=5, line_dash='dash', line_color='red',
annotation_text='LogP limit')
fig.add_vline(x=500, line_dash='dash', line_color='red',
annotation_text='MW limit')
st.plotly_chart(fig, use_container_width=True)
with tab2:
st.subheader("Distribution des IC50")
fig2 = px.histogram(
df_filtered,
x='IC50_uM',
color='Scaffold',
nbins=50,
log_x=True,
title='Distribution des valeurs d\'IC50'
)
st.plotly_chart(fig2, use_container_width=True)
with tab3:
st.subheader("Table de données")
# Affichage avec options
st.dataframe(
df_filtered.style.highlight_min(subset=['IC50_uM'], color='lightgreen'),
use_container_width=True
)
# Bouton de téléchargement
csv = df_filtered.to_csv(index=False)
st.download_button(
label="📥 Télécharger les données filtrées",
data=csv,
file_name="compounds_filtered.csv",
mime="text/csv"
)
# Footer
st.markdown("---")
st.caption("Dashboard créé avec Streamlit • Dernière mise à jour : 2025")
Astuces et bonnes pratiques Streamlit
Performance :
- Toujours utiliser
@st.cache_datapour chargements de données - Éviter les calculs lourds hors du cache
- Limiter le nombre de widgets pour réduire les reruns
UI/UX :
- Utilisez
st.columns()pour une mise en page propre - Exploitez la sidebar pour les contrôles
- Ajoutez des
st.spinner()pour les opérations longues - Utilisez
st.success(),st.error(),st.warning()pour le feedback
Organisation du code :
- Fonctions cachées pour logique métier
- Multi-pages pour applications complexes
session_statepour état partagé entre pages
Exemple de spinner :
with st.spinner("Calcul en cours..."):
resultat = fonction_longue()
st.success("Calcul terminé !")
Dash : L’écosystème de dashboards analytiques
Dash est un framework Python open-source créé par Plotly pour construire des applications web analytiques. Sa philosophie repose sur un principe simple : “Tout ce qui peut être exprimé en Python peut devenir une application web interactive”.
Architecture technique
Dash repose sur une architecture en trois couches :
┌─────────────────────────────────────┐
│ Frontend (React.js) │ ← Interface utilisateur
│ - Composants visuels │
│ - Gestion des événements │
└─────────────────────────────────────┘
↕ (JSON via HTTP)
┌─────────────────────────────────────┐
│ Backend (Flask) │ ← Logique applicative
│ - Callbacks Python │
│ - Traitement des données │
└─────────────────────────────────────┘
↕
┌─────────────────────────────────────┐
│ Visualisation (Plotly.js) │ ← Graphiques interactifs
└─────────────────────────────────────┘
Propriétés clés :
- Réactivité : Les composants réagissent automatiquement aux changements de données
- Composabilité : Les éléments peuvent être assemblés comme des briques LEGO
- Déclarativité : Le layout est défini de manière déclarative, pas impérative
Le paradigme des callbacks
Le concept central de Dash est le callback, une fonction Python décoratée qui définit comment les composants interagissent :
@app.callback(
Output('composant-cible', 'propriete-cible'),
Input('composant-source', 'propriete-source')
)
def fonction_de_mise_a_jour(valeur_input):
# Logique de traitement
return nouvelle_valeur
Définition : Un callback est une fonction qui s’exécute automatiquement lorsqu’une propriété d’un composant d’entrée change, mettant à jour les propriétés des composants de sortie.
Propriétés des callbacks :
- Asynchrones : Ne bloquent pas l’interface utilisateur
- Composables : Peuvent être chaînés pour créer des flux complexes
- Prévisibles : Ordre d’exécution déterministe basé sur les dépendances
Streamlit vs Dash : Différences fondamentales
| Aspect | Streamlit | Dash |
|---|---|---|
| Paradigme | Impératif (script) | Déclaratif (callbacks) |
| Courbe d’apprentissage | Très douce | Moyenne |
| Vitesse de prototypage | Extrême | Rapide |
| Contrôle du flux | Limité | Total |
| Personnalisation UI | Limitée | Élevée |
| Gestion de l’état | st.session_state |
Variables serveur + dcc.Store |
| Idéal pour | Prototypes, ML demos | Production, dashboards complexes |
Quand choisir Streamlit ?
- ✅ Prototypes rapides pour démonstration
- ✅ Applications ML/DS internes
- ✅ Démos de modèles de machine learning
- ✅ Outils d’exploration de données
- ❌ Dashboards complexes multi-pages (préférer Dash)
- ❌ Applications nécessitant contrôle fin du layout
- ❌ Haute personnalisation visuelle
Composants fondamentaux
Core Components (dcc)
Les Core Components sont les éléments interactifs prédéfinis de Dash. Chaque composant possède des propriétés configurables :
Graphiques (dcc.Graph) :
- Propriété
figure: Objet Plotly définissant le graphique - Propriété
config: Options d’interaction (zoom, export, etc.) - Événements :
clickData,hoverData,selectedDatadcc.Graph( id='mon-graphique', figure=fig, config={'displayModeBar': True} )
Dropdowns (dcc.Dropdown) :
- Propriété
options: Liste de dictionnaires{'label': ..., 'value': ...} - Propriété
multi: Permet la sélection multiple (booléen) - Propriété
searchable: Active la recherche dans les optionsdcc.Dropdown( id='selecteur', options=[{'label': 'Option A', 'value': 'a'}], multi=True, placeholder='Sélectionner...' )
Sliders (dcc.Slider et dcc.RangeSlider) :
- Propriété
marks: Dictionnaire définissant les graduations - Propriété
tooltip: Configuration de l’infobulle - Différence :
Sliderpour une valeur unique,RangeSliderpour une plage
HTML Components (html)
Les HTML Components reproduisent les balises HTML standards en Python :
html.Div([ # <div>
html.H1('Titre'), # <h1>
html.P('Texte') # <p>
])
Propriétés CSS : Utilisez style (dictionnaire) ou className (classes CSS)
Patterns de callbacks avancés
Pattern 1 : Callbacks en chaîne
Définition : Des callbacks qui dépendent séquentiellement les uns des autres, formant une cascade de mises à jour.
Usage : Filtrage progressif, navigation hiérarchique
# Callback 1 : Mise à jour des options
@app.callback(
Output('dropdown-2', 'options'),
Input('dropdown-1', 'value')
)
def update_options(selection_1):
return options_filtrees
# Callback 2 : Affichage basé sur les deux sélections
@app.callback(
Output('graphique', 'figure'),
Input('dropdown-2', 'value')
)
def update_graph(selection_2):
return figure
Propriété importante : L’ordre d’exécution est garanti par Dash en fonction du graphe de dépendances.
Pattern 2 : State vs Input
Définition clé :
- Input : Déclenche l’exécution du callback à chaque changement
- State : Fournit une valeur sans déclencher le callback
Usage typique : Boutons de soumission, formulaires
@app.callback(
Output('resultat', 'children'),
Input('bouton-submit', 'n_clicks'),
State('input-texte', 'value') # Ne déclenche pas !
)
def calculer(n_clicks, texte):
# Exécuté uniquement au clic du bouton
return f"Résultat pour : {texte}"
Pattern 3 : Pattern-matching callbacks
Définition : Callbacks qui ciblent dynamiquement des composants créés à la volée, identifiés par des patterns.
Types de patterns :
ALL: Cible tous les composants correspondantsMATCH: Cible les composants avec le même indexALLSMALLER: Cible les composants avec index inférieur ```python from dash import ALL, MATCH
Création dynamique de composants
html.Div([ html.Button(id={‘type’: ‘btn’, ‘index’: i}) for i in range(n) ])
Callback pattern-matching
@app.callback( Output({‘type’: ‘output’, ‘index’: MATCH}, ‘children’), Input({‘type’: ‘btn’, ‘index’: MATCH}, ‘n_clicks’) ) def update(n_clicks): # S’applique à chaque paire btn/output individuellement return f”Cliqué {n_clicks} fois”
## Comparaison Streamlit vs Dash : Quand utiliser quoi ?
**Utilisez Streamlit si :**
- 🚀 Vous voulez un prototype fonctionnel en < 1 heure
- 📊 Vous créez un outil d'exploration de données interne
- 🤖 Vous démontrez un modèle de machine learning
- 👥 Vos utilisateurs sont des data scientists (tolérants sur le design)
- 🔄 L'application est simple à moyenne complexité
**Utilisez Dash si :**
- 🏢 Vous développez un dashboard de production
- 🎨 Vous avez besoin de personnalisation avancée du layout
- ⚡ Vous devez optimiser finement les performances
- 🔐 Vous implémentez une authentification complexe
- 📱 Vous visez un design très responsive et professionnel
- 🔧 Vous avez besoin de contrôle total sur le flux de données
**Hybridation possible** : Prototypage en Streamlit, puis migration vers Dash pour mise en production.
## Optimisation et bonnes pratiques
### Gestion de la performance
**Problème** : Les callbacks peuvent être coûteux en temps de calcul, ralentissant l'interface.
**Solutions** :
1. **Cache memoization** : Mémoriser les résultats de calculs identiques
```python
from flask_caching import Cache
cache = Cache(app.server, config={
'CACHE_TYPE': 'filesystem',
'CACHE_DIR': 'cache-directory'
})
@cache.memoize(timeout=300) # Cache pendant 5 minutes
def fonction_couteuse(parametres):
# Calculs longs...
return resultats
- dcc.Store : Stocker des données intermédiaires côté client
dcc.Store(id='store-donnees', data=donnees_json) - prevent_initial_call : Éviter l’exécution au chargement
@app.callback(..., prevent_initial_call=True)
Structure modulaire
Pour les applications complexes, organisez votre code :
projet_dashboard/
├── app.py # Point d'entrée
├── layouts/
│ ├── main_layout.py # Layout principal
│ └── components.py # Composants réutilisables
├── callbacks/
│ ├── filtering.py # Callbacks de filtrage
│ └── visualization.py # Callbacks de visualisation
├── data/
│ └── loader.py # Chargement des données
└── assets/
├── style.css # Styles personnalisés
└── logo.png # Ressources
Dash Bootstrap Components
Dash Bootstrap Components (dbc) est une bibliothèque qui intègre les composants Bootstrap dans Dash, offrant un système de grille responsive et des composants stylisés.
Système de grille
Le système repose sur une grille de 12 colonnes :
dbc.Row([
dbc.Col(composant_1, width=4), # 4/12 = 33%
dbc.Col(composant_2, width=8) # 8/12 = 67%
])
Propriétés de colonnes :
width: Largeur (1-12)lg,md,sm,xs: Largeurs responsives selon la taille d’écranoffset: Décalage horizontal
Composants principaux
Cards : Conteneurs visuels pour regrouper du contenu
dbc.Card([
dbc.CardHeader("Titre"),
dbc.CardBody([
html.H4("Sous-titre"),
html.P("Contenu")
])
])
Tabs : Onglets pour organiser l’information
dbc.Tabs([
dbc.Tab(label="Vue 1", tab_id="tab-1"),
dbc.Tab(label="Vue 2", tab_id="tab-2")
], id="tabs", active_tab="tab-1")
Thèmes : Dash Bootstrap propose 26 thèmes prédéfinis (Flatly, Darkly, Cosmo, etc.)
app = dash.Dash(__name__,
external_stylesheets=[dbc.themes.FLATLY]
)
Flask : Framework web minimaliste et flexible
Philosophie et positionnement
Flask est un micro-framework web Python créé par Armin Ronacher. Contrairement à Dash qui est spécialisé dans les dashboards analytiques, Flask est un outil généraliste pour construire n’importe quel type d’application web.
Principe de minimalisme
Flask suit la philosophie WSGI (Web Server Gateway Interface) et se concentre sur les fonctionnalités essentielles :
- Routage des URLs
- Gestion des requêtes/réponses
- Templating (Jinja2)
- Sessions utilisateurs
Propriété clé : Flask n’impose pas de structure, vous êtes libre d’organiser votre application comme vous le souhaitez.
Flask vs Dash : Quand choisir quoi ?
| Critère | Flask | Dash |
|---|---|---|
| Courbe d’apprentissage | Plus raide | Plus douce |
| Flexibilité | Totale | Orientée dataviz |
| Rapidité de développement | Moyenne | Rapide pour dashboards |
| Contrôle | Total | Abstraction élevée |
| Usage idéal | Apps web complexes, APIs | Dashboards analytiques |
Concepts fondamentaux
Routes et vues
Définition : Une route est une URL qui déclenche l’exécution d’une fonction Python (appelée vue).
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/') # Route racine
def index():
return render_template('index.html')
@app.route('/api/data') # Route API
def get_data():
return {'molecules': [...]}
Types de routes :
- Routes statiques :
/about,/contact - Routes dynamiques :
/molecule/<id>,/search/<query> - Routes avec méthodes :
methods=['GET', 'POST']
Templating avec Jinja2
Jinja2 est un moteur de templates qui permet d’insérer dynamiquement du contenu Python dans du HTML.
Syntaxe de base :
{{ variable }}: Affiche une variable{% instruction %}: Exécute une instruction (boucle, condition){# commentaire #}: Commentaire (non rendu) ```html
{{ titre }}
{% for molecule in molecules %}
{{ molecule.nom }}
MW: {{ molecule.mw }}
{% endfor %}
### Requêtes et formulaires
Flask fournit l'objet `request` pour accéder aux données de la requête HTTP :
```python
from flask import request
@app.route('/search', methods=['GET', 'POST'])
def search():
if request.method == 'POST':
query = request.form['query'] # Données de formulaire
else:
query = request.args.get('q') # Paramètres URL
results = perform_search(query)
return render_template('results.html', results=results)
Intégration de visualisations
Plotly dans Flask
Pour intégrer des graphiques Plotly dans une application Flask, vous générez le JSON du graphique côté serveur et l’injectez dans le template :
import plotly
import plotly.express as px
import json
@app.route('/visualization')
def visualization():
fig = px.scatter(df, x='MW', y='LogP')
# Convertir en JSON
graphJSON = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
return render_template('viz.html', graphJSON=graphJSON)
<!-- templates/viz.html -->
<div id="myDiv"></div>
<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
<script>
var graphs = {{ graphJSON | safe }};
Plotly.newPlot('myDiv', graphs.data, graphs.layout);
</script>
Chart.js et autres bibliothèques
Flask est agnostique à la bibliothèque de visualisation. Vous pouvez utiliser :
- Chart.js : Bibliothèque JavaScript légère
- D3.js : Puissante mais complexe
- Bokeh : Alternative Python à Plotly
APIs REST pour dashboards
Définition : Une API REST (Representational State Transfer) est une interface qui permet à des applications de communiquer via HTTP en respectant certaines conventions.
Principes REST
Les APIs REST utilisent les verbes HTTP :
GET: Récupérer des donnéesPOST: Créer une nouvelle ressourcePUT: Mettre à jour une ressourceDELETE: Supprimer une ressource ```python @app.route(‘/api/molecules’, methods=[‘GET’]) def get_molecules(): molecules = query_database() return jsonify(molecules)
@app.route(‘/api/molecules/
@app.route(‘/api/molecules’, methods=[‘POST’]) def create_molecule(): data = request.get_json() new_molecule = save_molecule(data) return jsonify(new_molecule), 201
### CORS et sécurité
**CORS** (Cross-Origin Resource Sharing) : Mécanisme de sécurité qui restreint les requêtes entre domaines différents.
```python
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # Activer CORS pour toutes les routes
Autres considérations de sécurité :
- CSRF : Protection contre les attaques Cross-Site Request Forgery
- Authentification : JWT (JSON Web Tokens), OAuth
- Rate limiting : Limiter le nombre de requêtes par utilisateur
Architecture d’une application Flask pour la data science
Pattern Blueprint
Les Blueprints permettent de modulariser une application Flask :
# blueprints/analysis.py
from flask import Blueprint
analysis_bp = Blueprint('analysis', __name__)
@analysis_bp.route('/correlations')
def correlations():
return render_template('correlations.html')
# app.py
from blueprints.analysis import analysis_bp
app = Flask(__name__)
app.register_blueprint(analysis_bp, url_prefix='/analysis')
Pattern Factory
Le pattern factory crée l’application de manière modulaire :
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
# Initialiser extensions
db.init_app(app)
cache.init_app(app)
# Enregistrer blueprints
from .main import main_bp
app.register_blueprint(main_bp)
return app
Avantages :
- Facilite les tests unitaires
- Permet plusieurs instances avec configurations différentes
- Séparation des préoccupations
Electron : Applications desktop avec technologies web
Philosophie et architecture
Electron est un framework open-source développé par GitHub qui permet de créer des applications desktop multiplateformes (Windows, macOS, Linux) en utilisant des technologies web (HTML, CSS, JavaScript).
Architecture en deux processus
Electron fonctionne selon une architecture multi-processus :
┌──────────────────────────────────────┐
│ Processus Principal (Main) │
│ - Node.js │
│ - Gestion des fenêtres │
│ - Accès système (fichiers, etc.) │
└──────────────────────────────────────┘
↕ IPC
┌──────────────────────────────────────┐
│ Processus de Rendu (Renderer) │
│ - Chromium (moteur de navigateur) │
│ - Interface utilisateur │
│ - Logique frontend │
└──────────────────────────────────────┘
Propriétés clés :
- Isolation : Les processus communiquent via IPC (Inter-Process Communication)
- Sécurité : Le renderer ne peut pas accéder directement au système
- Performance : Chaque fenêtre a son propre processus
Pourquoi Electron pour la data science ?
Avantages :
- Interface native pour l’utilisateur final
- Accès complet au système de fichiers
- Pas besoin de serveur web
- Distribution facilitée (un exécutable)
- Intégration avec des outils Python via spawn
Inconvénients :
- Taille importante des applications (≈100-200 MB)
- Consommation mémoire élevée
- Complexité accrue vs web app simple
Structure d’une application Electron
Processus principal (main.js)
Le processus principal gère le cycle de vie de l’application et crée les fenêtres :
const { app, BrowserWindow } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false, // Sécurité
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
win.loadFile('index.html');
}
app.whenReady().then(createWindow);
Propriétés de BrowserWindow :
width,height: Dimensions de la fenêtrewebPreferences: Configuration de sécuritéframe: Afficher/masquer la barre de titretransparent: Fenêtre transparente
Preload script
Le preload script est un pont sécurisé entre le main et le renderer :
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
analyzeData: (data) => ipcRenderer.invoke('analyze-data', data),
onProgress: (callback) => ipcRenderer.on('progress', callback)
});
Définition : Le context bridge expose de manière sécurisée des fonctions du processus principal au processus de rendu.
Processus de rendu (HTML/JS)
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Chemical Data Analyzer</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<button id="analyze">Analyser</button>
<div id="results"></div>
<script>
document.getElementById('analyze').addEventListener('click', async () => {
const results = await window.api.analyzeData(data);
displayResults(results);
});
</script>
</body>
</html>
Communication IPC (Inter-Process Communication)
IPC synchrone vs asynchrone
IPC synchrone : Bloque le renderer en attendant la réponse
// Renderer
const result = ipcRenderer.sendSync('sync-message', data);
// Main
ipcMain.on('sync-message', (event, data) => {
event.returnValue = processData(data);
});
IPC asynchrone : Recommandé pour ne pas bloquer l’UI
// Renderer
ipcRenderer.invoke('async-message', data).then(result => {...});
// Main
ipcMain.handle('async-message', async (event, data) => {
return await processData(data);
});
Propriété importante : Utilisez toujours l’IPC asynchrone pour les opérations longues !
Pattern pub-sub pour les mises à jour
Pour diffuser des mises à jour (ex: progression d’un calcul) :
// Main : Émettre des événements
mainWindow.webContents.send('progress-update', { percent: 45 });
// Renderer : Écouter les événements
window.api.onProgress((event, data) => {
updateProgressBar(data.percent);
});
Intégration Python-Electron
Approche 1 : Spawn de processus Python
spawn exécute un script Python comme processus externe :
// main.js
const { spawn } = require('child_process');
function runPythonAnalysis(dataPath) {
return new Promise((resolve, reject) => {
const python = spawn('python', ['analyze.py', dataPath]);
let output = '';
python.stdout.on('data', (data) => {
output += data.toString();
});
python.on('close', (code) => {
if (code === 0) {
resolve(JSON.parse(output));
} else {
reject(new Error('Python script failed'));
}
});
});
}
# analyze.py
import sys
import json
import pandas as pd
data_path = sys.argv[1]
df = pd.read_csv(data_path)
# Analyse...
results = {'mean': df['value'].mean()}
print(json.dumps(results))
Avantages :
- Simplicité
- Isolation complète
- Utilisation de l’environnement Python existant
Inconvénients :
- Overhead de démarrage Python
- Communication limitée (stdin/stdout)
Approche 2 : Serveur Flask embarqué
Lancer un serveur Flask dans un thread et communiquer via HTTP :
// main.js
const { spawn } = require('child_process');
let flaskProcess;
function startFlaskServer() {
flaskProcess = spawn('python', ['server.py']);
// Attendre que le serveur démarre
setTimeout(() => {
console.log('Flask server ready');
}, 2000);
}
function stopFlaskServer() {
if (flaskProcess) {
flaskProcess.kill();
}
}
app.on('ready', () => {
startFlaskServer();
createWindow();
});
app.on('quit', stopFlaskServer);
# server.py
from flask import Flask, jsonify
import pandas as pd
app = Flask(__name__)
@app.route('/api/analyze', methods=['POST'])
def analyze():
# Analyse...
return jsonify({'results': ...})
if __name__ == '__main__':
app.run(port=5000)
Communication depuis le renderer :
fetch('http://localhost:5000/api/analyze', {
method: 'POST',
body: JSON.stringify(data)
})
.then(response => response.json())
.then(results => displayResults(results));
Approche 3 : PyInstaller pour embarquer Python
PyInstaller convertit un script Python en exécutable autonome :
pyinstaller --onefile --hidden-import pandas analyze.py
Ensuite, Electron lance cet exécutable au lieu de python analyze.py.
Avantage : L’utilisateur n’a pas besoin d’installer Python !
Distribution et packaging
Electron Builder
Electron Builder est l’outil standard pour packager des applications Electron :
// package.json
{
"name": "chemical-analyzer",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "electron-builder"
},
"build": {
"appId": "com.example.chemical",
"mac": {
"target": "dmg"
},
"win": {
"target": "nsis"
},
"linux": {
"target": "AppImage"
}
}
}
Formats de distribution :
- Windows :
.exe(NSIS installer),.msi - macOS :
.dmg,.pkg - Linux :
.AppImage,.deb,.rpm
Auto-update
Pour les mises à jour automatiques, utilisez electron-updater :
const { autoUpdater } = require('electron-updater');
autoUpdater.checkForUpdatesAndNotify();
Processus :
- L’application vérifie un serveur pour les nouvelles versions
- Télécharge la mise à jour en arrière-plan
- Installe au redémarrage de l’application
Comparaison et choix du framework
Matrice de décision
| Critère | Streamlit | Dash | Flask | Electron |
|---|---|---|---|---|
| Déploiement | Web (serveur) | Web (serveur) | Web (serveur) | Desktop (local) |
| Installation utilisateur | Navigateur seulement | Navigateur seulement | Navigateur seulement | Application complète |
| Interactivité | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Personnalisation | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Courbe d’apprentissage | Très douce | Douce | Moyenne | Raide |
| Vitesse de prototypage | ⚡ Extrême | Rapide | Moyenne | Lente |
| Accès fichiers locaux | Via upload | Limité | Via upload | Complet |
| Performance | Dépend serveur | Dépend serveur | Dépend serveur | Native |
| Distribution | URL | URL | URL | Exécutable |
| Contrôle du flux | ⭐⭐ (rerun model) | ⭐⭐⭐⭐ (callbacks) | ⭐⭐⭐⭐⭐ (total) | ⭐⭐⭐⭐⭐ (total) |
| Idéal pour production | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
Scénarios d’usage
Scénario 1 : Dashboard pour équipe interne
Besoin : Visualiser les résultats de criblage chimique pour 5-10 chercheurs
Recommandation : Dash ou Streamlit
- Streamlit si : Prototype rapide, besoin de démo immédiate
- Dash si : Dashboard destiné à durer, besoin de personnalisation
- Déploiement sur serveur interne
- Accès via navigateur (pas d’installation)
- Mise à jour centralisée
Scénario 2 : Outil d’analyse pour clients externes
Besoin : Distribuer un logiciel d’analyse de données à des clients
Recommandation : Electron
- Application standalone
- Pas de dépendance serveur
- Aspect professionnel
- Intégration système complète
Scénario 3 : Plateforme web complexe avec authentification
Besoin : Portail d’accès aux données avec gestion d’utilisateurs, rôles, API
Recommandation : Flask
- Contrôle total sur l’architecture
- Intégration avec bases de données
- Gestion d’authentification robuste
- APIs REST personnalisées
Scénario 4 : Prototype rapide de dashboard
Besoin : Démontrer rapidement un concept à des collaborateurs
Recommandation : Streamlit
- Développement ultra-rapide (< 1 heure)
- Pas de connaissances web requises
- Focus total sur la data
- Migration vers Dash possible ensuite
Scénario 5 : Démonstration de modèle ML
Besoin : Interface pour tester un modèle de machine learning avec des collègues
Recommandation : Streamlit
- Upload de fichiers natif
- Widgets parfaits pour hyperparamètres
- Cache intelligent pour modèles lourds
- Déploiement gratuit sur Streamlit Cloud
Scénario 6 : Dashboard opérationnel critique
Besoin : Monitoring temps réel pour production chimique, haute fiabilité
Recommandation : Dash + Flask
- Dash pour l’interface réactive
- Flask pour API robuste et authentification
- Contrôle total du backend
- Scalabilité garantie
Arbre de décision
Avez-vous besoin d'un prototype en < 2h ?
│
├─ OUI → STREAMLIT
│
└─ NON → L'application est-elle desktop ?
│
├─ OUI → ELECTRON
│
└─ NON → Besoin de personnalisation extrême ?
│
├─ OUI → FLASK
│
└─ NON → Production critique ?
│
├─ OUI → DASH
│
└─ NON → STREAMLIT ou DASH
Hybridation des approches
On peut se demander si l’approche optimale ne serait pas de combiner ces frameworks ?
Streamlit → Dash (migration progressive)
Pattern de migration : Commencez en Streamlit, migrez vers Dash quand nécessaire
Indicateurs de migration :
- Besoin de callbacks complexes et chaînés
- Personnalisation avancée du layout
- Optimisation fine des performances
- Authentification multi-utilisateurs
Stratégie :
- Prototyper en Streamlit (1-2 jours)
- Valider le concept avec les utilisateurs
- Identifier les limitations rencontrées
- Migrer progressivement vers Dash
Dash + Flask
Dash est construit sur Flask, vous pouvez donc accéder au serveur Flask sous-jacent :
app = dash.Dash(__name__)
server = app.server # Objet Flask
@server.route('/custom-endpoint')
def custom_route():
return "Endpoint Flask personnalisé"
@server.route('/api/export')
def export_data():
# Endpoint pour export personnalisé
return send_file('data.xlsx')
Usage :
- Ajouter des routes API personnalisées à une application Dash
- Intégrer authentification Flask
- Combiner dashboards Dash et pages Flask
Flask + Electron
Créer l’interface en Flask et l’embarquer dans Electron :
// main.js
const flaskProcess = spawn('python', ['app.py']);
setTimeout(() => {
mainWindow.loadURL('http://localhost:5000');
}, 2000);
Avantages :
- Développement web classique (Flask)
- Distribution desktop (Electron)
- Meilleur des deux mondes
Streamlit + API Flask
Utiliser Streamlit pour le frontend et Flask pour un backend API séparé :
# Streamlit app
import streamlit as st
import requests
st.title("Dashboard avec API Backend")
# Appel à l'API Flask
response = requests.get('http://localhost:5000/api/data')
data = response.json()
st.dataframe(data)
Usage :
- Backend partagé entre plusieurs interfaces
- Séparation des responsabilités
- Scalabilité du backend indépendamment
Bonnes pratiques transversales
Architecture des données
Quelle que soit la technologie, structurez vos données efficacement :
Principe de séparation :
- Données sources : CSV, bases de données (immuables)
- Données traitées : Cache, fichiers temporaires
- Données de session : État utilisateur, filtres actifs
Format de stockage :
- CSV/Excel : Petits datasets (<1M lignes)
- Parquet/Feather : Datasets moyens (rapides, compressés)
- Bases SQL : Gros datasets, requêtes complexes
- HDF5 : Données scientifiques multidimensionnelles
Gestion de l’état
État local vs état global :
| Type | Streamlit | Dash | Flask | Electron |
|---|---|---|---|---|
| Local | st.session_state |
dcc.Store |
Session Flask | localStorage |
| Global | Variables serveur | Variables serveur | Base de données | Main process |
| Persistance | Session uniquement | Session + Store | Configurable | Configurable |
Principes :
- État éphémère → Local (session utilisateur)
- État persistant → Base de données
- État partagé → Global avec verrous
- Streamlit spécifique : Utiliser
@st.cache_datapour données partagées
Exemples par framework :
# Streamlit
if 'compteur' not in st.session_state:
st.session_state.compteur = 0
# Dash
dcc.Store(id='store', data={'compteur': 0})
# Flask
session['compteur'] = session.get('compteur', 0) + 1
Tests et débogage
Tests unitaires
Streamlit :
from streamlit.testing.v1 import AppTest
def test_app():
at = AppTest.from_file("app.py")
at.run()
# Vérifier un élément
assert at.title[0].value == "Mon Dashboard"
# Simuler interaction
at.slider[0].set_value(50)
at.run()
# Vérifier résultat
assert at.text[0].value == "Valeur: 50"
Dash :
def test_callback():
app = dash.Dash(__name__)
# Définir layout et callbacks...
# Simuler interaction
app.callback_context.triggered[0]['prop_id'] = 'input.value'
result = callback_function('test')
assert result == expected
Flask :
def test_route():
client = app.test_client()
response = client.get('/api/data')
assert response.status_code == 200
Débogage
Streamlit :
- Mode debug : Pas de mode spécifique, mais logs avec
st.write()ouprint() st.exception()pour afficher les erreurs formatées- Hot reloading automatique
Dash :
- Mode debug :
app.run_server(debug=True) - Logs dans callbacks :
print()ou logger Python - DevTools Chrome pour frontend
Flask :
- Mode debug :
app.run(debug=True) - Flask DebugToolbar pour profiling
- Logging configurable
Electron :
- DevTools :
mainWindow.webContents.openDevTools() - Logs Node.js :
console.log() - Debugging VSCode intégré
Sécurité
Principes fondamentaux :
- Validation des entrées : Ne jamais faire confiance aux données utilisateur
- Échappement des sorties : Prévenir les injections XSS
- HTTPS : Toujours en production
- Authentification : JWT, OAuth2, ou session-based
- Rate limiting : Prévenir les abus
Streamlit spécifique :
- Pas d’authentification native (utiliser Streamlit-Authenticator ou services externes)
- Attention au secret management : utiliser
st.secrets - Déploiement : Streamlit Cloud gère HTTPS automatiquement
Dash spécifique :
- Utiliser
dash-authpour authentification basique - Pour production : intégrer avec Flask-Login
- CSRF protection via Flask sous-jacent
Flask spécifique :
- Flask-Login pour gestion sessions
- Flask-WTF pour protection CSRF
- Flask-Limiter pour rate limiting
Electron spécifique :
nodeIntegration: falsecontextIsolation: true- Whitelist des domaines autorisés
- Content Security Policy (CSP)
Performance et scalabilité
Optimisations frontend :
- Lazy loading des composants
- Virtualisation des listes longues
- Debouncing des inputs
- Compression des assets
Optimisations backend :
- Cache Redis/Memcached
- Pagination des résultats
- Async/await pour I/O
- Workers pour calculs lourds
Spécifique par framework :
Streamlit :
@st.cache_datapour TOUT chargement/calcul coûteux@st.cache_resourcepour modèles ML et connexions- Limiter les widgets pour réduire reruns
- Éviter calculs dans la boucle principale
Dash :
@cache.memoize()avec Flask-Cachingdcc.Storepour données côté clientprevent_initial_call=Truepour callbacks- Callbacks asynchrones pour opérations longues
Flask :
- Cache avec Redis
- Connexion pool pour bases de données
- Celery pour tâches asynchrones
Scalabilité Streamlit/Dash/Flask :
- Déploiement multi-workers (Gunicorn, Uvicorn)
- Load balancing (Nginx, HAProxy)
- CDN pour assets statiques
- Streamlit : Nécessite sticky sessions pour
session_state
Déploiement
Options par framework :
| Plateforme | Streamlit | Dash | Flask | Electron |
|---|---|---|---|---|
| Streamlit Cloud | ✅ Gratuit | ❌ | ❌ | ❌ |
| Heroku | ✅ | ✅ | ✅ | ❌ |
| AWS/GCP/Azure | ✅ | ✅ | ✅ | ❌ |
| Render | ✅ Gratuit | ✅ Gratuit | ✅ Gratuit | ❌ |
| Docker | ✅ | ✅ | ✅ | ✅ |
| Desktop (.exe) | ❌ | ❌ | ❌ | ✅ |
Recommandations :
- Streamlit : Streamlit Cloud pour démos/prototypes (gratuit, simple)
- Dash/Flask : Render ou Railway pour petits projets, AWS/GCP pour production
- Electron : Electron Builder + GitHub Releases
Cas pratique : Dashboard de criblage chimique
Cahier des charges
Objectif : Créer un dashboard pour analyser 1000 composés testés contre une cible biologique.
Fonctionnalités :
- Visualisation de l’espace chimique (MW, LogP, PSA)
- Filtrage par scaffold, activité, propriétés
- Analyse Structure-Activity Relationship (SAR)
- Export des résultats filtrés
- Calcul de descripteurs moléculaires
Contraintes :
- 10 utilisateurs simultanés
- Données mises à jour hebdomadairement
- Intégration avec RDKit pour descripteurs
Choix technologique : Dash
Justification :
- Focus sur la visualisation analytique
- Utilisateurs internes (pas de distribution)
- Développement rapide prioritaire
- Callbacks parfaits pour les filtres interactifs
Architecture proposée
chemical-screening-dashboard/
│
├── app.py # Application Dash principale
├── config.py # Configuration (chemins, paramètres)
│
├── data/
│ ├── loader.py # Chargement et preprocessing
│ ├── compounds.csv # Données source
│ └── cache/ # Cache des calculs
│
├── layouts/
│ ├── main_layout.py # Layout principal
│ ├── filters.py # Composants de filtrage
│ └── tabs.py # Onglets (Espace chimique, SAR, etc.)
│
├── callbacks/
│ ├── filtering.py # Logique de filtrage
│ ├── visualization.py # Mise à jour des graphiques
│ └── sar_analysis.py # Analyse SAR
│
├── utils/
│ ├── chemistry.py # Calculs RDKit
│ ├── stats.py # Analyses statistiques
│ └── export.py # Export CSV/Excel
│
├── assets/
│ ├── style.css # Styles personnalisés
│ └── logo.png
│
└── requirements.txt
Flux de données
Utilisateur
↓
Sélection de filtres (dropdowns, sliders)
↓
Callbacks Dash déclenchés
↓
Filtrage du DataFrame principal
↓
Calculs statistiques (si nécessaire)
↓
Génération des figures Plotly
↓
Mise à jour de l'interface
Optimisations :
- Cache des calculs RDKit (coûteux)
dcc.Storepour l’état des filtres- Pagination pour la table de données