StreamlitPlotlyDashboardData VisualizationPython

Comment créer un dashboard interactif avec Streamlit et Plotly en 2025

GP
Gaël Penessot

Comment créer un dashboard interactif avec Streamlit et Plotly en 2025

Les dashboards interactifs sont devenus essentiels pour visualiser et analyser les données en temps réel. En combinant Streamlit pour l'interface utilisateur et Plotly pour les visualisations, vous pouvez créer des tableaux de bord professionnels en quelques heures seulement.

Dans ce guide complet, je vais vous montrer comment créer un dashboard interactif moderne avec tous les éléments essentiels : graphiques dynamiques, filtres temps réel, KPIs, et export de données.

Pourquoi Streamlit + Plotly ?

Après avoir créé plus de 50 dashboards pour des clients dans différents secteurs (e-commerce, finance, marketing), je peux vous dire que Streamlit + Plotly est la combinaison la plus efficace pour créer des dashboards en Python.

Les avantages de cette stack

Streamlit :

  • Interface utilisateur en pur Python (zéro HTML/CSS/JavaScript)
  • Widgets interactifs natifs (sliders, selectbox, date_input)
  • Rechargement automatique lors des interactions
  • Déploiement gratuit sur Streamlit Cloud

Plotly :

  • Graphiques interactifs par défaut (zoom, pan, hover)
  • 40+ types de graphiques (ligne, barre, scatter, heatmap, etc.)
  • Personnalisation complète des couleurs et styles
  • Performance optimale même avec des millions de points

Ensemble, ils permettent de créer des dashboards professionnels 10x plus rapidement qu'avec des frameworks traditionnels comme Dash ou Flask.

Prérequis et installation

Ce dont vous avez besoin

  • Python 3.8 ou supérieur
  • Connaissances de base en Python et Pandas
  • 30 minutes de votre temps

Installation des dépendances

pip install streamlit plotly pandas numpy

Versions recommandées :

  • streamlit >= 1.30.0
  • plotly >= 5.18.0
  • pandas >= 2.0.0

Structure d'un dashboard professionnel

Un dashboard efficace contient généralement 4 composants clés :

  1. Header : Titre, logo, filtres globaux
  2. KPIs : Indicateurs clés de performance (métriques principales)
  3. Visualisations : Graphiques interactifs
  4. Tables : Données détaillées avec possibilité d'export

Voici la structure que j'utilise systématiquement :

import streamlit as st
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# Configuration de la page
st.set_page_config(
    page_title="Sales Dashboard",
    page_icon="📊",
    layout="wide",
    initial_sidebar_state="expanded"
)

# 1. HEADER
st.title("📊 Dashboard des Ventes")
st.markdown("---")

# 2. SIDEBAR - Filtres
with st.sidebar:
    st.header("Filtres")
    # Filtres ici

# 3. KPIs
col1, col2, col3, col4 = st.columns(4)
# Métriques ici

# 4. VISUALISATIONS
row1_col1, row1_col2 = st.columns(2)
# Graphiques ligne 1

row2_col1, row2_col2 = st.columns(2)
# Graphiques ligne 2

# 5. TABLES
st.subheader("📋 Données détaillées")
# Tables ici

Exemple complet : Dashboard des ventes

Je vais vous montrer un exemple complet et fonctionnel d'un dashboard de ventes e-commerce.

Étape 1 : Préparer les données

import streamlit as st
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from datetime import datetime, timedelta

# Fonction pour générer des données de démonstration
@st.cache_data
def load_data():
    """Génère des données de ventes réalistes"""
    np.random.seed(42)

    # Générer 365 jours de données
    dates = pd.date_range(
        start=datetime.now() - timedelta(days=365),
        end=datetime.now(),
        freq='D'
    )

    # Catégories de produits
    categories = ['Électronique', 'Vêtements', 'Maison', 'Sports', 'Livres']

    data = []
    for date in dates:
        for category in categories:
            # Ventes avec tendance et saisonnalité
            base_sales = np.random.poisson(50)
            trend = (date - dates[0]).days * 0.1
            seasonality = 20 * np.sin(2 * np.pi * (date.dayofyear / 365))
            sales = int(max(0, base_sales + trend + seasonality))

            revenue = sales * np.random.uniform(20, 200)

            data.append({
                'date': date,
                'category': category,
                'sales': sales,
                'revenue': round(revenue, 2),
                'customers': np.random.poisson(sales * 0.7)
            })

    df = pd.DataFrame(data)
    return df

