M55 - Data Science & Scientific Computing 5
  1. Exercices
  2. 11  🏭 Émissions de CO₂ des communes
  • 1  M55 - Data Science and Scientific Computing 5
  • Introduction
    • 2  Syllabus

  • Cours
    • 3  Introduction
    • 4  Series
    • 5  Data Frames
    • 6  🚱 Une analyse complĂšte : le Titanic

  • Exercices
    • 7  PremiĂšres manipulations
    • 8  📊 Notes L3 Math
    • 9  🛒 Ventes et ☔ pluie
    • 10  đŸ˜ïž Population France
    • 11  🏭 Émissions de CO₂ des communes
    • 12  đŸ“ș Netflix User
    • 13  đŸ—łïž PrĂ©sidentielles USA 2016

  • Downloads
    • 14  Fichiers sources

Table des matiĂšres

  • 11.1 Les donnĂ©es
  • 11.2 Exercice
    • 11.2.1 1. DĂ©couvrir les donnĂ©es
    • 11.2.2 2. Les valeurs manquantes
    • 11.2.3 3. Joindre Ă  la population
    • 11.2.4 4. Émissions par habitant
    • 11.2.5 5. Cartographier
  • 12 1. DĂ©couvrir les donnĂ©es
  • 13 2. Les valeurs manquantes
  • 14 3. Joindre Ă  la population
  • 15 4. Émissions par habitant
    • 15.1 AgrĂ©ger par dĂ©partement
    • 15.2 Total ou par habitant ?
  • 16 5. Cartographier
  • 17 Conclusion
  1. Exercices
  2. 11  🏭 Émissions de CO₂ des communes

11  🏭 Émissions de CO₂ des communes françaises

Auteur·rice

Gloria FACCANONI

Date de publication

30 octobre 2026

11.1 Les données

Le CITEPA, pour le compte du ministĂšre chargĂ© de l’Environnement, publie un inventaire des Ă©missions de gaz Ă  effet de serre commune par commune, ventilĂ© par secteur d’activitĂ© : agriculture, industrie, transport routier, rĂ©sidentiel, tertiaire, dĂ©chets.

Intitulé Inventaire de gaz à effet de serre territorialisé
Producteur CITEPA, pour la Direction gĂ©nĂ©rale de l’énergie et du climat
Année de référence 2016
Couverture 35 798 communes
UnitĂ© tonnes Ă©quivalent CO₂ par an
Licence Licence Ouverte (Etalab)
Source data.ademe.fr

Le producteur prĂ©cise que cet inventaire « a vocation Ă  donner des ordres de grandeur pertinents » et qu’il « n’a pas le niveau de finesse et de prĂ©cision des travaux d’inventaire rĂ©alisĂ©s au niveau territorial ». Autrement dit, ces chiffres servent Ă  comparer des territoires entre eux, pas Ă  Ă©tablir le bilan exact d’une commune donnĂ©e. C’est une limite qu’il faut connaĂźtre avant de conclure quoi que ce soit, et que nous rappellerons en fin d’exercice.

Nous croiserons ces Ă©missions avec la population communale dĂ©jĂ  utilisĂ©e dans l’exercice prĂ©cĂ©dent. Les Ă©missions datant de 2016, nous prendrons la population de cette mĂȘme annĂ©e, et non la plus rĂ©cente : diviser des Ă©missions de 2016 par une population de 2021 n’aurait pas de sens.

La question posée est simple à formuler et moins simple à trancher : quels territoires émettent le plus ?

11.2 Exercice

11.2.1 1. Découvrir les données

  • Charger emissions-communes.csv. Combien de communes, combien de colonnes ?
  • Quelles sont les colonnes de secteurs ? Afficher leurs statistiques descriptives.
  • La colonne INSEE commune est-elle un identifiant unique ? Et la colonne Commune ?

11.2.2 2. Les valeurs manquantes

  • Quelle proportion de valeurs manquantes dans chaque colonne ?
  • Deux colonnes sont trĂšs lacunaires. Afficher les communes oĂč Autres transports international est renseignĂ© et le plus Ă©levĂ©. Que reconnaissez-vous ? Qu’en dĂ©duire sur la signification du NaN ici ?
  • CrĂ©er une colonne total donnant les Ă©missions totales de chaque commune.

