Tous les guides
IntermédiaireÀ votre rythmeGuide gratuitSection 39/95

Mini-projet — Dashboard d'analyse de candidatures

Pratiquez les fondations du développement : programmation, web, API, données et usages de l’IA. Des sections et exercices pour construire vos premiers projets.

💡

Contenu gratuit, accessible sans compte. Les outils et modèles évoluent : consultez le dossier actuel pour vérifier leurs versions. Modèles et sources

Section 11.2.19 : Mini-projet — Dashboard d'analyse de candidatures

🎯 Objectif pédagogique

Intégrer toutes les compétences de la Semaine 2 dans un mini-projet complet : charger des données, les nettoyer, les analyser et les visualiser dans un dashboard interactif. Vous construirez un Job Search Analytics Dashboard — un outil pratique que Marc utilise vraiment.


Le projet : votre outil d'analyse personnel

Marc a 200+ candidatures dans sa base PostgreSQL. Il veut un dashboard qui répond à ces questions :

  1. →Quel est mon taux de conversion par étape ?
  2. →Quels secteurs/entreprises répondent le mieux ?
  3. →Combien de candidatures par semaine pour un objectif de 3 offres/mois ?
  4. →Quel est le salaire médian par statut ?

Architecture du projet

job-analytics/
├── data/
│   └── applications.csv        # Données source
├── notebooks/
│   └── analysis.ipynb          # Exploration (Jupyter)
├── src/
│   ├── data_loader.py          # Chargement et nettoyage
│   ├── analytics.py            # Fonctions d'analyse
│   └── dashboard.py            # Génération du dashboard
├── output/
│   └── dashboard.html          # Rapport final
├── requirements.txt
└── README.md

Étape 1 : data_loader.py

"""Chargement et nettoyage des données de candidatures."""
import pandas as pd
import numpy as np

def load_applications(filepath="data/applications.csv"):
    """Charge et nettoie les données de candidatures."""
    df = pd.read_csv(filepath)
    
    # Nettoyage
    df['company'] = df['company'].str.strip().str.title()
    df['status'] = df['status'].str.lower().str.strip()
    df['date'] = pd.to_datetime(df['date'], errors='coerce')
    df['salary'] = pd.to_numeric(df['salary'], errors='coerce')
    
    # Supprimer les doublons
    df = df.drop_duplicates(subset=['company', 'position', 'date'])
    
    # Colonnes dérivées
    df['week'] = df['date'].dt.isocalendar().week
    df['month'] = df['date'].dt.to_period('M')
    df['day_of_week'] = df['date'].dt.day_name()
    
    return df

def generate_sample_data(n=100):
    """Génère des données fictives réalistes."""
    np.random.seed(42)
    
    companies = ['Google', 'Meta', 'Stripe', 'Datadog', 'Mistral AI', 'OVH', 
                 'Vercel', 'BNP Paribas', 'Doctolib', 'Alan', 'Qonto', 'Swile',
                 'Deezer', 'Criteo', 'ContentSquare', 'Algolia', 'Ledger']
    positions = ['Frontend Developer', 'Backend Python', 'Full-Stack', 'Data Analyst',
                 'ML Engineer', 'DevOps', 'Product Manager', 'UX Designer']
    statuses = ['applied', 'interview', 'offer', 'rejected', 'no_response']
    
    data = \{
        'company': np.random.choice(companies, n),
        'position': np.random.choice(positions, n),
        'status': np.random.choice(statuses, n, p=[0.35, 0.15, 0.05, 0.20, 0.25]),
        'salary': np.random.normal(65000, 18000, n).astype(int).clip(35000, 120000),
        'date': pd.date_range('2026-01-01', periods=n, freq='2D')[:n]
    \}
    
    df = pd.DataFrame(data)
    df.to_csv('data/applications.csv', index=False)
    return df

Étape 2 : analytics.py