# Charger les données
df = load_data()

Note importante : J'utilise @st.cache_data pour mettre en cache les données. Cela évite de recharger les données à chaque interaction, ce qui améliore considérablement les performances.

Étape 2 : Configuration et Header

# Configuration de la page
st.set_page_config(
    page_title="Dashboard des Ventes",
    page_icon="📊",
    layout="wide",
    initial_sidebar_state="expanded"
)

# CSS personnalisé pour améliorer l'apparence
st.markdown("""
    <style>
    .main > div {
        padding-top: 2rem;
    }
    .stMetric {
        background-color: #f0f2f6;
        padding: 15px;
        border-radius: 5px;
    }
    </style>
    """, unsafe_allow_html=True)

# Header
st.title("📊 Dashboard des Ventes E-commerce")
st.markdown("Analyse en temps réel des performances commerciales")
st.markdown("---")

Étape 3 : Filtres interactifs dans la sidebar

with st.sidebar:
    st.header("⚙️ Filtres")

    # Filtre de date
    st.subheader("Période")
    date_range = st.date_input(
        "Sélectionner la période",
        value=(
            df['date'].min().date(),
            df['date'].max().date()
        ),
        min_value=df['date'].min().date(),
        max_value=df['date'].max().date()
    )

    # Filtre de catégorie
    st.subheader("Catégories")
    categories = st.multiselect(
        "Sélectionner les catégories",
        options=df['category'].unique(),
        default=df['category'].unique()
    )

    # Filtre de métrique
    st.subheader("Vue")
    metric_choice = st.radio(
        "Métrique principale",
        options=['Revenus', 'Ventes', 'Clients'],
        horizontal=True
    )

# Appliquer les filtres
if len(date_range) == 2:
    mask = (
        (df['date'].dt.date >= date_range[0]) &
        (df['date'].dt.date <= date_range[1]) &
        (df['category'].isin(categories))
    )
    filtered_df = df[mask].copy()
else:
    filtered_df = df[df['category'].isin(categories)].copy()

Astuce : Utilisez st.multiselect avec default=df['category'].unique() pour sélectionner toutes les catégories par défaut. Cela améliore l'expérience utilisateur.

Étape 4 : KPIs avec métriques dynamiques

# Calculer les KPIs
total_revenue = filtered_df['revenue'].sum()
total_sales = filtered_df['sales'].sum()
total_customers = filtered_df['customers'].sum()
avg_order_value = total_revenue / total_sales if total_sales > 0 else 0

# Calculer les variations (vs période précédente)
period_length = (date_range[1] - date_range[0]).days if len(date_range) == 2 else 365
previous_start = date_range[0] - timedelta(days=period_length) if len(date_range) == 2 else df['date'].min().date()
previous_end = date_range[0] - timedelta(days=1) if len(date_range) == 2 else df['date'].min().date()

previous_mask = (
    (df['date'].dt.date >= previous_start) &
    (df['date'].dt.date <= previous_end) &
    (df['category'].isin(categories))
)
previous_df = df[previous_mask]

prev_revenue = previous_df['revenue'].sum()
prev_sales = previous_df['sales'].sum()
prev_customers = previous_df['customers'].sum()
prev_aov = prev_revenue / prev_sales if prev_sales > 0 else 0

# Calculer les deltas
revenue_delta = ((total_revenue - prev_revenue) / prev_revenue * 100) if prev_revenue > 0 else 0
sales_delta = ((total_sales - prev_sales) / prev_sales * 100) if prev_sales > 0 else 0
customers_delta = ((total_customers - prev_customers) / prev_customers * 100) if prev_customers > 0 else 0
aov_delta = ((avg_order_value - prev_aov) / prev_aov * 100) if prev_aov > 0 else 0

# Afficher les KPIs
col1, col2, col3, col4 = st.columns(4)