11.2.3 3. Joindre Ă  la population

  • Charger base-pop-historiques-1876-2021.csv et en extraire le code, le nom, le dĂ©partement et la population de 2016 de chaque commune. Attention au format des nombres.
  • Joindre les deux tables. Combien de communes se raccordent ? Combien restent orphelines ?

11.2.4 4. Émissions par habitant

  • Écarter les communes sans habitant, puis crĂ©er par_habitant.
  • Quelles sont les 5 communes les plus Ă©mettrices au total ? Et par habitant ?
  • AgrĂ©ger par dĂ©partement, des deux façons. Comparer les classements et commenter.

11.2.5 5. Cartographier

  • ReprĂ©senter sur une carte de France les Ă©missions totales par dĂ©partement, puis les Ă©missions par habitant. Commenter la diffĂ©rence entre les deux cartes.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import json   # pour lire le fond de carte, qui est au format GeoJSON

from IPython.display import display, Markdown

import plotly.express as px

# Pour la génération automatique de graphiques interactifs dans le polycopié
import plotly.io as pio
import os
if os.getenv("QUARTO_OUTPUT_FORMAT") is not None:
    # Export Quarto HTML
    pio.renderers.default = "iframe_connected"
else:
    # Notebook interactif
    pio.renderers.default = "notebook_connected"

sns.set_theme(context="notebook", style="whitegrid", palette="pastel")
plt.rcParams['figure.figsize'] = (11, 5)

12 1. Découvrir les données

Le code INSEE commence par un zéro pour les départements 01 à 09. Si on laisse pandas deviner le type, il le lira comme un entier et 01001 deviendra 1001 : la jointure échouerait plus loin. On impose donc le type texte dÚs la lecture.

# dtype={'INSEE commune': str} force la lecture de cette colonne en texte,
# ce qui préserve le zéro initial des codes 01xxx à 09xxx
emissions = pd.read_csv('emissions-communes.csv', dtype={'INSEE commune': str})

# .shape renvoie le couple (nombre de lignes, nombre de colonnes)
display(Markdown(f"**{emissions.shape[0]}** communes, **{emissions.shape[1]}** colonnes"))

# .head() affiche les cinq premiĂšres lignes
emissions.head()

35798 communes, 12 colonnes

INSEE commune Commune Agriculture Autres transports Autres transports international CO2 biomasse hors-total Déchets Energie Industrie hors-énergie Résidentiel Routier Tertiaire
0 01001 L'ABERGEMENT-CLEMENCIAT 3711.425991 NaN NaN 432.751835 101.430476 2.354558 6.911213 309.358195 793.156501 367.036172
1 01002 L'ABERGEMENT-DE-VAREY 475.330205 NaN NaN 140.741660 140.675439 2.354558 6.911213 104.866444 348.997893 112.934207
2 01004 AMBERIEU-EN-BUGEY 499.043526 212.577908 NaN 10313.446515 5314.314445 998.332482 2930.354461 16616.822534 15642.420313 10732.376934
3 01005 AMBERIEUX-EN-DOMBES 1859.160954 NaN NaN 1144.429311 216.217508 94.182310 276.448534 663.683146 1756.341319 782.404357
4 01006 AMBLEON 448.966808 NaN NaN 77.033834 48.401549 NaN NaN 43.714019 398.786800 51.681756
# on construit la liste des colonnes de secteurs, c'est-Ă -dire
# toutes les colonnes sauf les deux colonnes d'identification
secteurs = [c for c in emissions.columns if c not in ('INSEE commune', 'Commune')]

display(Markdown(f"**{len(secteurs)} secteurs** : " + ", ".join(f"`{s}`" for s in secteurs)))

10 secteurs : Agriculture, Autres transports, Autres transports international, CO2 biomasse hors-total, Déchets, Energie, Industrie hors-énergie, Résidentiel, Routier, Tertiaire

# .describe() calcule les statistiques descriptives de chaque colonne numérique
# .T transpose le résultat : un secteur par ligne, plus lisible quand ils sont nombreux
emissions[secteurs].describe().round(0).T
count mean std min 25% 50% 75% max
Agriculture 35736.0 2460.0 2927.0 0.0 798.0 1559.0 3008.0 98949.0
Autres transports 9979.0 655.0 9233.0 0.0 53.0 107.0 237.0 513141.0
Autres transports international 2891.0 7692.0 113764.0 0.0 10.0 20.0 33.0 3303394.0
CO2 biomasse hors-total 35798.0 1774.0 7871.0 4.0 198.0 425.0 1095.0 576394.0
Déchets 35792.0 411.0 4122.0 0.0 26.0 55.0 111.0 275500.0
Energie 34490.0 663.0 26456.0 2.0 2.0 5.0 52.0 2535858.0
Industrie hors-énergie 34490.0 2423.0 56704.0 1.0 7.0 14.0 152.0 6765119.0
Résidentiel 35792.0 1784.0 8916.0 1.0 96.0 227.0 749.0 410676.0
Routier 35778.0 3536.0 9663.0 1.0 420.0 1071.0 3099.0 586055.0
Tertiaire 35798.0 1105.0 5164.0 0.0 95.0 216.0 576.0 288175.0

Les ordres de grandeur sont trĂšs Ă©talĂ©s : certaines communes Ă©mettent quelques tonnes, d’autres plusieurs millions. C’est attendu pour 35 000 communes allant du hameau Ă  la mĂ©tropole.

L’identifiant est-il unique ?

# .nunique() compte les valeurs distinctes, len() compte les lignes :
# si les deux coĂŻncident, la colonne est un identifiant
display(Markdown(
    f"codes INSEE distincts : **{emissions['INSEE commune'].nunique()}** sur {len(emissions)}  \n"
    f"noms de communes distincts : **{emissions['Commune'].nunique()}** sur {len(emissions)}"))

codes INSEE distincts : 35798 sur 35798
noms de communes distincts : 33338 sur 35798

Le code INSEE est unique, le nom ne l’est pas : plus de deux mille communes françaises portent le mĂȘme nom qu’une autre. C’est pourquoi toute jointure entre fichiers communaux se fait sur le code, jamais sur le libellĂ©.

13 2. Les valeurs manquantes

# .isnull() renvoie un tableau de boolĂ©ens de mĂȘme forme que le DataFrame ;
# .mean() sur des booléens donne la proportion de True, que l'on convertit en pourcentage
(emissions[secteurs].isnull().mean() * 100).round(1).sort_values(ascending=False).to_frame('% manquant')
% manquant
Autres transports international 91.9
Autres transports 72.1
Energie 3.7
Industrie hors-énergie 3.7
Agriculture 0.2
Routier 0.1
CO2 biomasse hors-total 0.0
Déchets 0.0
Résidentiel 0.0
Tertiaire 0.0

Deux colonnes sortent du lot : Autres transports international est vide Ă  prĂšs de 92 %, Autres transports Ă  72 %. Avant de dĂ©cider quoi en faire, regardons oĂč elles sont renseignĂ©es.

colonne = 'Autres transports international'

# .notna() sĂ©lectionne les lignes oĂč la valeur est prĂ©sente
renseignees = emissions[ emissions[colonne].notna() ]

display(Markdown(f"renseignée pour **{len(renseignees)}** communes sur {len(emissions)}"))

# .nlargest(12, colonne) renvoie les 12 lignes aux plus grandes valeurs de cette colonne
renseignees.nlargest(12, colonne)[['Commune', colonne]].round(0)

renseignée pour 2891 communes sur 35798

Commune Autres transports international
31099 MAUREGARD 3303394.0
31108 LE MESNIL-AMELOT 3303394.0
31111 MITRY-MORY 2202275.0
30438 LE HAVRE 2109460.0
35561 TREMBLAY-EN-FRANCE 1101146.0
35757 ROISSY-EN-FRANCE 1101131.0
1987 NICE 1003572.0
28017 COLOMBIER-SAUGNIEU 865809.0
35426 PARAY-VIEILLE-POSTE 785878.0
27580 MULHOUSE 615461.0
4397 MARIGNANE 527361.0
35487 WISSOUS 471527.0

Ces noms vous disent quelque chose.

Mauregard, Le Mesnil-Amelot, Mitry-Mory, Tremblay-en-France, Roissy-en-France : ce sont les communes de l’aĂ©roport Charles-de-Gaulle. Paray-Vieille-Poste et Wissous bordent Orly, Colombier-Saugnieu est Lyon-Saint-ExupĂ©ry, Marignane Marseille-Provence, Blagnac Toulouse. Le Havre est un port.

La valeur manquante ne signifie donc pas « donnĂ©e perdue » : elle signifie « il n’y a ni aĂ©roport ni port dans cette commune ». L’absence d’information est l’information.

Les conséquences sont directes :

  • remplacer par la moyenne serait absurde, puisqu’on attribuerait un trafic aĂ©rien Ă  des villages ;
  • supprimer les lignes incomplĂštes effacerait 92 % de la France ;
  • remplacer par zĂ©ro est ici le seul choix dĂ©fendable, et c’est exactement ce que fait sum() par dĂ©faut en ignorant les NaN.
# .sum(axis=1) additionne sur les colonnes, donc ligne par ligne.
# Par défaut les NaN sont ignorés, ce qui revient ici à les compter pour zéro.
emissions['total'] = emissions[secteurs].sum(axis=1)

emissions[['Commune', 'total']].head()
Commune total
0 L'ABERGEMENT-CLEMENCIAT 5724.424941
1 L'ABERGEMENT-DE-VAREY 1332.811619
2 AMBERIEU-EN-BUGEY 63259.689119
3 AMBERIEUX-EN-DOMBES 6792.867439
4 AMBLEON 1068.584766

14 3. Joindre Ă  la population

Le fichier de populations sĂ©pare les milliers par un espace insĂ©cable (1 000), que read_csv ne sait pas interprĂ©ter seul, comme nous l’avons vu dans le cours. On nettoie donc la colonne avant de la convertir.

Nous prenons la colonne PMUN2016, population municipale de 2016, pour la faire correspondre Ă  l’annĂ©e des Ă©missions.

# usecols limite la lecture aux colonnes utiles : le fichier en compte une
# par recensement depuis 1876, inutile de tout charger en mémoire
colonnes_pop = ['CODGEO', 'LIBGEO', 'DEP', 'REG', 'PMUN2016']

population = pd.read_csv('base-pop-historiques-1876-2021.csv', sep=';', low_memory=False,
                         dtype={'CODGEO': str, 'DEP': str, 'REG': str},
                         usecols=colonnes_pop)

# le séparateur n'apparaßt que sur les valeurs supérieures à mille :
# on repĂšre une telle ligne pour pouvoir l'afficher avant et aprĂšs conversion
grandes = population['PMUN2016'].astype(str).str.len() > 4
display(Markdown("avant nettoyage : `"
                 + repr(population.loc[grandes, 'PMUN2016'].iloc[0]) + "`"))

# chr(160) est l'espace insécable ; regex=False car on remplace un caractÚre littéral
population['PMUN2016'] = (population['PMUN2016'].astype(str)
                                               .str.replace(chr(160), '', regex=False)
                                               .astype(int))

display(Markdown("aprĂšs nettoyage : `"
                 + repr(population.loc[grandes, 'PMUN2016'].iloc[0]) + "`"))
population.head()

avant nettoyage : '1\xa0030'

aprĂšs nettoyage : 1030

CODGEO REG DEP LIBGEO PMUN2016
0 55039 44 55 Beaumont-en-Verdunois 0
1 55050 44 55 Bezonvaux 0
2 55139 44 55 CumiĂšres-le-Mort-Homme 0
3 55189 44 55 Fleury-devant-Douaumont 0
4 55239 44 55 Haumont-prĂšs-Samogneux 0
# how='left' conserve toutes les lignes de l'inventaire, mĂȘme sans correspondance ;
# validate='one_to_one' fait échouer la jointure si une clé était dupliquée d'un cÎté
df = emissions.merge(population,
                     left_on='INSEE commune', right_on='CODGEO',
                     how='left', validate='one_to_one')

display(Markdown(
    f"communes dans l'inventaire : **{len(emissions)}**  \n"
    f"aprĂšs la jointure : **{len(df)}**  \n"
    f"sans correspondance : **{df['CODGEO'].isna().sum()}**"))

communes dans l’inventaire : 35798
aprĂšs la jointure : 35798
sans correspondance : 966

Environ un millier de communes n’ont pas de correspondance. Les deux fichiers ne datent pas du mĂȘme millĂ©sime, et le dĂ©coupage communal bouge sans cesse : fusions, communes nouvelles. Nous les Ă©cartons, en sachant ce que nous Ă©cartons.

# .notna() garde les lignes oĂč la jointure a trouvĂ© une correspondance
df = df[ df['CODGEO'].notna() ]

display(Markdown(f"**{len(df)}** communes conservées"))

34832 communes conservées

15 4. Émissions par habitant

Quelques communes ont une population nulle, des villages détruits pendant la guerre de 1914-1918 et jamais reconstruits. Il faut les écarter avant toute division.

display(Markdown(f"communes sans habitant : **{(df['PMUN2016'] == 0).sum()}**"))
display(df.loc[df['PMUN2016'] == 0, ['Commune', 'DEP', 'PMUN2016']])

# .copy() crée un DataFrame indépendant : sans lui, l'ajout de colonne
# qui suit porterait sur une tranche du tableau d'origine
df = df[ df['PMUN2016'] > 0 ].copy()

df['par_habitant'] = df['total'] / df['PMUN2016']

communes sans habitant : 6

Commune DEP PMUN2016
20731 BEAUMONT-EN-VERDUNOIS 55 0.0
20742 BEZONVAUX 55 0.0
20817 CUMIERES-LE-MORT-HOMME 55 0.0
20861 FLEURY-DEVANT-DOUAUMONT 55 0.0
20898 HAUMONT-PRES-SAMOGNEUX 55 0.0
20957 LOUVEMONT-COTE-DU-POIVRE 55 0.0

Les cinq communes les plus émettrices au total :

# .nlargest(5, 'total') : les 5 plus grandes valeurs de la colonne 'total'
df.nlargest(5, 'total')[['Commune', 'DEP', 'PMUN2016', 'total']].round(0)
Commune DEP PMUN2016 total
4382 FOS-SUR-MER 13 15608.0 9533813.0
22671 DUNKERQUE 59 88108.0 8643550.0
4398 MARTIGUES 13 48783.0 4009663.0
30560 PORT-JEROME-SUR-SEINE 76 9903.0 3668151.0
31108 LE MESNIL-AMELOT 77 997.0 3471492.0

Et les cinq plus émettrices par habitant :

df.nlargest(5, 'par_habitant')[['Commune', 'DEP', 'PMUN2016', 'par_habitant']].round(0)
Commune DEP PMUN2016 par_habitant
31099 MAUREGARD 77 340.0 10120.0
31108 LE MESNIL-AMELOT 77 997.0 3482.0
23189 BAZANCOURT 60 129.0 2380.0
27426 CHALAMPE 68 950.0 1187.0
5682 SAINT-VALLIER 16 137.0 1127.0

Deux listes sans aucun point commun. La premiùre donne de grandes villes industrielles, la seconde de trùs petites communes abritant une installation lourde : quelques dizaines d’habitants pour une usine, et le rapport s’envole.

15.1 Agréger par département

Attention au piùge : la moyenne des rapports n’est pas le rapport des sommes.

Pour obtenir les Ă©missions par habitant d’un dĂ©partement, il ne faut pas faire la moyenne des par_habitant de ses communes : cela donnerait le mĂȘme poids Ă  un village de 80 habitants qu’à une ville de 200 000. Il faut diviser le total des Ă©missions par le total des habitants.

# .agg() applique une fonction différente à chaque colonne.
# La syntaxe nom_voulu=('colonne', 'fonction') nomme directement le résultat.
dep = df.groupby('DEP').agg(
    emissions = ('total',    'sum'),
    habitants = ('PMUN2016', 'sum'),
    communes  = ('CODGEO',   'size')
)

dep['par_habitant'] = (dep['emissions'] / dep['habitants']).round(1)             # correct
dep['moyenne_des_communes'] = df.groupby('DEP')['par_habitant'].mean().round(1)  # faux

dep.sort_values('par_habitant', ascending=False).head(5)
emissions habitants communes par_habitant moyenne_des_communes
DEP
13 2.421180e+07 1157506.0 118 20.9 15.5
23 2.327727e+06 119502.0 256 19.5 29.2
76 2.154345e+07 1255503.0 707 17.2 14.3
15 2.506101e+06 145969.0 246 17.2 32.7
55 3.199781e+06 189055.0 493 16.9 26.8

Comparons les rangs obtenus par les deux méthodes :

# .rank(ascending=False) donne le rang de chaque département, 1 pour le plus élevé
comparaison = pd.DataFrame({
    'rapport des sommes'   : dep['par_habitant'].rank(ascending=False).astype(int),
    'moyenne des communes' : dep['moyenne_des_communes'].rank(ascending=False).astype(int),
})
comparaison['écart de rang'] = (comparaison['rapport des sommes']
                                - comparaison['moyenne des communes']).abs()

comparaison.sort_values('rapport des sommes').head(10)
rapport des sommes moyenne des communes écart de rang
DEP
13 1 37 36
23 2 3 1
15 3 2 1
76 3 45 42
55 5 8 3
53 6 18 12
03 7 6 1
52 7 3 4
48 9 5 4
12 10 9 1

Les deux mĂ©thodes ne classent pas de la mĂȘme façon : le choix de l’agrĂ©gation n’est pas neutre.

15.2 Total ou par habitant ?

cote_a_cote = pd.DataFrame({
    'total (kt CO₂)'  : (dep['emissions'] / 1000).round(0),
    'par habitant (t)': dep['par_habitant'],
})

display(Markdown("**Classement par émissions totales**"))
display(cote_a_cote.nlargest(5, 'total (kt CO₂)'))

display(Markdown("**Classement par émissions par habitant**"))
display(cote_a_cote.nlargest(5, 'par habitant (t)'))

# .nsmallest() est le symétrique de .nlargest()
display(Markdown("**Les cinq départements les plus sobres par habitant**"))
display(cote_a_cote.nsmallest(5, 'par habitant (t)'))

Classement par émissions totales

total (kt CO₂) par habitant (t)
DEP
59 26075.0 10.0
13 24212.0 20.9
76 21543.0 17.2
77 18161.0 13.0
62 12069.0 8.2

Classement par émissions par habitant

total (kt CO₂) par habitant (t)
DEP
13 24212.0 20.9
23 2328.0 19.5
15 2506.0 17.2
76 21543.0 17.2
55 3200.0 16.9

Les cinq départements les plus sobres par habitant

total (kt CO₂) par habitant (t)
DEP
75 6730.0 3.1
92 5796.0 3.6
94 5254.0 3.8
93 6254.0 3.9
83 4301.0 4.1

Deux questions, deux réponses.

En volume, les dĂ©partements industriels et peuplĂ©s dominent : ce sont eux qui pĂšsent sur le bilan national, et c’est lĂ  que des rĂ©ductions en valeur absolue sont possibles.

En intensitĂ©, le classement se renverse. Les dĂ©partements ruraux arrivent en tĂȘte, avec peu d’habitants mais de l’agriculture et des trajets longs, tandis que Paris et la petite couronne sont les plus sobres de France par habitant, d’un facteur proche de cinq. La densitĂ© urbaine rĂ©duit les transports et le chauffage par personne.

Aucun des deux classements n’est « le bon ». Ils rĂ©pondent Ă  deux questions diffĂ©rentes, et confondre les deux est la façon la plus commune de faire mentir des donnĂ©es climatiques.

16 5. Cartographier

Un tableau de 96 lignes se lit mal ; une carte montre immĂ©diatement la gĂ©ographie du phĂ©nomĂšne. Nous utilisons le fond de carte departements.geojson, un fichier local : aucune connexion n’est nĂ©cessaire.

Chaque dĂ©partement y est dĂ©crit par une propriĂ©tĂ© code ('01', '02', jusqu’à '2A' et '2B' pour la Corse), qui correspond exactement Ă  notre colonne DEP.

# un fichier GeoJSON est un fichier JSON : on le lit avec le module json
with open('departements.geojson', encoding='utf-8') as f:
    france = json.load(f)

display(Markdown(f"**{len(france['features'])}** départements dans le fond de carte"))
display(Markdown("propriétés disponibles : `" + str(france['features'][0]['properties']) + "`"))

