Un tableau de bord métier transforme des données brutes en décisions. Pour une petite entreprise — atelier, boutique, prestataire de services — les solutions classiques (Power BI, Tableau, Looker Studio) imposent souvent des licences coûteuses ou un compte cloud. Streamlit propose une alternative concrète : on écrit du Python, on obtient une application web interactive en une après-midi, et on l’héberge soi-même sur un VPS à quelques dollars par mois. Ce tutoriel construit pas-à-pas un tableau de bord opérationnel : KPIs ventes, paiements par mode (carte, espèces, mobile), graphiques temporels, filtres interactifs, mise en cache.
Pour le contexte global, voir le guide principal sur la stack data 2026. Le cas d’usage est volontairement réaliste pour une petite entreprise : journal de ventes quotidien, montants, modes de paiement, et catégories de produits.
Prérequis
- Python 3.10+, streamlit 1.40+, pandas 3.0+, plotly 5.x
- Bases de pandas (filtre, groupby, resample)
- Un fichier CSV ou Parquet de ventes (date, montant, mode_paiement, catégorie)
- Temps estimé : 2 à 3 heures pour une version utilisable
Étape 1 — Installer Streamlit
Streamlit s’installe en une commande. Il fournit la bibliothèque cliente Python et une CLI pour lancer l’application. Plotly s’ajoute pour les graphiques interactifs — Streamlit propose ses propres graphiques natifs mais Plotly offre plus de contrôle pour les visualisations métier.
pip install streamlit pandas plotly pyarrow
streamlit --version
La sortie de la commande version confirme l’installation. Pour démarrer une application Streamlit, on crée un fichier Python et on le lance avec streamlit run dashboard.py — la CLI ouvre l’application dans le navigateur sur le port 8501. Aucune configuration HTML, JavaScript ou CSS n’est nécessaire pour les bases.
Étape 2 — Squelette de l’application
Toute application Streamlit est un script Python qui s’exécute du haut vers le bas à chaque interaction. Cette particularité simplifie la mécanique : pas de gestion d’état complexe, pas de callbacks. Le revers est qu’on ne peut pas faire des calculs lourds à chaque clic — c’est là qu’intervient la mise en cache, qu’on verra dans une étape ultérieure.
# dashboard.py
import streamlit as st
st.set_page_config(page_title="Tableau de bord ventes", layout="wide")
st.title("📊 Tableau de bord ventes")
st.caption("Suivi quotidien des ventes et des paiements")
set_page_config doit être la première commande Streamlit du script. Le paramètre layout="wide" élargit le contenu sur toute la largeur du navigateur — utile pour les tableaux de bord. st.title et st.caption structurent l’en-tête. À ce stade, lancer streamlit run dashboard.py affiche déjà une page propre, prête à recevoir des données.
Étape 3 — Charger les données avec cache
Le chargement de données est l’opération la plus coûteuse de l’application. Sans cache, chaque interaction de l’utilisateur (changement de filtre, toggle) relance la lecture du fichier. Le décorateur @st.cache_data mémorise la sortie d’une fonction : le calcul ne se refait que si les arguments changent ou si on appuie sur le bouton « Clear cache ».
import pandas as pd
@st.cache_data
def charger_ventes(chemin: str) -> pd.DataFrame:
df = pd.read_parquet(chemin)
df["date"] = pd.to_datetime(df["date"])
return df
df = charger_ventes("ventes_2026.parquet")
st.write(f"**{len(df):,}** lignes chargées sur la période **{df['date'].min().date()}** → **{df['date'].max().date()}**")
Le cache de Streamlit est intelligent : il se base sur le hash des arguments. Si on appelle charger_ventes("ventes_2026.parquet") deux fois, la seconde renvoie immédiatement la copie en cache. Si on change le chemin, un nouveau chargement s’effectue. Pour invalider le cache manuellement, on relance l’application ou on utilise st.cache_data.clear().
Étape 4 — Filtres dans la barre latérale
Une barre latérale héberge bien les filtres : elle ne mange pas l’espace du contenu principal et reste visible quand on défile. Streamlit fournit les widgets habituels — sélecteur de date, multi-sélection, slider — utilisables directement avec st.sidebar.
st.sidebar.header("Filtres")
plage = st.sidebar.date_input(
"Période",
value=(df["date"].min(), df["date"].max()),
min_value=df["date"].min(), max_value=df["date"].max(),
)
# st.date_input retourne un objet unique tant que l'utilisateur n'a pas
# sélectionné les deux bornes — garde indispensable pour ne pas planter
if not isinstance(plage, tuple) or len(plage) != 2:
st.warning("Sélectionnez une plage de dates (début et fin).")
st.stop()
date_debut, date_fin = plage
categories = st.sidebar.multiselect(
"Catégories",
options=sorted(df["categorie"].dropna().unique()),
default=sorted(df["categorie"].dropna().unique()),
)
modes = st.sidebar.multiselect(
"Modes de paiement",
options=["carte", "especes", "mobile", "virement"],
default=["carte", "especes", "mobile", "virement"],
)
masque = (
(df["date"] >= pd.Timestamp(date_debut))
& (df["date"] <= pd.Timestamp(date_fin))
& (df["categorie"].isin(categories))
& (df["mode_paiement"].isin(modes))
)
df_filtre = df[masque]
st.sidebar.caption(f"{len(df_filtre):,} lignes après filtres")
Les filtres sont reactifs : tout changement déclenche le réexécution du script et la mise à jour des graphiques. Le compteur de lignes filtrées en bas de la barre rassure l’utilisateur sur l’effet des filtres. Pour des filtres avancés (recherche textuelle, plages numériques), on combine st.text_input, st.slider, st.checkbox selon les besoins.
Étape 5 — Afficher des KPIs en haut
Les indicateurs clés (chiffre d’affaires, nombre de transactions, panier moyen) doivent être visibles immédiatement. Streamlit propose le widget st.metric qui affiche une grosse valeur avec un libellé et, optionnellement, une variation par rapport à une période de référence. Trois à quatre KPIs sur une ligne suffisent — au-delà, on dilue l’attention.
ca_total = df_filtre["montant"].sum()
nb_transactions = len(df_filtre)
panier_moyen = df_filtre["montant"].mean() if nb_transactions else 0
nb_clients = df_filtre["id_client"].nunique() if "id_client" in df_filtre else 0
col1, col2, col3, col4 = st.columns(4)
col1.metric("Chiffre d'affaires", f"{ca_total:,.0f}")
col2.metric("Transactions", f"{nb_transactions:,}")
col3.metric("Panier moyen", f"{panier_moyen:,.0f}")
col4.metric("Clients uniques", f"{nb_clients:,}")
st.columns(4) divise la zone en quatre colonnes égales. Sur écran étroit, Streamlit empile automatiquement les colonnes verticalement — pas besoin de gérer le responsive à la main. Pour ajouter une variation comparée à la période précédente, le troisième argument de st.metric accepte une chaîne avec signe (« +12 % », « -3 % »), affichée en vert ou rouge automatiquement.
Étape 6 — Évolution temporelle
Le graphique le plus utile sur un tableau de bord ventes est l’évolution temporelle du chiffre d’affaires. Plotly Express produit un graphique interactif (zoom, hover) en une ligne. Le regroupement par jour ou par semaine se règle dans l’agrégation pandas en amont.
import plotly.express as px
ca_journalier = df_filtre.set_index("date").resample("D")["montant"].sum().reset_index()
fig_temps = px.line(
ca_journalier, x="date", y="montant",
title="Chiffre d'affaires journalier",
labels={"montant": "CA", "date": "Date"},
)
fig_temps.update_layout(margin=dict(l=10, r=10, t=40, b=10))
st.plotly_chart(fig_temps, use_container_width=True)
use_container_width=True fait que le graphique remplit la colonne — c’est presque toujours ce qu’on veut sur un tableau de bord. Plotly gère le zoom, le pan, et l’export en PNG via les boutons en haut à droite du graphique. Pour un découpage hebdomadaire, on remplace resample("D") par resample("W").
Étape 7 — Répartition par mode de paiement
La répartition des paiements (carte, espèces, mobile, virement) est une métrique très opérationnelle pour une petite entreprise : elle pilote la trésorerie et la stratégie d’encaissement. Un graphique en barres horizontales avec les montants et un anneau pour la part en pourcentage donnent les deux lectures complémentaires.
repartition = df_filtre.groupby("mode_paiement")["montant"].agg(["sum", "count"]).reset_index()
repartition.columns = ["mode", "ca", "nb"]
repartition = repartition.sort_values("ca", ascending=False)
col_a, col_b = st.columns(2)
with col_a:
fig_bar = px.bar(repartition, x="mode", y="ca", title="CA par mode de paiement",
labels={"ca": "CA", "mode": "Mode"})
st.plotly_chart(fig_bar, use_container_width=True)
with col_b:
fig_donut = px.pie(repartition, names="mode", values="ca", hole=0.4,
title="Répartition du CA")
st.plotly_chart(fig_donut, use_container_width=True)
Le graphique en anneau (donut) met en évidence les parts. Sur de nombreuses petites entreprises, le mode mobile domine désormais largement, ce qui a des implications concrètes (commissions opérateur, délais de réception). Le graphique en barres garde la lecture des montants absolus, complément naturel.
Étape 8 — Top catégories et table interactive
Identifier les meilleures catégories de produits guide les décisions d’assortiment et de promotion. Un classement par chiffre d’affaires associé à un tableau interactif (tri, recherche) couvre les deux besoins : vue synthétique et exploration libre.
top_cat = (
df_filtre.groupby("categorie")["montant"]
.agg(["sum", "count", "mean"])
.sort_values("sum", ascending=False)
.head(10)
.reset_index()
)
top_cat.columns = ["Catégorie", "CA", "Transactions", "Panier moyen"]
st.subheader("Top 10 catégories")
st.dataframe(
top_cat.style.format({"CA": "{:,.0f}", "Transactions": "{:,}", "Panier moyen": "{:,.0f}"}),
use_container_width=True,
hide_index=True,
)
st.dataframe affiche un tableau interactif avec tri par colonne, redimensionnement et recherche. Le formatage via Pandas Styler ajoute la séparation des milliers et le bon nombre de décimales — un détail qui change l’expérience utilisateur. hide_index=True retire la colonne d’index de pandas qui n’apporte rien à un utilisateur métier.
Étape 9 — Téléchargement des données filtrées
Permettre à l’utilisateur d’exporter les données filtrées en CSV est attendu sur tout tableau de bord sérieux. Streamlit fournit st.download_button qui prend en charge la conversion et le téléchargement sans logique serveur supplémentaire. Les fichiers générés sont en mémoire, donc on encode soigneusement.
csv_bytes = df_filtre.to_csv(index=False).encode("utf-8-sig")
st.download_button(
"📥 Télécharger les données filtrées (CSV)",
data=csv_bytes,
file_name=f"ventes_{date_debut}_{date_fin}.csv",
mime="text/csv",
)
L’encodage utf-8-sig ajoute un BOM qui résout le problème d’accents mal lus quand on ouvre le CSV dans Excel — un piège classique qui a fait perdre des heures à beaucoup d’analystes. Le nom de fichier inclut les dates de filtre pour que l’utilisateur retrouve plus tard de quelle plage proviennent les données.
Étape 10 — Déployer sur un VPS
Pour mettre l’application à disposition d’utilisateurs distants, plusieurs options existent. La plus simple est Streamlit Community Cloud qui héberge gratuitement à partir d’un dépôt GitHub public. Pour un usage privé ou professionnel, on déploie sur un VPS avec Docker et Nginx en reverse proxy. Le Dockerfile reste minimal.
# Dockerfile
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8501
CMD ["streamlit", "run", "dashboard.py", "--server.address=0.0.0.0", "--server.port=8501"]
L’image se construit avec docker build -t dashboard . et tourne avec docker run -p 8501:8501 -v $(pwd)/ventes_2026.parquet:/app/ventes_2026.parquet dashboard. Pour une mise en ligne propre, on ajoute Nginx avec un certificat Let’s Encrypt et une authentification basique sur l’URL — quelques lignes dans le fichier nginx.conf qui suffisent à rendre le tableau de bord accessible uniquement aux utilisateurs autorisés.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
| Application lente à chaque interaction | Lecture du fichier sans cache | @st.cache_data sur la fonction de chargement |
| set_page_config raises error | Pas la première commande Streamlit | La placer en haut du fichier, avant tout autre st.* |
| Cache obsolète après modification du CSV | Cache toujours valide | Bouton « Clear cache » ou modifier les arguments |
| Accents mal lus dans Excel | Encodage UTF-8 sans BOM | Encoder en utf-8-sig pour le download_button |
| Date_input retourne 1 valeur | L’utilisateur n’a sélectionné qu’une borne | Vérifier len(...) == 2 avant déballage |
| Multiselect vide casse les filtres | Aucun élément coché | Mettre default=options à l’initialisation |
| Conteneur Docker plante au démarrage | Adresse 0.0.0.0 oubliée | –server.address=0.0.0.0 dans le CMD |
Tutoriels associés
- Analyse exploratoire avec pandas et matplotlib
- Servir un modèle ML en API avec FastAPI
- Analytics local avec dbt et DuckDB
- 🔝 Retour au guide principal : Sciences de données : la stack pratique 2026
Ressources officielles
FAQ
Streamlit ou Dash ?
Streamlit pour la rapidité de développement et la simplicité. Dash pour des applications plus structurées avec mise en page complexe et beaucoup d’utilisateurs simultanés. Streamlit gagne sur le « time to value », Dash sur la flexibilité.
Streamlit supporte combien d’utilisateurs en parallèle ?
Une instance Streamlit standard tient entre 10 et 50 utilisateurs simultanés selon la complexité. Au-delà, on met plusieurs instances derrière un load balancer ou on bascule sur une solution professionnelle (Streamlit Snowflake, ou un autre framework).
Comment ajouter une authentification ?
Streamlit propose st.user (anciennement st.experimental_user) pour s’intégrer à un OIDC. En self-hosted, l’authentification basique au niveau Nginx est plus simple. Pour un usage avancé, le package streamlit-authenticator ajoute une couche login avec utilisateurs et hashes en YAML.
Comment intégrer un modèle ML ?
Charger le modèle joblib dans une fonction décorée par @st.cache_resource (cache pour les ressources non-picklables), puis appeler model.predict dans le flux normal. Pour l’inférence à grand volume, préférer une API FastAPI séparée — voir le tutoriel correspondant.