"""Fonctions d'analyse pour le dashboard de candidatures."""
import pandas as pd

def conversion_funnel(df):
    """Calcule le funnel de conversion."""
    total = len(df)
    responded = len(df[df['status'] != 'no_response'])
    interviews = len(df[df['status'].isin(['interview', 'offer'])])
    offers = len(df[df['status'] == 'offer'])
    
    return \{
        'total': total,
        'responded': responded,
        'response_rate': responded / total * 100,
        'interviews': interviews,
        'interview_rate': interviews / total * 100,
        'offers': offers,
        'offer_rate': offers / total * 100
    \}

def weekly_stats(df):
    """Statistiques hebdomadaires."""
    return df.groupby('week').agg(
        applications=('company', 'count'),
        interviews=('status', lambda x: (x == 'interview').sum()),
        offers=('status', lambda x: (x == 'offer').sum()),
        avg_salary=('salary', 'mean')
    ).round(0)

def company_performance(df):
    """Performance par entreprise."""
    return df.groupby('company').agg(
        total=('status', 'count'),
        interviews=('status', lambda x: (x.isin(['interview', 'offer'])).sum()),
        avg_salary=('salary', 'mean')
    ).assign(
        conversion=lambda x: (x['interviews'] / x['total'] * 100).round(1)
    ).sort_values('conversion', ascending=False)

def salary_analysis(df):
    """Analyse des salaires par statut."""
    return df.groupby('status')['salary'].agg(['mean', 'median', 'std', 'count']).round(0)

Étape 3 : dashboard.py

"""Génération du dashboard visuel."""
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
from data_loader import load_applications, generate_sample_data
from analytics import conversion_funnel, weekly_stats, company_performance, salary_analysis

def create_dashboard():
    # Charger les données
    try:
        df = load_applications()
    except FileNotFoundError:
        df = generate_sample_data()
    
    funnel = conversion_funnel(df)
    
    # Dashboard 6 graphiques
    fig, axes = plt.subplots(2, 3, figsize=(20, 12))
    fig.suptitle("Job Search Analytics Dashboard — Mars 2026", fontsize=18, fontweight='bold')
    
    # 1. Funnel de conversion
    stages = ['Candidatures', 'Réponses', 'Entretiens', 'Offres']
    values = [funnel['total'], funnel['responded'], funnel['interviews'], funnel['offers']]
    colors = ['#94a3b8', '#3b82f6', '#f59e0b', '#22c55e']
    axes[0,0].barh(stages[::-1], values[::-1], color=colors[::-1])
    for i, (s, v) in enumerate(zip(stages[::-1], values[::-1])):
        axes[0,0].text(v + 1, i, str(v), va='center', fontweight='bold')
    axes[0,0].set_title(f"Funnel : \{funnel['offer_rate']:.1f\}% de conversion")
    
    # 2. Candidatures par semaine
    weekly = df.groupby(df['date'].dt.isocalendar().week).size()
    axes[0,1].plot(weekly.index, weekly.values, marker='o', color='#0891b2', linewidth=2)
    axes[0,1].fill_between(weekly.index, weekly.values, alpha=0.2, color='#0891b2')
    axes[0,1].axhline(weekly.mean(), color='red', linestyle='--', alpha=0.7, label=f'Moyenne: \{weekly.mean():.0f\}/sem')
    axes[0,1].legend()
    axes[0,1].set_title("Rythme de candidature")
    
    # 3. Distribution des salaires
    for status in ['interview', 'offer', 'applied']:
        subset = df[df['status'] == status]['salary'].dropna()
        if len(subset):
            axes[0,2].hist(subset, bins=15, alpha=0.5, label=status)
    axes[0,2].legend()
    axes[0,2].set_title("Distribution des salaires par statut")
    
    # 4. Top entreprises
    top = company_performance(df).head(8)
    axes[1,0].barh(top.index, top['conversion'], color='#0891b2')
    axes[1,0].set_title("Top entreprises (taux conversion %)")
    axes[1,0].set_xlabel("%")
    
    # 5. Répartition par statut
    status_counts = df['status'].value_counts()
    colors_pie = \{'applied':'#3b82f6','interview':'#f59e0b','offer':'#22c55e','rejected':'#ef4444','no_response':'#94a3b8'\}
    axes[1,1].pie(status_counts.values, labels=status_counts.index, 
                  colors=[colors_pie.get(s, '#94a3b8') for s in status_counts.index],
                  autopct='%1.0f%%', startangle=90)
    axes[1,1].set_title("Répartition par statut")
    
    # 6. Salaire moyen par jour de la semaine
    day_salary = df.groupby('day_of_week')['salary'].median()
    day_order = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']
    day_salary = day_salary.reindex(day_order).dropna()
    axes[1,2].bar(range(len(day_salary)), day_salary.values, color='#8b5cf6')
    axes[1,2].set_xticks(range(len(day_salary)))
    axes[1,2].set_xticklabels([d[:3] for d in day_salary.index], rotation=45)
    axes[1,2].set_title("Salaire médian par jour de candidature")
    
    plt.tight_layout(rect=[0, 0.03, 1, 0.95])
    
    # Insight en bas
    fig.text(0.5, 0.01, 
             f"💡 Insight : \{funnel['offer_rate']:.1f\}% de conversion | "
             f"Salaire médian entretiens : \{df[df['status']=='interview']['salary'].median():,.0f\}€ | "
             f"Meilleur jour : \{day_salary.idxmax()\}", 
             ha='center', fontsize=11, fontstyle='italic')
    
    plt.savefig('output/dashboard.png', dpi=200, bbox_inches='tight')
    plt.show()
    print("\n✅ Dashboard sauvegardé dans output/dashboard.png")

