Solutions complètes & expliquées — 7 exercices
Club D.I.A.M 💎 Données Africaines Réelles 🌍Fondations · Arrays · DataFrames · Sélection
Array NumPy de 12 mois · argmin/argmax · indexation booléenne · statistiques vectorielles.
np.argmin() et np.argmax() retournent l'indice du min/max — parfait pour retrouver le mois correspondant.import numpy as np precipitations = np.array([200, 180, 220, 150, 80, 20, 10, 15, 50, 160, 240, 210]) mois = ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'] # 1. Précipitation totale annuelle total = np.sum(precipitations) print(f"1. Total annuel : {total} mm") # 2. Mois le plus sec et le plus pluvieux idx_sec = np.argmin(precipitations) idx_pluvieux = np.argmax(precipitations) print(f"2. Mois le + sec : {mois[idx_sec]} ({precipitations[idx_sec]} mm)") print(f" Mois le + pluvieux : {mois[idx_pluvieux]} ({precipitations[idx_pluvieux]} mm)") # 3. Moyenne mensuelle moyenne = np.mean(precipitations) print(f"3. Moyenne mensuelle : {moyenne:.1f} mm") # 4. Mois de saison des pluies (> 150 mm) masque = precipitations > 150 mois_pluies = [mois[i] for i in range(12) if masque[i]] print(f"4. Saison des pluies : {mois_pluies} ({np.sum(masque)} mois)") # 5. Différence max - min diff = np.max(precipitations) - np.min(precipitations) print(f"5. Différence max - min : {diff} mm")
DataFrame 6 pays · PIB/habitant calculé · filtrage · corrélation · colonne conditionnelle · tri.
apply(lambda x: ...) est le moyen le plus lisible pour créer une colonne catégorielle à partir d'une condition.import pandas as pd import numpy as np data = { 'Pays' : ['Gabon', 'Cameroun', "Côte d'Ivoire", 'Sénégal', 'Mali', 'Ghana'], 'PIB_Mds' : [20.3, 44.1, 61.4, 27.6, 18.1, 72.3], 'Population_M' : [2.3, 27.9, 26.9, 17.2, 22.4, 33.5], 'Taux_Scolarisation': [88, 78, 62, 57, 52, 74], 'Esperance_Vie' : [65, 59, 58, 62, 55, 64], } df = pd.DataFrame(data) # 1. PIB par habitant le plus élevé df['PIB_par_hab'] = df['PIB_Mds'] / df['Population_M'] * 1000 idx = df['PIB_par_hab'].idxmax() print(f"1. Meilleur PIB/hab : {df.loc[idx, 'Pays']} ({df.loc[idx, 'PIB_par_hab']:.0f} $/hab)") # 2. Pays avec taux de scolarisation > 75 % scol = df[df['Taux_Scolarisation'] > 75][['Pays', 'Taux_Scolarisation']] print(f"2. Scolarisation > 75 % :\n{scol.to_string(index=False)}") # 3. Corrélation PIB / scolarisation corr = df['PIB_par_hab'].corr(df['Taux_Scolarisation']) print(f"3. Corrélation PIB ↔ Scolarisation : {corr:.3f}") # 4. Colonne Catégorie df['Categorie'] = df['PIB_par_hab'].apply( lambda x: 'Riche' if x > 5000 else 'Émergent') print(f"4. Catégories :\n{df[['Pays','PIB_par_hab','Categorie']].to_string(index=False)}") # 5. Tri par espérance de vie décroissante df_trie = df.sort_values('Esperance_Vie', ascending=False) print(f"5. Tri espérance de vie :\n{df_trie[['Pays','Esperance_Vie']].to_string(index=False)}")
Groupby · Pivot · Merge · Valeurs manquantes
groupby multi-colonnes · pivot_table · gestion des NaN avec fillna(médiane) · classement.
pivot_table() = groupby en 2D. La médiane est préférable à la moyenne pour remplacer les NaN car elle est robuste aux valeurs extrêmes.import pandas as pd import numpy as np np.random.seed(42) regions = ['Nord', 'Sud', 'Est', 'Ouest', 'Centre'] * 10 matieres = ['Maths', 'Science', 'Français', 'Histoire'] * 12 + ['Maths', 'Science'] rows = [] for i in range(50): rows.append({ 'Region' : regions[i], 'Matiere' : ['Maths','Science','Français','Histoire'][i % 4], 'Score' : np.random.randint(50, 96), 'Nb_eleves' : np.random.randint(80, 151), }) df = pd.DataFrame(rows) df.loc[df.sample(5).index, 'Score'] = np.nan # valeurs manquantes # 1. Moyenne par région pour chaque matière print("1. Moyenne par région x matière :") print(df.groupby(['Region', 'Matiere'])['Score'].mean().round(1).unstack()) # 2. Région avec le meilleur taux de réussite moy_region = df.groupby('Region')['Score'].mean().sort_values(ascending=False) print(f"\n2. Meilleure région : {moy_region.idxmax()} ({moy_region.max():.1f}/100)") # 3. Pivot table print("\n3. Pivot table (Score × Région × Matière) :") pivot = df.pivot_table( values='Score', index='Region', columns='Matiere', aggfunc='mean').round(1) print(pivot) # 4. Gérer les valeurs manquantes (médiane) mediane = df['Score'].median() df['Score'] = df['Score'].fillna(mediane) print(f"\n4. NaN remplacés par médiane = {mediane:.1f}") print(f" NaN restants : {df['Score'].isnull().sum()}") # 5. Classement régions par score moyen décroissant classement = df.groupby('Region')['Score'].mean().sort_values(ascending=False).round(1) print("\n5. Classement des régions :") for rang, (region, score) in enumerate(classement.items(), 1): print(f" {rang}. {region:<8} : {score}")
inner join · left join · détection de pays manquants · corrélation inter-datasets.
left join est idéal pour "auditer" un dataset : les NaN dans les colonnes droites révèlent exactement les lignes sans correspondance.import pandas as pd # Dataset économique (7 pays) df_eco = pd.DataFrame({ 'Pays' : ['Gabon', 'Cameroun', "Côte d'Ivoire", 'Sénégal', 'Mali', 'Ghana', 'Burkina'], 'PIB_Mds' : [20.3, 44.1, 61.4, 27.6, 18.1, 72.3, 16.1], 'Dep_Sante_%': [4.2, 3.1, 2.8, 3.5, 2.2, 3.8, 5.1], }) # Dataset sanitaire (6 pays — sans Burkina, +Nigeria) df_sante = pd.DataFrame({ 'Pays' : ['Gabon', 'Cameroun', "Côte d'Ivoire", 'Sénégal', 'Ghana', 'Nigeria'], 'Esperance_Vie': [65, 59, 58, 62, 64, 53], 'Medecins_10k' : [5.8, 2.3, 1.4, 0.7, 1.1, 0.4], }) # 1. Inner join inner = pd.merge(df_eco, df_sante, on='Pays', how='inner') print(f"1. INNER JOIN — {len(inner)} pays communs :") print(inner[['Pays', 'PIB_Mds', 'Esperance_Vie']].to_string(index=False)) # 2. Left join — pays sans données sanitaires left = pd.merge(df_eco, df_sante, on='Pays', how='left') sans_sante = left[left['Esperance_Vie'].isnull()]['Pays'].tolist() print(f"\n2. LEFT JOIN — pays sans données sanitaires : {sans_sante}") # 3. PIB par médecin (relatif : Mds $ / nb médecins pour 10 000 hab.) inner['PIB_par_med'] = (inner['PIB_Mds'] / inner['Medecins_10k']).round(2) print("\n3. PIB relatif par médecin :") print(inner[['Pays', 'PIB_Mds', 'Medecins_10k', 'PIB_par_med']].to_string(index=False)) # 4. Corrélation dépenses santé ↔ espérance de vie corr = inner['Dep_Sante_%'].corr(inner['Esperance_Vie']) print(f"\n4. Corrélation dépenses santé ↔ espérance de vie : {corr:.3f}") interp = "positive forte" if corr > 0.5 else "positive modérée" if corr > 0 else "faible ou négative" print(f" → Corrélation {interp}")
Matplotlib · Seaborn · 6 étapes EDA · Heatmap
Subplots 2×2 · barres annotées · pie chart · line avec fill_between · scatter avec colorbar · export PNG.
fig.suptitle() donne un titre global au dashboard. plt.tight_layout() évite les chevauchements entre sous-graphiques.import matplotlib.pyplot as plt import numpy as np # Données santé publique africaine pays = ['Gabon', 'Cameroun', 'Congo', 'Sénégal', "C. d'Ivoire"] cas_palu = [1250, 890, 650, 720, 580] budgets = [4.2, 3.1, 2.8, 3.5, 2.6] # % du PIB annees = list(range(2014, 2024)) cas_evo = [2100, 1950, 1800, 1650, 1500, 1380, 1280, 1150, 1050, 890] bud_abs = [35, 45, 62, 28, 72, 40] # budget santé absolu (Mds $) esperance= [65, 59, 58, 62, 61, 63] couleurs = ['#009e60', '#3a75c4', '#fcd116', '#009e60', '#3a75c4'] fig, axes = plt.subplots(2, 2, figsize=(14, 10)) fig.suptitle('Dashboard Santé Publique — Afrique', fontsize=16, fontweight='bold') # 1. Barres : cas de paludisme bars = axes[0,0].bar(pays, cas_palu, color=couleurs, edgecolor='white', linewidth=1.2) axes[0,0].set_title('Cas de Paludisme par Pays (2024)', fontweight='bold') axes[0,0].set_ylabel('Nombre de cas') for bar, val in zip(bars, cas_palu): axes[0,0].text(bar.get_x() + bar.get_width()/2, bar.get_height() + 15, f'{val:,}', ha='center', fontsize=9, fontweight='bold') axes[0,0].set_ylim(0, max(cas_palu) * 1.15) # 2. Pie : répartition des budgets santé axes[0,1].pie(budgets, labels=pays, autopct='%1.1f%%', colors=couleurs, startangle=90, pctdistance=0.82) axes[0,1].set_title('Répartition des Budgets Santé (% PIB)', fontweight='bold') # 3. Line : évolution des cas sur 10 ans axes[1,0].plot(annees, cas_evo, color='#009e60', linewidth=2.5, marker='o', markersize=6, markerfacecolor='white', markeredgewidth=2) axes[1,0].fill_between(annees, cas_evo, alpha=0.12, color='#009e60') axes[1,0].set_title('Évolution des Cas de Paludisme (2014-2023)', fontweight='bold') axes[1,0].set_xlabel('Année'); axes[1,0].set_ylabel('Nombre de cas') axes[1,0].grid(alpha=0.3, linestyle='--') axes[1,0].tick_params(axis='x', rotation=45) # 4. Scatter : budget santé vs espérance de vie sc = axes[1,1].scatter(bud_abs, esperance, c=bud_abs, cmap='YlGn', s=100, alpha=0.85, edgecolors='#555', linewidths=0.8) plt.colorbar(sc, ax=axes[1,1], label='Budget (Mds $)') axes[1,1].set_xlabel('Budget Santé (Mds $)') axes[1,1].set_ylabel('Espérance de vie (ans)') axes[1,1].set_title('Budget Santé vs Espérance de Vie', fontweight='bold') axes[1,1].grid(alpha=0.3) plt.tight_layout() plt.savefig('dashboard_sante_afrique.png', dpi=150, bbox_inches='tight') plt.show() print("✅ Dashboard sauvegardé : dashboard_sante_afrique.png")
6 étapes méthodologiques : chargement, nettoyage (NaN+doublons), describe, 4 visualisations, heatmap Seaborn, 5 insights.
drop_duplicates() avant fillna() évite de biaiser les statistiques de remplacement.import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns sns.set_theme(style='whitegrid', palette='viridis') np.random.seed(42) # ═══ ÉTAPE 1 — CHARGEMENT & INSPECTION ═══ regions = ['Nord', 'Sud', 'Est', 'Ouest', 'Centre'] * 10 pays_l = ['Gabon', 'Cameroun', "C. d'Ivoire", 'Sénégal', 'Mali'] * 10 df = pd.DataFrame({ 'Region' : regions, 'Pays' : pays_l, 'Taux_Scolaris' : np.random.randint(40, 95, 50), 'Budget_Educ_pct': np.random.uniform(2.0, 8.0, 50).round(1), 'Score_PISA' : np.random.randint(280, 450, 50).astype(float), 'Ratio_Elev_Prof': np.random.randint(20, 65, 50), }) # Introduire NaN + doublon df.loc[df.sample(4).index, 'Score_PISA'] = np.nan df.loc[df.sample(2).index, 'Taux_Scolaris'] = np.nan df = pd.concat([df, df.iloc[[0]]], ignore_index=True) print(f"Shape : {df.shape} | Colonnes : {list(df.columns)}") print(df.head(3)) # ═══ ÉTAPE 2 — NETTOYAGE ═══ print(f"\nNaN :\n{df.isnull().sum()}") print(f"Doublons : {df.duplicated().sum()}") df = df.drop_duplicates().reset_index(drop=True) df['Score_PISA'] = df['Score_PISA'].fillna(df['Score_PISA'].median()) df['Taux_Scolaris'] = df['Taux_Scolaris'].fillna(df['Taux_Scolaris'].mean()) print(f"Shape après nettoyage : {df.shape} | NaN restants : {df.isnull().sum().sum()}") # ═══ ÉTAPE 3 — STATISTIQUES DESCRIPTIVES ═══ print("\nStatistiques descriptives :") print(df.describe().round(1)) print("\nRépartition par région :") print(df['Region'].value_counts()) # ═══ ÉTAPE 4 — VISUALISATIONS ═══ fig, axes = plt.subplots(2, 2, figsize=(14, 10)) fig.suptitle('EDA — Données Éducatives Africaines', fontsize=14, fontweight='bold') axes[0,0].hist(df['Taux_Scolaris'], bins=12, color='#009e60', edgecolor='white', alpha=0.8) axes[0,0].set_title('Distribution du Taux de Scolarisation') axes[0,0].set_xlabel('Taux (%)') df.boxplot(column='Score_PISA', by='Region', ax=axes[0,1], patch_artist=True) axes[0,1].set_title('Score PISA par Région') axes[0,1].set_xlabel('') moy_bud = df.groupby('Pays')['Budget_Educ_pct'].mean().sort_values(ascending=False) axes[1,0].bar(moy_bud.index, moy_bud.values, color='#3a75c4', edgecolor='white') axes[1,0].set_title('Budget Éducation Moyen (% PIB) par Pays') axes[1,0].set_ylabel('% du PIB') axes[1,1].scatter(df['Budget_Educ_pct'], df['Score_PISA'], alpha=0.6, color='#fcd116', edgecolors='#333', s=60) axes[1,1].set_xlabel('Budget Éducation (% PIB)') axes[1,1].set_ylabel('Score PISA') axes[1,1].set_title('Budget Éducation vs Score PISA') plt.tight_layout() plt.savefig('eda_education_afrique.png', dpi=150, bbox_inches='tight') plt.show() # ═══ ÉTAPE 5 — HEATMAP DES CORRÉLATIONS ═══ plt.figure(figsize=(8, 6)) num_cols = ['Taux_Scolaris', 'Budget_Educ_pct', 'Score_PISA', 'Ratio_Elev_Prof'] corr_mat = df[num_cols].corr() sns.heatmap(corr_mat, annot=True, cmap='RdYlGn', fmt='.2f', linewidths=0.5, square=True, xticklabels=['Scolaris.', 'Budget', 'PISA', 'Ratio'], yticklabels=['Scolaris.', 'Budget', 'PISA', 'Ratio']) plt.title('Heatmap des Corrélations — Éducation Africaine', fontweight='bold') plt.tight_layout() plt.show() # ═══ ÉTAPE 6 — INSIGHTS ═══ print("\n5 INSIGHTS ACTIONNABLES :") insights = [ f"Budget ↔ PISA : corr = {corr_mat.loc['Budget_Educ_pct','Score_PISA']:.3f} " f"— {'relation positive' if corr_mat.loc['Budget_Educ_pct','Score_PISA'] > 0 else 'relation faible'}", f"Ratio élèves/prof ↔ PISA : corr = {corr_mat.loc['Ratio_Elev_Prof','Score_PISA']:.3f} " f"— plus le ratio est élevé, {'plus le score baisse' if corr_mat.loc['Ratio_Elev_Prof','Score_PISA'] < 0 else 'moins prévisible'}", f"Meilleure région : {df.groupby('Region')['Score_PISA'].mean().idxmax()} " f"({df.groupby('Region')['Score_PISA'].mean().max():.0f}/450)", f"Taux scolarisation moyen : {df['Taux_Scolaris'].mean():.1f}%", f"Budget éducation moyen : {df['Budget_Educ_pct'].mean():.1f}% du PIB", ] for i, insight in enumerate(insights, 1): print(f" 💡 {i}. {insight}")
Régression · Scipy · Intervalles de confiance
scipy.stats.pearsonr · linregress · droite de régression · IC à 95% avec fill_between · analyse des résidus · prédiction.
import numpy as np import pandas as pd import matplotlib.pyplot as plt from scipy import stats np.random.seed(42) # Données : précipitations (mm) et rendements agricoles (kg/ha) precipitations = np.array([350, 420, 480, 510, 560, 620, 680, 720, 780, 830, 890, 950]) rendements = (precipitations * 0.85 + np.random.normal(0, 40, 12)).clip(min=200) regions = ['Sahel N', 'Sahel S', 'Guinée N', 'Guinée C', 'Guinée S', 'Côtier N', 'Côtier C', 'Côtier S', 'Central N', 'Central C', 'Forêt N', 'Forêt S'] # 1. Corrélation de Pearson r, p_val = stats.pearsonr(precipitations, rendements) print(f"1. Corrélation de Pearson : r = {r:.4f} (p = {p_val:.6f})") force = "très forte" if abs(r) > 0.8 else "forte" if abs(r) > 0.6 else "modérée" print(f" → Corrélation {force}") # 2. Régression linéaire slope, intercept, r_value, p_value, std_err = stats.linregress(precipitations, rendements) print(f"\n2. Modèle : Rendement = {slope:.3f} × Précip. + {intercept:.1f}") print(f" R² : {r_value**2:.4f} ({r_value**2*100:.1f}% de la variance expliquée)") print(f" p-value : {p_value:.6f} ({'significatif' if p_value < 0.05 else 'non significatif'})") # 3. Visualisation avec droite de régression et IC à 95 % x_range = np.linspace(precipitations.min() - 30, precipitations.max() + 30, 200) y_pred_l = slope * x_range + intercept n, se = len(precipitations), std_err * np.sqrt(n) fig, axes = plt.subplots(1, 2, figsize=(14, 6)) # Scatter + régression + IC axes[0].scatter(precipitations, rendements, color='#009e60', s=90, zorder=4, label='Données observées', edgecolors='white', linewidths=0.8) axes[0].plot(x_range, y_pred_l, color='#3a75c4', linewidth=2.5, label=f'Régression R² = {r_value**2:.3f}') axes[0].fill_between(x_range, y_pred_l - 1.96*se, y_pred_l + 1.96*se, alpha=0.12, color='#3a75c4', label='IC à 95 %') for i, reg in enumerate(regions): axes[0].annotate(reg, (precipitations[i], rendements[i]), textcoords='offset points', xytext=(4, 3), fontsize=7, color='#555') axes[0].set_xlabel('Précipitations (mm/an)', fontsize=11) axes[0].set_ylabel('Rendement agricole (kg/ha)', fontsize=11) axes[0].set_title('Précipitations → Rendements Agricoles', fontweight='bold') axes[0].legend(); axes[0].grid(alpha=0.3, linestyle='--') # Analyse des résidus y_pts = slope * precipitations + intercept residus = rendements - y_pts axes[1].scatter(y_pts, residus, color='#fcd116', s=80, edgecolors='#333', zorder=3) axes[1].axhline(y=0, color='red', linestyle='--', linewidth=1.5) axes[1].set_xlabel('Valeurs ajustées'); axes[1].set_ylabel('Résidus') axes[1].set_title('Analyse des Résidus', fontweight='bold') axes[1].grid(alpha=0.3) plt.tight_layout() plt.savefig('regression_agricole.png', dpi=150, bbox_inches='tight') plt.show() # 4. Interprétation du R² print(f"\n4. R² = {r_value**2:.4f} → le modèle explique {r_value**2*100:.1f}% de la variance") fiab = "fiable" if r_value**2 > 0.75 else "à améliorer (variables climatiques manquantes ?)" print(f" → Modèle {fiab}") # 5. Prédiction pour 800 mm precip_cible = 800 pred = slope * precip_cible + intercept ic_low = pred - 1.96 * se ic_high = pred + 1.96 * se print(f"\n5. Prédiction pour {precip_cible} mm de précipitations :") print(f" Rendement estimé : {pred:.0f} kg/ha") print(f" IC à 95 % : [{ic_low:.0f} ; {ic_high:.0f}] kg/ha")