<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 2A.i - Table de mortalité dans plusieurs containers
Pas de calcul d'espérence de vie, seulement différentes façons de lire les données d'une table de mortalité.
Step1:  Récupération des données
Les données sont recensées sur Data Publica 
Step2:  Puis on le charge sous forme de dataframe 
Step3:  La première colonne contient une aggrégation de champs. On souhaite transformer cette table de telle sorte qu'on ait un nombre réduit de colonnes 
Step4:  Graphe d'une coupe de la table de mortalité 
Step5:  SQLite
SQLite est un outils de gestion de base de données locales. Intégré à Python, il ne nécessite aucune installation. Il est très utile lorsque Microsoft Excel ne peut pas contenir toutes les données qu'on souhaite consulter. Plus de deux millions de lignes dans le cas de cette table.
version 1 
Step7:  On utilise une requête SQL pour récupérer les données équivalent au code pandas cité ci-dessous 
Step8:  version 2 
Step10:  Ensuite, on peut facilement consulter les données avec le logiciel (sous Windows) SQLiteSpy ou l'extension sqlite-manager pour Firefox sous toutes les plates-formes. Pour cet exercice, on exécute 
Step11:  Visuellement, cela donne 
Step12:  Cube de données
On utilise l'expression cube de données pour désigner à tableaux à plusieurs dimensions. On le représente souvent par une liste coordonnées, valeurs. C'est souvent beaucoup de données et pas forcément de moyen pratique de les manipuler. On utilise le module xarray. pandas propose automatiquement d'exporter les données vers ce module avec to_xarray.
Step13:  On passe du côté index toutes les colonnes excepté valeur.
Step14:  On laisse tomber les valeurs manquantes.
Step15:  On vérifie qu'il n'y a pas de doublons car la conversion en cube ne fonctionne pas dans ce cas puisque deux valeurs seraient indexées avec les mêmes coordonnées.
Step16:  On vérifie que xarray est installé.
Step17:  Et on convertit en cube.
Step18:  Et on prend le maximum par indicateur et genre.
Step19:  On ajoute une colonne avec un ratio où on divise par le maximum sur une classe d'âge.
<ASSISTANT_TASK:>
Python Code:
%matplotlib inline
import matplotlib.pyplot as plt
from jyquickhelper import add_notebook_menu
add_notebook_menu()
Explanation: 2A.i - Table de mortalité dans plusieurs containers
Pas de calcul d'espérence de vie, seulement différentes façons de lire les données d'une table de mortalité.
End of explanation
url = "http://ec.europa.eu/eurostat/estat-navtree-portlet-prod/BulkDownloadListing?file=data/"
file = "demo_mlifetable.tsv.gz"
import pyensae.datasource
local = pyensae.datasource.download_data("demo_mlifetable.tsv.gz", url=url)
local = local[0]+".gz"
import gzip
with gzip.open(local, 'rb') as f: 
    file_content = f.read()
content = str(file_content, encoding="utf8")
with open("mortalite.txt", "w", encoding="utf8") as f:
    f.write(content)
Explanation: Récupération des données
Les données sont recensées sur Data Publica : Table de mortalité qui les a récupéré depuis le site d'Eurostat via le listing suivant : listing. Pour faire court, le lien est le suivant : demo_mlifetable.tsv.gz. Le fichier est compressé au format gzip. On le télécharge, on le décompresse.
End of explanation
import pandas
dff = pandas.read_csv("mortalite.txt", sep="\t", encoding="utf8")
dff.head()
dff.shape
Explanation: Puis on le charge sous forme de dataframe :
End of explanation
def format_age(s):
    if s.startswith("Y_") : 
        if s.startswith("Y_LT"): s = "Y00_LT" + s[4:]
        elif s.startswith("Y_GE"): s = "Y85_GE" + s[4:]
        else: raise FormatError(s)
    else:
        i = int(s.strip("Y"))
        return "Y%02d" % i
    
def format_value(s):
    if s.strip() == ":" : return -1
    else : return float(s.strip(" ebp"))
    