96 départements dans le fond de carte

propriétés disponibles : {'code': '02', 'nom': 'Aisne'}

# .reset_index() ramÚne le code département, qui était l'index, dans une colonne :
# plotly a besoin d'une colonne pour faire la correspondance avec le fond de carte
carte = dep.reset_index()

# on ne garde que les départements présents dans le fond de carte
codes_du_fond = {f['properties']['code'] for f in france['features']}
carte = carte[ carte['DEP'].isin(codes_du_fond) ]

display(Markdown(f"**{len(carte)}** départements cartographiables "
                 "(l'outre-mer n'est pas dans ce fond de carte)"))

96 dĂ©partements cartographiables (l’outre-mer n’est pas dans ce fond de carte)

Les émissions totales :

# locations indique la colonne du DataFrame qui identifie chaque zone,
# featureidkey indique oĂč trouver l'identifiant correspondant dans le GeoJSON
fig = px.choropleth(carte,
                    geojson=france, locations='DEP', featureidkey='properties.code',
                    color='emissions', color_continuous_scale='Reds',
                    labels={'emissions': 't CO₂'},
                    title="Émissions totales par dĂ©partement (2016)")

# fitbounds recentre la carte sur les zones affichées, visible=False masque le globe
fig.update_geos(fitbounds="locations", visible=False)
fig.update_layout(height=550, margin={"r":0, "t":40, "l":0, "b":0})
fig.show()

Et les émissions par habitant :

fig = px.choropleth(carte,
                    geojson=france, locations='DEP', featureidkey='properties.code',
                    color='par_habitant', color_continuous_scale='Reds',
                    labels={'par_habitant': 't CO₂ / hab'},
                    title="Émissions par habitant (2016)")
fig.update_geos(fitbounds="locations", visible=False)
fig.update_layout(height=550, margin={"r":0, "t":40, "l":0, "b":0})
fig.show()

Les deux cartes n’ont presque rien en commun.

La premiĂšre dessine la France industrielle et peuplĂ©e : vallĂ©e de la Seine, Nord, pourtour de l’étang de Berre, grandes agglomĂ©rations.

La seconde dessine une France rurale, et fait apparaĂźtre l’Île-de-France en creux : les dĂ©partements les plus denses du pays sont les plus sobres par habitant. Une carte des Ă©missions totales de l’Île-de-France aurait suggĂ©rĂ© l’inverse.

C’est la mĂȘme leçon que plus haut, rendue visible d’un coup d’Ɠil : la carte que l’on publie est dĂ©jĂ  une rĂ©ponse Ă  une question que l’on a choisie.

17 Conclusion

Ce que cet exercice voulait montrer

  • Une valeur manquante peut ĂȘtre une information : ici, NaN signifiait « pas d’aĂ©roport ». Avant de combler les trous, il faut comprendre pourquoi ils existent.
  • Une jointure se fait sur un identifiant, jamais sur un libellĂ©, puisque plus de deux mille communes françaises sont homonymes.
  • Les deux tables doivent porter sur la mĂȘme annĂ©e, sans quoi le rapport calculĂ© n’a pas de sens.
  • La moyenne des rapports n’est pas le rapport des sommes.
  • Et surtout : total et par habitant rĂ©pondent Ă  deux questions distinctes et donnent deux classements opposĂ©s. Choisir l’un ou l’autre, c’est dĂ©jĂ  prendre parti.

Une derniĂšre prudence. Le producteur de ces donnĂ©es Ă©crit lui-mĂȘme qu’elles donnent « des ordres de grandeur » et qu’elles n’ont pas « le niveau de finesse et de prĂ©cision des travaux d’inventaire rĂ©alisĂ©s au niveau territorial ». Nos comparaisons entre dĂ©partements sont donc lĂ©gitimes ; affirmer le bilan exact d’une commune prĂ©cise Ă  partir de ce fichier ne le serait pas.

Savoir ce qu’une source permet de dire, et ce qu’elle ne permet pas, fait partie du travail d’analyse.

10  đŸ˜ïž Population France
12  đŸ“ș Netflix User

© Copyright 2026, Gloria Faccanoni

 

This page is built with ❀ and Quarto.