Skip to content

Commit

Permalink
Petite viz sympa des prenoms (#242)
Browse files Browse the repository at this point in the history
* prépare partie deces

* plus d'éléments

* animation

* labesl

* ajoute minio

* get from minio

* get

* bucket

* bucket

* modif

* ajoute lien

* ajoute lien

* add

* change access rule

* use url

* convert

* animation

* change ordre

* Automated changes

* Automated changes

* un peu de texte

* Automated changes

* Automated changes

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
linogaliana and github-actions[bot] committed Jul 7, 2022
1 parent 23cd4a1 commit 2812ef4
Show file tree
Hide file tree
Showing 2 changed files with 361 additions and 0 deletions.
275 changes: 275 additions & 0 deletions content/course/visualisation/matplotlib/index.qmd
Expand Up @@ -460,6 +460,281 @@ Alors qu'avec le thème sombre (question 2), on obtient :

{{< chart data="plotly2" >}}

# Exercices supplémentaires

Pour ces exercices, il est recommandé de s'inspirer
des modèles présents dans la librairie
de graphiques `Python` présentée
dans https://www.python-graph-gallery.com/

## Les lollipop chart

Cet exercice permet de s'entraîner
sur le fichier des naissances et des
décès de l'Insee. Il s'inspire d'une
excellente visualisation faite
par [Jean Dupin](https://www.jdupin.com/)
sur `Twitter` mettant en avant l'évolution,
année par année, des décomptes des
personnes nommées _"Jean"_ parmi les
personnes nées ou décédées:

{{< tweet 1539567143972487169 >}}

L'animation de Jean Dupin
est beaucoup plus raffinée que
celle que nous allons mettre en
oeuvre.

### Récupération des données

La récupération des données étant un peu complexe,
le code est donné pour vous permettre de vous
concentrer sur l'essentiel (si vous
voulez vous exercer avec le package `requests`,
essayez de le faire vous-même).

Les données des décès sont disponibles de manière
historique dans des zip pour chaque année.

```{python}
#| label: import-death
#| eval: false
import shutil
import requests
import zipfile
import os
import glob
import pandas as pd
def import_by_decade(decennie = 1970):
url = f"https://www.insee.fr/fr/statistiques/fichier/4769950/deces-{decennie}-{decennie+9}-csv.zip"
req = requests.get(url)
with open(f"deces_{decennie}.zip",'wb') as f:
f.write(req.content)
with zipfile.ZipFile(f"deces_{decennie}.zip", 'r') as zip_ref:
zip_ref.extractall(f"deces_{decennie}")
csv_files = glob.glob(os.path.join(f"deces_{decennie}", "*.csv"))
df = [pd.read_csv(f, sep = ";", encoding="utf-8").assign(annee = f) for f in csv_files]
df = pd.concat(df)
df[['nom','prenom']] = df['nomprenom'].str.split("*", expand=True)
df['prenom'] = df['prenom'].str.replace("/","")
df['annee'] = df['annee'].str.rsplit("/").str[-1].str.replace("(Deces_|.csv|deces-)","").astype(int)
shutil.rmtree(f"deces_{decennie}")
os.remove(f"deces_{decennie}.zip")
return df
dfs = [import_by_decade(d) for d in [1970, 1980, 1990, 2000, 2010]]
deces = pd.concat(dfs)
```

Le fichier des naissances est plus simple à récupérer.
Voici le code pour l'obtenir:

```{python}
#| label: import-birth
#| eval: false
year = 2021
url_naissance = f"https://www.insee.fr/fr/statistiques/fichier/2540004/nat{year}_csv.zip"
req = requests.get(url_naissance)
with open(f"naissance_{year}.zip",'wb') as f:
f.write(req.content)
with zipfile.ZipFile(f"naissance_{year}.zip", 'r') as zip_ref:
zip_ref.extractall(f"naissance_{year}")
naissance = pd.read_csv(f"naissance_{year}/nat{year}.csv", sep = ";")
naissance = naissance.dropna(subset = ['preusuel'] )
```

On peut enfin restructurer les `DataFrames` pour obtenir un
seul jeu de données, en se restreignant aux _"JEAN"_:

```{python}
#| label: restructure
#| eval: false
jean_naiss = naissance.loc[naissance['preusuel'] == "JEAN"].loc[:, ['annais', 'nombre']]
jean_naiss = jean_naiss.rename({"annais": "annee"}, axis = "columns")
jean_naiss = jean_naiss.groupby('annee').sum().reset_index()
jean_deces = deces.loc[deces["prenom"] == "JEAN"]
jean_deces = jean_deces.groupby('annee').size().reset_index()
jean_deces.columns = ['annee', "nombre"]
jean_naiss.columns = ['annee', "nombre"]
df = pd.concat(
[
jean_deces.assign(source = "deces"),
jean_naiss.assign(source = "naissance")
])
df = df.loc[df['annee'] != "XXXX"]
df['annee']=df['annee'].astype(int)
df = df.loc[df['annee'] > 1971]
df.head(3)
```

```{python}
#| echo: false
#| label: get-from-minio
import pyarrow as pa
import pyarrow.parquet as pq
from pyarrow import fs
#bucket = "lgaliana"
#s3 = fs.S3FileSystem(endpoint_override="https://"+"minio.lab.sspcloud.fr")
#df = pq.read_pandas(f'{bucket}/diffusion/prenoms.parquet', filesystem=s3).to_pandas()
import requests
req = requests.get("https://minio.lab.sspcloud.fr/lgaliana/diffusion/prenoms.parquet")
with open(f"prenoms.parquet",'wb') as f:
f.write(req.content)
df = pq.read_pandas("prenoms.parquet").to_pandas()
df.head(3)
```


## Représentation graphique

Vous pouvez vous aider du modèle présent
dans https://www.python-graph-gallery.com

{{% box status="exercise" title="Exercice"
icon="fas fa-pencil-alt" %}}

Pour commencer, on va se concentrer sur la
production d'un seul graphique
(décès ou naissance, vous choisissez)

1. Créer un objet `df_plot` qui se restreint à une
source
2. Fixer une année sous le nom `max_year` (par exemple
votre année de naissance). Elle servira ensuite de paramètre
à une fonction
3. Restreindre `df_plot` aux années antérieures à `max_year`
4. Créer une variable `my_range` fixant la séquence des années
entre la plus petite année du dataset et `max_year` (inclus)
5. Créer un array numpy qui vaut `orange` lorsque l'observation
en question est `max_year` et `skyblue` sinon
6. Utiliser les fonctions adéquates de `matplotlib` pour créer
le _lollipop chart_
{{% /box %}}

A ce stade, vous devriez avoir une version fonctionnelle
qui peut servir de
base à la généralisation.

```{python}
#| echo: false
import numpy as np
import matplotlib.pyplot as plt
def plot_jean_year(df, max_year, savefig = True):
fig, axes = plt.subplots(1, 2, sharey = True)
for x, source in enumerate(df.source.value_counts().index.values):
df_plot = df.loc[df['source'] == source]
# Reorder it based on values:
ordered_df = df_plot.copy()#.sort_values(by='nombre')
ordered_df = ordered_df.loc[ordered_df['annee']<=max_year]
my_range=range(ordered_df.annee.min(), max_year + 1)
my_color=np.where(ordered_df['annee']==max_year, 'orange', 'skyblue')
# The horizontal plot is made using the hline() function
axes[x].hlines(y=my_range, xmin=0, xmax=ordered_df['nombre'], color = my_color, alpha=0.4)
axes[x].scatter(ordered_df['nombre'], my_range, color = my_color, alpha=1)
axes[x].set_title(f'{source.upper()}',
fontsize = 14)
axes[x].set_ylim([1972, 2016])
axes[x].set_xlim([0, 5000])
if savefig:
fig.savefig(f'figure_{max_year}.png', bbox_inches='tight')
```


{{% box status="exercise" title="Exercice"
icon="fas fa-pencil-alt" %}}


1. A partir du code précédent, généraliser en utilisant
une boucle `for` à partir du résultat de
`enumerate(df.source.value_counts().index.values)` pour
créer un graphique pour une année donnée de `maxyear`.
Avant cette boucle, ne pas oublier de créer un objet
`matplotlib` vide à remplir dans la boucle

```python
fig, axes = plt.subplots(1, 2, sharey = True)
```

2. Encapsuler ce code dans une fonction qui
prend en argument un `DataFrame` et une
année `max_year`

Voici un exemple d'output pour `max_year = 2010`:

```{python}
#| echo: false
#| label: example-function
plot_jean_year(df, 2010, savefig = False)
```

3. Pour créer une animation, on propose
d'utiliser la solution présentée
dans https://www.python-graph-gallery.com/animation/.
et qui nécessite le logiciel `imagemagick`.
Sauvegarder chaque itération dans un fichier
dont le nom a la structure `figure_{year}.png`.

```{python}
#| echo: false
#| output: false
[plot_jean_year(df, t) for t in range(1972,2016)]
```

Enfin, pour animer les images, on peut utiliser
la librairie `imageio`:

```{python}
import glob
import imageio.v2 as imageio
#os.system("convert -delay 15 figure_*.png animation.gif")
filenames=glob.glob("figure_*.png")
filenames.sort()
with imageio.get_writer('animation.gif', mode='I') as writer:
for filename in filenames:
image = imageio.imread(filename)
writer.append_data(image)
```

L'animation obtenue est la suivante:

![Animation](animation.gif)

{{% /box %}}




# Exercices supplémentaires

https://plotly.com/python/v3/3d-network-graph/
86 changes: 86 additions & 0 deletions content/course/visualisation/matplotlib/script_save_minio.py
@@ -0,0 +1,86 @@
import shutil
import requests
import zipfile
import os
import glob
import pandas as pd

import pyarrow as pa
import pyarrow.parquet as pq
from pyarrow import fs


def import_by_decade(decennie = 1970):

url = f"https://www.insee.fr/fr/statistiques/fichier/4769950/deces-{decennie}-{decennie+9}-csv.zip"

req = requests.get(url)

with open(f"deces_{decennie}.zip",'wb') as f:
f.write(req.content)

with zipfile.ZipFile(f"deces_{decennie}.zip", 'r') as zip_ref:
zip_ref.extractall(f"deces_{decennie}")

csv_files = glob.glob(os.path.join(f"deces_{decennie}", "*.csv"))

df = [pd.read_csv(f, sep = ";", encoding="utf-8").assign(annee = f) for f in csv_files]
df = pd.concat(df)
df[['nom','prenom']] = df['nomprenom'].str.split("*", expand=True)
df['prenom'] = df['prenom'].str.replace("/","")
df['annee'] = df['annee'].str.rsplit("/").str[-1].str.replace("(Deces_|.csv|deces-)","").astype(int)

shutil.rmtree(f"deces_{decennie}")
os.remove(f"deces_{decennie}.zip")

return df


dfs = [import_by_decade(d) for d in [1970, 1980, 1990, 2000, 2010]]
deces = pd.concat(dfs)


# NAISSANCES -----------------

year = 2021
url_naissance = f"https://www.insee.fr/fr/statistiques/fichier/2540004/nat{year}_csv.zip"

req = requests.get(url_naissance)

with open(f"naissance_{year}.zip",'wb') as f:
f.write(req.content)

with zipfile.ZipFile(f"naissance_{year}.zip", 'r') as zip_ref:
zip_ref.extractall(f"naissance_{year}")

naissance = pd.read_csv(f"naissance_{year}/nat{year}.csv", sep = ";")
naissance = naissance.dropna(subset = ['preusuel'] )


# RESTRUCTURE --------------

jean_naiss = naissance.loc[naissance['preusuel'] == "JEAN"].loc[:, ['annais', 'nombre']]
jean_naiss = jean_naiss.rename({"annais": "annee"}, axis = "columns")
jean_naiss = jean_naiss.groupby('annee').sum().reset_index()
jean_deces = deces.loc[deces["prenom"] == "JEAN"]
jean_deces = jean_deces.groupby('annee').size().reset_index()
jean_deces.columns = ['annee', "nombre"]
jean_naiss.columns = ['annee', "nombre"]
df = pd.concat(
[
jean_deces.assign(source = "deces"),
jean_naiss.assign(source = "naissance")
])
df = df.loc[df['annee'] != "XXXX"]
df['annee']=df['annee'].astype(int)
df = df.loc[df['annee'] > 1971]


# SAVE IN MINIO --------------

s3 = fs.S3FileSystem(endpoint_override="http://"+"minio.lab.sspcloud.fr")

bucket = "lgaliana"
table = pa.Table.from_pandas(df, preserve_index=False)
pq.write_table(table, f'{bucket}/diffusion/prenoms.parquet', filesystem=s3)

0 comments on commit 2812ef4

Please sign in to comment.