if False:  # sur les données complètes, c'est plutôt long, réduire la taille pour essayer
    dfsmall = dff.head(n = 1000) # on réduit la taille pour 
    df = dfsmall # implémenter la transformation
else:
    df = dff
print("étape 1", df.shape)
dfi = df.reset_index().set_index("indic_de,sex,age,geo\\time")
dfi = dfi.drop('index', axis=1)
dfs = dfi.stack()
dfs = pandas.DataFrame({"valeur": dfs } )
print("étape 2", dfs.shape)
dfs["valeur"] = dfs["valeur"].astype(str)
dfs["valeur"] = dfs["valeur"].apply( format_value )
dfs = dfs[ dfs.valeur >= 0 ].copy()
dfs = dfs.reset_index()
dfs.columns = ["index", "annee", "valeur"]
print("étape 3", dfs.shape)
dfs["age"] = dfs["index"].apply ( lambda i : format_age(i.split(",")[2]))
dfs["indicateur"] = dfs["index"].apply ( lambda i : i.split(",")[0])
dfs["genre"] = dfs["index"].apply ( lambda i : i.split(",")[1])
dfs["pays"] = dfs["index"].apply ( lambda i : i.split(",")[3])
print("étape 4")
dfy = dfs.drop('index', axis=1)
dfy.to_csv("mortalite_5column.txt", sep="\t", encoding="utf8", index=False)
dfy.head()
Explanation: La première colonne contient une aggrégation de champs. On souhaite transformer cette table de telle sorte qu'on ait un nombre réduit de colonnes :
indicateur
genre
age
pays (ou ensemble de pays)
annee
valeur
L'âge est représenté sous forme de chaîne de caractères pour pouvoir écrire Y_LT1 (moins d'un an), Y_GE85 (plus de 85 ans). On change un peu le format pour pouvoir les trier par ordre croissant (en effet Y2 est après Y10). On sauve le tout dans un fichier pour ne pas avoir à recommencer ultérieurement. Malgré tout, le code ci-dessous est très lent pour la table complète qui contiendra au final près de 5 millions de lignes. On supprime les valeurs manquantes.
End of explanation
view = dfs [    (dfs.pays=="FR") &
                (dfs.age == "Y80") &
                (dfs.indicateur == "DEATHRATE") &
                (dfs.genre == "T")  ]
view = view.sort_values("annee")
view.plot(x="annee", y="valeur");
Explanation: Graphe d'une coupe de la table de mortalité :
End of explanation
import sqlite3
con = sqlite3.connect("mortalite_sqlite3_y2.db3")
dfy.to_sql("table_mortalite",con)
con.close()  # il faut fermer la base qui sinon reste ouverte tant que le notebook
             # n'est pas fermé --> elle n'est pas modifiable pas d'autre que ce notebook
import os
[ _ for _ in os.listdir(".") if "sqlite3" in _ ]
Explanation: SQLite
SQLite est un outils de gestion de base de données locales. Intégré à Python, il ne nécessite aucune installation. Il est très utile lorsque Microsoft Excel ne peut pas contenir toutes les données qu'on souhaite consulter. Plus de deux millions de lignes dans le cas de cette table.
version 1 : pandas to SQLite
On utilise pour la méthode to_sql et le module sqlite3. Ca prend un peu de temps (deux à trois minutes).
End of explanation
con = sqlite3.connect("mortalite_sqlite3_y2.db3")
view = pandas.read_sql(SELECT * FROM table_mortalite WHERE pays=="FR" 
            AND age == "Y80" 
            AND indicateur == "DEATHRATE"
            AND genre == "T"
            ORDER BY annee, con)
con.close()
view.plot(x="annee", y="valeur");
Explanation: On utilise une requête SQL pour récupérer les données équivalent au code pandas cité ci-dessous :
End of explanation
from pyensae.sql import import_flatfile_into_database
import_flatfile_into_database("mortalite.db3", "mortalite_5column.txt")
Explanation: version 2 : pyensae
import_flatfile_into_database est une fonction à utiliser lorsqu'on ne sait pas toujours quel est le séparateur de colonnes dans le fichier à importer. La fonction le devine pour vous ainsi que le type de chaque colonne (quand c'est possible). L'autre aspect intéressant est qu'elle affiche son état d'avancement. On repère plus rapidement que quelque chose se passe mal. Enfin, pour les gros fichiers, la fonction ne charge pas tout le fichier en mémoire. Cela permet de placer dans une base SQLite des milliards de lignes (cela peut prendre plus d'une heure). Ce n'est pas le cas ici, c'est juste à titre d'exemple. On stocke l'ensemble des données au format SQLite 3 de façon à pouvoir les consulter plus facilement.
End of explanation
sql = SELECT * FROM mortalite_5column WHERE pays=="FR" 
            AND age == "Y80" 
            AND indicateur == "DEATHRATE"
            AND genre == "T"
            ORDER BY annee
from pyensae.sql import Database
db = Database("mortalite.db3", LOG = lambda *l : None)
db.connect()
view = db.to_df(sql)
view
Explanation: Ensuite, on peut facilement consulter les données avec le logiciel (sous Windows) SQLiteSpy ou l'extension sqlite-manager pour Firefox sous toutes les plates-formes. Pour cet exercice, on exécute :
End of explanation
view.plot(x="annee", y="valeur")
Explanation: Visuellement, cela donne :
End of explanation
import pandas 
df = pandas.read_csv("mortalite_5column.txt", sep="\t", encoding="utf8")
df.shape
df.head(n=2)
Explanation: Cube de données
On utilise l'expression cube de données pour désigner à tableaux à plusieurs dimensions. On le représente souvent par une liste coordonnées, valeurs. C'est souvent beaucoup de données et pas forcément de moyen pratique de les manipuler. On utilise le module xarray. pandas propose automatiquement d'exporter les données vers ce module avec to_xarray.
End of explanation
cols = [_ for _ in df.columns if _ != "valeur"]
cols
Explanation: On passe du côté index toutes les colonnes excepté valeur.
End of explanation
df.shape
df = df.dropna()
df.shape
Explanation: On laisse tomber les valeurs manquantes.
End of explanation
dup = df.groupby(cols).count().sort_values("valeur", ascending=False)
dup = dup[dup.valeur > 1]
dup.head(n=2)
dup.shape
dfi = df.set_index(cols, verify_integrity=True)
dfi.head(n=2)
type(dfi.index)
Explanation: On vérifie qu'il n'y a pas de doublons car la conversion en cube ne fonctionne pas dans ce cas puisque deux valeurs seraient indexées avec les mêmes coordonnées.
End of explanation
import xarray
Explanation: On vérifie que xarray est installé.
End of explanation
cube = xarray.Dataset.from_dataframe(dfi) # ou dfi.to_xarray()
cube
back_to_pandas = cube.to_dataframe().reset_index(drop=True)
back_to_pandas.head(n=2)
Explanation: Et on convertit en cube.
End of explanation
cube.max(dim=["age", "annee", "pays"]).to_dataframe().reset_index().pivot("indicateur", "genre", "valeur")
cube.to_dataframe().groupby('indicateur').max()
try:
    cube.groupby("indicateur").max().to_dataframe().head()
except ValueError as e:
    # It used to be working in 0.12 but not in 0.13...
    print(e)
cube["valeur"].sel(indicateur="LIFEXP", genre="F", annee=slice(1980, 1985)).to_dataframe().head()
cube["valeur"].max(dim=["age"]).to_dataframe().head()
Explanation: Et on prend le maximum par indicateur et genre.
End of explanation
cube["max_valeur"] = cube["valeur"] / cube["valeur"].max(dim=["age"])
cube.to_dataframe().head()
Explanation: On ajoute une colonne avec un ratio où on divise par le maximum sur une classe d'âge.
End of explanation
<END_TASK>