Skip to content

Commit 46f038a

Browse files
authored
Mise à jour du premier chapitre sur les figures (#553)
* Intro * up * up * Go ggplot * restructuration
1 parent 9b6c192 commit 46f038a

File tree

13 files changed

+522
-392
lines changed

13 files changed

+522
-392
lines changed

content/manipulation/02_pandas_suite.qmd

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ Le chapitre précédent utilisait quasi exclusivement la librairie `Pandas`. Nou
111111

112112
Comme expliqué ci-dessous, nous allons utiliser une librairie nommée `pynsee` pour récupérer les données de l'Insee utiles à enrichir notre jeu de données de l'Ademe. Cette librairie n'est pas installée par défaut dans `Python`. Avant de pouvoir l'utiliser,
113113
il est nécessaire de l'installer, comme la librairie `great_tables` que nous verrons à la fin de ce chapitre:
114+
:::
114115

115116
::: {.content-visible when-profile="en"}
116117
## Environment
@@ -1377,11 +1378,15 @@ To read this type of file optimally, it is recommended to use the `DuckDB` libra
13771378

13781379

13791380
::: {.content-visible when-profile="fr"}
1381+
13801382
Bien sûr, pour aller plus loin, il faudrait mieux normaliser les données, vérifier que l'information recherchée n'est pas à cheval sur plusieurs colonnes et bien sûr faire de l'inspection visuelle pour détecter les jeux de mots cachés. Mais déjà, en quelques minutes, on a des statistiques partielles sur le phénomène des coiffeurs blagueurs.
1383+
13811384
:::
13821385

13831386
::: {.content-visible when-profile="en"}
1387+
13841388
Of course, to go further, it would be better to normalize the data more thoroughly, check that the information sought is not spread across multiple columns, and conduct visual inspections to detect hidden puns. But already, in just a few minutes, we have partial statistics on the phenomenon of punny hairdressers.
1389+
13851390
:::
13861391

13871392
::: {.content-visible when-profile="fr"}
@@ -1399,9 +1404,11 @@ Pour pallier ce problème, a récémment été mis en oeuvre le [code statistiqu
13991404

14001405

14011406
[^flou]: Autrement, on rentre dans le monde des appariements flous ou des appariements probabilistes. Les appariements flous sont des situations où on ne dispose plus d'un identifiant exact pour associer deux bases mais d'une information partiellement bruitée entre deux sources pour faire cette mise en relation. Par exemple, dans une base de données produit on aura `Coca Cola 33CL` et dans une autre `Coca Cola canette` mais sous ces deux noms sont cachés le même produit. Le chapitre d'[ouverture aux enjeux de recherche textuelle avec `ElasticSearch`](/content/modern-ds/elastic.qmd) est consacré à cette problématique. Les appariements probabilistes sont un autre type d'approche. Dans ceux-ci, on associe des observations dans deux bases non pas sur la base d'un identifiant mais sur la distance entre un ensemble de caractéristiques dans les deux bases. Cette technique est très utilisée dans les statistiques médicales ou dans l'évaluation de politiques publiques sur la base du [_propensity score matching_](https://en.wikipedia.org/wiki/Propensity_score_matching).
1407+
14021408
:::
14031409

14041410
::: {.content-visible when-profile="en"}
1411+
14051412
### The social security number and the issue of individual identifiers' confidentiality
14061413

14071414
For individuals, there exists a unique identifier that allows linking them across different data sources: the [NIR](https://www.cnil.fr/fr/definition/nir-numero-dinscription-au-repertoire), also known as the INSEE number or social security number.
@@ -1414,14 +1421,17 @@ This identifier is mainly present in management databases related to payroll, so
14141421
To address this problem, the [non-significant statistical code (CSNS)](https://www.insee.fr/fr/information/7635825?sommaire=7635842) or hashed NIR, a non-identifying anonymous individual identifier, was recently implemented. The goal of this anonymized identifier is to reduce the dissemination of personal information that, although allowing civil servants and researchers to deterministically link numerous databases, provided analysts with non-essential information about the individuals in question.
14151422

14161423
[^flou]: Otherwise, we enter the realm of fuzzy matching or probabilistic matching. Fuzzy matching occurs when we no longer have an exact identifier to link two databases but have partially noisy information between two sources to make the connection. For example, in a product database, we might have `Coca Cola 33CL` and in another `Coca Cola canette`, but these names hide the same product. The chapter on [Introduction to Textual Search with ElasticSearch](/content/modern-ds/elastic.qmd) addresses this issue. Probabilistic matching is another approach. In these, observations in two databases are associated not based on an identifier but on the distance between a set of characteristics in both databases. This technique is widely used in medical statistics or in the evaluation of public policies based on [_propensity score matching_](https://en.wikipedia.org/wiki/Propensity_score_matching).
1424+
14171425
:::
14181426

14191427
::: {.content-visible when-profile="fr"}
1428+
14201429
## Exercices d'application
14211430

14221431
### Pourquoi a-t-on besoin d'un code commune quand on a déjà son nom ?
14231432

14241433
Cet exercice va revenir un peu en arrière afin de saisir pourquoi nous avons pris comme hypothèse ci-dessus que le code commune était la clé de jointure.
1434+
14251435
:::
14261436

14271437
::: {.content-visible when-profile="en"}
@@ -1436,24 +1446,33 @@ This exercise will take a step back to understand why we assumed above that the
14361446
{{< include "02_pandas_suite/_exo3_solution.qmd" >}}
14371447

14381448
::: {.content-visible when-profile="fr"}
1449+
14391450
Ce petit exercice permet donc de se rassurer car les libellés dupliqués
14401451
sont en fait des noms de commune identiques mais qui ne sont pas dans le même département.
14411452
Il ne s'agit donc pas d'observations dupliquées.
14421453
On peut donc se fier aux codes communes, qui eux sont uniques.
1454+
14431455
:::
14441456

14451457
::: {.content-visible when-profile="en"}
1458+
14461459
This small exercise reassures us as the duplicated labels are actually the same commune names but in different departments. So, these are not duplicated observations. We can thus rely on the commune codes, which are unique.
1460+
14471461
:::
14481462

14491463
::: {.content-visible when-profile="fr"}
1464+
14501465
### Calculer une empreinte carbone grâce à l'association entre des sources
1466+
14511467
:::
14521468

14531469
::: {.content-visible when-profile="en"}
1470+
14541471
### Associating different sources to compute carbon footprints
1472+
14551473
:::
14561474

1475+
14571476
{{< include "02_pandas_suite/_exo4.qmd" >}}
14581477
{{< include "02_pandas_suite/_exo4_solution.qmd" >}}
14591478

@@ -1493,6 +1512,7 @@ directement. Nous allons repartir de ce jeu de données:
14931512
:::
14941513

14951514
::: {.content-visible when-profile="en"}
1515+
14961516
# Formatting descriptive statistics tables
14971517

14981518
A `Pandas` DataFrame is automatically formatted when viewed from a notebook as a minimally styled HTML table. This formatting is convenient for viewing data, a necessary task for data scientists, but it doesn't go much beyond that.
@@ -1540,6 +1560,7 @@ To learn more about constructing tables with `great_tables`, you can replicate t
15401560
:::
15411561

15421562
::: {.content-visible when-profile="fr"}
1563+
15431564
# `Pandas`: vers la pratique et au-delà
15441565

15451566
## `Pandas` dans une chaine d'opérations
@@ -1552,6 +1573,7 @@ Cette manière de procéder est le coeur de la syntaxe `dplyr` en `R` mais n'est
15521573
:::
15531574

15541575
::: {.content-visible when-profile="en"}
1576+
15551577
# `Pandas`: towards practice and beyond
15561578

15571579
## `Pandas` in a chain of operations
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
::: {.exercise}
2+
## Exercice 1 : Produire un premier graphique
3+
4+
Les données comportent plusieurs dimensions pouvant faire l'objet d'une analyse statistique. Nous allons commencer par nous focaliser sur le volume de passage à tel ou tel compteur.
5+
6+
Puisque nous avons comme objectif de synthétiser l'information présente dans notre jeu de données, nous devons d'abord mettre en oeuvre quelques agrégations _ad hoc_ pour produire un
7+
graphique lisible.
8+
9+
1. Garder les dix bornes à la moyenne la plus élevée. Comme pour obtenir un graphique ordonné du plus grand au plus petit avec les méthodes `plot` de `Pandas`, il faut avoir les données ordonnées du plus petit au plus grand (oui c'est bizarre mais c'est comme ça...), réordonner les données.
10+
11+
2. En premier lieu, sans se préoccuper des éléments de style ni de la beauté
12+
du graphique, créer la structure du _barplot_ (diagramme en batons) de la
13+
[page d'analyse des données](https://opendata.paris.fr/explore/dataset/comptage-velo-donnees-compteurs/dataviz/?disjunctive.id_compteur&disjunctive.nom_compteur&disjunctive.id&disjunctive.name).
14+
15+
3. Pour préparer le travail sur la deuxième figure, ne conserver
16+
que les 10 compteurs ayant comptabilisés le plus de vélos.
17+
18+
4. Comme pour la question 2, créer un _barplot_
19+
pour reproduire la figure 2 de l'_open data_ parisien
20+
21+
:::
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
```{python}
2+
df1 = (
3+
df
4+
.groupby('nom_compteur')
5+
.agg({'sum_counts': "mean"})
6+
.sort_values('sum_counts', ascending = False)
7+
.head(10)
8+
.sort_values('sum_counts')
9+
)
10+
```
11+
12+
Les 10 principales stations à l'issue de la question 1
13+
14+
```{python}
15+
#| echo: false
16+
df1.head()
17+
```
18+
19+
20+
```{python}
21+
#| output: false
22+
23+
# Question 2
24+
p1 = df1.plot(kind = "barh", color = 'red')
25+
```
26+
27+
<details>
28+
29+
<summary>
30+
31+
Figure 1 sans travail sur le style:
32+
33+
</summary>
34+
35+
```{python}
36+
#| echo: false
37+
p1.figure
38+
```
39+
40+
</details>
41+
42+
```{python}
43+
# Question 4
44+
df2 = (
45+
df
46+
.groupby('nom_compteur')
47+
.agg({'sum_counts': "sum"})
48+
.sort_values('sum_counts', ascending = False)
49+
.head(10)
50+
.sort_values('sum_counts')
51+
)
52+
```
53+
54+
```{python}
55+
#| output: false
56+
# Question 5
57+
p2 = df2.plot(kind = "barh", color = 'green')
58+
```
59+
60+
::: {.cell .markdown}
61+
```{=html}
62+
<details>
63+
<summary>
64+
Figure 2 sans travail sur le style:
65+
</summary>
66+
```
67+
68+
```{python}
69+
p2.figure
70+
```
71+
72+
```{=html}
73+
</details>
74+
```
75+
:::
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
::: {.exercise}
2+
## Exercice 2: reproduire la première figure avec seaborn
3+
4+
1. Réinitialiser l'index des _dataframes_ `df1` et `df2`
5+
pour avoir une colonne *'Nom du compteur'*. Réordonner les données
6+
de manière décroissante pour obtenir un graphique ordonné dans
7+
le bon sens avec `seaborn`.
8+
9+
2. Refaire le graphique précédent avec la fonction `catplot` de `seaborn`. Pour
10+
contrôler la taille du graphique vous pouvez utiliser les arguments `height` et
11+
`aspect`.
12+
13+
3. Ajouter les titres des axes et le titre du graphique pour le premier graphique
14+
15+
4. Essayez de colorer en rouge l'axe des `x`. Vous pouvez pré-définir un
16+
style avec `sns.set_style("ticks", {"xtick.color": "red"})`
17+
18+
:::
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
```{python}
2+
#| output: false
3+
4+
# Question 1. Reset index and order
5+
df1 = df1.reset_index().sort_values("sum_counts", ascending = False)
6+
df2 = df2.reset_index().sort_values("sum_counts", ascending = False)
7+
```
8+
9+
```{python}
10+
#| output: false
11+
import seaborn as sns
12+
import matplotlib.pyplot as plt
13+
14+
# 2. Graphique avec sns.catplot
15+
g = sns.catplot(
16+
x='sum_counts', y='nom_compteur',
17+
data=df1, kind = "bar",
18+
height = 5, aspect = 4,
19+
color = "red")
20+
```
21+
22+
A l'issue de la question 2, c'est-à-dire en utilisant
23+
`seaborn` pour reproduire de manière minimale
24+
un _barplot_, on obtient :
25+
26+
```{python}
27+
#| echo: false
28+
g
29+
plt.show()
30+
```
31+
32+
Après quelques réglages esthétiques, à l'issue des questions 3 et 4,
33+
on obtient une figure proche de celle du portail _open data_ parisien.
34+
35+
```{python}
36+
#| output: false
37+
38+
# Question 3
39+
g = sns.catplot(
40+
x='sum_counts', y='nom_compteur',
41+
data=df1,
42+
kind = "bar", height = 5, aspect = 4, color = "red"
43+
)
44+
g.set_axis_labels('Moyenne du comptage par heure sur la période sélectionnée', 'Nom du compteur')
45+
plt.title('Les 10 compteurs avec la moyenne horaire la plus élevée')
46+
```
47+
48+
Les paramètres supplémentaires proposés à la question 4 permettent finalement d'obtenir la figure
49+
50+
```{python}
51+
#| output: false
52+
53+
# Question 4
54+
sns.set_style("ticks", {"xtick.color": "red"})
55+
g = sns.catplot(x='sum_counts', y='nom_compteur', data=df1, kind = "bar", height = 10, aspect = 2, color = "red")
56+
g.set_axis_labels('Moyenne du comptage par heure sur la période sélectionnée', 'Nom du compteur')
57+
plt.title('Les 10 compteurs avec la moyenne horaire la plus élevée')
58+
plt.savefig('top10_sns.png', bbox_inches='tight')
59+
```
60+
61+
```{python}
62+
#| echo: false
63+
g
64+
plt.show()
65+
```
66+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
::: {.exercise}
2+
## Exercice 3 (optionnel) : reproduire la figure 2 avec un _lollipop chart_
3+
4+
En suivant l'approche graduelle de l'exercice 2,
5+
refaire le graphique *Les 10 compteurs ayant comptabilisé le plus de vélos*.
6+
7+
💡 Ne pas hésiter à consulter [python-graph-gallery.com/](https://python-graph-gallery.com/) ou à demander de l'aide à `ChatGPT`
8+
9+
:::
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
::: {.exercise}
2+
## Exercice 4: reproduire la première figure avec `plotnine`
3+
4+
Ceci est le même exercice que l'exercice 2. L'objectif est de faire cette figure avec `plotnine`
5+
6+
:::
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
```{python}
2+
df1["nom_compteur"] = pd.Categorical(
3+
df1["nom_compteur"], categories = df1["nom_compteur"]
4+
)
5+
6+
(
7+
ggplot(df1, aes(x = "nom_compteur", y = "sum_counts")) +
8+
geom_bar(stat = "identity", fill = "red") +
9+
coord_flip() +
10+
labs(
11+
title = "Les 10 compteurs avec la moyenne horaire la plus élevée",
12+
x = "Nom du compteur",
13+
y = "Moyenne horaire"
14+
) +
15+
theme(
16+
axis_text_x = element_text(angle = 45, hjust = 1, color = "red"),
17+
axis_title_x = element_text(color = "red"),
18+
plot_title = element_text(hjust = 0.5)
19+
)
20+
)
21+
```
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
::: {.exercise}
3+
## Exercice 5: barplot des comptages mensuels
4+
5+
6+
1. Créer une variable `month`
7+
dont le format respecte, par exemple, le schéma `2019-08` grâce à la bonne option de la méthode `dt.to_period`
8+
1. Appliquer les conseils précédents pour construire et améliorer
9+
graduellement un graphique afin d'obtenir une figure similaire
10+
à la 3e production sur la page de l'_open data_ parisien. Faire cette figure d'abord depuis début 2022 puis sur toute la période de notre historique
11+
1. Question optionnelle: représenter la même information sous forme de _lollipop_
12+
13+
:::

0 commit comments

Comments
 (0)