TP9 - Cartographie sous python

Analyse de Données Massives - Master 1ère année

Dans ce dernier TP, nous allons aborder l'aspect cartographie sous python avec le package folium (à installer donc). En effet, dans les données horodateurs, nous disposons de deux niveaux d'informations géographiques :

  • les géolocalisation de chaque station
  • les arrondissements correspondants à chaque station

Avec ces deux informations, nous allons donc pouvoir créer des cartes, soit avec des points pour chaque station, soit des formes pour les arrondissements. L'idée sera d'ajouter des couleurs à ces formes, en fonction d'une information tierce (le nombre de transactions, le montant moyen, le type de station, ...).

Création de cartes

Le package folium est une interface entre python et la librairie leaflet. Elle s'utilise très facilement, comme nous pouvons le voir ci-dessous pour la création d'une carte zoomée sur la capitale. Les coordonnées indiquées sont la latitude et la longitude de Paris.

In [1]:
import folium
paris = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)

Pour obtenir la carte, il suffit d'appeler l'objet ainsi créé.

In [2]:
paris
Out[2]:

Ajout de marqueurs

La fonction Marker() permet d'ajouter une marque sur la carte pour signifier un emplacement précis. Il est possible de spécifier un contenu à afficher (sous la forme d'un pop-up) lorsque l'utilisateur va cliquer sur le marqueur. Celui-ci sera placé à la localisation fournie en premier, au format [latitude, longitude]. Ce contenu est spécifié dans l'argument popup (ici, une simple chaîne de caractères, mais cela peut être plus élaboré). Il est aussi possible customiser l'icône du marqueur. Une fois ce marqueur créé, il faut l'ajouter à la carte, via la fonction add_to().

In [3]:
folium.Marker([48.856578, 2.351828], popup = "PARIS").add_to(paris)
paris
Out[3]:

Ajout de cercles

La fonction CircleMarker() permet elle de créer des cercles, en donnant la localisation du centre et le rayon (via le paramètre radius). Idem que pour Marker(), on peut spécifier le contenu de la popup affichée lors d'un clic, et il est nécessaire de l'ajouter à la carte. Il est possible aussi de spécifier la couleur du cercle (via color) et la couleur de remplissage (via fillColor).

In [4]:
folium.CircleMarker([48.856578, 2.351828], popup = "PARIS", radius = 10).add_to(paris)
paris
Out[4]:

Informations géographiques

Pour pouvoir ajouter ces informations, nous allons importer les contours des 20 arrondissements parisiens disponibles en téléchargement sur cette page. Les données à récupérer sont celles au format GeoJSON. Vous devez les sauvegarder, idéalement dans le même répertoire que votre notebook. Dans mon cas, le fichier est dans le répertoire donnees.

In [5]:
import json
geo = json.load(open("donnees/arrondissements.geojson"))

Le format GeoJSON est de plus en plus utilisé pour représenter des données de type géographique. Il suit des spécifications précises, et peut être utilisé directement comme nous le verrons plus loin.

Dans cet objet, transformé en dictionnaire sous python, il y a deux éléments : le type et les informations (nommées features)

In [6]:
geo.keys()
Out[6]:
dict_keys(['type', 'features'])
In [7]:
geo["type"]
Out[7]:
'FeatureCollection'

Nous vérifions bien que nous avons les 20 arrondissements de Paris dans l'objet features, qui est une list.

In [8]:
len(geo["features"])
Out[8]:
20

Dans chaque objet de cette liste, nous disposons aussi de différents objets de type Feature.

In [9]:
geo["features"][0].keys()
Out[9]:
dict_keys(['type', 'geometry', 'properties'])
In [10]:
geo["features"][0]['type']
Out[10]:
'Feature'

Dans le champs properties, il y a différentes informations utiles, dont les coordonnées longitude et latitude du centre.

In [11]:
geo["features"][0]['properties']
Out[11]:
{'c_ar': 2,
 'c_arinsee': 75102,
 'geom_x_y': [48.86827922252252, 2.3428025468913636],
 'l_ar': '2ème Ardt',
 'l_aroff': 'Bourse',
 'longueur': 4553.93876373,
 'n_sq_ar': 750000002,
 'n_sq_co': 750001537,
 'objectid': 2,
 'perimetre': 4554.10435957,
 'surface': 991153.74474596}

Et dans la partie geometry, nous voyons que c'est un Polygon qui est représenté, avec toutes les coordonnées des sommets de ce polygone.

In [12]:
geo["features"][0]['geometry']
Out[12]:
{'coordinates': [[[2.351518483670821, 48.8644258050741],
   [2.350949105218923, 48.86340592861751],
   [2.346676032763327, 48.864430925901665],
   [2.346675453051013, 48.86443106483368],
   [2.345101655171463, 48.864809197959836],
   [2.341271025930368, 48.86572767724484],
   [2.34126849090564, 48.86572828653819],
   [2.341204510696185, 48.865743681005995],
   [2.341178272058699, 48.86574963323163],
   [2.341083555178273, 48.86577201721946],
   [2.337371969067098, 48.86664907439458],
   [2.335869691238243, 48.86699647535598],
   [2.335869054057415, 48.86699662650754],
   [2.333675321300195, 48.867516125009374],
   [2.33172601351949, 48.867954816599685],
   [2.331725629348361, 48.86795490259037],
   [2.330656733960091, 48.86819218066118],
   [2.330306795320876, 48.86835619167468],
   [2.329965588686572, 48.86851416917429],
   [2.328007329038849, 48.86991742140715],
   [2.32984034160984, 48.87023196300484],
   [2.332082346211126, 48.87061659840104],
   [2.334150445666464, 48.870970071095634],
   [2.337008699424837, 48.87145858186232],
   [2.337096350892028, 48.87147459083911],
   [2.339735558883667, 48.87192782312048],
   [2.339898092256945, 48.87195573212607],
   [2.34004423190851, 48.87198082642372],
   [2.343068934219441, 48.87148887381476],
   [2.347880282483183, 48.87070414850945],
   [2.350960685471093, 48.870049489007904],
   [2.3526310157278463, 48.86969449880021],
   [2.354260580416611, 48.869330050725615],
   [2.352975454399392, 48.867032060091404],
   [2.352530424441132, 48.86623248929979],
   [2.351974400952567, 48.86523967487127],
   [2.351518483670821, 48.8644258050741]]],
 'type': 'Polygon'}