with col1:
    st.metric(
        label="💰 Revenus",
        value=f"{total_revenue:,.0f} €",
        delta=f"{revenue_delta:+.1f}%"
    )

with col2:
    st.metric(
        label="🛒 Ventes",
        value=f"{total_sales:,.0f}",
        delta=f"{sales_delta:+.1f}%"
    )

with col3:
    st.metric(
        label="👥 Clients",
        value=f"{total_customers:,.0f}",
        delta=f"{customers_delta:+.1f}%"
    )

with col4:
    st.metric(
        label="📊 Panier Moyen",
        value=f"{avg_order_value:.2f} €",
        delta=f"{aov_delta:+.1f}%"
    )

st.markdown("---")

Le composant st.metric est parfait pour afficher des KPIs avec variation. Le delta s'affiche automatiquement en vert (positif) ou rouge (négatif).

Étape 5 : Graphiques Plotly interactifs

Graphique 1 : Évolution temporelle

row1_col1, row1_col2 = st.columns(2)

with row1_col1:
    st.subheader("📈 Évolution dans le temps")

    # Agréger par jour
    daily_data = filtered_df.groupby('date').agg({
        'revenue': 'sum',
        'sales': 'sum',
        'customers': 'sum'
    }).reset_index()

    # Choisir la métrique à afficher
    metric_map = {
        'Revenus': 'revenue',
        'Ventes': 'sales',
        'Clients': 'customers'
    }
    y_column = metric_map[metric_choice]

    # Créer le graphique
    fig_timeline = px.line(
        daily_data,
        x='date',
        y=y_column,
        title=f"Évolution des {metric_choice.lower()}",
        labels={
            'date': 'Date',
            y_column: metric_choice
        }
    )

    # Personnaliser l'apparence
    fig_timeline.update_traces(
        line_color='#1f77b4',
        line_width=2,
        hovertemplate='<b>%{x|%d/%m/%Y}</b><br>' +
                      f'{metric_choice}: %{{y:,.0f}}<extra></extra>'
    )

    fig_timeline.update_layout(
        hovermode='x unified',
        showlegend=False,
        height=400,
        margin=dict(l=0, r=0, t=40, b=0)
    )

    st.plotly_chart(fig_timeline, use_container_width=True)

Graphique 2 : Répartition par catégorie

with row1_col2:
    st.subheader("🎯 Répartition par catégorie")

    # Agréger par catégorie
    category_data = filtered_df.groupby('category').agg({
        'revenue': 'sum',
        'sales': 'sum',
        'customers': 'sum'
    }).reset_index()

    y_column = metric_map[metric_choice]

    # Créer le graphique en barres
    fig_category = px.bar(
        category_data.sort_values(y_column, ascending=False),
        x='category',
        y=y_column,
        title=f"{metric_choice} par catégorie",
        labels={
            'category': 'Catégorie',
            y_column: metric_choice
        },
        color=y_column,
        color_continuous_scale='Blues'
    )

    fig_category.update_traces(
        hovertemplate='<b>%{x}</b><br>' +
                      f'{metric_choice}: %{{y:,.0f}}<extra></extra>'
    )

    fig_category.update_layout(
        showlegend=False,
        height=400,
        margin=dict(l=0, r=0, t=40, b=0),
        xaxis_tickangle=-45
    )

    st.plotly_chart(fig_category, use_container_width=True)

Graphique 3 : Top produits (Treemap)

row2_col1, row2_col2 = st.columns(2)

with row2_col1:
    st.subheader("🏆 Performance par catégorie")

    # Treemap pour visualiser la contribution de chaque catégorie
    fig_treemap = px.treemap(
        category_data,
        path=['category'],
        values='revenue',
        title="Contribution au chiffre d'affaires",
        color='revenue',
        color_continuous_scale='RdYlGn',
        hover_data={'revenue': ':,.0f'}
    )

    fig_treemap.update_traces(
        hovertemplate='<b>%{label}</b><br>' +
                      'Revenus: %{value:,.0f} €<extra></extra>'
    )

    fig_treemap.update_layout(
        height=400,
        margin=dict(l=0, r=0, t=40, b=0)
    )

    st.plotly_chart(fig_treemap, use_container_width=True)

Graphique 4 : Heatmap jour de la semaine

