<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Tech - manipulation de données avec pandas
pandas est la librairie incontournable pour manipuler les données. Elle permet de manipuler aussi bien les données sous forme de tables qu'elle peut récupérer ou exporter en différents formats. Elle permet également de créer facilement des graphes.
Step1:  Enoncé
La librairie pandas implémente la classe DataFrame. C'est une structure de table, chaque colonne porte un nom et contient un seul type de données. C'est très similaire au langage SQL.
Création d'un dataframe
Il existe une grande variété pour créer un DataFrame. Voici les deux principaux. Le premier 
Step2:  La lecture depuis un fichier 
Step3:  La maîtrise des index
Les index fonctionnent à peu près comme numpy mais offre plus d'options puisque les colonnes mais aussi les lignes ont un nom.
Accès par colonne
Step4:  Accès par ligne (uniquement avec 
Step5:  Accès par positions avec loc.
Step6:  Accès par positions entières avec iloc.
Step7:  La maîtrise des index des lignes
La création d'un dataframe donne l'impression que les index des lignes sont des entiers mais cela peut être changer
Step8:  Il faut se souvenir de cette particularité lors de la fusion de tables.
La maîtrise des index des colonnes
Les colonnes sont nommées.
Step9:  On peut les renommer.
Step10:  L'opérateur 
Step11:  Lien vers numpy
pandas utilise numpy pour stocker les données. Il est possible de récupérer des matrices depuis des DataFrame avec values.
Step12:  La maîtrise du nan
nan est une convention pour désigner une valeur manquante.
Step13:  La maîtrise des types
Un dataframe est défini par ses dimensions et chaque colonne a un type potentiellement différent.
Step14:  On peut changer un type, donc convertir toutes les valeurs d'une colonne vers un autre type.
Step15:  Création de colonnes
On peut facilement créer de nouvelles colonnes.
Step16:  Modifications de valeurs
On peut les modifier une à une en utilisant les index. Les notations sont souvent intuitives. Elles ne seront pas toutes détaillées. Ci-dessous un moyen de modifer certaines valeurs selon une condition.
Step17:  Une erreur ou warning fréquent
Step18:  A value is trying to be set on a copy of a slice from a DataFrame. 
Step19:  La maîtrise des fonctions
Les fonctions de pandas créent par défaut un nouveau dataframe plutôt que de modifier un dataframe existant. Cela explique pourquoi parfois la mémoire se retrouve congestionnée. La page 10 minutes to pandas est un bon début.
création 
Step20:  Les dates sont considérées comme des chaînes de caractères. Il est plus simple pour réaliser des opérations de convertir la colonne sous forme de dates.
Step21:  On supprime les colonnes relatives aux régions et à l'âge puis on aggrège par jour.
Step22:  Avec échelle logarithmique.
Step23:  Q1 
Step24:  Q2 
Step25:  Q3 
Step26:  Petit aparté
On veut savoir combien de temps les gens restent à l'hôpital avant de sortir, en supposant que le temps de guérison est à peu près identique au temps passé lorsque l'issue est tout autre. Je pensais calculer les corrélations entre la série des décès et celles de réanimations décalées de plusieurs jours en me disant qu'un pic de corrélation pourrait indiquer une sorte de durée moyenne de réanimation.
Step27:  Il apparaît que ces corrélations sont très différentes selon qu'on les calcule sur les dernières données et les premières semaines. Cela semblerait indiquer que les données médicales sont très différentes. On pourrait chercher plusieurs jours mais le plus simple serait sans de générer des données artificielles avec un modèle SIR et vérifier si ce raisonnement tient la route sur des données propres.
Q4 
Step28:  Le code suivant explique comment trouver la valeur ISO-8859-1.
Step29:  Q5 
<ASSISTANT_TASK:>
Python Code:
from jyquickhelper import add_notebook_menu
add_notebook_menu()
%matplotlib inline
Explanation: Tech - manipulation de données avec pandas
pandas est la librairie incontournable pour manipuler les données. Elle permet de manipuler aussi bien les données sous forme de tables qu'elle peut récupérer ou exporter en différents formats. Elle permet également de créer facilement des graphes.
End of explanation
from pandas import DataFrame
rows = [{'col1': 0.5, 'col2': 'schtroumph'},
        {'col1': 0.6, 'col2': 'schtroumphette'}]
DataFrame(rows)
Explanation: Enoncé
La librairie pandas implémente la classe DataFrame. C'est une structure de table, chaque colonne porte un nom et contient un seul type de données. C'est très similaire au langage SQL.
Création d'un dataframe
Il existe une grande variété pour créer un DataFrame. Voici les deux principaux. Le premier : une liste de dictionnaires. Chaque clé est le nom de la colonne.
End of explanation
%%writefile data.csv
col1,col2
0.5,alpha
0.6,beta
import os
os.getcwd()
from pandas import read_csv
df = read_csv('data.csv')
df
Explanation: La lecture depuis un fichier :
End of explanation
df
df['col1']
df[['col1', 'col2']]
Explanation: La maîtrise des index
Les index fonctionnent à peu près comme numpy mais offre plus d'options puisque les colonnes mais aussi les lignes ont un nom.
Accès par colonne
End of explanation
df[:1]
Explanation: Accès par ligne (uniquement avec :). On se sert principalement de l'opérateur : pour les lignes.
End of explanation
df.loc[0, 'col1']
Explanation: Accès par positions avec loc.
End of explanation
df.iloc[0, 0]
Explanation: Accès par positions entières avec iloc.
End of explanation
df
dfi = df.set_index('col2')
dfi
dfi.loc['alpha', 'col1']
Explanation: La maîtrise des index des lignes
La création d'un dataframe donne l'impression que les index des lignes sont des entiers mais cela peut être changer
End of explanation
df.columns
Explanation: Il faut se souvenir de cette particularité lors de la fusion de tables.
La maîtrise des index des colonnes
Les colonnes sont nommées.
End of explanation
df.columns = ["valeur", "nom"]
df
Explanation: On peut les renommer.
End of explanation
df.loc[:, 'valeur':'nom']
Explanation: L'opérateur : peut également servir pour les colonnes.
End of explanation
df.values
df[['valeur']].values
Explanation: Lien vers numpy
pandas utilise numpy pour stocker les données. Il est possible de récupérer des matrices depuis des DataFrame avec values.
End of explanation
rows = [{'col1': 0.5, 'col2': 'schtroumph'},
        {'col2': 'schtroumphette'}]
DataFrame(rows)
Explanation: La maîtrise du nan
nan est une convention pour désigner une valeur manquante.
End of explanation
df.dtypes
Explanation: La maîtrise des types
Un dataframe est défini par ses dimensions et chaque colonne a un type potentiellement différent.
End of explanation
import numpy
df['valeur'].astype(numpy.float32)
import numpy
df['valeur'].astype(numpy.int32)
Explanation: On peut changer un type, donc convertir toutes les valeurs d'une colonne vers un autre type.
End of explanation
df['sup055'] = df['valeur'] >= 0.55
df
df['sup055'] = (df['valeur'] >= 0.55).astype(numpy.int64)
df
df['sup055+'] = df['valeur'] + df['sup055']
df
Explanation: Création de colonnes
On peut facilement créer de nouvelles colonnes.
End of explanation
df.loc[df['nom'] == 'alpha', 'sup055+'] += 1000
df
Explanation: Modifications de valeurs
On peut les modifier une à une en utilisant les index. Les notations sont souvent intuitives. Elles ne seront pas toutes détaillées. Ci-dessous un moyen de modifer certaines valeurs selon une condition.
End of explanation
rows = [{'col1': 0.5, 'col2': 'schtroumph'},
        {'col1': 1.5, 'col2': 'schtroumphette'}]
df = DataFrame(rows)
df
df1 = df[df['col1'] > 1.]
df1
df1["col3"] = df1["col1"] + 1.
df1
Explanation: Une erreur ou warning fréquent
End of explanation
df2 = df1.copy()
df2["col3"] = df2["col1"] + 1.
Explanation: A value is trying to be set on a copy of a slice from a DataFrame. : Par défaut, l'instruction df[df['col1'] &gt; 1.] ne crée pas un nouveau DataFrame, elle crée ce qu'on appelle une vue pour éviter de copier les données. Le résultat ne contient que l'index des lignes qui ont été sélectionnées et un lien vers le dataframe original. L'avertissement stipule que pandas ne peut pas modifier le dataframe original mais qu'il doit effectuer une copie.
La solution pour faire disparaître ce warning est de copier le dataframe.
End of explanation
# https://www.data.gouv.fr/en/datasets/r/63352e38-d353-4b54-bfd1-f1b3ee1cabd7
from pandas import read_csv
url = "https://www.data.gouv.fr/en/datasets/r/08c18e08-6780-452d-9b8c-ae244ad529b3"
covid = read_csv(url, sep=";")
covid.tail()
covid.dtypes
Explanation: La maîtrise des fonctions
Les fonctions de pandas créent par défaut un nouveau dataframe plutôt que de modifier un dataframe existant. Cela explique pourquoi parfois la mémoire se retrouve congestionnée. La page 10 minutes to pandas est un bon début.
création : read_csv, read_excel
index : set_index, reset_index
utilitaires : astype, isna, fillna, to_datetime, dtypes, shape, values, head, tail, isin, T, drop
concaténation : concat
SQL : filter, groupby, join, merge, pivot, pivot_table
calcul : sum, cumsum, quantile, var
On récupère les données du COVID par région et par âge et premier graphe
A cette adresse : Données hospitalières relatives à l'épidémie de COVID-19
End of explanation
from pandas import to_datetime
covid['jour'] = to_datetime(covid['jour'])
covid.tail()
covid.dtypes
Explanation: Les dates sont considérées comme des chaînes de caractères. Il est plus simple pour réaliser des opérations de convertir la colonne sous forme de dates.
End of explanation
agg_par_jour = covid.drop(['reg', 'cl_age90'], axis=1).groupby('jour').sum()
agg_par_jour.tail()
agg_par_jour.plot(title="Evolution des hospitalisations par jour",
                  figsize=(14, 4));
Explanation: On supprime les colonnes relatives aux régions et à l'âge puis on aggrège par jour.
End of explanation
agg_par_jour.plot(title="Evolution des hospitalisations par jour",
                  figsize=(14, 4), logy=True);
Explanation: Avec échelle logarithmique.
End of explanation
set(covid['cl_age90'])
covid49 = covid[covid.cl_age90 == 49]
agg_par_jour49 = covid49.drop(['reg', 'cl_age90'], axis=1).groupby('jour').sum()
agg_par_jour49.tail()
agg_par_jour49.plot(title="Evolution des hospitalisations par jour\nage=49",
                    figsize=(14, 4), logy=True);
Explanation: Q1 : refaire le graphique précédent pour votre classe d'âge
Q2 : faire de même avec les séries différenciées
Q3 : faire de même avec des séries lissées sur sur 7 jours
Q4 : fusion de tables par départements
Réponses
Q1 : refaire le graphique précédent pour votre classe d'âge
End of explanation
covid.tail()
diff = covid.drop(['reg', 'cl_age90'], axis=1).groupby(
    ['jour']).sum().diff()
diff.tail(n=2)
diff.plot(title="Séries différenciées", figsize=(14, 4));
Explanation: Q2 : faire de même avec les séries différenciées
End of explanation
diff.rolling(7)
roll = diff.rolling(7).mean()
roll.tail(n=2)
roll.plot(title="Séries différenciées lissées", figsize=(14, 4));
Explanation: Q3 : faire de même avec des séries lissées sur sur 7 jours
End of explanation
data = agg_par_jour49.diff().rolling(7).mean()
data.tail(n=2)
data_last = data.tail(n=90)
cor = []
for i in range(0, 35):
    ts = DataFrame(dict(rea=data_last.rea, dc=data_last.dc,
                        dclag=data_last["dc"].shift(i),
                        realag=data_last["rea"].shift(i)))
    ts_cor = ts.corr()
    cor.append(dict(delay=i, corr_dc=ts_cor.iloc[1, 3],
                    corr_rea=ts_cor.iloc[0, 3]))
DataFrame(cor).set_index('delay').plot(title="Corrélation entre décès et réanimation");
Explanation: Petit aparté
On veut savoir combien de temps les gens restent à l'hôpital avant de sortir, en supposant que le temps de guérison est à peu près identique au temps passé lorsque l'issue est tout autre. Je pensais calculer les corrélations entre la série des décès et celles de réanimations décalées de plusieurs jours en me disant qu'un pic de corrélation pourrait indiquer une sorte de durée moyenne de réanimation.
End of explanation
hosp = read_csv("https://www.data.gouv.fr/en/datasets/r/63352e38-d353-4b54-bfd1-f1b3ee1cabd7",
               sep=";")
hosp.tail()
indic = read_csv("https://www.data.gouv.fr/fr/datasets/r/4acad602-d8b1-4516-bc71-7d5574d5f33e",
                encoding="ISO-8859-1")
indic.tail()
Explanation: Il apparaît que ces corrélations sont très différentes selon qu'on les calcule sur les dernières données et les premières semaines. Cela semblerait indiquer que les données médicales sont très différentes. On pourrait chercher plusieurs jours mais le plus simple serait sans de générer des données artificielles avec un modèle SIR et vérifier si ce raisonnement tient la route sur des données propres.
Q4 : fusion de tables par départements
On récupère deux jeux de données :
* Données hospitalières relatives à l'épidémie de COVID-19
* Indicateurs de suivi de l’épidémie de COVID-19
End of explanation
# import chardet
# with open("indicateurs-covid19-dep.csv", "rb") as f:
#     content = f.read()
# chardet.detect(content)  # {'encoding': 'ISO-8859-1', 'confidence': 0.73, 'language': ''}
Explanation: Le code suivant explique comment trouver la valeur ISO-8859-1.
End of explanation
dep_pos = read_csv("https://raw.githubusercontent.com/sdpython/ensae_teaching_cs/"
                   "master/src/ensae_teaching_cs/data/data_shp/departement_french_2018.csv")
dep_pos.tail()
last_extract_date = max(set(indic.extract_date))
last_extract_date
indic_last = indic[indic.extract_date == last_extract_date]
merge = indic_last.merge(dep_pos, left_on='departement', right_on='code_insee')
final = merge[['code_insee', 'nom', 'DEPLONG', 'DEPLAT', 'taux_occupation_sae', 'R']]
metro = final[final.DEPLAT > 40]
metro
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(14, 4))
bigR1 = metro.R >= 1
bigR2 = metro.R >= 1.4
ax[0].scatter(metro.loc[bigR2, 'DEPLONG'], metro.loc[bigR2, 'DEPLAT'], c='red', label='R>=1.4');
ax[0].scatter(metro.loc[bigR1 & ~bigR2, 'DEPLONG'], metro.loc[bigR1 & ~bigR2, 'DEPLAT'], c='orange', label='1.3>=R>=1');
ax[0].scatter(metro.loc[~bigR1, 'DEPLONG'], metro.loc[~bigR1, 'DEPLAT'], c='blue', label='R<1');
ax[0].legend()
bigR1 = metro.taux_occupation_sae >= 25
bigR2 = metro.taux_occupation_sae >= 45
ax[1].scatter(metro.loc[bigR2, 'DEPLONG'], metro.loc[bigR2, 'DEPLAT'], c='red', label='SAE>=45');
ax[1].scatter(metro.loc[bigR1 & ~bigR2, 'DEPLONG'], metro.loc[bigR1 & ~bigR2, 'DEPLAT'], c='orange', label='45>SAE>=25');
ax[1].scatter(metro.loc[~bigR1, 'DEPLONG'], metro.loc[~bigR1, 'DEPLAT'], c='blue', label='SAE<25');
ax[1].legend();
metro[metro.nom == "Ardennes"]
Explanation: Q5 : une carte ?
Tracer une carte n'est jamais simple. Il faut tout d'abord récupérer les coordonnées des départements : Contours des départements français issus d'OpenStreetMap. Ensuite... de ces fichiers ont été extraits les barycentres de chaque département français : departement_french_2018.csv. Ce fichier a été créé avec la fonction implémentée dans le fichier data_shape_files.py. Ce qui suit est une approximation de carte : on suppose que là où se trouve, les coordonnées longitude et latitude ne sont pas trop éloignées de ce qu'elles pourraient être si elles étaient projetées sur une sphère.
End of explanation
<END_TASK>