3. Chapitre 3 : Web Viewers et Dashboarding

3.1. Introduction

Les applications web interactives permettent de transformer des analyses statiques en outils dynamiques d’exploration et de communication. Ce chapitre présente Streamlit pour Python et Shiny pour R.

3.2. Vue d’ensemble

3.2.1. Comparaison des frameworks

Streamlit vs Shiny

Aspect

Streamlit (Python)

Shiny (R)

Architecture

Script Python linéaire

Structure UI/Server séparée

Courbe d’apprentissage

Très facile, intuitif

Plus complexe, plus flexible

Déploiement

Streamlit Cloud gratuit

Shinyapps.io (limité gratuit)

Réactivité

Automatique (top-down)

Explicite (reactive programming)

Performance

Bon pour petites/moyennes apps

Excellent, scalable

3.3. Streamlit (Python)

3.3.1. Installation

pip install streamlit pandas numpy matplotlib seaborn plotly

3.3.2. Structure de base

# app.py
import streamlit as st
import pandas as pd
import numpy as np

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

# Titre principal
st.title("📊 Mon Dashboard")

# Sidebar
st.sidebar.header("Paramètres")

# Contenu principal
st.write("Bienvenue!")

# Lancer : streamlit run app.py

3.3.3. Composants essentiels

Texte et affichage :

import streamlit as st

# Titres et texte
st.title("Titre principal")
st.header("En-tête")
st.subheader("Sous-titre")
st.text("Texte simple")
st.markdown("**Markdown** *supporté*")
st.latex(r"\sum_{i=1}^{n} x_i")

# Messages
st.success("Succès!")
st.info("Information")
st.warning("Attention!")
st.error("Erreur!")

Widgets d’entrée :

# Inputs basiques
bouton = st.button("Cliquer")
checkbox = st.checkbox("Cocher")
radio = st.radio("Choisir", ["A", "B", "C"])
select = st.selectbox("Sélectionner", ["Option 1", "Option 2"])
multiselect = st.multiselect("Multiple", ["A", "B", "C", "D"])

# Inputs numériques
slider = st.slider("Valeur", 0, 100, 50)
number = st.number_input("Nombre", min_value=0, max_value=100)

# Inputs texte
text = st.text_input("Texte")
area = st.text_area("Zone de texte")

# Date et fichier
date = st.date_input("Date")
file = st.file_uploader("Fichier", type=['csv', 'xlsx'])

Affichage de données :

import pandas as pd

df = pd.DataFrame({
    'A': [1, 2, 3],
    'B': [4, 5, 6]
})

# Tableaux
st.dataframe(df)  # Interactif
st.table(df)      # Statique

# Métriques
st.metric("Température", "25°C", "2°C")

# Colonnes pour layout
col1, col2, col3 = st.columns(3)
with col1:
    st.metric("Métrique 1", "100")
with col2:
    st.metric("Métrique 2", "200")
with col3:
    st.metric("Métrique 3", "300")

Graphiques :

import matplotlib.pyplot as plt
import plotly.express as px

# Matplotlib
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
st.pyplot(fig)

# Plotly (interactif)
fig = px.scatter(df, x='A', y='B')
st.plotly_chart(fig)

# Graphiques natifs
st.line_chart(df)
st.bar_chart(df)
st.area_chart(df)

3.3.4. Exemple d’application Streamlit

import streamlit as st
import pandas as pd
import numpy as np
import plotly.express as px

st.set_page_config(page_title="Dashboard Ventes", layout="wide")

# Données simulées
@st.cache_data
def load_data():
    dates = pd.date_range('2023-01-01', periods=365, freq='D')
    df = pd.DataFrame({
        'date': dates,
        'ventes': np.random.randint(100, 1000, 365),
        'region': np.random.choice(['Nord', 'Sud', 'Est', 'Ouest'], 365),
        'produit': np.random.choice(['A', 'B', 'C'], 365)
    })
    return df

df = load_data()

# Sidebar - Filtres
st.sidebar.header("Filtres")
regions = st.sidebar.multiselect(
    "Régions",
    options=df['region'].unique(),
    default=df['region'].unique()
)

produits = st.sidebar.multiselect(
    "Produits",
    options=df['produit'].unique(),
    default=df['produit'].unique()
)

# Filtrer les données
df_filtered = df[
    (df['region'].isin(regions)) &
    (df['produit'].isin(produits))
]

# Titre
st.title("📊 Dashboard des Ventes")

# KPIs
col1, col2, col3 = st.columns(3)