with row2_col2:
    st.subheader("📅 Heatmap par jour de la semaine")

    # Préparer les données pour la heatmap
    filtered_df['day_of_week'] = filtered_df['date'].dt.day_name()
    filtered_df['week'] = filtered_df['date'].dt.isocalendar().week

    # Ordre des jours
    days_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
    days_fr = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche']

    heatmap_data = filtered_df.groupby(['week', 'day_of_week'])['revenue'].sum().reset_index()
    heatmap_pivot = heatmap_data.pivot(index='day_of_week', columns='week', values='revenue')
    heatmap_pivot = heatmap_pivot.reindex(days_order)

    # Créer la heatmap
    fig_heatmap = go.Figure(data=go.Heatmap(
        z=heatmap_pivot.values,
        x=heatmap_pivot.columns,
        y=days_fr,
        colorscale='Viridis',
        hovertemplate='Semaine %{x}<br>%{y}<br>Revenus: %{z:,.0f} €<extra></extra>'
    ))

    fig_heatmap.update_layout(
        title="Revenus par jour et semaine",
        xaxis_title="Semaine",
        yaxis_title="Jour",
        height=400,
        margin=dict(l=0, r=0, t=40, b=0)
    )

    st.plotly_chart(fig_heatmap, use_container_width=True)

Étape 6 : Tables et export de données

st.markdown("---")
st.subheader("📋 Données détaillées")

# Créer des onglets pour différentes vues
tab1, tab2, tab3 = st.tabs(["📊 Par catégorie", "📅 Par jour", "💾 Données brutes"])

with tab1:
    # Tableau agrégé par catégorie
    summary_category = filtered_df.groupby('category').agg({
        'revenue': 'sum',
        'sales': 'sum',
        'customers': 'sum'
    }).reset_index()

    summary_category['avg_order_value'] = summary_category['revenue'] / summary_category['sales']
    summary_category = summary_category.sort_values('revenue', ascending=False)

    # Formater les colonnes
    summary_category['revenue'] = summary_category['revenue'].apply(lambda x: f"{x:,.2f} €")
    summary_category['avg_order_value'] = summary_category['avg_order_value'].apply(lambda x: f"{x:.2f} €")

    st.dataframe(
        summary_category.rename(columns={
            'category': 'Catégorie',
            'revenue': 'Revenus',
            'sales': 'Ventes',
            'customers': 'Clients',
            'avg_order_value': 'Panier Moyen'
        }),
        use_container_width=True,
        hide_index=True
    )

with tab2:
    # Tableau agrégé par jour
    summary_daily = filtered_df.groupby('date').agg({
        'revenue': 'sum',
        'sales': 'sum',
        'customers': 'sum'
    }).reset_index()

    summary_daily = summary_daily.sort_values('date', ascending=False)
    summary_daily['date'] = summary_daily['date'].dt.strftime('%d/%m/%Y')

    st.dataframe(
        summary_daily.rename(columns={
            'date': 'Date',
            'revenue': 'Revenus (€)',
            'sales': 'Ventes',
            'customers': 'Clients'
        }).head(30),
        use_container_width=True,
        hide_index=True
    )

with tab3:
    # Données brutes avec possibilité d'export
    st.write(f"**{len(filtered_df):,} lignes** dans la période sélectionnée")

    # Bouton de téléchargement CSV
    csv = filtered_df.to_csv(index=False).encode('utf-8')
    st.download_button(
        label="📥 Télécharger les données (CSV)",
        data=csv,
        file_name=f'sales_data_{datetime.now().strftime("%Y%m%d")}.csv',
        mime='text/csv',
    )

    # Afficher un échantillon
    st.dataframe(
        filtered_df.head(100),
        use_container_width=True,
        hide_index=True
    )

Le composant st.download_button permet d'exporter facilement les données au format CSV. C'est une fonctionnalité très appréciée par les utilisateurs.

Techniques avancées

1. Caching pour optimiser les performances

@st.cache_data(ttl=3600)  # Cache pendant 1 heure
def load_data_from_database():
    """Charge les données depuis une base de données"""
    # Connexion à la base de données
    conn = create_connection()
    df = pd.read_sql("SELECT * FROM sales", conn)
    conn.close()
    return df

@st.cache_data
def expensive_computation(df):
    """Calculs coûteux qui ne changent pas souvent"""
    # Traitement intensif
    result = df.groupby('category').apply(complex_function)
    return result

Utilisez ttl (time to live) pour forcer le rafraîchissement du cache après un certain temps. Essentiel pour les données qui évoluent régulièrement.

2. Session State pour la persistance

# Initialiser le session state
if 'selected_category' not in st.session_state:
    st.session_state.selected_category = None

# Bouton qui modifie le state
if st.button("Sélectionner Électronique"):
    st.session_state.selected_category = "Électronique"
    st.rerun()

# Utiliser le state dans les filtres
if st.session_state.selected_category:
    filtered_df = df[df['category'] == st.session_state.selected_category]

Session State permet de conserver des informations entre les reruns. Parfait pour créer des dashboards avec navigation complexe.

3. Graphiques combinés avec go.Figure

from plotly.subplots import make_subplots

# Créer un graphique avec 2 axes Y
fig = make_subplots(specs=[[{"secondary_y": True}]])

# Ajouter les courbes
fig.add_trace(
    go.Scatter(x=df['date'], y=df['revenue'], name="Revenus"),
    secondary_y=False,
)

fig.add_trace(
    go.Scatter(x=df['date'], y=df['sales'], name="Ventes"),
    secondary_y=True,
)

# Configurer les axes
fig.update_xaxes(title_text="Date")
fig.update_yaxes(title_text="Revenus (€)", secondary_y=False)
fig.update_yaxes(title_text="Nombre de ventes", secondary_y=True)

st.plotly_chart(fig, use_container_width=True)

Les graphiques combinés sont parfaits pour comparer des métriques avec des échelles différentes.

4. Personnalisation du thème Plotly

# Définir un thème personnalisé
custom_template = {
    'layout': {
        'paper_bgcolor': '#ffffff',
        'plot_bgcolor': '#f8f9fa',
        'font': {'family': 'Arial', 'color': '#2c3e50'},
        'colorway': ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6'],
        'hovermode': 'x unified',
        'hoverlabel': {
            'bgcolor': '#2c3e50',
            'font': {'color': 'white', 'family': 'Arial'}
        }
    }
}

# Appliquer le thème
fig = px.line(df, x='date', y='revenue', template=custom_template)

5. Indicateurs avancés avec Plotly

# Créer un gauge (jauge)
fig_gauge = go.Figure(go.Indicator(
    mode="gauge+number+delta",
    value=total_revenue,
    domain={'x': [0, 1], 'y': [0, 1]},
    title={'text': "Revenus du mois"},
    delta={'reference': prev_revenue},
    gauge={
        'axis': {'range': [None, prev_revenue * 1.5]},
        'bar': {'color': "#1f77b4"},
        'steps': [
            {'range': [0, prev_revenue * 0.7], 'color': "#ffcccc"},
            {'range': [prev_revenue * 0.7, prev_revenue], 'color': "#fff4cc"},
            {'range': [prev_revenue, prev_revenue * 1.5], 'color': "#ccffcc"}
        ],
        'threshold': {
            'line': {'color': "red", 'width': 4},
            'thickness': 0.75,
            'value': prev_revenue
        }
    }
))

st.plotly_chart(fig_gauge, use_container_width=True)

Déploiement sur Streamlit Cloud

Une fois votre dashboard terminé, déployez-le gratuitement sur Streamlit Cloud :

1. Créer requirements.txt

streamlit>=1.30.0
plotly>=5.18.0
pandas>=2.0.0
numpy>=1.24.0

2. Déployer sur Streamlit Cloud

  1. Poussez votre code sur GitHub
  2. Connectez-vous sur share.streamlit.io
  3. Sélectionnez votre repository
  4. Cliquez sur "Deploy"

En moins de 2 minutes, votre dashboard est en ligne et accessible au monde entier !

Pour un guide détaillé du déploiement, consultez mon article : Déployer une app Streamlit sur Streamlit Cloud.

Erreurs courantes à éviter

1. Ne pas utiliser le caching

❌ Mauvais :

