Données spatiales dans Python : GeoPandas¶
Pour traiter de l'information géographique, il convient de maitriser des outils et des méthodes particulières. En termes d'outils, les SIG permettent surtout de rassembler dans un même outil des formats géographiques variés. Pour charger ces formats dans un environnement Python, il conviendra donc d'utiliser des bibliothèques dédiées comme GeoPandas. GeoPandas repose notamment sur la bibliothèque Pandas qui n'est pas spécifique aux données spatiales. Pandas est directement accessible depuis l'environnement Anaconda de base. Concernant GeoPandas, il faudra l'installer.
Les données pour la partie "Traitement de données avec Pandas" reposent sur un fichier CSV produit avec Excel. Pour la suite, on utilisera le shapefile Departements de ce dossier zippé.
Taitement de données avec Pandas¶
La bibliothèque Pandas est très utilisée pour manipuler des données avec Python. Ses fonctions read_* permettent par exemple d'ouvrir simplement de nombreux types de fichiers dans Python. Il suffira la plupart du temps de préciser l'adresse du fichier. Ici notre fichier CSV utilise des ; pour séparer les colonnes de notre tableau, c'est pourquoi on utilise l'argument sep.
import pandas as pd
df = pd.read_csv('C:/Users/Serge/Desktop/TPR.csv',sep =';')
La bibliothèque Pandas propose de nombreuses fonctions pratiques pour visualiser les données comme la fonction head(), la fonction describe() ou la fonction columns(). La fonction sort_values() permet aussi de simplement trier le tableau en fonction des valeurs d'une colonne.
df.shape
(96, 10)
df.head()
ID | NOM | Agriculteur | Artisan Commercant | Cadre | Profession Intermediaire | Employe | Ouvrier | Retraite | Autres | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 01 | Ain | 4067 | 16745 | 36426 | 70663 | 77349 | 78998 | 30417 | 29910 |
1 | 02 | Aisne | 5201 | 11414 | 18629 | 48889 | 71578 | 78906 | 32063 | 42108 |
2 | 03 | Allier | 6159 | 9396 | 11842 | 31102 | 46196 | 42101 | 24218 | 21344 |
3 | 04 | Alpes-de-Haute-Provence | 2027 | 6387 | 6951 | 16140 | 20885 | 15560 | 10687 | 10278 |
4 | 05 | Hautes-Alpes | 2040 | 5286 | 5883 | 15687 | 19707 | 12415 | 8865 | 7222 |
df.describe()
Agriculteur | Artisan Commercant | Cadre | Profession Intermediaire | Employe | Ouvrier | Retraite | Autres | |
---|---|---|---|---|---|---|---|---|
count | 96.000000 | 96.000000 | 96.000000 | 96.000000 | 96.000000 | 96.000000 | 96.000000 | 96.000000 |
mean | 5167.927083 | 16791.958333 | 44756.697917 | 72467.885417 | 87531.510417 | 71985.291667 | 36122.697917 | 38277.260417 |
std | 2880.807943 | 11351.777258 | 66336.081640 | 61831.768172 | 66873.834456 | 49306.632356 | 22541.444178 | 32808.739639 |
min | 106.000000 | 2680.000000 | 2670.000000 | 7431.000000 | 10310.000000 | 7209.000000 | 5195.000000 | 4535.000000 |
25% | 3336.750000 | 8060.750000 | 10946.500000 | 27883.000000 | 36901.250000 | 39219.500000 | 18981.250000 | 16427.750000 |
50% | 4752.500000 | 13935.000000 | 23069.500000 | 53551.500000 | 70585.500000 | 64643.000000 | 31231.500000 | 27480.500000 |
75% | 6588.750000 | 23142.250000 | 46584.500000 | 94845.000000 | 113523.500000 | 94012.250000 | 50156.250000 | 48593.000000 |
max | 12517.000000 | 55687.000000 | 503432.000000 | 281989.000000 | 329242.000000 | 319098.000000 | 135770.000000 | 199332.000000 |
print(df.columns)
Index(['ID', 'NOM', 'Agriculteur', 'Artisan Commercant', 'Cadre', 'Profession Intermediaire', 'Employe', 'Ouvrier', 'Retraite', 'Autres'], dtype='object')
df.sort_values(by='Cadre',ascending=False)
ID | NOM | Agriculteur | Artisan Commercant | Cadre | Profession Intermediaire | Employe | Ouvrier | Retraite | Autres | |
---|---|---|---|---|---|---|---|---|---|---|
75 | 75 | Paris | 520 | 55687 | 503432 | 281989 | 252536 | 96986 | 67814 | 111042 |
92 | 92 | Hauts-de-Seine | 379 | 34216 | 277629 | 203704 | 201237 | 82504 | 52406 | 69392 |
78 | 78 | Yvelines | 1139 | 28397 | 195091 | 189244 | 181152 | 95537 | 60583 | 72112 |
69 | 69 | Rhone | 6642 | 42359 | 156210 | 216844 | 217093 | 157509 | 76906 | 89749 |
59 | 59 | Nord | 7763 | 45594 | 147874 | 273832 | 329242 | 319098 | 135770 | 199332 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
4 | 05 | Hautes-Alpes | 2040 | 5286 | 5883 | 15687 | 19707 | 12415 | 8865 | 7222 |
8 | 09 | Ariege | 2348 | 4888 | 5537 | 14442 | 20788 | 16635 | 10379 | 9741 |
14 | 15 | Cantal | 7431 | 4976 | 4547 | 12517 | 19508 | 17183 | 10386 | 7786 |
21 | 21 | Cote-dOr | 5259 | 3696 | 3669 | 9387 | 16039 | 12764 | 9811 | 6780 |
48 | 48 | Lozere | 3381 | 2706 | 2670 | 7431 | 10310 | 7209 | 5195 | 4535 |
96 rows × 10 columns
Ces fonctions s'appliquent à des dataframes (l'objet créé en utilisant les fonctions read_*). Ces dataframes s'utilisent comme ci-dessous :
cadre = df['Cadre'] # Récupère les valeurs d'une colonne
cadre_proint = df[['Cadre','Profession Intermediaire']] # Récupère les valeurs de plusieurs colonnes
df[df.columns[1]] # Si on ne connait pas les noms des colonnes on peut utiliser les index des colonnnes
0 Ain 1 Aisne 2 Allier 3 Alpes-de-Haute-Provence 4 Hautes-Alpes ... 91 Essonne 92 Hauts-de-Seine 93 Seine-Saint-Denis 94 Val-de-Marne 95 Val-dOise Name: NOM, Length: 96, dtype: object
Pandas permet aussi d'avoir recours simplement à des fonctions permettant d'effectuer des graphiques.
df.plot.scatter(x='Employe', y='Profession Intermediaire')
<Axes: xlabel='Employe', ylabel='Profession Intermediaire'>
cadre_proint.plot.box()
<Axes: >
Pour filtrer les valeurs, il convient d'utiliser des tests comme ci-dessous :
df[df["Cadre"] > 200000]
ID | NOM | Agriculteur | Artisan Commercant | Cadre | Profession Intermediaire | Employe | Ouvrier | Retraite | Autres | |
---|---|---|---|---|---|---|---|---|---|---|
75 | 75 | Paris | 520 | 55687 | 503432 | 281989 | 252536 | 96986 | 67814 | 111042 |
92 | 92 | Hauts-de-Seine | 379 | 34216 | 277629 | 203704 | 201237 | 82504 | 52406 | 69392 |
df[df["NOM"] == 'Val-de-Marne']
ID | NOM | Agriculteur | Artisan Commercant | Cadre | Profession Intermediaire | Employe | Ouvrier | Retraite | Autres | |
---|---|---|---|---|---|---|---|---|---|---|
94 | 94 | Val-de-Marne | 191 | 29593 | 146443 | 177963 | 200237 | 102786 | 51540 | 66572 |
A noter que la fonction iloc() permet de travailler à partir des index :
df.iloc[94,:]
ID 94 NOM Val-de-Marne Agriculteur 191 Artisan Commercant 29593 Cadre 146443 Profession Intermediaire 177963 Employe 200237 Ouvrier 102786 Retraite 51540 Autres 66572 Name: 94, dtype: object
df.iloc[94,4]
146443
sum(df.iloc[94,2:10])
775325
Pour généraliser le dernier calcul, on pourra utiliser la fonction apply(). On pourra alors simplement créer une nouvelle colonne "Actif". Attention, par défaut, apply() s'applique au niveau des colonnes. Pour effectuer le calcul par ligne, il faudra utiliser l'argument axis.
df.iloc[:,2:10].apply(sum)
Agriculteur 496121 Artisan Commercant 1612028 Cadre 4296643 Profession Intermediaire 6956917 Employe 8403025 Ouvrier 6910588 Retraite 3467779 Autres 3674617 dtype: int64
df['Actif'] = df.iloc[:,2:10].apply(sum,axis=1)
df.head()
ID | NOM | Agriculteur | Artisan Commercant | Cadre | Profession Intermediaire | Employe | Ouvrier | Retraite | Autres | Actif | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 01 | Ain | 4067 | 16745 | 36426 | 70663 | 77349 | 78998 | 30417 | 29910 | 344575 |
1 | 02 | Aisne | 5201 | 11414 | 18629 | 48889 | 71578 | 78906 | 32063 | 42108 | 308788 |
2 | 03 | Allier | 6159 | 9396 | 11842 | 31102 | 46196 | 42101 | 24218 | 21344 | 192358 |
3 | 04 | Alpes-de-Haute-Provence | 2027 | 6387 | 6951 | 16140 | 20885 | 15560 | 10687 | 10278 | 88915 |
4 | 05 | Hautes-Alpes | 2040 | 5286 | 5883 | 15687 | 19707 | 12415 | 8865 | 7222 | 77105 |
Enfin, la fonction to_numpy() permet de repasser en array.
adf = df.to_numpy()
Pandas propose plein d'autres fonctions très pertinentes. Je pense notamment à la fonction groupby() pour faire des regroupements attributaires ou la fonction merge() qui permet des jointures attributaires. Cela sera présenté dans la partie "Manipulations de la table attributaire", car les fonctions Pandas fonctionnent pour GeoPandas.
Installer GeoPandas et produire ses premières cartes¶
Pour installer la bibliothèque GeoPandas, qui n'est pas installée par défaut, c'est en théorie assez simple. Il est possible d'utiliser l'Anaconda Powershell Prompt ou l'Anaconda Prompt. Il suffit alors de rentrer l'une des deux lignes de codes ci-dessous.
Attention, n'exécutez pas immédiatement les lignes de codes ci-dessous et lisez le paragraphe suivant.
conda install geopandas
conda install --channel conda-forge geopandas
Néanmoins, pour traiter des données géographiques, il peut être pertinent d'installer une autre bibliothèque nommée PySAL, ce qui est notamment nécessaire dans un autre notebook. Or PySAL dépend justement de GeoPandas pour fonctionner. PySAL peut alors nécessiter une autre version que celle installée précédemment et ça peut poser problème. De plus, que ce soit GeoPandas et PySAL, ces bibliothèques reposent sur des bibliothèques Python, mais aussi sur des bibliothèques codées en C (autre langage de programmation) comme GEOS, GDAL, PROJ et ces bibliothèques ne sont pas si simples à installer et à faire fonctionner avec Python. Plus généralement, des difficultés apparaissent souvent dès que l'on commence à installer de nombreuses bibliothèques Python. Par conséquent, et par expérience, lorsque l'on connait les bibliothèques que l'on va être amené à utiliser, je conseille de commencer par installer les bibliothèques les plus spécialisées, c'est-à-dire celles qui reposent sur de nombreuses dépendances de telle sorte à ce qu'Anaconda traite dès le début cette problématique. Ici, je conseille donc d'installer PySAL en premier et l'installation de Geopandas se fera alors dans ce cadre. Il faut alors plutôt taper la ligne de code suivante pour installer GeoPandas :
conda install --channel conda-forge pysal
En cas d'échec, il est possible d'utiliser PIP :
pip install pysal
Dès lors, il est possible d'importer dans Spyder la bibliothèque GeoPandas et d'utiliser sa fonction read_file(), qui permet de charger de nombreux formats de fichiers géographiques (geojson, shp...). Combinée à sa fonction plot(), cela vous permettra de produire simplement votre première carte.
import geopandas as gpd
gdf = gpd.read_file('C:/Users/Serge/Desktop/Donnees/Departements.shp')
gdf.plot()
<Axes: >
Différents aspects cartographiques sont personnalisables à l'aide des arguments. Par exemple, la gestion des couleurs passe bien entendu par l'argument color, mais aussi par l'argument column qui permet de créer des dégradés de couleurs en fonction des valeurs de cette colonne. Pour utiliser cet argument, il faut donc connaitre la table attributaire et les noms de colonnes. Pour cela, il est possible d'utiliser les mêmes fonctions que Pandas. En effet, read_file() crée un objet de type geodataframe qui ne diffère des dataframes Pandas que par l'existence d'une colonne geometry.
print(type(gdf))
<class 'geopandas.geodataframe.GeoDataFrame'>
gdf.head()
ID_GEOFLA | CODE_DEPT | NOM_DEPT | NOM_CHF | CODE_REG | NOM_REGION | Pop_hommes | Pop_femmes | Pop_Etrang | Pop_totale | Naissances | Deces | Solde_nat | Solde_mig | Tx_Etrange | Superfice | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 01 | AIN | BOURG-EN-BRESSE | 82 | RHONE-ALPES | 287336 | 294023 | 45109 | 581355 | 61000 | 37114 | 23886 | 41991 | 0.0776 | 5784.556 | POLYGON ((919195.047 6541469.120, 918932.086 6... |
1 | 2 | 02 | AISNE | LAON | 22 | PICARDIE | 262006 | 276794 | 13447 | 538790 | 62669 | 48087 | 14582 | -11105 | 0.0250 | 7419.598 | POLYGON ((735604.502 6861427.402, 735235.539 6... |
2 | 3 | 03 | ALLIER | MOULINS | 83 | AUVERGNE | 164392 | 178416 | 9394 | 342807 | 30652 | 38838 | -8186 | 6378 | 0.0274 | 7380.145 | POLYGON ((753768.890 6537042.440, 753553.839 6... |
3 | 4 | 04 | ALPES-DE-HAUTE-PROVENCE | DIGNE-LES-BAINS | 93 | PROVENCE-ALPES-COTE-D'AZUR | 76580 | 81387 | 6388 | 157965 | 13741 | 13972 | -231 | 18513 | 0.0404 | 6995.771 | POLYGON ((992637.810 6305621.223, 992262.815 6... |
4 | 5 | 05 | HAUTES-ALPES | GAP | 93 | PROVENCE-ALPES-COTE-D'AZUR | 65494 | 68714 | 4192 | 134205 | 12882 | 10793 | 2089 | 10485 | 0.0312 | 5689.465 | POLYGON ((1012912.430 6402903.638, 1012576.422... |
print(gdf.columns)
Index(['ID_GEOFLA', 'CODE_DEPT', 'NOM_DEPT', 'NOM_CHF', 'CODE_REG', 'NOM_REGION', 'Pop_hommes', 'Pop_femmes', 'Pop_Etrang', 'Pop_totale', 'Naissances', 'Deces', 'Solde_nat', 'Solde_mig', 'Tx_Etrange', 'Superfice', 'geometry'], dtype='object')
gdf.plot(color='red')
<Axes: >
gdf.plot(column='Tx_Etrange', legend=True)
<Axes: >
L'argument cmap permet de choisir son dégradé de couleurs.
gdf.plot(column='Tx_Etrange', cmap='OrRd', legend=True, legend_kwds={"label": "Tx Etranger", "orientation": "horizontal"})
<Axes: >
Néanmoins, pour créer une carte en aplat de couleurs discrétisée, il faut utiliser la bibliothèque mapclassify. Cette bibliothèque mapclassify n'est pas installé par défaut avec GeoPandas. Elle dépend en fait de PySAL. C'est un argument supplémentaire pour installer PySAL.
import mapclassify
gdf.plot(column='Tx_Etrange', cmap='OrRd', scheme='Quantiles', k = 4, legend=True)
<Axes: >
Enfin, l'argument column peut s'adapter à des variables qualitatives discrètes en associant l'argument cmap à une palette de couleurs adaptée à des valeurs discrètes : tab10 et tab20 par exemple.
gdf.plot(cmap='tab10')
gdf.plot(column='NOM_REGION', cmap='tab20')
<Axes: >
Manipulations de la table attributaire¶
Pour manipuler le tableau géographique (la table attributaire), on pourra donc fonctionner avec les geodataframes comme avec les dataframes. Ainsi, pour récupérer les données des colonnes, les combiner ou les afficher dans un graphique, on pourra fonctionner comme cela.
gdf['Pop_hommes']
print(sum(gdf['Pop_hommes']))
gdf['Pop_hommes'] + gdf['Pop_femmes']
gdf[['NOM_DEPT','Pop_hommes','Pop_femmes']].head()
30088079
NOM_DEPT | Pop_hommes | Pop_femmes | |
---|---|---|---|
0 | AIN | 287336 | 294023 |
1 | AISNE | 262006 | 276794 |
2 | ALLIER | 164392 | 178416 |
3 | ALPES-DE-HAUTE-PROVENCE | 76580 | 81387 |
4 | HAUTES-ALPES | 65494 | 68714 |
gdf.plot.scatter(x='Pop_hommes', y='Pop_femmes')
<Axes: xlabel='Pop_hommes', ylabel='Pop_femmes'>
Si on souhaite utiliser les index, on récupère les lignes du tableau, c'est à dire les objets géographiques. Si on souhaite utiliser les index pour les lignes et les colonnes, on peut utiliser la fonction iloc().
gdf[0:3]
gdf[0:3]['Pop_hommes']
0 287336 1 262006 2 164392 Name: Pop_hommes, dtype: int64
gdf.iloc[3:5, 6:8]
Pop_hommes | Pop_femmes | |
---|---|---|
3 | 76580 | 81387 |
4 | 65494 | 68714 |
Pour récupérer un objet géographique, ou plus généralement faire une sélection attributaire, on pourra passer par les tests :
gdf[gdf['NOM_DEPT'] == 'PARIS']
ID_GEOFLA | CODE_DEPT | NOM_DEPT | NOM_CHF | CODE_REG | NOM_REGION | Pop_hommes | Pop_femmes | Pop_Etrang | Pop_totale | Naissances | Deces | Solde_nat | Solde_mig | Tx_Etrange | Superfice | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
75 | 76 | 75 | PARIS | PARIS--1ER-ARRONDISSEMENT | 11 | ILE-DE-FRANCE | 1042203 | 1169094 | 329922 | 2211297 | 284597 | 138230 | 146367 | -60921 | 0.1492 | 105.365 | POLYGON ((657172.410 6861181.210, 657135.406 6... |
gdf[gdf['NOM_REGION'] == 'RHONE-ALPES']['NOM_DEPT']
0 AIN 6 ARDECHE 24 DROME 38 ISERE 42 LOIRE 69 RHONE 73 SAVOIE 74 HAUTE-SAVOIE Name: NOM_DEPT, dtype: object
gdf[gdf['NOM_REGION'] == 'RHONE-ALPES'].plot()
<Axes: >
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
gdf.plot(ax=ax, color='white', edgecolor='black')
gdf[gdf['NOM_REGION'] == 'RHONE-ALPES'].plot(ax=ax)
plt.show()
Bien entendu, les tests (et donc les sélections attributaires) peuvent s'effectuer sur des variables quantitatives :
gdf['Tx_Etrange'] > 0.1
gdf[gdf['Tx_Etrange'] > 0.1]
gdf[gdf['Tx_Etrange'] > 0.1]['NOM_DEPT']
21 CREUSE 28 CORSE-DU-SUD 75 PARIS 92 HAUTS-DE-SEINE 93 SEINE-SAINT-DENIS 94 VAL-DE-MARNE 95 VAL-D'OISE Name: NOM_DEPT, dtype: object
On peut aussi effectuer des tests plus complexes avec des connecteurs logiques ou des fonctions pratiques comme isin(). On peut ainsi reconstituer la région RHONE-ALPES-AUVERGNE.
rho_auv = gdf[gdf['NOM_REGION'].isin(['RHONE-ALPES','AUVERGNE'])]
rho_auv['NOM_DEPT']
0 AIN 2 ALLIER 6 ARDECHE 14 CANTAL 24 DROME 38 ISERE 42 LOIRE 43 HAUTE-LOIRE 63 PUY-DE-DOME 69 RHONE 73 SAVOIE 74 HAUTE-SAVOIE Name: NOM_DEPT, dtype: object
rho_auv2 = gdf[(gdf['NOM_REGION'] == 'RHONE-ALPES') | (gdf['NOM_REGION'] == 'AUVERGNE')]
rho_auv2['NOM_DEPT']
0 AIN 2 ALLIER 6 ARDECHE 14 CANTAL 24 DROME 38 ISERE 42 LOIRE 43 HAUTE-LOIRE 63 PUY-DE-DOME 69 RHONE 73 SAVOIE 74 HAUTE-SAVOIE Name: NOM_DEPT, dtype: object
txetrang_peup = gdf[(gdf['Tx_Etrange'] > 0.1) & (gdf['Pop_totale'] > 1000000)]
txetrang_peup['NOM_DEPT']
75 PARIS 92 HAUTS-DE-SEINE 93 SEINE-SAINT-DENIS 94 VAL-DE-MARNE 95 VAL-D'OISE Name: NOM_DEPT, dtype: object
La fonction groupby() permet d'effectuer des regroupements attributaires. On peut aussi utiliser la fonction value_counts() pour compter les objets géographique ayant des valeurs communes.
gr = gdf[['Pop_totale','NOM_REGION']].groupby(['NOM_REGION']).sum()
gr.head()
Pop_totale | |
---|---|
NOM_REGION | |
ALSACE | 1837087 |
AQUITAINE | 3177625 |
AUVERGNE | 1341863 |
BASSE-NORMANDIE | 1467425 |
BOURGOGNE | 1638588 |
gdf['NOM_REGION'].value_counts().head()
NOM_REGION RHONE-ALPES 8 MIDI-PYRENEES 8 ILE-DE-FRANCE 8 PROVENCE-ALPES-COTE-D'AZUR 6 CENTRE 6 Name: count, dtype: int64
Enfin, une des manipulations attributaires les plus précieuses, la jointure attributaire permettant d'enrichir les tableaux d'information géographique, est possible avec la fonction merge(). Ici, on va joindre nos données du shapefile (gdf) avec celle de notre csv (df) à l'aide des champs CODE_DEPT et ID qui sont communs aux deux tableaux.
gdf2 = gdf.merge(df, left_on='CODE_DEPT',right_on='ID')
gdf2.head()
ID_GEOFLA | CODE_DEPT | NOM_DEPT | NOM_CHF | CODE_REG | NOM_REGION | Pop_hommes | Pop_femmes | Pop_Etrang | Pop_totale | ... | NOM | Agriculteur | Artisan Commercant | Cadre | Profession Intermediaire | Employe | Ouvrier | Retraite | Autres | Actif | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 01 | AIN | BOURG-EN-BRESSE | 82 | RHONE-ALPES | 287336 | 294023 | 45109 | 581355 | ... | Ain | 4067 | 16745 | 36426 | 70663 | 77349 | 78998 | 30417 | 29910 | 344575 |
1 | 2 | 02 | AISNE | LAON | 22 | PICARDIE | 262006 | 276794 | 13447 | 538790 | ... | Aisne | 5201 | 11414 | 18629 | 48889 | 71578 | 78906 | 32063 | 42108 | 308788 |
2 | 3 | 03 | ALLIER | MOULINS | 83 | AUVERGNE | 164392 | 178416 | 9394 | 342807 | ... | Allier | 6159 | 9396 | 11842 | 31102 | 46196 | 42101 | 24218 | 21344 | 192358 |
3 | 4 | 04 | ALPES-DE-HAUTE-PROVENCE | DIGNE-LES-BAINS | 93 | PROVENCE-ALPES-COTE-D'AZUR | 76580 | 81387 | 6388 | 157965 | ... | Alpes-de-Haute-Provence | 2027 | 6387 | 6951 | 16140 | 20885 | 15560 | 10687 | 10278 | 88915 |
4 | 5 | 05 | HAUTES-ALPES | GAP | 93 | PROVENCE-ALPES-COTE-D'AZUR | 65494 | 68714 | 4192 | 134205 | ... | Hautes-Alpes | 2040 | 5286 | 5883 | 15687 | 19707 | 12415 | 8865 | 7222 | 77105 |
5 rows × 28 columns
Réaliser des traitements spatiaux avec GeoPandas¶
La bibliothèque GeoPandas va surtout être utile pour traiter de l'information géographique, car elle permet de réaliser des géotraitements et d'appeler des algorithmes permettant de calculer des superficies (area), des longueurs (length), des distances et des centroides (centroid).
Attention, ces fonctions requièrent des coordonnées planes.
a = gdf.area
print('Superficie ' + str(gdf['NOM_DEPT'][0]) + ' = ' + str(a[0] / 1000000) + ' km2')
l = gdf.length
print('Périmetre ' + str(gdf['NOM_DEPT'][0]) + ' = ' + str(l[0] / 1000) + ' km')
geom_ain = gdf[gdf['NOM_DEPT'] == 'AIN']['geometry']
geom_aisne = gdf[gdf['NOM_DEPT'] == 'AISNE']['geometry']
dist = geom_ain.distance(geom_aisne,align=False)
print('Distance Ain-Aisne ' + str(dist[0] / 1000) + ' km' )
b = gdf.centroid
b.plot()
Superficie AIN = 5773.959872775968 km2 Périmetre AIN = 477.70417195337177 km Distance Ain-Aisne 280.9775357285964 km
<Axes: >
Il est possible d'utiliser les centroides pour créer des cartes avec des symboles proportionnels.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
gdf.plot(ax=ax, color='white', edgecolor='black', linewidth=0.5)
sizes = gdf['Pop_totale'] / 100000
b.plot(ax=ax, marker='o', color='red', markersize=sizes)
plt.show()
Bien entendu, Geopandas permet de calculer des buffers, ici autour des centroides.
c = b.buffer(20000)
fig, ax = plt.subplots()
gdf.plot(ax=ax, color='white', edgecolor='grey', linewidth=0.5)
c.plot(ax=ax, color='grey', edgecolor='black', linewidth=0.5)
b.plot(ax=ax, marker='o', color='blue', markersize=4)
plt.show()
A l'aide de la fonction overlay, il est possible de réaliser des intersections, des unions et des différences. Cette fonction nécessite deux geodataframes, or la fonction buffer() crée des GeoSeries. Il faut donc transformer les buffers en geodataframe avant de les intersecter aux départements.
c = b.buffer(40000)
cgdf = gpd.GeoDataFrame(c)
cgdf = cgdf.set_geometry(0)
cgdf['NOM'] = gdf['NOM_DEPT']
inter = gdf.overlay(cgdf, how='intersection')
inter.plot()
inter['NOM'].value_counts()
NOM YVELINES 10 VAL-D'OISE 10 ESSONNE 9 VAL-DE-MARNE 9 SEINE-SAINT-DENIS 9 .. HAUTE-CORSE 2 VENDEE 2 PYRENEES-ATLANTIQUES 2 DORDOGNE 2 LANDES 1 Name: count, Length: 96, dtype: int64
Pour les regroupements, il faut utiliser la fonction dissolve() :
reg = gdf.dissolve(by='NOM_REGION') #Reroupement
reg.plot(edgecolor='white', linewidth=0.5)
<Axes: >
reg.head()
geometry | ID_GEOFLA | CODE_DEPT | NOM_DEPT | NOM_CHF | CODE_REG | Pop_hommes | Pop_femmes | Pop_Etrang | Pop_totale | Naissances | Deces | Solde_nat | Solde_mig | Tx_Etrange | Superfice | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
NOM_REGION | ||||||||||||||||
ALSACE | POLYGON ((1040403.562 6789657.178, 1040676.601... | 68 | 67 | BAS-RHIN | STRASBOURG | 42 | 531185 | 559832 | 78393 | 1091015 | 118888 | 73112 | 45776 | 19216 | 0.0719 | 4794.081 |
AQUITAINE | MULTIPOLYGON (((427834.547 6198552.139, 427106... | 23 | 24 | DORDOGNE | PERIGUEUX | 72 | 196699 | 212696 | 11639 | 409388 | 33062 | 44924 | -11862 | 32865 | 0.0284 | 9223.041 |
AUVERGNE | POLYGON ((634691.847 6393269.982, 634356.813 6... | 3 | 03 | ALLIER | MOULINS | 83 | 164392 | 178416 | 9394 | 342807 | 30652 | 38838 | -8186 | 6378 | 0.0274 | 7380.145 |
BASSE-NORMANDIE | MULTIPOLYGON (((533473.967 6789000.545, 533191... | 14 | 14 | CALVADOS | CAEN | 25 | 326305 | 351904 | 12898 | 678206 | 74824 | 51028 | 23796 | 6111 | 0.0190 | 5604.691 |
BOURGOGNE | POLYGON ((805986.882 6568662.419, 805127.846 6... | 20 | 21 | COTE-D'OR | DIJON | 26 | 251747 | 269866 | 10896 | 521608 | 54190 | 39748 | 14442 | 157 | 0.0209 | 8801.267 |
L'argument aggfunc permet de choisir des fonctions d'agrégation à appliquer à tous les champs.
gdf.dissolve(by='NOM_REGION', aggfunc='sum').head()
geometry | ID_GEOFLA | CODE_DEPT | NOM_DEPT | NOM_CHF | CODE_REG | Pop_hommes | Pop_femmes | Pop_Etrang | Pop_totale | Naissances | Deces | Solde_nat | Solde_mig | Tx_Etrange | Superfice | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
NOM_REGION | ||||||||||||||||
ALSACE | POLYGON ((1040403.562 6789657.178, 1040676.601... | 6869 | 6768 | BAS-RHINHAUT-RHIN | STRASBOURGCOLMAR | 4242 | 897015 | 940078 | 139481 | 1837087 | 200406 | 125462 | 74944 | 28411 | 0.1538 | 8326.383 |
AQUITAINE | MULTIPOLYGON (((427834.547 6198552.139, 427106... | 2334414865 | 2433404764 | DORDOGNEGIRONDELANDESLOT-ET-GARONNEPYRENEES-AT... | PERIGUEUXBORDEAUXMONT-DE-MARSANAGENPAU | 7272727272 | 1528017 | 1649629 | 122177 | 3177625 | 295132 | 272356 | 22776 | 245896 | 0.1879 | 41798.243 |
AUVERGNE | POLYGON ((634691.847 6393269.982, 634356.813 6... | 3154464 | 03154363 | ALLIERCANTALHAUTE-LOIREPUY-DE-DOME | MOULINSAURILLACLE PUY-EN-VELAYCLERMONT-FERRAND | 83838383 | 649618 | 692256 | 43644 | 1341863 | 124443 | 130164 | -5721 | 38928 | 0.1049 | 26174.396 |
BASSE-NORMANDIE | MULTIPOLYGON (((533473.967 6789000.545, 533191... | 145162 | 145061 | CALVADOSMANCHEORNE | CAENSAINT-LOALENCON | 252525 | 710862 | 756580 | 27432 | 1467425 | 155150 | 122193 | 32957 | 12032 | 0.0588 | 17753.150 |
BOURGOGNE | POLYGON ((805986.882 6568662.419, 805127.846 6... | 20597290 | 21587189 | COTE-D'ORNIEVRESAONE-ET-LOIREYONNE | DIJONNEVERSMACONAUXERRE | 26262626 | 793427 | 845175 | 54237 | 1638588 | 160843 | 155706 | 5137 | 23044 | 0.1314 | 31749.352 |
dep_pop = gdf[['NOM_REGION', 'geometry', 'Pop_totale']]
reg_pop = dep_pop.dissolve(by='NOM_REGION', aggfunc='sum')
reg_pop2 = dep_pop.dissolve(by='NOM_REGION', aggfunc={"Pop_totale": ["min", "max"]})
reg_popdef = gdf.dissolve(by='NOM_REGION', aggfunc={"Pop_hommes": "sum","Pop_femmes": "sum","Pop_totale": "sum",'NOM_REGION':"first"})
reg_pop.head()
geometry | Pop_totale | |
---|---|---|
NOM_REGION | ||
ALSACE | POLYGON ((1040403.562 6789657.178, 1040676.601... | 1837087 |
AQUITAINE | MULTIPOLYGON (((427834.547 6198552.139, 427106... | 3177625 |
AUVERGNE | POLYGON ((634691.847 6393269.982, 634356.813 6... | 1341863 |
BASSE-NORMANDIE | MULTIPOLYGON (((533473.967 6789000.545, 533191... | 1467425 |
BOURGOGNE | POLYGON ((805986.882 6568662.419, 805127.846 6... | 1638588 |
reg_pop2.head()
geometry | (Pop_totale, min) | (Pop_totale, max) | |
---|---|---|---|
NOM_REGION | |||
ALSACE | POLYGON ((1040403.562 6789657.178, 1040676.601... | 746072 | 1091015 |
AQUITAINE | MULTIPOLYGON (((427834.547 6198552.139, 427106... | 326399 | 1421276 |
AUVERGNE | POLYGON ((634691.847 6393269.982, 634356.813 6... | 148737 | 628485 |
BASSE-NORMANDIE | MULTIPOLYGON (((533473.967 6789000.545, 533191... | 292282 | 678206 |
BOURGOGNE | POLYGON ((805986.882 6568662.419, 805127.846 6... | 220653 | 553968 |
reg_popdef.head()
geometry | Pop_hommes | Pop_femmes | Pop_totale | NOM_REGION | |
---|---|---|---|---|---|
NOM_REGION | |||||
ALSACE | POLYGON ((1040403.562 6789657.178, 1040676.601... | 897015 | 940078 | 1837087 | ALSACE |
AQUITAINE | MULTIPOLYGON (((427834.547 6198552.139, 427106... | 1528017 | 1649629 | 3177625 | AQUITAINE |
AUVERGNE | POLYGON ((634691.847 6393269.982, 634356.813 6... | 649618 | 692256 | 1341863 | AUVERGNE |
BASSE-NORMANDIE | MULTIPOLYGON (((533473.967 6789000.545, 533191... | 710862 | 756580 | 1467425 | BASSE-NORMANDIE |
BOURGOGNE | POLYGON ((805986.882 6568662.419, 805127.846 6... | 793427 | 845175 | 1638588 | BOURGOGNE |
Enfin, il est possible d'effectuer des jointures spatiales à l'aide de la fonction sjoin(). L'argument predicate permet de préciser la liaison. Ici, on va se servir de la région Ile-de-France pour récupérer les centroides des départements de la région Ile-de-France. Pour effectuer cette jointure, il faut deux geodataframes, or la fonction centroid() crée des GeoSeries. Il faut donc transformer les centroides en geodataframe avant de les intersecter aux départements de l'Ile-de-France.
idf = reg[reg.index=='ILE-DE-FRANCE']
cen = gpd.GeoDataFrame(b)
cen = cen.set_geometry(0)
centroid_idf = cen.sjoin(idf, predicate='intersects')
fig, ax = plt.subplots()
idf.plot(ax=ax, color='white', edgecolor='black')
centroid_idf.plot(ax=ax, marker='o', markersize=20)
plt.show()
Bonus : première carte interactive avec Python¶
Dans cette partie, je vais exploiter le potentiel de MapClassify et de ipywidgets pour créer des cartes interactives dans Jupyter. Cette partie ne fonctionne pas avec Spyder.
On l'a vu plus haut, mapclassify permet de réaliser facilement des cartes thématiques en utilisant l'argument scheme dans un plot(). On peut alors chercher à faire varier les paramètres de ce plot() pour obtenir différentes représentations (notamment ce paramètre scheme pour appeler d'autres méthodes de discrétisation). Pour le faire dynamiquement, on peut utiliser la bibliothèque ipywidgets qui permet d'insérer dans les cellules Jupyter des widgets comme des listes déroulantes. Ensuite, il faut simplement faire communiquer ces widgets avec les paramètres de notre plot().
import geopandas as gpd
import mapclassify as mc
import ipywidgets as widgets
from IPython.display import display, clear_output
import matplotlib.pyplot as plt
# Charger les données des départements français
gd = gpd.read_file('C:/Users/Serge/Desktop/Donnees/Departements.shp')
# Liste des méthodes de discrétisation disponibles
methods = ["Quantiles", "EqualInterval", "NaturalBreaks"]
# Création du widget pour choisir la méthode de discrétisation
dropdown = widgets.Dropdown(
options=methods,
value="Quantiles",
description="Méthode :",
style={'description_width': 'initial'}
)
# Zone de sortie pour afficher la carte
output = widgets.Output()
# Fonction pour mettre à jour la carte
def update_map(change):
with output:
clear_output(wait=True) # Effacer l'ancienne carte
ax = gd.plot(column='Tx_Etrange', cmap='OrRd', scheme=dropdown.value, k = 4, legend=True)
ax.set_axis_off()
plt.show()
# Lier la mise à jour au changement de sélection
dropdown.observe(update_map, names="value")
# Afficher les widgets et la carte initiale
display(dropdown,output)
update_map(None)
Ci-dessous, une vidéo pour visualiser le résultat. La méthode des seuils naturels est un peu longue. A noter, que ipywidgets doit être installé et bien souvent mis à jour : pip install --upgrade ipywidgets.
On peut alors faire varier l'aplat de couleurs, le nombre de classes...