Installations préalables :
!pip install pandas fiona shapely pyproj rtree # à faire obligatoirement en premier pour utiliser rtree ou pygeos pour les jointures spatiales
!pip install contextily
!pip install geopandas
!pip install pygeos
Les instructions d’installation du package cartiflette
sont quant à elles détaillées dans le chapitre
précédent.
import geopandas as gpd
Lire et enrichir des données spatiales
Dans cette partie,
nous utiliserons
le package cartiflette
qui facilite la récupération de contours de cartes.
Une version antérieure de cet exercice, présentée sous forme
d’exercice supplémentaire 👇️, utilisait des fonds de carte issus
de data.gouv
.
/src/cartiflette/cartiflette/download/dev.py:153: SyntaxWarning:
"is" with a literal. Did you mean "=="?
Exercice 1: lire et explorer la structure de fichiers géographiques
- S’inspirer des exemples de code présents dans le chapitre précédent, mobilisant
le package
cartiflette
pour télécharger les données communales des départements 75, 92, 93 et 94. Vous pouvez nommer l’objetcommunes_borders
- Regarder les premières lignes des données. Identifier la différence avec un DataFrame standard.
- Afficher l’attribut
crs
decommunes_borders
. Ce dernier contrôle la transformation de l’espace tridimensionnel terrestre en une surface plane. Utiliserto_crs
pour transformer les données en Lambert 93 (code EPSG 2154). - Afficher les communes des Hauts de Seine (département 92) et utiliser la méthode
plot
- Réprésenter la carte de Paris : quel est le problème ?
:::
ID | NOM | NOM_M | INSEE_COM | STATUT | POPULATION | INSEE_CAN | INSEE_ARR | INSEE_DEP | INSEE_REG | SIREN_EPCI | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | COMMUNE_0000000009736048 | Paris | PARIS | 75056 | Capitale d'état | 2165423 | NR | 1 | 75 | 11 | 200054781 | POLYGON ((2.36420 48.81640, 2.36333 48.81615, ... |
0 | COMMUNE_0000000009736037 | Levallois-Perret | LEVALLOIS-PERRET | 92044 | Commune simple | 66082 | 16 | 2 | 92 | 11 | 200054781/200057982 | POLYGON ((2.28739 48.90364, 2.28846 48.90302, ... |
1 | COMMUNE_0000000009736055 | Bois-Colombes | BOIS-COLOMBES | 92009 | Commune simple | 28841 | 11 | 2 | 92 | 11 | 200054781/200057990 | POLYGON ((2.26639 48.90629, 2.26645 48.90615, ... |
2 | COMMUNE_0000000009736538 | Malakoff | MALAKOFF | 92046 | Commune simple | 30950 | 18 | 1 | 92 | 11 | 200054781/200057966 | POLYGON ((2.27818 48.81425, 2.28066 48.81469, ... |
3 | COMMUNE_0000000009736038 | Clichy | CLICHY | 92024 | Commune simple | 63089 | 09 | 2 | 92 | 11 | 200054781/200057990 | POLYGON ((2.30377 48.89415, 2.30258 48.89487, ... |
<Geographic 2D CRS: EPSG:4326>
Name: WGS 84
Axis Info [ellipsoidal]:
- Lat[north]: Geodetic latitude (degree)
- Lon[east]: Geodetic longitude (degree)
Area of Use:
- name: World.
- bounds: (-180.0, -90.0, 180.0, 90.0)
Datum: World Geodetic System 1984 ensemble
- Ellipsoid: WGS 84
- Prime Meridian: Greenwich
La carte du 92 est la suivante:

Quant à Paris, à l’issue de la question 5, la carte aura l’aspect suivant:

En effet, on ne dispose ainsi pas des limites des arrondissements parisiens, ce
qui appauvrit grandement la carte de Paris.
On pourrait les récupérer directement
depuis le site d’open-data du Grand Paris, ce qui est proposé
en exercice supplémentaire 👇️.
On propose ici d’utiliser à nouveau
cartiflette
pour cela afin de disposer du fonds de carte officiel.
Exercice 2: compléter des données spatiales issues de sources différentes
- Importer les données de découpage des arrondissements parisiens à l’adresse à l’aide de
cartiflette
. - Vérifier sur une carte que les découpages des arrondissements sont bien présents.
- Vérifier l’attribut
crs
. Est-il cohérent avec celui des données communales ? Si non, transformer en Lambert 93 (code EPSG 2154). - Retirer Paris du jeu de données communales et utiliser les arrondissements
pour enrichir (nommer l’objet obtenu
data_borders
). - Représenter à nouveau les communes de la petite couronne parisienne (75, 92, 93, 94)
La carte de Paris intra-muros est, après la
récupération des arrondissements avec
cartiflette
de ce type là:

epsg:4326
epsg:2154
False
La carte obtenue à l’issue de la question 6, c’est-à-dire après avoir consolidé les données, devrait avoir l’aspect suivant:

Utiliser des données géographiques comme des couches graphiques
Souvent, le découpage communal ne sert qu’en fond de cartes, pour donner des repères. En complément de celui-ci, on peut désirer exploiter un autre jeu de données.
On va partir des données de localisation des stations velib, disponibles sur le site d’open data de la ville de Paris et requêtables directement par l’url https://opendata.paris.fr/explore/dataset/velib-emplacement-des-stations/download/?format=geojson&timezone=Europe/Berlin&lang=fr
Exercice 3: importer et explorer les données velib
- Importer les données velib sous le nom
station
- Vérifier la projection géographique de
station
(attributcrs
). Si celle-ci est différente des données communales, reprojeter ces dernières dans le même système de projection que les stations de vélib - Représenter sur une carte les 50 stations les plus importantes (variable
capacity
). Vous pouvez également afficher le fonds de carte des arrondissements de Paris. Cette page peut vous aider pour comprendre comment afficher plusieurs couches à la fois. Vous pouvez customiser la carte en retirant les axes grâce à la méthodeset_axis_off
et mettre un titre tel que “Les 50 principales stations de Vélib” avec la méthodeset_title
. - Afficher également (trait bleu et épais) les réseaux de transport en communs, disponibles ici. L’url à requêter est https://data.iledefrance-mobilites.fr/explore/dataset/traces-du-reseau-ferre-idf/download/?format=geojson&timezone=Europe/Berlin&lang=fr
capacity | name | stationcode | geometry | |
---|---|---|---|---|
0 | 21 | Toudouze - Clauzel | 9020 | POINT (2.33736 48.87930) |
1 | 24 | Théâtre des Amandiers - Palais des sports | 92002 | POINT (2.21369 48.89265) |
2 | 14 | Abbeville - Faubourg Poissonnière | 9002 | POINT (2.34915 48.87922) |
3 | 24 | Quai de la Seine | 19003 | POINT (2.37039 48.88449) |
4 | 46 | Saint-Maur - Roquette | 11023 | POINT (2.38355 48.85869) |
La carte attendu à l’issue de la question 3 a l’aspect suivant:
Text(0.5, 1.0, 'Les 50 principales stations de Vélib')

['TRAMWAY' 'RER' 'TRAIN' 'TER' 'NAVETTE' 'METRO' 'RER C']
L’ajout du réseau de métro permet d’obtenir une carte ressemblant à celle-ci:
Text(0.5, 1.0, 'Les 50 principales stations de Vélib')

Pour faire une belle carte, il faudrait couper les lignes de métro via une jointure spatiale ou utiliser un fonds de carte conceptuel. L’exercice suivant propose de mettre en oeuvre la deuxième méthode. La première est proposée en exercice supplémentaire 👇️.
Exercice 4: ajouter un fond de carte
- Recréer par couche successive la carte précédente, que vous pouvez nommer
base
- Utiliser
add_basemap
du packagecontextily
pour ajouter, en arrière plan, un fonds de carte - Jouer avec les fonds disponibles en utilisant l’argument
source
Par exemple, en utilisant le fond Stamen.Watercolor
, on obtient la carte
suivante. Celle-ci permet déjà de mieux localiser les stations.

Jointures spatiales
Les jointures attributaires fonctionnent comme avec un DataFrame pandas
.
Pour conserver un objet spatial in fine, il faut faire attention à utiliser en premier (base de gauche) l’objet GeoPandas
.
En revanche, l’un des intérêts des objets geopandas est qu’on peut également faire une jointure sur la dimension spatiale grâce à sjoin
.
La documentation à laquelle se référer est ici.
Exercice 5 : Associer les stations aux communes et arrondissements auxquels elles appartiennent
- Faire une jointure spatiale pour enrichir les données de stations en y ajoutant des informations de
data_paris
. Appeler cet objetstations_info
- Représenter la carte des stations du 19e arrondissement (s’aider de la variable
c_ar
). Vous pouvez mettre en fond de carte les arrondissements parisiens. - Compter le nombre de stations velib et le nombre de places velib par arrondissement ou commune (pour vous aider, vous pouvez compléter vos connaissances avec ce tutoriel). Représenter sur une carte chacune des informations
- Représenter les mêmes informations mais en densité (diviser par la surface de l’arrondissement ou commune en km2)
- (optionnel) Choisir une des cartes de densité et la nettoyer (retirer les axes, mettre les titres…)
capacity | name | stationcode | geometry | index_right | ID | NOM | NOM_M | INSEE_COM | STATUT | POPULATION | INSEE_CAN | INSEE_ARR | INSEE_DEP | INSEE_REG | SIREN_EPCI | INSEE_ARM | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 21 | Toudouze - Clauzel | 9020 | POINT (2.33736 48.87930) | 5 | ARR_MUNI0000000009736043 | Paris 9e Arrondissement | PARIS 9E ARRONDISSEMENT | 75056 | NaN | 60026 | NaN | NaN | 75 | NaN | NaN | 75109 |
2 | 14 | Abbeville - Faubourg Poissonnière | 9002 | POINT (2.34915 48.87922) | 5 | ARR_MUNI0000000009736043 | Paris 9e Arrondissement | PARIS 9E ARRONDISSEMENT | 75056 | NaN | 60026 | NaN | NaN | 75 | NaN | NaN | 75109 |
37 | 17 | Choron - Martyrs | 9016 | POINT (2.33967 48.87785) | 5 | ARR_MUNI0000000009736043 | Paris 9e Arrondissement | PARIS 9E ARRONDISSEMENT | 75056 | NaN | 60026 | NaN | NaN | 75 | NaN | NaN | 75109 |
42 | 22 | Godot de Mauroy - Madeleine | 9034 | POINT (2.32635 48.86979) | 5 | ARR_MUNI0000000009736043 | Paris 9e Arrondissement | PARIS 9E ARRONDISSEMENT | 75056 | NaN | 60026 | NaN | NaN | 75 | NaN | NaN | 75109 |
70 | 14 | Boudreau - Auber | 9106 | POINT (2.32942 48.87219) | 5 | ARR_MUNI0000000009736043 | Paris 9e Arrondissement | PARIS 9E ARRONDISSEMENT | 75056 | NaN | 60026 | NaN | NaN | 75 | NaN | NaN | 75109 |
Pour la question 2, la première méthode consiste à afficher toute la ville mais à ne représenter que les points des stations du 19e:
<AxesSubplot: >

Néanmoins, il est préférable de se centrer sur le 19e en premier lieu, ce qui donne une carte comme celle-ci:

ID | NOM | NOM_M | INSEE_COM | STATUT | POPULATION | INSEE_CAN | INSEE_ARR | INSEE_DEP | INSEE_REG | SIREN_EPCI | geometry | INSEE_ARM | stationcode | capacity | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | COMMUNE_0000000009736037 | Levallois-Perret | LEVALLOIS-PERRET | 92044 | Commune simple | 66082 | 16 | 2 | 92 | 11 | 200054781/200057982 | POLYGON ((2.28739 48.90364, 2.28846 48.90302, ... | NaN | 11 | 359 |
1 | COMMUNE_0000000009736055 | Bois-Colombes | BOIS-COLOMBES | 92009 | Commune simple | 28841 | 11 | 2 | 92 | 11 | 200054781/200057990 | POLYGON ((2.26639 48.90629, 2.26645 48.90615, ... | NaN | 2 | 60 |
2 | COMMUNE_0000000009736538 | Malakoff | MALAKOFF | 92046 | Commune simple | 30950 | 18 | 1 | 92 | 11 | 200054781/200057966 | POLYGON ((2.27818 48.81425, 2.28066 48.81469, ... | NaN | 8 | 238 |
3 | COMMUNE_0000000009736038 | Clichy | CLICHY | 92024 | Commune simple | 63089 | 09 | 2 | 92 | 11 | 200054781/200057990 | POLYGON ((2.30377 48.89415, 2.30258 48.89487, ... | NaN | 13 | 422 |
4 | COMMUNE_0000000009736052 | Nanterre | NANTERRE | 92050 | Préfecture | 96277 | 99 | 2 | 92 | 11 | 200054781/200057982 | POLYGON ((2.22910 48.90603, 2.23037 48.90377, ... | NaN | 8 | 222 |
La carte des places disponibles est celle-ci:
Text(0.5, 1.0, 'Nombre de places disponibles')

Alors que la carte des capacités de stations est plutôt celle-là:
Text(0.5, 1.0, 'Nombre de stations')

Pas vraiment de différence marquée entre les deux, on peut se contenter de regarder la capacité.
Enfin, dans la question 4, si on représente plutôt la capacité sous forme de densité, pour tenir compte de la taille différente des arrondissements, on obtient cette carte:
<AxesSubplot: >

Avec une palette plasma_r
, cela donne plutôt cette carte:
<AxesSubplot: >

Avec un peu de travail sur l’esthétique, la carte que vous obtenez à l’issue de l’exercice ressemble à celle-ci:

Trouver les toilettes publiques les plus proches
Objectif
Jusqu’à présent, nous nous sommes familiarisés avec
la manipulation de données spatiales et la représentation
rapide de celle-ci grâce aux fonctionalités de GeoPandas
.
A partir de maintenant, nous allons utiliser GeoPandas
pour des tâches de manipulation géométrique.
Ces opérations reposeront sur des tâches classiques
de la géomatique qui sont facilitées par le fait que
GeoPandas
offre une surcouche au package Shapely
de la même manière que Pandas
était une sur-couche
de Numpy
pour les opérations numériques.
L’exemple suivant permet d’illustrer le principe d’une des opérations que nous allons utiliser, à savoir la recherche de plus proche point:
from shapely.ops import Polygon
from shapely.ops import nearest_points
triangle = Polygon([(0, 0), (1, 0), (0.5, 1), (0, 0)])
square = Polygon([(0, 2), (1, 2), (1, 3), (0, 3), (0, 2)])
[o.wkt for o in nearest_points(triangle, square)]
['POINT (0.5 1)', 'POINT (0.5 2)']
GeoPandas
va permettre de généraliser ce processus
en utilisant non plus deux listes modifiées (les
polygones de Shapely
) mais des DataFrames
géographiques.
Cela permettra, au passage, d’enrichir les
jointures spatiales avec les attributs des DataFrames
concernés.
Sur Shapely
, vous pourrez trouver une aide ici.
Néanmoins, à mesure que GeoPandas
se développe, il
devient de moins en moins nécessaire d’utiliser directement
Shapely
.
Mise en application
Nous allons rechercher les toilettes publiques les
plus proches de chaque station.
Sans les fonctionalités de GeoPandas
,
cette recherche serait assez pénible.
Exercice 5 (optionnel) : Trouver les toilettes publiques les plus proches d'une station de vélib
- Charger la localisation des toilettes publiques présente ici : https://data.ratp.fr/explore/dataset/sanitaires-reseau-ratp/download/?format=geojson&timezone=Europe/Berlin&lang=fr. Appelez-la
toilettes_publiques
. - Convertir les objets
toilettes_publiques
etstations
en projection Lambert-93 (CRS 2154). Cette conversion permettra de mesurer en mètres les distances entre objets géographiques. Sans celle-ci, nous ferions des distances entre coordonnées GPS, ce qui n’aide pas l’analyse et l’interprétation. - Utiliser la jointure spatiale par plus proche distance
sjoin_nearest
pour associer à chaque station les toilettes publiques les plus proches - Trouver les toilettes publiques les plus proches des stations de vélib autour d’Edgard Quinet.
- Représenter un histogramme des distances aux toilettes les plus proches
Le jeu de données open-data des toilettes publiques présente l’aspect suivant:
accessible_au_public | acces_bouton_poussoir | tarif_gratuit_payant | accessibilite_pmr | gestionnaire | ligne | acces_passe_navigo_ou_ticket_t | localisation | en_zone_controlee | station | hors_zone_controlee_station | hors_zone_controlee_voie_publique | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | None | None | None | None | Toilette publique RATP | 4 | None | Il est accessible depuis le guichet au niveau ... | None | Bagneux Lucie Aubrac | None | None | POINT (2.31740 48.80357) |
1 | oui | None | payant | oui | Toilette publique RATP | 10 | oui | Sortie 2 boulevard St-Germain, à proximité du ... | None | Cluny - La Sorbonne | None | None | POINT (2.34503 48.85080) |
Les toilettes les plus proches d’Edgar Quinet sont les suivantes:
localisation | station | name | distance | |
---|---|---|---|---|
564 | Sur le quai ligne B, en direction de Saint-Rém... | Denfert-Rochereau | Gare Montparnasse - Edgar Quinet | 1177.501002 |
1183 | Sur le quai ligne B, en direction de Saint-Rém... | Denfert-Rochereau | Edgar Quinet - Raspail | 737.660246 |
1191 | Sur le quai ligne B, en direction de Saint-Rém... | Denfert-Rochereau | Edgar Quinet - Gaité | 1091.971078 |
Il va donc falloir se retenir un peu car s’agit de toilettes situées à la station Denfert Rochereau !
Enfin, de manière plus globale, voici la distribution des distances aux toilettes les plus proches:
<AxesSubplot: ylabel='Frequency'>

Le mode de la distribution est entre 1 et 2 km, ce qui est une petite distance tout de même ! C’est normal, il ne s’agit pas de l’ensemble des toilettes publiques de la ville de Paris mais de celles gérées par la RATP. Rassurez-vous, au moins dans Paris intra-muros, vous n’avez pas à systématiquement marcher (ou rouler) autant.
Exercices supplémentaires
Voici une fonction pour télécharger et dézipper
facilement un fonds de carte issu de data.gouv
import requests
import tempfile
import zipfile
temporary_location = tempfile.gettempdir()
def download_unzip(url, dirname = tempfile.gettempdir(), destname = "borders"):
myfile = requests.get(url)
open("{}/{}.zip".format(dirname, destname), 'wb').write(myfile.content)
with zipfile.ZipFile("{}/{}.zip".format(dirname, destname), 'r') as zip_ref:
zip_ref.extractall(dirname + '/' + destname)
Exercice optionnel 1: télécharger et dézipper vous-même le fonds de carte
Importer le fichier avec le package GeoPandas
(si vous avez laissé les paramètres par défaut,
le fichier devrait
être à l’emplacement temporary_location + "/borders/communes-20210101.shp"
).
Exercice optionnel 2 : Utiliser les arrondissements fournis par l'open data parisien
- Importer les données de découpage des arrondissements parisiens à l’adresse https://opendata.paris.fr/explore/dataset/arrondissements/download/?format=geojson&timezone=Europe/Berlin&lang=fr
- Vérifier sur une carte que les découpages des arrondissements sont bien présents.
- Vérifier l’attribut
crs
. Est-il cohérent avec celui des données communales ? - Retirer Paris du jeu de données communales et utiliser les arrondissements
pour enrichir (nommer l’objet obtenu
data_borders
). Ici, on peut ne pas se soucier de la variable commune de superficie aux niveaux différents car on va la recréer. En revanche, renommer la variablec_arinsee
eninsee
avec la méthoderename
et faire attention aux types des variables
<AxesSubplot: >

epsg:4326
False
/tmp/ipykernel_1031/349994121.py:6: FutureWarning:
The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
ID | NOM | NOM_M | INSEE_COM | STATUT | POPULATION | INSEE_CAN | INSEE_ARR | INSEE_DEP | INSEE_REG | SIREN_EPCI | geometry | c_ar | l_aroff | surface | l_ar | n_sq_co | n_sq_ar | perimetre | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
18 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 75 | NaN | NaN | POLYGON ((653665.113 6861929.286, 653699.365 6... | 4.0 | Hôtel-de-Ville | 1.600586e+06 | 4ème Ardt | 750001537.0 | 750000004.0 | 5420.908434 |
19 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 75 | NaN | NaN | POLYGON ((656928.376 6864099.214, 656933.359 6... | 20.0 | Ménilmontant | 5.983446e+06 | 20ème Ardt | 750001537.0 | 750000020.0 | 10704.940486 |
Jointures spatiales
L’objectif de cet exercice est de ne conserver que les lignes de transports à l’intérieur de Paris intra-muros. Il s’agit d’appliquer les jointures spatiales de manière un petit peu différente à précédemment.
Exercice optionnel 3 : Les lignes de transport dans Paris
- Utiliser l’URL https://data.iledefrance-mobilites.fr/explore/dataset/traces-du-reseau-ferre-idf/download/?format=geojson&timezone=Europe/Berlin&lang=fr pour récupérer les lignes de transport
de la RATP. L’appeler
transports
. - A partir des arrondissements parisiens, utiliser
unary_union
pour créer un unique polygone parisien. Utiliserwithin
pour ne conserver que les points detransports
qui se trouvent dans Paris intra-muros - Représenter graphiquement
:::
La carte obtenue aura l’aspect suivant:
/miniconda/envs/python-ENSAE/lib/python3.9/site-packages/geopandas/plotting.py:693: UserWarning:
The GeoDataFrame you are attempting to plot is empty. Nothing has been displayed.
<AxesSubplot: >
Error in callback <function _draw_all_if_interactive at 0x7fe67c037f70> (for post_execute):
ValueError: cannot convert float NaN to integer
ValueError: cannot convert float NaN to integer
<Figure size 768x480 with 1 Axes>
Cette fois, on a bien conservé que les lignes de transport dans Paris. Un peu de travail sur le rendu serait nécessaire pour obtenir une belle carte. Vous pouvez le faire en exercice, après avoir consulté le chapitre relatif à la cartographie dans la partie visualisation de données.