def load_data():
    df = pd.read_csv('huge_file.csv')  # Rechargé à chaque interaction !
    return df

df = load_data()

✅ Bon :

@st.cache_data
def load_data():
    df = pd.read_csv('huge_file.csv')  # Chargé une seule fois
    return df

df = load_data()

2. Trop de reruns inutiles

❌ Mauvais :

# Chaque widget déclenche un rerun complet
date_filter = st.date_input("Date")
category_filter = st.selectbox("Catégorie", categories)
metric_filter = st.radio("Métrique", metrics)

✅ Bon :

# Grouper les filtres dans la sidebar
with st.sidebar:
    st.header("Filtres")
    date_filter = st.date_input("Date")
    category_filter = st.selectbox("Catégorie", categories)
    metric_filter = st.radio("Métrique", metrics)

3. Graphiques non responsives

❌ Mauvais :

fig = px.line(df, x='date', y='revenue')
st.plotly_chart(fig)  # Largeur fixe

✅ Bon :

fig = px.line(df, x='date', y='revenue')
st.plotly_chart(fig, use_container_width=True)  # S'adapte à l'écran

4. Pas d'export de données

❌ Mauvais :

st.dataframe(df)  # Les utilisateurs ne peuvent pas exporter

✅ Bon :

st.dataframe(df)

csv = df.to_csv(index=False).encode('utf-8')
st.download_button(
    "📥 Télécharger CSV",
    data=csv,
    file_name='data.csv',
    mime='text/csv'
)

Cas d'usage réels

Voici 3 dashboards que j'ai créés pour des clients avec Streamlit + Plotly :

1. Dashboard Marketing (Agence)

  • Objectif : Suivre les campagnes Google Ads et Facebook Ads
  • Métriques : CPM, CPC, CTR, ROAS, conversions
  • Sources : APIs Google Ads + Meta Ads
  • Impact : Réduction de 40% du temps d'analyse hebdomadaire

2. Dashboard RH (Scale-up)

  • Objectif : Visualiser les KPIs RH (turnover, recrutement, satisfaction)
  • Métriques : Headcount, coût par recrutement, délai de recrutement
  • Sources : HRIS interne + Google Sheets
  • Impact : Décisions data-driven sur les recrutements

3. Dashboard Financier (E-commerce)

  • Objectif : Suivi des revenus, marge, cash-flow
  • Métriques : MRR, CAC, LTV, churn
  • Sources : Stripe + base de données produit
  • Impact : Détection précoce des problèmes de cash-flow

Ressources complémentaires

Documentation officielle

Mes formations

Si vous voulez maîtriser Streamlit de A à Z, j'ai créé une formation complète :

👉 Streamlit Unleashed - La formation la plus complète sur Streamlit en français (20h de contenu, 50+ exercices pratiques)

Outils gratuits

Conclusion

Créer un dashboard interactif avec Streamlit et Plotly est devenu extrêmement simple en 2025. En suivant ce guide, vous pouvez :

✅ Créer un dashboard professionnel en quelques heures ✅ Ajouter des graphiques interactifs avec Plotly ✅ Implémenter des filtres temps réel ✅ Exporter les données au format CSV ✅ Déployer gratuitement sur Streamlit Cloud

La combinaison Streamlit + Plotly est 10x plus rapide que des frameworks traditionnels, tout en offrant des résultats professionnels.

Prochaines étapes :

  1. Téléchargez le code complet ci-dessus
  2. Adaptez-le à vos propres données
  3. Déployez votre premier dashboard
  4. Partagez-le avec votre équipe !

Vous avez des questions sur la création de dashboards ? Envoyez-moi un message sur LinkedIn ou par email à gael@mes-formations-data.fr.


Cet article fait partie de ma série sur Streamlit. Pour aller plus loin, consultez aussi :

Livre Business Intelligence avec Python

📚 Approfondir avec mon livre

"Business Intelligence avec Python" - Le guide complet pour maîtriser l'analyse de données

Voir sur Amazon →

📬 Ne manquez rien de l'actualité data

Rejoignez +1000 professionnels qui reçoivent chaque semaine mes analyses, conseils et découvertes data.

S'abonner gratuitement
Prochaine révision : Trimestre prochain