Une introduction à Pandas¶
- les Series
- les Dataframes
- Des exemples de traitement de données publiques
Contenu sous licence CC BY-SA 4.0, inspiré de https://github.com/pnavaro/big-data
Un outil pour l'analyse de données¶
- première version en 2011
- basé sur NumPy
- largement inspiré par la toolbox R pour la manipulation de données
- structures de données auto-descriptives
- Fonctions de chargement et écriture vers les formats de fichiers courants
- Fonctions de tracé
- Outils statistiques basiques
Les Pandas series¶
Une series Pandas :
- un tableau 1D de données (éventuellement hétérogènes)
- une séquence d'étiquettes appelée index de même longueur que le tableau 1D
l'index peut être du contenu numérique, des chaînes de caractères, ou des dates-heures.
si l'index est une valeur temporelle, alors il s'agit d'une time series
l'index par défaut est
range(len(data))
Illustration¶
import pandas as pd
import numpy as np
pd.set_option("display.max_rows", 8) # Pour limiter le nombre de lignes affichées
print(pd.Series([10, 8, 7, 6, 5]))
print(pd.Series([4, 3, 2, 1, 0.]))
0 10 1 8 2 7 3 6 4 5 dtype: int64 0 4.0 1 3.0 2 2.0 3 1.0 4 0.0 dtype: float64
Une série temporelle¶
Par exemple, les jours qui nous séparent du nouvel an.
today = pd.Timestamp.today()
next_year = today.year + 1
time_period = pd.period_range(today, f'01/01/{next_year}', freq="D")
pd.Series(index=time_period, data=range(len(time_period) - 1, -1, -1))
2023-12-08 24 2023-12-09 23 2023-12-10 22 2023-12-11 21 .. 2023-12-29 3 2023-12-30 2 2023-12-31 1 2024-01-01 0 Freq: D, Length: 25, dtype: int64
Un exemple de traitement¶
On exploite un texte tiré de ce site non officiel : http://www.sacred-texts.com/neu/mphg/mphg.htm
with open("exos/nee.txt") as f:
nee = f.read()
print(nee)
HEAD KNIGHT: Nee! Nee! Nee! Nee! ARTHUR: Who are you? HEAD KNIGHT: We are the Knights Who Say... Nee! ARTHUR: No! Not the Knights Who Say Nee! HEAD KNIGHT: The same! BEDEMIR: Who are they? HEAD KNIGHT: We are the keepers of the sacred words: Nee, Pen, and Nee-wom! RANDOM: Nee-wom! ARTHUR: Those who hear them seldom live to tell the tale! HEAD KNIGHT: The Knights Who Say Nee demand a sacrifice! ARTHUR: Knights of Nee, we are but simple travellers who seek the enchanter who lives beyond these woods. HEAD KNIGHT: Nee! Nee! Nee! Nee! ARTHUR and PARTY: Oh, ow! HEAD KNIGHT: We shall say 'nee' again to you if you do not appease us. ARTHUR: Well, what is it you want? HEAD KNIGHT: We want... a shrubbery! [dramatic chord] ARTHUR: A what? HEAD KNIGHT: Nee! Nee! ARTHUR and PARTY: Oh, ow! ARTHUR: Please, please! No more! We shall find a shrubbery. HEAD KNIGHT: You must return here with a shrubbery or else you will never pass through this wood alive! ARTHUR: O Knights of Nee, you are just and fair, and we will return with a shrubbery. HEAD KNIGHT: One that looks nice. ARTHUR: Of course. HEAD KNIGHT: And not too expensive. ARTHUR: Yes. HEAD KNIGHTS: Now... go!
Dénombrer les occurrences de mots¶
On supprime la ponctuation
for s in '.', '!', ',', '?', ':', '[', ']', 'ARTHUR', 'HEAD KNIGHT', 'PARTY':
nee = nee.replace(s, '')
On transforme en minuscule et on découpe en une liste de mots
nees = nee.lower().split()
print(nees)
['nee', 'nee', 'nee', 'nee', 'who', 'are', 'you', 'we', 'are', 'the', 'knights', 'who', 'say', 'nee', 'no', 'not', 'the', 'knights', 'who', 'say', 'nee', 'the', 'same', 'bedemir', 'who', 'are', 'they', 'we', 'are', 'the', 'keepers', 'of', 'the', 'sacred', 'words', 'nee', 'pen', 'and', 'nee-wom', 'random', 'nee-wom', 'those', 'who', 'hear', 'them', 'seldom', 'live', 'to', 'tell', 'the', 'tale', 'the', 'knights', 'who', 'say', 'nee', 'demand', 'a', 'sacrifice', 'knights', 'of', 'nee', 'we', 'are', 'but', 'simple', 'travellers', 'who', 'seek', 'the', 'enchanter', 'who', 'lives', 'beyond', 'these', 'woods', 'nee', 'nee', 'nee', 'nee', 'and', 'oh', 'ow', 'we', 'shall', 'say', "'nee'", 'again', 'to', 'you', 'if', 'you', 'do', 'not', 'appease', 'us', 'well', 'what', 'is', 'it', 'you', 'want', 'we', 'want', 'a', 'shrubbery', 'dramatic', 'chord', 'a', 'what', 'nee', 'nee', 'and', 'oh', 'ow', 'please', 'please', 'no', 'more', 'we', 'shall', 'find', 'a', 'shrubbery', 'you', 'must', 'return', 'here', 'with', 'a', 'shrubbery', 'or', 'else', 'you', 'will', 'never', 'pass', 'through', 'this', 'wood', 'alive', 'o', 'knights', 'of', 'nee', 'you', 'are', 'just', 'and', 'fair', 'and', 'we', 'will', 'return', 'with', 'a', 'shrubbery', 'one', 'that', 'looks', 'nice', 'of', 'course', 'and', 'not', 'too', 'expensive', 'yes', 's', 'now', 'go']
On crée un object compteur
from collections import Counter
c = Counter(nees)
On ne retient que les mots qui apparaissent plus de 2 fois
c = Counter({x: c[x] for x in c if c[x] > 2})
c
Counter({'nee': 16, 'who': 8, 'the': 8, 'you': 7, 'we': 7, 'are': 6, 'and': 6, 'a': 6, 'knights': 5, 'say': 4, 'of': 4, 'shrubbery': 4, 'not': 3})
Création d'une série Pandas à partir de l'objet c
¶
Notons que la série est ordonnée avec un index croissant (dans l'ordre alphabétique).
words = pd.Series(c)
words
nee 16 who 8 are 6 you 7 .. of 4 and 6 a 6 shrubbery 4 Length: 13, dtype: int64
Représentation dans un histogramme¶
On commence par positionner certains paramètres de tracé
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
# Pour un rendu plus abouti https://seaborn.pydata.org/introduction.html
import seaborn as sns
sns.set()
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (9, 6) # Pour obtenir des figures plus grandes
words.plot(kind='bar');
Indexation et slicing¶
L'indexation et le slicing est une sorte de mélange entre les listes et les dictionnaires :
series[index]
pour accéder à la donnée correspondant Ãindex
series[i]
oùi
est un entier qui suit les règles de l'indexation en python
Nombre d'occurrences de la chaîne nee
print(words.index) # Pour rappel
words["nee"]
Index(['nee', 'who', 'are', 'you', 'we', 'the', 'knights', 'say', 'not', 'of', 'and', 'a', 'shrubbery'], dtype='object')
16
Trois dernières données de la série
words[-3:]
and 6 a 6 shrubbery 4 dtype: int64
Ordonner la série¶
words.sort_values(inplace=True)
words.plot(kind='barh'); # On change pour un histogramme horizontal
Les Pandas Dataframes¶
- C'est la structure de base de Pandas
- un Dataframe est une structure de données tabulées à deux dimensions, potentiellement hétérogène
- un Dataframe est constitué de lignes et colonnes portant des étiquettes
- C'est en quelque sorte un "dictionnaire de Series".
Un exemple avec les arbres de la ville de Strasbourg¶
Conformément à l'ordonnance du 6 juin 2005 (qui prolonge la loi CADA), la ville de Strasbourg a commencé à mettre en ligne ses données publiques.
En particulier des données sur ses arbres : https://www.strasbourg.eu/arbres-alignements-espaces-verts
On veut exploiter ces données. Pour ce faire, on va :
- télécharger les données
- les charger dans un Dataframe
- les nettoyer/filtrer
- les représenter graphiquement
On télécharge et on nettoie¶
On commence par définir une fonction qui télécharge et extrait une archive zip.
from io import BytesIO
from urllib.request import urlopen
from zipfile import ZipFile
def download_unzip(zipurl, destination):
"""Download zipfile from URL and extract it to destination"""
with urlopen(zipurl) as zipresp:
with ZipFile(BytesIO(zipresp.read())) as zfile:
zfile.extractall(destination)
On l'utilise pour télécharger l'archive des données ouvertes de la ville de Strasbourg.
download_unzip("https://www.strasbourg.eu/documents/976405/1168331/CUS_CUS_DEPN_ARBR.zip", "arbres")
On liste le contenu de l'archive
%ls -R arbres
arbres: CUS_CUS_DEPN_ARBR.csv
On charge le fichier csv comme un Dataframe.
arbres_all = pd.read_csv("arbres/CUS_CUS_DEPN_ARBR.csv",
encoding='latin', # Pour prendre en compte l'encoding qui n'est pas utf-8
delimiter=";", # Le caractère séparateur des colonnes
decimal=',') # Pour convertir les décimaux utilisant la notation ,
arbres_all
Num point vert | point vert NOM_USUEL | point vert ADRESSE | point vert VILLE | Point vert Quartier usuel | point vert TYPOLOGIE | n°arbre SIG | Libellé_Essence | Diam fût à 1m | Hauteur arbre | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 450.0 | Rue du Houblon | Houblon (rue du) | STRASBOURG | CENTRE | ACCE - Accompagnement de cours d'eau | 15783 | Tilia x 'Euchlora' | 25.0 | 8.0 |
1 | 450.0 | Rue du Houblon | Houblon (rue du) | STRASBOURG | CENTRE | ACCE - Accompagnement de cours d'eau | 15784 | Tilia x 'Euchlora' | 8.0 | 6.5 |
2 | 450.0 | Rue du Houblon | Houblon (rue du) | STRASBOURG | CENTRE | ACCE - Accompagnement de cours d'eau | 15785 | Tilia x 'Euchlora' | 33.0 | 7.5 |
3 | 450.0 | Rue du Houblon | Houblon (rue du) | STRASBOURG | CENTRE | ACCE - Accompagnement de cours d'eau | 15786 | Tilia x 'Euchlora' | 23.0 | 9.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
79134 | 859.0 | Krummerort | Oberjaegerhof (route de l') | STRASBOURG | STOCKFELD | ACJF - Accompagnement de jardins familiaux | 87652 | Picea abies | 30.0 | 10.0 |
79135 | 859.0 | Krummerort | Oberjaegerhof (route de l') | STRASBOURG | STOCKFELD | ACJF - Accompagnement de jardins familiaux | 87653 | Picea abies | 30.0 | 10.0 |
79136 | 859.0 | Krummerort | Oberjaegerhof (route de l') | STRASBOURG | STOCKFELD | ACJF - Accompagnement de jardins familiaux | 87654 | Picea abies | 30.0 | 10.0 |
79137 | 859.0 | Krummerort | Oberjaegerhof (route de l') | STRASBOURG | STOCKFELD | ACJF - Accompagnement de jardins familiaux | 87655 | Picea abies | 30.0 | 10.0 |
79138 rows × 10 columns
print(f"{len(arbres_all)} arbres recensés !")
79138 arbres recensés !
On commence par lister les villes citées.
print(set(arbres_all['point vert VILLE']))
{'LINGOLSHEIM', 'WOLFISHEIM', 'ESCHAU', 'OBERSCHAEFFOLSHEIM', 'ILLKIRCH-GRAFFENSTADEN', 'HOENHEIM', 'FEGERSHEIM', 'MITTELHAUSBERGEN', 'HOLTZHEIM', 'ECKBOLSHEIM', 'WANTZENAU (LA)', 'NIEDERHAUSBERGEN', 'LIPSHEIM', 'BLAESHEIM', 'VENDENHEIM', 'WANTZENAU (La)', 'REICHSTETT', 'STRASBOURG', 'SOUFFELWEYERSHEIM', 'PLOBSHEIM', 'GEISPOLSHEIM', 'LAMPERTHEIM', 'OSTWALD', 'SCHILTIGHEIM', 'BISCHHEIM', 'MUNDOLSHEIM', 'ECKWERSHEIM', 'ENTZHEIM', 'OBERHAUSBERGEN', nan}
On ne s'intéresse qu'à la ville de Strasbourg
arbres = arbres_all[arbres_all['point vert VILLE'] == "STRASBOURG"]
print(f"Il ne reste plus que {len(arbres)} arbres.")
Il ne reste plus que 64624 arbres.
On enlève les données incomplètes.
arbres = arbres.dropna(axis=0, how='any')
print(f"Il ne reste plus que {len(arbres)} arbres.")
Il ne reste plus que 61382 arbres.
On veut comptabiliser les essences¶
On extrait la série des essences.
essences = set(arbres['Libellé_Essence'])
print(f"Il y a {len(essences)} essences différentes !")
Il y a 456 essences différentes !
Les 5 premières dans l'ordre alphabétique :
sorted(list(essences))[:5]
['Abies (sp non determinée)', 'Abies alba', 'Abies cephalonica', 'Abies concolor', 'Abies grandis']
C'est bientôt Noël, on se limite aux sapins !
sapins = arbres[arbres['Libellé_Essence'].str.match("^Abies")]
sapins
Num point vert | point vert NOM_USUEL | point vert ADRESSE | point vert VILLE | Point vert Quartier usuel | point vert TYPOLOGIE | n°arbre SIG | Libellé_Essence | Diam fût à 1m | Hauteur arbre | |
---|---|---|---|---|---|---|---|---|---|---|
2656 | 620.0 | Parc des Contades | Hirschler (rue René) | STRASBOURG | CONSEIL-XV | PARC - Parcs | 20379 | Abies concolor | 26.0 | 14.0 |
2657 | 620.0 | Parc des Contades | Hirschler (rue René) | STRASBOURG | CONSEIL-XV | PARC - Parcs | 20380 | Abies concolor | 23.0 | 13.5 |
9235 | 704.0 | Groupe scolaire Ampère | Wattwiller (39, rue de) | STRASBOURG | NEUDORF | EESE2 - Espaces des établissements sociaux et ... | 25143 | Abies alba | 10.0 | 6.0 |
9575 | 1151.0 | Groupe scolaire Charles Adolphe Wurtz | Rieth (51, rue du) | STRASBOURG | CRONENBOURG | EESE2 - Espaces des établissements sociaux et ... | 44237 | Abies nordmanniana | 10.0 | 4.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
75935 | 318.0 | Parc de la Citadelle -(01)- Secteur Centre et Est | Belges (quai des) | STRASBOURG | ESPLANADE | PARC - Parcs | 11419 | Abies nordmanniana | 38.0 | 18.6 |
75940 | 318.0 | Parc de la Citadelle -(01)- Secteur Centre et Est | Belges (quai des) | STRASBOURG | ESPLANADE | PARC - Parcs | 11424 | Abies concolor | 28.0 | 11.6 |
75941 | 318.0 | Parc de la Citadelle -(01)- Secteur Centre et Est | Belges (quai des) | STRASBOURG | ESPLANADE | PARC - Parcs | 11425 | Abies concolor | 18.0 | 5.4 |
78276 | 997.0 | Parc de Pourtalès | Mélanie (rue) | STRASBOURG | ROBERTSAU | PARC - Parcs | 40616 | Abies alba | 29.0 | 16.0 |
114 rows × 10 columns
On trace leur répartition
ax = sapins['Libellé_Essence'].value_counts().plot(kind="barh");
ax.set_xlabel("nombre d'arbres")
Text(0.5, 0, "nombre d'arbres")
On veut faire des stastistiques par essence¶
On veut connaître la hauteur moyenne par essence pour chaque type Abies.
hauteurs_sapins = sapins.groupby(['Libellé_Essence'])["Hauteur arbre"]
pd.concat([hauteurs_sapins.min().rename('min'),
hauteurs_sapins.mean().rename('moyenne'),
hauteurs_sapins.max().rename('max')],
axis=1).plot(kind='barh');
Représentation géographique¶
On voudrait maintenant représenter la répartition des arbres par quartiers.
On utilise à nouveau les données ouvertes de la ville de Strasbourg, cette fois-ci concernant les quartiers : https://data.strasbourg.eu/explore/dataset/strasbourg-15-quartiers/information/
On télécharge, on extrait l'archive et on liste son contenu.
download_unzip("https://data.strasbourg.eu/explore/dataset/strasbourg-15-quartiers/download/?format=shp&timezone=Europe/Berlin&lang=fr", "quartiers")
%ls -R quartiers
quartiers: strasbourg-15-quartiers.dbf strasbourg-15-quartiers.shp strasbourg-15-quartiers.prj strasbourg-15-quartiers.shx
%pip install geopandas folium
Requirement already satisfied: geopandas in /home/jovyan/.local/lib/python3.11/site-packages (0.14.1) Requirement already satisfied: folium in /home/jovyan/.local/lib/python3.11/site-packages (0.15.1) Requirement already satisfied: fiona>=1.8.21 in /opt/conda/lib/python3.11/site-packages (from geopandas) (1.9.4.post1) Requirement already satisfied: packaging in /opt/conda/lib/python3.11/site-packages (from geopandas) (23.1) Requirement already satisfied: pandas>=1.4.0 in /home/jovyan/.local/lib/python3.11/site-packages (from geopandas) (2.1.4) Requirement already satisfied: pyproj>=3.3.0 in /opt/conda/lib/python3.11/site-packages (from geopandas) (3.6.1) Requirement already satisfied: shapely>=1.8.0 in /opt/conda/lib/python3.11/site-packages (from geopandas) (2.0.1) Requirement already satisfied: branca>=0.6.0 in /opt/conda/lib/python3.11/site-packages (from folium) (0.6.0) Requirement already satisfied: jinja2>=2.9 in /opt/conda/lib/python3.11/site-packages (from folium) (3.1.2) Requirement already satisfied: numpy in /home/jovyan/.local/lib/python3.11/site-packages (from folium) (1.26.2) Requirement already satisfied: requests in /opt/conda/lib/python3.11/site-packages (from folium) (2.31.0) Requirement already satisfied: xyzservices in /home/jovyan/.local/lib/python3.11/site-packages (from folium) (2023.10.1) Requirement already satisfied: attrs>=19.2.0 in /opt/conda/lib/python3.11/site-packages (from fiona>=1.8.21->geopandas) (23.1.0) Requirement already satisfied: certifi in /opt/conda/lib/python3.11/site-packages (from fiona>=1.8.21->geopandas) (2023.7.22) Requirement already satisfied: click~=8.0 in /opt/conda/lib/python3.11/site-packages (from fiona>=1.8.21->geopandas) (8.1.7) Requirement already satisfied: click-plugins>=1.0 in /opt/conda/lib/python3.11/site-packages (from fiona>=1.8.21->geopandas) (1.1.1) Requirement already satisfied: cligj>=0.5 in /opt/conda/lib/python3.11/site-packages (from fiona>=1.8.21->geopandas) (0.7.2) Requirement already satisfied: six in /opt/conda/lib/python3.11/site-packages (from fiona>=1.8.21->geopandas) (1.16.0) Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.11/site-packages (from jinja2>=2.9->folium) (2.1.3) Requirement already satisfied: python-dateutil>=2.8.2 in /opt/conda/lib/python3.11/site-packages (from pandas>=1.4.0->geopandas) (2.8.2) Requirement already satisfied: pytz>=2020.1 in /opt/conda/lib/python3.11/site-packages (from pandas>=1.4.0->geopandas) (2023.3.post1) Requirement already satisfied: tzdata>=2022.1 in /opt/conda/lib/python3.11/site-packages (from pandas>=1.4.0->geopandas) (2023.3) Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/lib/python3.11/site-packages (from requests->folium) (3.2.0) Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.11/site-packages (from requests->folium) (3.4) Requirement already satisfied: urllib3<3,>=1.21.1 in /opt/conda/lib/python3.11/site-packages (from requests->folium) (2.0.4) Note: you may need to restart the kernel to use updated packages.
On charge le fichier comme un GeoDataFrame
:
import geopandas as gpd
gdf_quartiers = gpd.read_file("quartiers/strasbourg-15-quartiers.shp")
print(f"gdf_quartiers est de type {type(gdf_quartiers)}.")
gdf_quartiers
gdf_quartiers est de type <class 'geopandas.geodataframe.GeoDataFrame'>.
code_sct | libelle | geometry | |
---|---|---|---|
0 | 1B | CRONENBOURG | POLYGON ((7.71391 48.58707, 7.71380 48.58767, ... |
1 | 10B | PORT DU RHIN | POLYGON ((7.78154 48.57908, 7.78152 48.57925, ... |
2 | 5 | BOURSE-ESPLANADE-KRUTENAU | POLYGON ((7.75002 48.57454, 7.74983 48.57463, ... |
3 | 11A | NEUHOF1 | POLYGON ((7.75904 48.56022, 7.75924 48.55992, ... |
... | ... | ... | ... |
11 | 6 | ORANGERIE-CONSEIL DES XV | POLYGON ((7.78101 48.57854, 7.78127 48.57923, ... |
12 | 2A | KOENIGSHOFFEN | POLYGON ((7.72644 48.57736, 7.72633 48.57730, ... |
13 | 11B | NEUHOF2 | POLYGON ((7.76317 48.52212, 7.76333 48.52212, ... |
14 | 4 | CENTRE | POLYGON ((7.75878 48.58799, 7.75891 48.58792, ... |
15 rows × 3 columns
Avec Folium, on commence par représenter ces données géographiques sur un fond de carte.
import folium
# On crée une carte initialement centrée sur Strasbourg
STRASBOURG_COORD = (48.58, 7.75)
stras_map = folium.Map(STRASBOURG_COORD, zoom_start=11, tiles='cartodbpositron')
# On ajoute les données des quartiers
folium.GeoJson(gdf_quartiers).add_to(stras_map)
# On enregistre dans un fichier html
stras_map.save('stras_map.html')
# On trace dans le notebook
display(stras_map)
À l'emplacement de ces quartiers, on souhaite représenter une échelle de couleur en fonction de la densité d'arbres.
On constate que les noms de quartiers sont différents de ceux du jeu de données sur les arbres.
from pprint import pformat
set_quartiers = set(gdf_quartiers['libelle'])
set_arbres = set(arbres['Point vert Quartier usuel'])
def print_set_data(s: set):
"""Print set and its length"""
print(f"{pformat(s)} -> {len(s)}")
print_set_data(set_quartiers)
print_set_data(set_arbres)
{'BOURSE-ESPLANADE-KRUTENAU', 'CENTRE', 'CRONENBOURG', 'ELSAU', 'HAUTEPIERRE', 'KOENIGSHOFFEN', 'MEINAU', 'MONTAGNE-VERTE', 'NEUDORF', 'NEUHOF1', 'NEUHOF2', 'ORANGERIE-CONSEIL DES XV', 'PORT DU RHIN', 'ROBERTSAU', 'TRIBUNAL-GARE-PORTE DE SCHIRMECK'} -> 15 {'BOURSE', 'CENTRE', 'CONSEIL-XV', 'CRONENBOURG', 'ELSAU', 'ESPLANADE', 'GARE', 'HAUTEPIERRE', 'KOENIGSHOFFEN', 'KRUTENAU', 'MEINAU', 'MONTAGNE VERTE', 'MUSAU', 'NEUDORF', 'NEUHOF', 'ORANGERIE', 'PLAINE DES BOUCHERS', 'POLYGONE', 'PORT DU RHIN', 'PORTE DE SCHIRMECK', 'ROBERTSAU', 'STOCKFELD', 'TRIBUNAL', 'WACKEN'} -> 24
Certains noms figurent dans les deux jeux de données :
intersection = set_quartiers.intersection(set_arbres)
print_set_data(intersection)
{'CENTRE', 'CRONENBOURG', 'ELSAU', 'HAUTEPIERRE', 'KOENIGSHOFFEN', 'MEINAU', 'NEUDORF', 'PORT DU RHIN', 'ROBERTSAU'} -> 9
D'autres sont différents :
difference = set_arbres.difference(set_quartiers)
print_set_data(difference)
{'BOURSE', 'CONSEIL-XV', 'ESPLANADE', 'GARE', 'KRUTENAU', 'MONTAGNE VERTE', 'MUSAU', 'NEUHOF', 'ORANGERIE', 'PLAINE DES BOUCHERS', 'POLYGONE', 'PORTE DE SCHIRMECK', 'STOCKFELD', 'TRIBUNAL', 'WACKEN'} -> 15
Afin d'obtenir de faire correspondre parfaitement les noms des deux jeux de données, on convertit les noms dans le Dataframe arbres
en supposant les correspondances ci-dessous.
convertion_dict = {
'BOURSE': 'BOURSE-ESPLANADE-KRUTENAU',
'CONSEIL-XV': 'ORANGERIE-CONSEIL DES XV',
'ESPLANADE': 'BOURSE-ESPLANADE-KRUTENAU',
'GARE': 'TRIBUNAL-GARE-PORTE DE SCHIRMECK',
'KRUTENAU': 'BOURSE-ESPLANADE-KRUTENAU',
'MONTAGNE VERTE': 'MONTAGNE-VERTE',
'MUSAU': 'NEUDORF',
'NEUHOF': 'NEUHOF1',
'ORANGERIE': 'ORANGERIE-CONSEIL DES XV',
'PLAINE DES BOUCHERS': 'MEINAU',
'POLYGONE': 'NEUHOF1',
'PORTE DE SCHIRMECK': 'TRIBUNAL-GARE-PORTE DE SCHIRMECK',
'STOCKFELD': 'NEUHOF2',
'TRIBUNAL': 'TRIBUNAL-GARE-PORTE DE SCHIRMECK',
'WACKEN': 'ROBERTSAU'
}
for k, v in convertion_dict.items():
arbres['Point vert Quartier usuel'] = \
arbres['Point vert Quartier usuel'].replace(to_replace=k, value=v)
arbres
Num point vert | point vert NOM_USUEL | point vert ADRESSE | point vert VILLE | Point vert Quartier usuel | point vert TYPOLOGIE | n°arbre SIG | Libellé_Essence | Diam fût à 1m | Hauteur arbre | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 450.0 | Rue du Houblon | Houblon (rue du) | STRASBOURG | CENTRE | ACCE - Accompagnement de cours d'eau | 15783 | Tilia x 'Euchlora' | 25.0 | 8.0 |
1 | 450.0 | Rue du Houblon | Houblon (rue du) | STRASBOURG | CENTRE | ACCE - Accompagnement de cours d'eau | 15784 | Tilia x 'Euchlora' | 8.0 | 6.5 |
2 | 450.0 | Rue du Houblon | Houblon (rue du) | STRASBOURG | CENTRE | ACCE - Accompagnement de cours d'eau | 15785 | Tilia x 'Euchlora' | 33.0 | 7.5 |
3 | 450.0 | Rue du Houblon | Houblon (rue du) | STRASBOURG | CENTRE | ACCE - Accompagnement de cours d'eau | 15786 | Tilia x 'Euchlora' | 23.0 | 9.0 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
79134 | 859.0 | Krummerort | Oberjaegerhof (route de l') | STRASBOURG | NEUHOF2 | ACJF - Accompagnement de jardins familiaux | 87652 | Picea abies | 30.0 | 10.0 |
79135 | 859.0 | Krummerort | Oberjaegerhof (route de l') | STRASBOURG | NEUHOF2 | ACJF - Accompagnement de jardins familiaux | 87653 | Picea abies | 30.0 | 10.0 |
79136 | 859.0 | Krummerort | Oberjaegerhof (route de l') | STRASBOURG | NEUHOF2 | ACJF - Accompagnement de jardins familiaux | 87654 | Picea abies | 30.0 | 10.0 |
79137 | 859.0 | Krummerort | Oberjaegerhof (route de l') | STRASBOURG | NEUHOF2 | ACJF - Accompagnement de jardins familiaux | 87655 | Picea abies | 30.0 | 10.0 |
61382 rows × 10 columns
On vérifie que l'ensemble des quartiers est le même pour les deux Dataframes quartiers
et arbres
.
set(arbres['Point vert Quartier usuel']) == set_quartiers
True
On construit une série qui contient le nombre d'arbres par quartier.
arbres_quartiers = arbres['Point vert Quartier usuel'].value_counts()
On trace le graphique en barres correspondant.
arbres_quartiers.plot(kind='barh');
On construit une nouvelle Series correspondant à l'aire de chaque quartier en $m^2$.
Pour que le calcul des aires soit fiables, les données de gdf_quartier
doivent être projetées.
Pour la France métropolitaine, on utilise la projection EPSG:2154, c'est-à -dire Lambert 93.
aires = gdf_quartiers.to_crs(2154).area
aires.index = gdf_quartiers["libelle"]
aires.sum()
78225408.33604598
On calcule la densité d'arbres par hectare.
densite = arbres_quartiers / aires * 10000
densite
BOURSE-ESPLANADE-KRUTENAU 15.316666 CENTRE 39.367782 CRONENBOURG 12.038048 ELSAU 10.169214 ... ORANGERIE-CONSEIL DES XV 19.344847 PORT DU RHIN 4.874460 ROBERTSAU 5.607219 TRIBUNAL-GARE-PORTE DE SCHIRMECK 6.536751 Length: 15, dtype: float64
On trace une carte colorée par la densité d'arbres avec l'objet Choropleth
.
folium.Choropleth(geo_data=gdf_quartiers,
data=densite,
key_on='feature.properties.libelle',
fill_color='YlGn',
fill_opacity=0.5,
line_opacity=0.2,
legend_name=r"Nombre d\'arbres par hectare").add_to(stras_map)
stras_map.save('stras_tree.html')
display(stras_map)
Exercice¶
Ecrire la fonction plot_essence()
qui prend en argument une essence d'arbres et qui trace le nombre d'arbres correspondant par quartier en utilisant choropleth
.
def plot_essence(essence):
pass
# Votre code ici
plot_essence("Acer")
# Décommentez puis exécutez pour afficher le corrigé :
#%load exos/snippets/plot_essence.py
Utilisation des widgets ipython¶
On souhaite proposer à l'utilisateur un menu de sélection pour afficher le nombre d'essences par quartier. Pour limiter la taille du menu, on regroupe les essences par genre (première partie du nom latin).
genres = set([nom.split()[0] for nom in essences])
La bibliothèque ipywidgets permet de générer très facilement un menu déroulant.
La fonction plot_essence()
est alors appelée avec comme
from ipywidgets import interact
interact(plot_essence, essence=sorted(genres));
interactive(children=(Dropdown(description='essence', options=('Abies', 'Acer', 'Aesculus', 'Ailanthus', 'Albi…
Vers des applications web¶
ipywidgets permet de faire beaucoup plus que l'exemple ci-dessus. De plus, on peut transformer facilement un notebook en application avec voilà . Par exemple, transformons le notebook exos/stras_arbres.ipynb :
Dans un terminal, installer voila
:
pip install voila
Exécuter voila
sur le notebook :
voila exos/stras_arbres.ipynb
Références¶
- La documentation officielle
- Le cours de Pierre Navaro
- Le cours de Jake Vanderplas
- Des sites personnels de développeurs :
Annexe : une autre façon de représenter les occurences de mots¶
Cette fois, on n'utilise pas pandas
mais le module wordcloud
.
from wordcloud import WordCloud
# On crée un objet Wordcloud
wcloud = WordCloud(background_color="white", width=480, height=480, margin=0).generate(nee)
# On affiche l'image avec matplotlib
plt.imshow(wcloud, interpolation='bilinear')
plt.axis("off")
plt.margins(x=0, y=0)