Marqueur par arrondissement

Avec les informations contenues dans geom_x_y, nous allons pouvoir ajouter un marqueur pour chaque arrondissement, en mettant dans la popup, le nom de l'arrondissement.

In [13]:
paris_arr_marker = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)
for arr in geo["features"]:
    prop = arr["properties"]
    folium.Marker(prop["geom_x_y"], popup = prop["l_ar"]).add_to(paris_arr_marker)
paris_arr_marker
Out[13]:

Cercle par arrondissement

Toujours avec les mêmes informations, nous pouvons disposer des cercles pour chaque arrondissement. Ici, nous décidons de régler la taille des cercles (via radius) en fonction de la surface des arrondissements. Nous remarquons que nous devons faire cela manuellement (ici, on divise la surface par 1000000 pour avoir des tailles raisonnables).

In [14]:
paris_arr_circle = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)
for arr in geo["features"]:
    prop = arr["properties"]
    folium.CircleMarker(prop["geom_x_y"], 
                        popup = prop["l_ar"],
                        radius = prop["surface"] / 1000000).add_to(paris_arr_circle)
paris_arr_circle
Out[14]:

Arrondissements

Enfin, il est possible de faire des cartes choroplètes, dans lesquelles les zones sont coloriées selon une mesure statistique.

Pour cela, nous devons devons utiliser la fonction choropleth() de la carte créée. Nous passons dans le paramètre geo_str les données contenues dans le fichier GeoJSON importées plus haut.

In [15]:
paris = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)
paris.choropleth(geo_str = geo)
paris
Out[15]:

Avec une information tierce

Bien évidemment, nous désirons ajouter une couleur. Pour cela, nous avons besoin d'un DataFrame contenant deux variables :

  • un identifiant commun avec les informations contenues dans le GeoJSON
  • la variable statistique d'intérêt (pour cet exemple, ce sera la surface)

Pour le construire, nous allons récupérer le numéro d'arrondissement (item c_ar dans properties) et la surface (surface dans properties). Voici comment les récupérer.

In [16]:
[arr["properties"]["c_ar"] for arr in geo["features"]]
Out[16]:
[2, 3, 12, 1, 4, 8, 14, 19, 20, 6, 9, 17, 15, 5, 7, 10, 11, 13, 16, 18]
In [17]:
[arr["properties"]["surface"] for arr in geo["features"]]
Out[17]:
[991153.74474596,
 1170882.82818778,
 16314782.6372674,
 1824612.86048666,
 1600585.63150251,
 3880036.39704363,
 5614877.30907921,
 6792651.12902648,
 5983446.03718297,
 2153095.58639283,
 2178303.27487137,
 5668834.50445393,
 8494994.08101075,
 2539374.62284532,
 4090057.18546976,
 2891739.44162064,
 3665441.55248808,
 7149311.09107136,
 16372542.1289739,
 5996051.30811905]

Nous créons donc maintenant le DataFrame avec ces deux informations.

In [18]:
import pandas

df = pandas.DataFrame({
    "Arrondissement" : [arr["properties"]["c_ar"] for arr in geo["features"]], 
    "Surface" : [arr["properties"]["surface"] for arr in geo["features"]]
})
df
Out[18]:
Arrondissement Surface
0 2 9.911537e+05
1 3 1.170883e+06
2 12 1.631478e+07
3 1 1.824613e+06
4 4 1.600586e+06
5 8 3.880036e+06
6 14 5.614877e+06
7 19 6.792651e+06
8 20 5.983446e+06
9 6 2.153096e+06
10 9 2.178303e+06
11 17 5.668835e+06
12 15 8.494994e+06
13 5 2.539375e+06
14 7 4.090057e+06
15 10 2.891739e+06
16 11 3.665442e+06
17 13 7.149311e+06
18 16 1.637254e+07
19 18 5.996051e+06

Pour relier ces informations à la carte, toujours dans la fonction choropleth(), nous devons définir les éléments suivants :

  • geo_str : les contours au format GeoJSON
  • key_on : l'item dans le GeoJSON avec lequel nous ferons la jointure
  • data : le DataFrame dans lequel nous avons les informations statistiques
  • columns : les deux colonnes à prendre
    1. clé de jointure
    2. mesure statistique
  • fill_color : la palette de couleurs à utiliser (provenant de Color Brewer)

Nous obtenons maintenant la carte suivante.

In [19]:
paris = folium.Map(location = [48.856578, 2.351828], zoom_start = 12)
paris.choropleth(geo_str = geo, key_on = "feature.properties.c_ar",
                 data = df, columns = ["Arrondissement", "Surface"],
                 fill_color = "Spectral")
paris
Out[19]: