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 :
- →Quel est mon taux de conversion par étape ?
- →Quels secteurs/entreprises répondent le mieux ?
- →Combien de candidatures par semaine pour un objectif de 3 offres/mois ?
- →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 :
- →Générez ou récupérez vos données de candidatures (CSV ou PostgreSQL)
- →Nettoyez (pipeline complet : types, NaN, doublons, texte)
- →Analysez : funnel de conversion, top entreprises, stats salariales
- →Visualisez : minimum 4 graphiques avec titres = insights
- →Documentez : un Jupyter Notebook avec Markdown entre les sections
- →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.
Quel est l'ordre correct pour un projet data complet ?
Pourquoi créer un dashboard plutôt qu'un simple tableau ?
Dans le funnel de conversion (45 candidatures → 12 entretiens → 3 offres), quel est le plus gros goulot d'étranglement ?