with col1:
    st.metric("Total Ventes", f"{df_filtered['ventes'].sum():,}")
with col2:
    st.metric("Moyenne", f"{df_filtered['ventes'].mean():.0f}")
with col3:
    st.metric("Transactions", len(df_filtered))

# Graphiques
st.subheader("Evolution temporelle")
fig_line = px.line(
    df_filtered.groupby('date')['ventes'].sum().reset_index(),
    x='date', y='ventes'
)
st.plotly_chart(fig_line, use_container_width=True)

# Comparaisons
col1, col2 = st.columns(2)

with col1:
    st.subheader("Par région")
    fig_bar = px.bar(
        df_filtered.groupby('region')['ventes'].sum().reset_index(),
        x='region', y='ventes', color='region'
    )
    st.plotly_chart(fig_bar, use_container_width=True)

with col2:
    st.subheader("Par produit")
    fig_pie = px.pie(
        df_filtered.groupby('produit')['ventes'].sum().reset_index(),
        values='ventes', names='produit'
    )
    st.plotly_chart(fig_pie, use_container_width=True)

# Données brutes
with st.expander("Voir les données"):
    st.dataframe(df_filtered)

    # Télécharger
    csv = df_filtered.to_csv(index=False)
    st.download_button(
        "📥 Télécharger CSV",
        data=csv,
        file_name='ventes.csv',
        mime='text/csv'
    )

3.4. Shiny (R)

3.4.1. Installation

install.packages(c("shiny", "shinydashboard", "ggplot2", "plotly", "DT"))

3.4.2. Structure de base

# app.R
library(shiny)

# Interface utilisateur
ui <- fluidPage(
  titlePanel("Mon Application Shiny"),

  sidebarLayout(
    sidebarPanel(
      sliderInput("bins", "Nombre de bins:",
                  min = 1, max = 50, value = 30)
    ),

    mainPanel(
      plotOutput("distPlot")
    )
  )
)

# Serveur
server <- function(input, output) {
  output$distPlot <- renderPlot({
    x <- faithful[, 2]
    bins <- seq(min(x), max(x), length.out = input$bins + 1)
    hist(x, breaks = bins, col = 'darkgray')
  })
}

# Lancer l'app
shinyApp(ui = ui, server = server)

3.4.3. Composants UI

# Widgets d'entrée
ui <- fluidPage(
  # Inputs basiques
  actionButton("button", "Cliquer"),
  checkboxInput("checkbox", "Cocher", TRUE),
  radioButtons("radio", "Choisir:", c("A", "B", "C")),
  selectInput("select", "Sélectionner:", c("Opt1", "Opt2")),

  # Inputs numériques
  sliderInput("slider", "Valeur:", 0, 100, 50),
  numericInput("number", "Nombre:", 10, min = 1, max = 100),

  # Inputs texte
  textInput("text", "Texte:"),
  textAreaInput("area", "Zone:", rows = 5),

  # Date et fichier
  dateInput("date", "Date:"),
  fileInput("file", "Fichier:", accept = c(".csv", ".xlsx")),

  # Outputs
  plotOutput("plot"),
  tableOutput("table"),
  textOutput("text"),
  DT::dataTableOutput("datatable")
)

3.4.4. Exemple d’application Shiny

library(shiny)
library(shinydashboard)
library(ggplot2)
library(dplyr)

# Interface
ui <- dashboardPage(
  dashboardHeader(title = "Dashboard Ventes"),

  dashboardSidebar(
    sidebarMenu(
      menuItem("Vue d'ensemble", tabName = "overview",
               icon = icon("dashboard")),
      menuItem("Données", tabName = "data",
               icon = icon("table"))
    ),

    # Filtres
    selectInput("region", "Région:",
                choices = c("Toutes", "Nord", "Sud", "Est", "Ouest"),
                selected = "Toutes"),

    selectInput("produit", "Produit:",
                choices = c("Tous", "A", "B", "C"),
                selected = "Tous")
  ),

  dashboardBody(
    tabItems(
      tabItem(tabName = "overview",
        # KPIs
        fluidRow(
          valueBoxOutput("totalBox"),
          valueBoxOutput("avgBox"),
          valueBoxOutput("countBox")
        ),

        # Graphiques
        fluidRow(
          box(title = "Evolution", width = 12,
              plotOutput("timePlot"))
        ),

        fluidRow(
          box(title = "Par région", width = 6,
              plotOutput("regionPlot")),
          box(title = "Par produit", width = 6,
              plotOutput("productPlot"))
        )
      ),

      tabItem(tabName = "data",
        h2("Données brutes"),
        DT::dataTableOutput("dataTable"),

        downloadButton("downloadData", "Télécharger CSV")
      )
    )
  )
)

