2A.data - Pandas et itérateurs - correction

pandas a tendance a prendre beaucoup d'espace mémoire pour charger les données, environ trois fois plus que sa taille sur disque. Quand la mémoire n'est pas assez grande, que peut-on faire ?

In [1]:
from jyquickhelper import add_notebook_menu
add_notebook_menu()
Out[1]:
run previous cell, wait for 2 seconds
In [2]:
from sklearn.datasets import load_iris
data = load_iris()
import pandas
df = pandas.DataFrame(data.data)
df.column = "X1 X2 X3 X4".split()
df["target"] = data.target
df.head(n=2)
Out[2]:
0 1 2 3 target
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0

On mélange les lignes car le dataframe est trié et cela masque quelques effets aléatoires.

In [3]:
import sklearn.utils
df = sklearn.utils.shuffle(df)
In [4]:
df.to_csv("iris.txt", sep="\t", index=False)

Exercice 1 : itérer sur un grand fichier

Le paramètre iterator de la fonction read_csv sert à parcourir un fichier par blocs dont la taille est définie par le paramètres chunksize. La fonction read_csv implémente ce mécanisme.

In [5]:
for df in pandas.read_csv("iris.txt", sep="\t", iterator=True, chunksize=60):
    print(df.shape)
(60, 5)
(60, 5)
(30, 5)

Exercice 2 : split train test

La solution proposée est implémentée par train_test_split.

In [6]:
from sklearn.model_selection import train_test_split

df_full_it = pandas.read_csv('iris.txt', sep='\t', chunksize=10, encoding='utf-8',
                             engine='python')

first_exec = True

for df_full_chunk in df_full_it:
    X_train_chunk, X_test_chunk = train_test_split(df_full_chunk)
    if first_exec:
        X_train_chunk.to_csv("X_train.csv", sep="\t", index=False)
        X_test_chunk.to_csv("X_test.csv", sep="\t", index=False)
        first_exec = False
    else:
        X_train_chunk.to_csv("X_train.csv", sep="\t", index=False, mode='a', header=False)
        X_test_chunk.to_csv("X_test.csv", sep="\t", index=False, mode='a', header=False)
In [7]:
X_train = pandas.read_csv("X_train.csv", sep="\t")
X_train.head(n=2)
Out[7]:
0 1 2 3 target
0 5.7 2.9 4.2 1.3 1
1 6.1 3.0 4.6 1.4 1
In [8]:
X_train.shape
Out[8]:
(105, 5)
In [9]:
X_test = pandas.read_csv("X_test.csv", sep="\t")
X_test.head(n=2)
Out[9]:
0 1 2 3 target
0 7.0 3.2 4.7 1.4 1
1 5.6 3.0 4.5 1.5 1
In [10]:
X_test.shape
Out[10]:
(45, 5)
In [11]:
X_train.groupby("target").count()
Out[11]:
0 1 2 3
target
0 30 30 30 30
1 36 36 36 36
2 39 39 39 39

La répartition des classes n'est pas uniforme. Lorsque les classes sont bien représentées, cela ne nuit pas aux résultats. En revanche, des classes sous-représentées pourraient disparaître de l'une des deux parties.

Exercice 3 : stratify ?

Le paramètre stratify est intéressant pour un problème de classification et quand une classes et sous-représentée. Il est fort probable que cette classe ne soit pas assez représentée dans l'un des deux jeux et c'est pourquoi il existe une option pour imposer un nombre d'exemples de cette dans chaque des deux jeux (train, test). La qualité des modèles est accrue tout comme la qualité des sondages sur un échantillonnage stratifié.

Si jamais tout ces exemples sont placés au début du gros fichier à lire, le programme commence à avoir une fausse idée de la répartition des classes. La seule façon de faire est de faire d'abord une division train/test par classe (indiqué par la variable de stratification) puis de recomposer les bases d'apprentissage et de tests en imposant les proportions voulues.

In [12]:
from sklearn.model_selection import train_test_split

strat_name = 'target'

df_full_it = pandas.read_csv('iris.txt', sep='\t', chunksize=10, encoding='utf-8',
                             dtype=object, engine='python')

strat_list = []

for df_full_chunk in df_full_it:
    for current_strat in df_full_chunk[strat_name].unique():
        if str(current_strat) in strat_list:
            selection = df_full_chunk[df_full_chunk[strat_name] == current_strat]
            selection.to_csv("strat_{}.csv".format(current_strat), sep="\t", index=False,
                             encoding='utf-8', mode='a', header=False)
        else:
            strat_list.append(str(current_strat))
            selection = df_full_chunk[df_full_chunk[strat_name] == current_strat]
            selection.to_csv("strat_{}.csv".format(current_strat), sep="\t", index=False, encoding='utf-8')

first_exec = True

for current_strat in strat_list:
    df_strat_it = pandas.read_csv("strat_{}.csv".format(current_strat), sep='\t', chunksize=1000,
                                  encoding='utf-8', dtype=object, engine='python')
    for df_strat_chunk in df_strat_it:
        X_train_chunk, X_test_chunk = train_test_split(df_strat_chunk)
        if first_exec:
            X_train_chunk.to_csv("X_train_strat.csv", sep="\t", index=False, encoding='utf-8')
            X_test_chunk.to_csv("X_test_strat.csv", sep="\t", index=False, encoding='utf-8')
            first_exec = False
        else:
            X_train_chunk.to_csv("X_train_strat.csv", sep="\t", index=False, encoding='utf-8', mode='a', header=False)
            X_test_chunk.to_csv("X_test_strat.csv", sep="\t", index=False, encoding='utf-8', mode='a', header=False)

On vérifie que l'échantillon est stratifiée.

In [13]:
X_train = pandas.read_csv("X_train_strat.csv", sep="\t")
X_train.head(n=2)
Out[13]:
0 1 2 3 target
0 5.1 3.8 1.6 0.2 0
1 4.7 3.2 1.3 0.2 0
In [14]:
X_train.groupby("target").count()
Out[14]:
0 1 2 3
target
0 37 37 37 37
1 37 37 37 37
2 37 37 37 37
In [15]:
X_test = pandas.read_csv("X_test_strat.csv", sep="\t")
X_test.head(n=2)
Out[15]:
0 1 2 3 target
0 5.7 3.8 1.7 0.3 0
1 4.8 3.4 1.9 0.2 0
In [16]:
X_test.groupby("target").count()
Out[16]:
0 1 2 3
target
0 13 13 13 13
1 13 13 13 13
2 13 13 13 13

Les classes sont bien réparties.

Exercice 4 : quelques idées pour un group by ?

La fonction groupby implémente une façon de faire. Il faut distinguer deux cas possibles. Premier cas, l'agrégation aboutit à un résultat qui tient en mémoire auquel on peut s'en sortir aisément. Dans le second cas, l'agrégation ne tient pas en mémoire et il faudra probablement passer par un fichier intermédiaire ou comme le suggère la solution proposé par StreamingDataFrame.groupby, on peut agréger un cours de route à condition que l'agrégration implémentée soit compatible avec ce type de méthode.

In [17]:
 
In [18]:
 

Notes

Notebook en grande partie issue de la contribution des étudiants.

In [19]: