From e56f6fd50e2d4615ea5f2b1489198fb734a46ec5 Mon Sep 17 00:00:00 2001 From: Lino Galiana <33896139+linogaliana@users.noreply.github.com> Date: Sat, 3 Dec 2022 17:00:55 +0100 Subject: [PATCH] Corrige typos exo compteurs (#329) * install * exo * Automated changes * Automated changes * geoplot * geoplot * Automated changes * Automated changes * note * Automated changes * Automated changes * cartiflette * up * points * points * Automated changes * Automated changes * carte * Automated changes * Automated changes Co-authored-by: github-actions[bot] --- content/course/visualisation/maps/index.qmd | 363 +++++++++++++++----- 1 file changed, 272 insertions(+), 91 deletions(-) diff --git a/content/course/visualisation/maps/index.qmd b/content/course/visualisation/maps/index.qmd index b85679d0e..af6967a6d 100644 --- a/content/course/visualisation/maps/index.qmd +++ b/content/course/visualisation/maps/index.qmd @@ -48,11 +48,22 @@ La pratique de la cartographie se fera, dans ce cours, en répliquant des cartes la page de l'*open-data* de la ville de Paris [ici](https://opendata.paris.fr/explore/dataset/comptage-velo-donnees-compteurs/information/?disjunctive.id_compteur&disjunctive.nom_compteur&disjunctive.id&disjunctive.name). -{{% box status="note" title="Note" icon="fa fa-comment" %}} +::: {.cell .markdown} +```{=html} + +``` +::: @@ -71,57 +82,81 @@ qui ne nous servirons pas mais ralentissent l'import) * [La localisation précise des stations](https://parisdata.opendatasoft.com/explore/dataset/comptage-velo-compteurs/download/?format=geojson&timezone=Europe/Berlin&lang=fr) * [Arrondissements parisiens](https://opendata.paris.fr/explore/dataset/arrondissements/download/?format=geojson&timezone=Europe/Berlin&lang=fr) -Dans la première partie, nous allons utiliser les packages suivants: - -```{python} -import pandas as pd -import geopandas as gpd -import contextily as ctx -import geoplot -import matplotlib.pyplot as plt -import folium +::: {.cell .markdown} +```{=html} + +``` +::: -Installations préalables : +Avant de pouvoir commencer, il est nécessaire d'installer quelques +packages au préalable: -~~~python +```{python} #| eval: false #| echo: true -#| include: true -# Sur anaconda -conda install rtree --yes -~~~ - -~~~python -#| eval: false -#| echo: true -#| include: true +# Installation rtree via Anaconda sur windows +# !conda install rtree --yes # Sur colab !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 geoplot -~~~ +``` + + +Dans la première partie, nous allons utiliser les packages suivants: + +```{python} +import pandas as pd +import geopandas as gpd +import contextily as ctx +import geoplot +import matplotlib.pyplot as plt +import folium +``` + -## Première carte avec l'API `matplotlib` de `geopandas` -{{% box status="exercise" title="Exercice" -icon="fas fa-pencil-alt" %}} -**Exercice 1: Importer les données** +## Première carte avec l'API `matplotlib` de `geopandas` + +::: {.cell .markdown} +```{=html} + +``` +::: + + ```{python} #| include: false @@ -133,7 +168,6 @@ comptages = pd.read_csv('https://github.com/linogaliana/python-datascientist/raw ``` -2. Importer les données de localisation des compteurs à partir de l'url . Nommer cet objet `compteurs`. ```{python} #| include: false @@ -144,17 +178,11 @@ compteurs = gpd.read_file("https://parisdata.opendatasoft.com/explore/dataset/co #compteurs.head() ``` -3. Faire attention à deux valeurs aberrantes. Utiliser -la fonctionalité `str.contains` pour exclure les -observations contenant _"Bike IN"_ ou _"Bike OUT"_ -dans la variable -`nom_compteur` ```{python} compteurs = compteurs.loc[~compteurs["nom_compteur"].str.contains(r"(Bike IN|Bike OUT)")] ``` -3. On va également utiliser les données d'arrondissements de la ville de Paris. Importer ces données depuis . Nommer cet objet `arrondissements`. ```{python} @@ -166,9 +194,6 @@ arrondissements = gpd.read_file("https://opendata.paris.fr/explore/dataset/arron # arrondissements.head() ``` -4. Utiliser la méthode `plot` pour représenter les localisations des compteurs dans l'espace. C'est, on peut l'avouer, peu informatif sans apport extérieur. Il va donc falloir travailler un peu l'esthétique -{{< /box >}} - ```{python} #| include: false #| show: false @@ -177,12 +202,19 @@ arrondissements = gpd.read_file("https://opendata.paris.fr/explore/dataset/arron compteurs.plot() ``` - -{{% box status="warning" title="Warning" icon="fa fa-exclamation-triangle" %}} -On serait tenté de faire un *merge* de la base compteurs et comptages. En l'occurrence, il s'agirait d'un produit cartésien puisqu'il s'agit de faire exploser la base spatiale. Avec des données spatiales, c'est souvent une très mauvaise idée. Cela duplique les points, créant des difficultés à représenter les données mais aussi ralentit les calculs. Sauf à utiliser la méthode `dissolve` (qui va agréger *k* fois la même géométrie...), les géométries sont perdues lorsqu'on effectue des `groupby`. -{{% /box %}} - - +::: {.cell .markdown} +```{=html} + +``` +::: Maintenant, tout est prêt pour une première carte. `matplotlib` fonctionne selon @@ -191,11 +223,11 @@ en surface. L'exception est lorsqu'on ajoute un fond de carte `contextily` via `ctx.add_basemap`: on met cet appel en dernier. -{{% box status="exercise" title="Exercice" -icon="fas fa-pencil-alt" %}} - -**Exercice 2: Première carte** - +::: {.cell .markdown} +```{=html} + +``` +::: ```{python} #| include: false @@ -223,14 +259,12 @@ ax.get_figure() ``` -{{< /box >}} - - -{{% box status="exercise" title="Exercice" -icon="fas fa-pencil-alt" %}} - -**Exercice 3 : Ajouter un fonds de carte avec contextily** +::: {.cell .markdown} +```{=html} + +``` +::: + + ```{python} #| include: false #| echo: false @@ -258,8 +300,6 @@ ax ax.get_figure() ``` -2. Trouver un fonds de carte plus esthétique, qui permette de visualiser les grands axes, parmi ceux possibles. Pour tester l'esthétique, vous pouvez utiliser [cet url](http://leaflet-extras.github.io/leaflet-providers/preview/index.html). La documentation de référence sur les tuiles disponibles est [ici](https://contextily.readthedocs.io/en/latest/providers_deepdive.html) - ```{python} #| include: false #| echo: false @@ -276,7 +316,6 @@ ax ax.get_figure() ``` -{{< /box >}} ```{python} #| output: hide @@ -286,7 +325,11 @@ ax.get_figure().savefig("featured.png") Le principe de la *heatmap* est de construire, à partir d'un nuage de point bidimensionnel, une distribution 2D lissée. La méthode repose sur les estimateurs à noyaux qui sont des méthodes de lissage local. -{{% box status="note" title="Conseil" icon="fa fa-comment" %}} +::: {.cell .markdown} +```{=html} + +``` +::: -{{% box status="exercise" title="Exercice" -icon="fas fa-pencil-alt" %}} -**Exercice 4 : Data cleaning avant de pouvoir faire une heatmap** +::: {.cell .markdown} +```{=html} + +``` +::: + ```{python} #| include: false #| echo: false @@ -355,10 +416,7 @@ df2 = compteurs.merge(df2, left_on = "id_compteur", right_on = 'Identifiant du c ``` -2. Nous allons désormais préparer les données de manière à faire une heatmap. Après avoir compris ce que permet de faire la fonction `expand_points` ci-dessus, créer une fonction `explode_data` qui suive les étapes suivantes. - + Convertir un DataFrame dans le système de projection Lambert 93 (epsg: 2154) - + Appliquer `expand_points` aux noms de variable adéquats. Vous pouvez fixer la valeur de `radius_sd` à `100`. - + Reconvertir l'output au format WGS84 (epsg: 4326) + ```{python} #| include: false @@ -377,9 +435,8 @@ def explode_data(data, index_var = "id_compteur", weight_var = 'Comptage horaire ``` -3. Appliquer cette fonction à `df1` et `df2` -```{python explode points} +```{python} #| include: false #| echo: false @@ -393,12 +450,13 @@ df2_exploded = explode_data(df2) #df1_exploded[["name","npoints","x","y"]].head() ``` -{{< /box >}} -{{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} - -**Exercice 5 : Heatmap, enfin !** +::: {.cell .markdown} +```{=html} + +``` +::: + +```{python} #| include: false #| echo: false @@ -435,9 +498,6 @@ ax.get_figure() -{{< /box >}} - - ## Des cartes réactives grâce à `folium` De plus en plus de données de visualisation reposent sur la cartographie réactive. Que ce soit dans l'exploration des données ou dans la représentation finale de résultats, la cartographie réactive est très appréciable. @@ -469,19 +529,35 @@ ne = compteurs[['lat', 'lon']].max().values.tolist() print(sw, ne) ``` +::: {.cell .markdown} +```{=html} + +``` +::: -{{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} -**Exercice 6 : Visualiser la localisation des stations** +::: {.cell .markdown} +```{=html} + +``` +::: ```{python} #| include: false @@ -495,7 +571,6 @@ sw = compteurs[['lat', 'lon']].min().values.tolist() ne = compteurs[['lat', 'lon']].max().values.tolist() ``` -2. Représenter la localisation des stations en utilisant un zoom optimal. ```{python} @@ -513,7 +588,7 @@ for i in range(0,len(compteurs)): m.fit_bounds([sw, ne]) ``` -{{% /box %}} + {{< rawhtml >}} ```{python} @@ -524,9 +599,11 @@ m {{< /rawhtml >}} -{{% box status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} - -**Exercice 7: Représenter les stations** +::: {.cell .markdown} +```{=html} + +``` +::: + ```{python} #| include: false #| echo: false @@ -570,8 +652,6 @@ for i in range(0,len(df1)): m.fit_bounds([sw, ne]) ``` -{{< /box >}} - La carte obtenue doit ressembler à la suivante: {{< rawhtml >}} @@ -582,3 +662,104 @@ m ``` {{< /rawhtml >}} +# Exercices supplémentaires + +## Densité de population dans la petite couronne parisienne + +Pour cet exercice, le package [`cartiflette`](https://github.com/InseeFrLab/cartogether) +va être pratique pour récupérer un fonds de carte mélangeant arrondissements +parisiens et communes dans les autres villes. + +Nous allons privilégier une carte à ronds proportionnels (_bubble map_) +aux cartes chorolèpthes qui trompent +l'oeil. + +::: {.cell .markdown} +```{=html} + +``` +::: + +```{python} +#| echo: false +import pandas as pd +import matplotlib.pyplot as plt +from cartiflette.s3 import download_vectorfile_url_all + +# 1/ Fonds communaux +df = download_vectorfile_url_all( + values = ["75", "92", "93", "94"], + level="COMMUNE_ARRONDISSEMENT", + decoupage="departement") +# 2/ Départements +departements = df.dissolve("INSEE_DEP") +``` + +```{python} +#| echo: false +# 3/ et 4/ +df['surface'] = df.area.div(10**6) +df['densite'] = (df['POPULATION']/df['surface']) +#5/ +df['markersize'] = 8*df['densite'].div(df.area.div(10**6).sum()) +df['markercolor'] = pd.cut( + df['densite'], + [0, 5000, 15000, 30000, 50000], labels=["Moins de 5", "Entre 5 et 15", "Entre 15 et 30","Plus de 30"]) +``` + +```{python} +#| echo: false +# 6/ +df_points = df.copy() +df_points["geometry"] = df_points["geometry"].centroid +``` + +La carte obtenue devrait ressembler à celle-ci: + +```{python} +#| echo: false +# 7/ +ax = df.plot( + color="lightgray", edgecolor="grey", + figsize=(7,7), + linewidth=0.4, alpha = 0.3) +df_points.plot( + ax=ax, + column="markercolor", markersize="markersize", + alpha=0.7, #categorical=False, + legend = True, + legend_kwds={'loc': 'upper center', "ncol": 2, "bbox_to_anchor": (0.5, 0.05)}, + cmap='viridis') +departements.boundary.plot( + ax = ax, edgecolor = "black", alpha = 0.3) +ax.axis("off") +ax.set(title='Densité de population dans la petite couronne') +ax.get_legend().set_title("Densité de population, en milliers d'habitants par km²") +plt.figtext(0.3, 0.15, + "Source: IGN - AdminExpress", + wrap=True, horizontalalignment='center', + fontsize=8, style = "italic") +``` + +