# Serveur
server <- function(input, output) {
  # Données simulées
  data <- reactive({
    df <- data.frame(
      date = seq.Date(as.Date("2023-01-01"),
                     by = "day", length.out = 365),
      ventes = sample(100:1000, 365, replace = TRUE),
      region = sample(c("Nord", "Sud", "Est", "Ouest"),
                     365, replace = TRUE),
      produit = sample(c("A", "B", "C"), 365, replace = TRUE)
    )

    # Appliquer les filtres
    if(input$region != "Toutes") {
      df <- df %>% filter(region == input$region)
    }
    if(input$produit != "Tous") {
      df <- df %>% filter(produit == input$produit)
    }

    df
  })

  # KPIs
  output$totalBox <- renderValueBox({
    valueBox(
      value = formatC(sum(data()$ventes), format = "d", big.mark = ","),
      subtitle = "Total Ventes",
      icon = icon("euro"),
      color = "blue"
    )
  })

  output$avgBox <- renderValueBox({
    valueBox(
      value = round(mean(data()$ventes)),
      subtitle = "Moyenne",
      icon = icon("chart-line"),
      color = "green"
    )
  })

  output$countBox <- renderValueBox({
    valueBox(
      value = nrow(data()),
      subtitle = "Transactions",
      icon = icon("shopping-cart"),
      color = "yellow"
    )
  })

  # Graphiques
  output$timePlot <- renderPlot({
    data() %>%
      group_by(date) %>%
      summarise(total = sum(ventes)) %>%
      ggplot(aes(x = date, y = total)) +
      geom_line(color = "steelblue", size = 1) +
      theme_minimal() +
      labs(title = "Evolution des ventes", x = "Date", y = "Ventes")
  })

  output$regionPlot <- renderPlot({
    data() %>%
      group_by(region) %>%
      summarise(total = sum(ventes)) %>%
      ggplot(aes(x = region, y = total, fill = region)) +
      geom_bar(stat = "identity") +
      theme_minimal() +
      theme(legend.position = "none") +
      labs(x = "Région", y = "Ventes")
  })

  output$productPlot <- renderPlot({
    data() %>%
      group_by(produit) %>%
      summarise(total = sum(ventes)) %>%
      ggplot(aes(x = "", y = total, fill = produit)) +
      geom_bar(stat = "identity", width = 1) +
      coord_polar("y") +
      theme_void() +
      labs(fill = "Produit")
  })

  # Table
  output$dataTable <- DT::renderDataTable({
    data()
  })

  # Téléchargement
  output$downloadData <- downloadHandler(
    filename = function() {
      paste("ventes-", Sys.Date(), ".csv", sep = "")
    },
    content = function(file) {
      write.csv(data(), file, row.names = FALSE)
    }
  )
}

shinyApp(ui = ui, server = server)

3.5. Bonnes pratiques

3.5.1. Performance

Streamlit :

  • Utiliser @st.cache_data pour les données

  • Utiliser @st.cache_resource pour les modèles

  • Minimiser les rechargements avec st.form

Shiny :

  • Utiliser reactive() pour les calculs réutilisés

  • Utiliser isolate() pour éviter les dépendances inutiles

  • Utiliser observeEvent() plutôt que observe()

3.5.2. Design

  • Organiser l’interface de manière logique

  • Utiliser des titres et sections clairs

  • Fournir des instructions et aide contextuelle

  • Implémenter des messages d’erreur utiles

  • Permettre le téléchargement des résultats

3.5.3. Déploiement

Streamlit :

# Streamlit Cloud (gratuit)
# 1. Push sur GitHub
# 2. Connecter à streamlit.io
# 3. Déployer

# Local
streamlit run app.py --server.port 8080

Shiny :

# shinyapps.io
library(rsconnect)
deployApp()

# Shiny Server (open source)
# Installer Shiny Server
# Placer l'app dans /srv/shiny-server/

3.6. Conclusion

Ce chapitre a présenté les frameworks de création d’applications web interactives :

  • Streamlit : Simple et rapide pour Python

  • Shiny : Puissant et flexible pour R

Ces outils permettent de : - Créer des dashboards interactifs - Partager des analyses avec des non-techniciens - Explorer les données de manière dynamique - Automatiser les rapports

Les compétences acquises dans ce chapitre sont essentielles pour la communication moderne des résultats d’analyse de données.