<jupyter_start><jupyter_text>Séance big dataDécouverte de plusieurs façons de calculer des statistiques sur des fichiers volumineux.<jupyter_code>from jyquickhelper import add_notebook_menu
add_notebook_menu()
%matplotlib inline<jupyter_output><empty_output><jupyter_text>Les donnéesOn récupère un fichier de la [DAMIR](https://www.data.gouv.fr/fr/datasets/open-damir-base-complete-sur-les-depenses-dassurance-maladie-inter-regimes/). Le fichier une fois décompressé fait plus de quatre Go. Vous pouvez le télécharger directement depuis le site ou prendre une version de backup. Vous trouvez d'autres données intéressantes sur le sujet sur le projet [SGMAP-AGD/DAMIR](https://github.com/SGMAP-AGD/DAMIR).<jupyter_code>import os
filename = "bigdata/A201612_small.csv"
# pour travailler avec un fichier plus gros (4 Go)
# filename = "bigdata/A201612.csv"
xlsfile = "bigdata/Lexique_open-DAMIR.xls"
if not os.path.exists(filename):
    from pyensae.datasource import download_data
    if not os.path.exists("bigdata"):
        os.mkdir("bigdata")
    filenamegz = filename.replace(".csv", ".csv.gz")
    download_data(os.path.split(filenamegz)[-1], whereTo=".")
    download_data(os.path.split(xlsfile)[-1], whereTo="bigdata")
    if not os.path.exists(filename):
        filename = os.path.split(filename)[-1]
os.stat(filename).st_size / 2**30<jupyter_output><empty_output><jupyter_text>Il est impossible de le charger en mémoire en entier. On regarde les premières lignes.<jupyter_code>with open(filename, "r") as f:
    for i, line in enumerate(f):
        print(line.strip("\r\n"))
        if i >= 5:
            break<jupyter_output>FLX_ANN_MOI;ORG_CLE_REG;AGE_BEN_SNDS;BEN_RES_REG;BEN_CMU_TOP;BEN_QLT_COD;BEN_SEX_COD;DDP_SPE_COD;ETE_CAT_SNDS;ETE_REG_COD;ETE_TYP_SNDS;ETP_REG_COD;ETP_CAT_SNDS;MDT_TYP_COD;MFT_COD;PRS_FJH_TYP;PRS_ACT_COG;PRS_ACT_NBR;PRS_ACT_QTE;PRS_DEP_MNT;PRS_PAI_MNT;PRS_REM_BSE;PRS_REM_MNT;FLT_ACT_COG;FLT_ACT_NBR;FLT_ACT_QTE;FLT_PAI_MNT;FLT_DEP_MNT;FLT_REM_MNT;SOI_ANN;SOI_MOI;ASU_NAT;ATT_NAT;CPL_COD;CPT_ENV_TYP;DRG_AFF_NAT;ETE_IND_TAA;EXO_MTF;MTM_NAT;PRS_NAT;PRS_PPU_SEC;PRS_REM_TAU;PRS_REM_TYP;PRS_PDS_QCP;EXE_INS_REG;PSE_ACT_SNDS;PSE_ACT_CAT;PSE_SPE_SNDS;PSE_STJ_SNDS;PRE_INS_REG;PSP_ACT_SNDS;PSP_ACT_CAT;PSP_SPE_SNDS;PSP_STJ_SNDS;TOP_PS5_TRG;

201612;76;20;76;9;1;1;0;1101;76;1;76;1101;9;3;0;2;1;1;0;46;46;46;2;1;1;46;0;46;2016;12;40;0;0;9;99;1;99;9;1117;1;100;99;3;99;0;0;14;2;99;0;0;1;2;9;

201612;28;30;28;9;1;2;0;9999;99;99;24;1102;9;99;0;1;5;1;0;.51;.51;.51;1;5;1;.51;0;.51;2016;12;10;0;0;9;99;9;99;9;3380;2;100;99;31;28;50;2;0;1;99;0;0;6;2;9;<jupyter_text>On calcule le nombre de lignes :<jupyter_code>with open(filename, "r") as f:
    nb = 0
    for line in f:
        nb += 1
nb<jupyter_output><empty_output><jupyter_text>On peut aussi utiliser [pandas.read_csv](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html) pour regarder les premières lignes.<jupyter_code>import time
import pandas
begin = time.perf_counter()
df = pandas.read_csv(filename, sep=";", nrows=100)
print(time.perf_counter() - begin)
df.head()
df.head().T
variables = pandas.read_excel(xlsfile, skiprows=2)
cols = variables.columns[:2]
variables[cols].dropna()<jupyter_output><empty_output><jupyter_text>Répartition des dépenses selon l'âgeC'est la variable ``AGE_BEN_SNDS`` mais on ne peut toujours pas charger le fichier en mémoire.  Itérateur sur des dataframesIl faut utiliser le paramètre [iterator](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.read_csv.html).<jupyter_code>dfs = []
for i, df in enumerate(pandas.read_csv(filename, 
            sep=";", iterator=True, chunksize=10000)):
    dfs.append(df)
    if i >= 5:
        break        
dfs[0].head()<jupyter_output><empty_output><jupyter_text>Il faut calculer la distribution des âges pour chaque dataframe.<jupyter_code>from datetime import datetime
ages = []
for i, df in enumerate(pandas.read_csv(filename, 
                    sep=";", iterator=True, chunksize=10000)):
    sub = df[["AGE_BEN_SNDS", "PRS_REM_MNT"]].copy()
    gr = sub.groupby("AGE_BEN_SNDS").sum()
    ages.append(gr)
    if len(ages) % 100 == 0:
        print(datetime.now(), len(ages), "lignes", 10000 * len(ages))<jupyter_output><empty_output><jupyter_text>Les versions de pandas ont quelques différences. Voici celle utilisées pour ce notebook.<jupyter_code>pandas.__version__
age = pandas.concat(ages)
agg = age.groupby("AGE_BEN_SNDS").sum()
agg.plot(kind="bar");<jupyter_output><empty_output><jupyter_text>Difficile d'interpréter ce graphique sans le comparer à la pyramide des âges mais ce n'est pas l'objectif ici. Chaque statistique prend un peu de temps. Et si nous tirions un échantillon aléatoire à 1%.  Echantillon<jupyter_code>ech = []
for i, df in enumerate(pandas.read_csv(filename, 
                    sep=";", iterator=True, chunksize=10000)):
    sub = df.sample(frac=0.01)
    ech.append(sub)
    if len(ech) % 100 == 0:
        print(datetime.now(), len(ech), "lignes", 10000 * len(ech))
echantillon = pandas.concat(ech)
echantillon.shape
echantillon[["AGE_BEN_SNDS", "PRS_REM_MNT"]] \
        .groupby("AGE_BEN_SNDS").sum().plot(kind="bar");<jupyter_output><empty_output><jupyter_text>pickleLe module [pickle](https://docs.python.org/3/library/pickle.html) permet de [sérialiser](https://fr.wikipedia.org/wiki/S%C3%A9rialisation). Cette opération transforme le contenu d'un objet tel qu'il est en mémoire en une séquence d'octets qu'on peut alors enregistrer sur disque. Comme ce format de fichier est plus proche de la mémoire, il se charge beaucoup plus vite. Cela permet de gagner un peu de temps. L'inconvénient est que ce format n'est pas toujours compatible d'une version de python ou pandas à l'autre.<jupyter_code>echantillon.to_csv("echantillon.csv", sep=";", index=False)
import os
os.stat("echantillon.csv").st_size / 2**20
begin = time.perf_counter()
ech2 = pandas.read_csv("echantillon.csv", sep=";")
print(time.perf_counter()-begin)
ech2.shape
echantillon.to_pickle("echantillon.pickle")
begin = time.perf_counter()
ech3 = pandas.read_pickle("echantillon.pickle")
print(time.perf_counter()-begin)<jupyter_output>0.011920500000002221<jupyter_text>SQLite3Un échantillon fait disparaître les classes les moins représentées. On peut accélérer le traitement en passant par [Sqlite](https://www.sqlite.org/) et disponible via le module [sqlite](https://docs.python.org/3.6/library/sqlite.html). Première étape : créer la base.<jupyter_code>import sqlite3
cnx = sqlite3.connect('damir2.db3')
for i, df in enumerate(pandas.read_csv(filename, 
            sep=";", iterator=True, chunksize=100000)):
    df.to_sql(con=cnx, name="damir", if_exists="replace" if i == 0 else "append")
    if (i+1) % 10 == 0:
        print(datetime.now(), (i+1) * 100000, "lignes")
print("done")<jupyter_output>c:\python372_x64\lib\site-packages\pandas\core\generic.py:2612: UserWarning: The spaces in these column names will not be changed. In pandas versions < 0.14, spaces were converted to underscores.
  method=method,<jupyter_text>C'est un peu long mais le calcul de statistique est plus rapide par la suite.<jupyter_code>import time
begin = time.perf_counter()
dfage = pandas.read_sql(
    'select AGE_BEN_SNDS, count(*) from damir group by AGE_BEN_SNDS', cnx)
print("time", time.perf_counter() - begin)
dfage<jupyter_output>time 0.08782719999999244<jupyter_text>On peut aussi programmer des [commandes magiques](http://www.xavierdupre.fr/app/pyensae/helpsphinx/notebooks/pyensae_sql_magic.html) pour alléger la syntaxe.<jupyter_code>%load_ext pyensae
%SQL_connect damir.db3
%SQL_tables
%%SQL
SELECT * FROM damir LIMIT 5
%lsmagic<jupyter_output><empty_output><jupyter_text>dask[dask](http://dask.pydata.org/en/latest/) fonctionne un peu comme pandas excepté que l'exécution des requêtes est différée. On utilise [read_csv](http://dask.pydata.org/en/latest/dataframe-api.htmldask.dataframe.read_csv).<jupyter_code>import dask.dataframe
dfdk = dask.dataframe.read_csv(filename, sep=";")
dfdk.head(n=2)<jupyter_output><empty_output><jupyter_text>Les trois lignes qui suivent sont très rapides.<jupyter_code>import dask
dask.__version__
dfdk2 = dfdk[["AGE_BEN_SNDS", "PRS_REM_MNT"]]
grdk = dfdk2.groupby("AGE_BEN_SNDS").sum()<jupyter_output><empty_output><jupyter_text>La suivante, beaucoup moins.<jupyter_code>begin = time.perf_counter()
res = grdk.compute()
print(time.perf_counter() - begin)
ech4 = dfdk.sample(frac=0.01)
dfech = ech4.compute()
type(dfech)
res<jupyter_output><empty_output><jupyter_text>C'est plus qu'avec SQLite3 mais dans cet exemple, dask relit le fichier à chaque calcul car il ne stocke pas un dataframe dans un format intermédiaire plus efficace tel que [parquet](http://parquet.apache.org/). dask propose des idées pour optimiser les calculs [Dask DataFrame Performance Tips](http://dask.pydata.org/en/latest/dataframe-performance.html).  Grandes matricesLes dataframes acceptent un type différent pour chaque colonne. Si toutes les colonnes sont numériques et de même types, on peut s'orienter vers d'autres solutions.  Zarr[zarr](http://zarr.readthedocs.io/en/latest/) permet de manipuler des matrices qui ne tiennent pas en mémoire de la même façon que [numpy](http://www.numpy.org/). Les données sont compressés et parfois stockées sur disque. Ce module masque beaucoup de détails en proposant une interface connue.<jupyter_code>import zarr
z = zarr.zeros((10000, 10000), chunks=(1000, 1000), dtype='i4')
z
z[4,5]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12814282695964505, 'fraction_numerical': 0.038981618423832666, 'mean_word_length': 6.070201643017177, 'pattern_counts': {'":': 0, '<': 66, '<?xml version=': 0, '>': 67, 'https://': 6, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '17', 'dir': 'jupyter-structured-clean-dedup', 'id': '118646', 'license': 'MIT', 'n_tokens_mistral': 3949, 'n_tokens_neox': 3697, 'n_words': 717, 'path': '_doc/notebooks/sessions/bigdata_ways.ipynb', 'repo_name': 'Pandinosaurus/actuariat_python', 'size': '9466'}