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)11 đ Ămissions de COâ des communes françaises
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 communeest-elle un identifiant unique ? Et la colonneCommune?
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 internationalest renseignĂ© et le plus Ă©levĂ©. Que reconnaissez-vous ? Quâen dĂ©duire sur la signification duNaNici ? - CrĂ©er une colonne
totaldonnant les émissions totales de chaque commune.
11.2.3 3. Joindre Ă la population
- Charger
base-pop-historiques-1876-2021.csvet 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.
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
| 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
| % 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 lesNaN.
| 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.
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 :
| 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 :
| 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,
NaNsignifiait « 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.