if __name__ == "__main__":
    create_dashboard()

🏋️ Exercice final — Semaine 2 (45 minutes)

Construisez votre propre dashboard :

  1. →Générez ou récupérez vos données de candidatures (CSV ou PostgreSQL)
  2. →Nettoyez (pipeline complet : types, NaN, doublons, texte)
  3. →Analysez : funnel de conversion, top entreprises, stats salariales
  4. →Visualisez : minimum 4 graphiques avec titres = insights
  5. →Documentez : un Jupyter Notebook avec Markdown entre les sections
  6. →Exportez : dashboard en PNG + notebook en HTML

Bonus : ajoutez une analyse de tendance temporelle et une recommandation chiffrée.

🎉 Semaine 2 terminée !

En une semaine, Marc est passé de "je sais copier un tableau Excel" à :

  • →✅ Requêter des bases SQL (SELECT, JOIN, CRUD)
  • →✅ Configurer un environnement Python pro
  • →✅ Manipuler des données avec NumPy et pandas
  • →✅ Créer des visualisations avec Matplotlib et Seaborn
  • →✅ Scraper le web et charger des données Open Data
  • →✅ Produire un dashboard d'analyse complet

La Semaine 3 appliquera ces compétences à l'automatisation — scripts qui tournent tout seuls, workflows, et intégration IA.

Vérification rapide

Quel est l'ordre correct pour un projet data complet ?

Vérification rapide

Pourquoi créer un dashboard plutôt qu'un simple tableau ?

Vérification rapide

Dans le funnel de conversion (45 candidatures → 12 entretiens → 3 offres), quel est le plus gros goulot d'étranglement ?

Votre progression

Cette progression est déclarative et conservée dans votre navigateur. L’attestation finale ne constitue pas un diplôme ni une évaluation indépendante.

Voir ma progression et mon attestation

Apprendre l’IA, du premier prompt aux agents

15 guides gratuitsQuiz, schémas et ateliersSans compte

Un cas pratique IA dans votre boîte mail

Méthodes, ressources et nouveaux exercices. Gratuit, avec désinscription à tout moment.