7 Techniques Doptimisation De La Memoire Avec Pandas

La conception et l’élaboration de modèles d’apprentissage automatique applicables dans le monde réel ont toujours suscité un grand intérêt chez les scientifiques des données. Cela les a inévitablement conduits à exploiter des méthodes optimisées, efficaces et précises à grande échelle.

L’optimisation, tant au niveau de l’exécution que de la mémoire, joue un rôle fondamental dans la fourniture durable de solutions logicielles concrètes et orientées vers l’utilisateur.

Optimisation Temps Execution Et Memoire
Catégorisation des optimisations

Dans cet article, je vais te présenter une 7 techniques incroyables pour optimiser l’utilisation de la mémoire de tes DataFrames Pandas.

Ces astuces t’aideront à effectuer efficacement tes tâches typiques d’analyse, de gestion et de traitement des données tabulaires dans Pandas.

#1 – Apporter des modifications « inplace » au DataFrame

Une fois que nous avons chargé un DataFrame dans l’environnement Python, nous effectuons généralement un large éventail de modifications sur le DataFrame. Il s’agit notamment d’ajouter de nouvelles colonnes, de renommer les en-têtes, de supprimer des colonnes, de modifier les valeurs des lignes, de remplacer les valeurs NaN, et bien d’autres choses encore.

Ces opérations peuvent être effectuées de deux manières différentes, comme indiqué ci-dessous :

2 Techniques Manipulation Dataframe Pandas
Catégorisation de la manipulation des DataFrame Pandas
Affectation standard

L’affectation standard vise à créer une nouvelle copie du DataFrame après transformation, en laissant le DataFrame d’origine intact.

Remplissage Des Valeurs Nan
Création d’un nouveau DataFrame à partir d’un DataFrame donné
df_copy = df.fillna(0)
print(df_copy)
   colA colB  colC
0   1.0    A   1.3
1   2.0    B   2.9
2   0.0    C   5.6

En raison de l’affectation standard, deux DataFrames Pandas distincts (celui d’origine et un transformé) coexistent dans l’environnement (df et df_copy ci-dessus), ce qui double l’utilisation de la mémoire.

Affectation inplace

Contrairement aux opérations d’affectation standard, les opérations d’affectation « inplace » visent à modifier le DataFrame d’origine sans créer un nouvel objet Pandas DataFrame. La démonstration est faite ci-dessous :

Technique Remplacement Inplace
Exécution d’une opération de remplacement « inplace »
df.fillna(0, inplace = True)
print(df)
   colA colB  colC
0   1.0    A   1.3
1   2.0    B   2.9
2   0.0    C   5.6

Par conséquent, si la copie intermédiaire du DataFrame (df_copy ci-dessus) n’est d’aucune utilité dans ton projet, l’affectation inplace est la solution idéale pour les applications à mémoire limitée.

Principaux enseignements/réflexions finales :

  1. Utilise l’affectation standard (ou inplace=False) lorsque le DataFrame intermédiaire est nécessaire et que tu ne souhaites pas modifier l’entrée.
  2. Utilise l’affectation inplace (ou inplace=True) si tu travailles avec des contraintes de mémoire et que tu n’as pas d’usage particulier du DataFrame intermédiaire.

#2 – Lire uniquement les colonnes requises à partir d’un CSV

Lire Uniquement Les Colonnes Requises
Lecture des seules colonnes intéressantes. Remarque : un fichier CSV est un fichier texte et l’illustration ci-dessus ne correspond pas à l’aspect d’un fichier CSV. Il s’agit simplement d’élaborer le point de vue intuitif.

Imagine la situation dans laquelle tu as des centaines de colonnes dans ton fichier CSV, et dont seul un sous-ensemble de colonnes t’intéresse.

Prenons par exemple les 5 premières lignes du DataFrame fictif que j’ai créé avec 25 colonnes et 10⁵ lignes à l’aide de Faker (nom de fichier : dummy_dataset.csv) :

Dummy Dataset Csv

Parmi ces 25 colonnes, disons que seules 5 colonnes nous intéressent au plus haut point et que tu souhaites les charger sous la forme d’un DataFrame Pandas. Ces colonnes sont les suivantes : Employee_IDFirst_NameSalaryRating et Company.

  • Chargement de TOUTES les colonnes

Si tu devais lire l’intégralité du fichier CSV dans l’environnement Python, cela obligerait Pandas à charger les colonnes qui ne sont pas utiles et à déduire leurs types de données, ce qui entraînerait une augmentation du temps d’exécution et de l’utilisation de la mémoire.

Nous pouvons connaître l’utilisation de la mémoire d’un DataFrame Pandas en utilisant la méthode info() comme indiqué ci-dessous :

data = pd.read_csv("dummy_dataset.csv")
data.info(memory_usage = "deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 25 columns):
 #   Column           Non-Null Count   Dtype  
---  ------           --------------   -----  
 0   Employee_ID      100000 non-null  int64  
 1   First_Name       100000 non-null  object 
 2   Last_Name        100000 non-null  object 
 3   DOB              100000 non-null  object 
 4   Address          100000 non-null  object 
 5   Zipcode          100000 non-null  int64  
 6   Salary           100000 non-null  float64
 7   Employment_Type  100000 non-null  object 
 8   Rating           60107 non-null   float64
 9   City             100000 non-null  object 
 10  State            100000 non-null  object 
 11  Country          100000 non-null  object 
 12  Company          100000 non-null  object 
 13  Company_Email    100000 non-null  object 
 14  Country_Code     99532 non-null   object 
 15  Language         100000 non-null  object 
 16  Domain_Name      100000 non-null  object 
 17  Currenct_Code    100000 non-null  object 
 18  Color            100000 non-null  object 
 19  URL              100000 non-null  object 
 20  Fav_Number       100000 non-null  int64  
 21  Father_Name      100000 non-null  object 
 22  Father_DOB       100000 non-null  object 
 23  Mother_Name      100000 non-null  object 
 24  Mother_DOB       100000 non-null  object 
dtypes: float64(2), int64(3), object(20)
memory usage: 137.0 MB

Le DataFrame occupe 137 Mo d’espace en mémoire avec les 25 colonnes chargées. Le temps d’exécution pour charger le fichier CSV est calculé ci-dessous :

%timeit pd.read_csv("dummy_dataset.csv")
728 ms ± 19.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  • Chargement des colonnes nécessaires :

Contrairement à la lecture de toutes les colonnes, si seul un sous-ensemble de colonnes nous intéresse, tu peux les passer sous la forme de liste à l’argument usecols de la méthode pd.read_csv().

Le calcul de l’utilisation de la mémoire est illustré ci-dessous :

col_list = ["Employee_ID", "First_Name", "Salary", "Rating", "Company"]

data = pd.read_csv("dummy_dataset.csv", usecols=col_list)
data.info(memory_usage = "deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 5 columns):
 #   Column       Non-Null Count   Dtype  
---  ------       --------------   -----  
 0   Employee_ID  100000 non-null  int64  
 1   First_Name   100000 non-null  object 
 2   Salary       100000 non-null  float64
 3   Rating       60107 non-null   float64
 4   Company      100000 non-null  object 
dtypes: float64(2), int64(1), object(2)
memory usage: 15.3 MB

Le chargement des seules colonnes intéressantes a permis de réduire l’utilisation de la mémoire de près de 9 fois, en occupant environ 15 Mo d’espace au lieu de 137 Mo auparavant.

Le temps d’exécution du chargement est également réduit de manière significative, offrant un gain de près de 4 fois par rapport au chargement de toutes les colonnes.

%timeit pd.read_csv("dummy_dataset.csv", usecols=col_list)
271 ms ± 19.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Principaux enseignements/réflexions finales :

  1. Le fait de ne charger que les colonnes nécessaires peut améliorer de manière significative le temps d’exécution et l’utilisation de la mémoire. Par conséquent, avant de charger un fichier CSV volumineux, ne charge que quelques lignes (par exemple les 5 premières) et répertorie les colonnes qui t’intéressent.

#3 à #5 Modifier le type de données des colonnes

Modifier Type De Donnees
Conversion des types de données avec Pandas

Par défaut, Pandas attribue toujours le type de données de mémoire le plus élevé aux colonnes. Par exemple, si Pandas interprète une colonne comme étant de type entier (integer), il est possible de choisir entre quatre sous-catégories :

  • int8 : entier de 8 bits qui couvre les entiers de [-2⁷, 2⁷].
  • int16 : entier de 16 bits couvrant les entiers compris entre [-2¹⁵, 2¹⁵].
  • int32 : entier de 32 bits couvrant les entiers de [-2³¹, 2³¹].
  • int64 : entier de 64 bits qui couvre les entiers de [-2⁶³, 2⁶³].

Cependant, Pandas attribuera toujours int64 comme type de données de la colonne à valeurs entières, quelle que soit la plage des valeurs actuelles de la colonne.

Des connotations similaires existent également pour les nombres à valeurs flottantes : float16, float32 et float64.

Remarque : je vais me référer au même ensemble de données fictives (dummy_dataset.csv) que celui dont nous avons parlé dans la partie précédente. De nouveau, voici les types de données :

data = pd.read_csv("dummy_dataset.csv")
data.info(memory_usage = "deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 25 columns):
 #   Column           Non-Null Count   Dtype  
---  ------           --------------   -----  
 0   Employee_ID      100000 non-null  int64  
 1   First_Name       100000 non-null  object 
 2   Last_Name        100000 non-null  object 
 3   DOB              100000 non-null  object 
 4   Address          100000 non-null  object 
 5   Zipcode          100000 non-null  int64  
 6   Salary           100000 non-null  float64
 7   Employment_Type  100000 non-null  object 
 8   Rating           60107 non-null   float64
 9   City             100000 non-null  object 
 10  State            100000 non-null  object 
 11  Country          100000 non-null  object 
 12  Company          100000 non-null  object 
 13  Company_Email    100000 non-null  object 
 14  Country_Code     99532 non-null   object 
 15  Language         100000 non-null  object 
 16  Domain_Name      100000 non-null  object 
 17  Currenct_Code    100000 non-null  object 
 18  Color            100000 non-null  object 
 19  URL              100000 non-null  object 
 20  Fav_Number       100000 non-null  int64  
 21  Father_Name      100000 non-null  object 
 22  Father_DOB       100000 non-null  object 
 23  Mother_Name      100000 non-null  object 
 24  Mother_DOB       100000 non-null  object 
dtypes: float64(2), int64(3), object(20)
memory usage: 137.0 MB

L’utilisation actuelle de la mémoire du DataFrame est de 137 Mo.

#3 – Modification du type de données des colonnes d’entiers

Modifier Colonnes De Type Entier
Rétrogradation des types de données entiers

Considérons la colonne Employee_ID et trouvons ses valeurs maximale et minimale.

print("Le type de données de la colonne Employee_ID est", data.Employee_ID.dtype)
print("La valeur maximale de la colonne Employee_ID est de", data.Employee_ID.max())
print("La valeur minimale de la colonne Employee_ID est de", data.Employee_ID.min())
Le type de données de la colonne Employee_ID est int64
La valeur maximale de la colonne Employee_ID est de 100000
La valeur minimale de la colonne Employee_ID est de 1

Remarque que même si la colonne peut être potentiellement interprétée comme int32 (2¹⁵< 10⁵ < 2³¹), Pandas a quand même adopté le type int64 pour la colonne.

Heureusement, Pandas offre la possibilité de changer le type de données d’une colonne en utilisant la méthode astype().

La conversion de la colonne Employee_ID, ainsi que l’utilisation de la mémoire avant et après la conversion, sont démontrées ci-dessous :

print("Utilisation de la mémoire avant la modification du type de données :
", data.Employee_ID.memory_usage())

data["Employee_ID"] = data.Employee_ID.astype(np.int32)

print("Utilisation de la mémoire après modification du type de données :", data.Employee_ID.memory_usage())
Utilisation de la mémoire avant la modification du type de données : 800128
Utilisation de la mémoire après modification du type de données : 400128

La mémoire totale utilisée par la colonne Employee_ID a été divisée par 2 grâce à cette simple transformation de type de données (en une seule ligne).

Avec une analyse min-max similaire, tu peux également modifier le type de données d’autres colonnes à valeurs entières et flottantes.

#4 – Modification du type de données des colonnes représentant des données catégorielles

Conversion En Colonne Categorielle
Conversion en une colonne catégorielle

Comme son nom l’indique, une colonne catégorielle est une colonne qui ne comporte que quelques valeurs uniques répétées à l’infini dans toute la colonne.

Par exemple, trouvons le nombre de valeurs uniques dans ces quelques colonnes en utilisant la méthode nunique() comme indiqué ci-dessous :

print("Nombre d'enregistrements uniques :", data.shape[0])
print("Nombre de pays uniques           :", data.Country.nunique())
print("Nombre de types d'emploi uniques :", data.Employment_Type.nunique())
print("Nombre de langues uniques        :", data.Language.nunique())
print("Nombre de couleurs uniques       :", data.Color.nunique())
print("Nombre de codes pays uniques     :", data.Country_Code.nunique())
Nombre d'enregistrements uniques : 100000
Nombre de pays uniques           : 243
Nombre de types d'emploi uniques : 2
Nombre de langues uniques        : 182
Nombre de couleurs uniques       : 140
Nombre de codes pays uniques     : 194

Le nombre de valeurs uniques dans ces colonnes par rapport à la taille du DataFrame indique qu’il s’agit de colonnes catégorielles.

Cependant, par défaut, Pandas a déduit que le type de données de toutes ces colonnes était object, qui est essentiellement un type de chaîne string.

print("Datatype de Countries        :", data.Country.dtype)
print("Datatype de Employment Types :", data.Employment_Type.dtype)
print("Datatype de Languages        :", data.Language.dtype)
print("Datatype de Color            :", data.Color.dtype)
print("Datatype de Country Codes    :", data.Country_Code.dtype)
Datatype de Countries        : object
Datatype de Employment Types : object
Datatype de Languages        : object
Datatype de Color            : object
Datatype de Country Codes    : object

En utilisant la méthode astype(), tu peux changer le type de données d’une colonne catégorielle en category. La réduction de l’utilisation de la mémoire est démontrée ci-dessous :

print("Utilisation de la mémoire avant la modification du type de données :", data.Country.memory_usage())

data["Country"] = data.Country.astype("category")

print("Utilisation de la mémoire après modification du type de données :", data.Country.memory_usage())
Utilisation de la mémoire avant la modification du type de données : 800128
Utilisation de la mémoire après modification du type de données : 210368

Avec la conversion de string à categorical, nous remarquons une diminution de l’utilisation de la mémoire de 75%, ce qui est énorme.

Avec une analyse similaire de l’élément unique, tu peux modifier le type de données d’autres colonnes catégorielles potentielles.

#5 – Modification du type de données des colonnes contenant des valeurs NaN

Modifier Type Colonne Avec Nans
Conversion de divers types de données en types Sparse

Les valeurs manquantes sont inévitables dans les ensembles de données du monde réel, n’est-ce pas ? Considérons qu’une colonne de notre DataFrame comporte une proportion importante de valeurs NaN, comme illustré ci-dessous :

print("Nombre d'enregistrements :", data.shape[0])
print("Nombre de valeurs NaN    :", data.Rating.isna().sum())
print("Datatype de Rating       :", data.Rating.dtype)
Nombre d'enregistrements : 100000
Nombre de valeurs NaN    : 39893
Datatype de Rating       : float64

Dans un tel scénario, la représentation de la colonne en tant que structure de données Sparse peut permettre d’améliorer considérablement l’efficacité de la mémoire.

En utilisant la méthode astype(), tu peux changer le type de données d’une colonne sparse en type de données Sparse[str] / Sparse[float] / Sparse[int]. La réduction de l’utilisation de la mémoire et la conversion du type de données sont démontrées ci-dessous :

print("Utilisation de la mémoire avant la modification du type de données :", data.Rating.memory_usage())

data["Rating"] = data.Rating.astype("Sparse[float32]")

print("Utilisation de la mémoire après modification du type de données :", data.Rating.memory_usage())
Utilisation de la mémoire avant la modification du type de données : 800128
Utilisation de la mémoire après modification du type de données : 480984

La conversion de float32 en Sparse[float32] réduit l’utilisation de la mémoire de près de 40 %, ce qui correspond approximativement au pourcentage de valeurs NaN dans la colonne Rating.

Principaux enseignements/réflexions finales :

  1. Pandas interprète toujours ses colonnes avec le plus grand type de données de la mémoire. Si la plage de valeurs de ta colonne ne couvre pas la portée du type de données, envisage de rétrograder le type de données de la colonne vers le type le plus optimal.

Tu peux trouver un code de référence pour exécuter ces conversions de type de données dans ce post StackOverflow.

#6 – Spécifier le type de données d’une colonne lors de la lecture d’un CSV

Les conseils présentés dans les sections #3 à #5 ci-dessus supposent que tu as déjà chargé un DataFrame Pandas dans l’environnement Python. En d’autres termes, il s’agit de techniques d’optimisation de l’utilisation de la mémoire après l’entrée.

Cependant, dans les situations où le chargement de l’ensemble de données est le principal défi, tu peux prendre le contrôle de la tâche d’interprétation du type de données effectuée par Pandas pendant l’entrée et spécifier le type de données particulier que tu veux pour trs colonnes.

Specifier Datatype Colonne
Fournir des instructions de type de données à Pandas. Remarque : un fichier CSV est un fichier texte, et l’illustration ci-dessus ne correspond pas à l’aspect d’un CSV. Il s’agit simplement d’élaborer le point de vue intuitif.

Tu peux y parvenir en passant l’argument dtype à la méthode pd.read_csv() comme suit :

col_list = ["Employee_ID", "First_Name", "Salary", "Rating", "Country_Code"]
data = pd.read_csv("dummy_dataset.csv", usecols=col_list, 
                   dtype = {"Employee_ID":np.int32, "Country_Code":"category"})

data.info(memory_usage = "deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000 entries, 0 to 99999
Data columns (total 5 columns):
 #   Column        Non-Null Count   Dtype   
---  ------        --------------   -----   
 0   Employee_ID   100000 non-null  int32   
 1   First_Name    100000 non-null  object  
 2   Salary        100000 non-null  float64 
 3   Rating        60107 non-null   float64 
 4   Country_Code  99532 non-null   category
dtypes: category(1), float64(2), int32(1), object(1)
memory usage: 8.1 MB

Comme indiqué ci-dessus, l’argument dtype attend un dictionnaire de correspondance entre le nom de la colonne (column-name) et le type de données (data-type).

Principaux enseignements/réflexions finales :

  1. Si tu connais le type de données contenues dans certaines (ou toutes) les colonnes d’un CSV, que ce soit par le biais d’un dictionnaire de données ou d’une autre source, essaye de déduire toi-même le type de données le plus approprié et passe-le à l’argument dtype de la méthode pd.read_csv().

#7 – Lire des données en morceaux à partir d’un fichier CSV

Lecture Fichiers En Morceaux
Lecture d’un fichier par morceaux. Remarque : un fichier CSV est un fichier texte, et l’illustration ci-dessus ne correspond pas à l’aspect d’un CSV. Il s’agit simplement d’élaborer le point de vue intuitif.

Enfin, supposons que tu aies fait tout ce que vous pouviez faire dans l’astuce #6, mais que le CSV soit toujours impossible à charger en raison de contraintes de mémoire.

Bien que ma dernière technique ne permette pas d’optimiser l’utilisation nette de la mémoire, il s’agit plutôt d’une solution de contournement pour le chargement de grands ensembles de données, que tu peux utiliser dans de telles situations extrêmes.

Les méthodes d’entrée de Pandas sont sérialisées. Par conséquent, elles ne lisent qu’une ligne à la fois à partir d’un fichier CSV.

Lecture Ligne Par Ligne
Lecture d’une ligne à la fois. Remarque : un fichier CSV est un fichier texte, et l’illustration ci-dessus ne correspond pas à l’aspect d’un CSV. Il s’agit simplement d’élaborer le point de vue intuitif.

Si le nombre de lignes est trop important pour être chargé en mémoire en une seule fois, tu peux charger un segment (ou morceau = chunk en anglais) de lignes, le traiter, puis lire le segment suivant du fichier CSV. Cette méthode est illustrée ci-dessous :

Traitement Donnees Par Morceau
Traitement des données par morceaux avec Pandas. Remarque : un fichier CSV est un fichier texte, et l’illustration ci-dessus ne correspond pas à l’aspect d’un CSV. Il s’agit simplement d’élaborer le point de vue intuitif.

Tu peux tirer parti du processus d’entrée basé sur les morceaux (chunks) ci-dessus en passant l’argument chunksize à la méthode pd.read_csv() comme suit :

for chunk in pd.read_csv("dummy_dataset.csv", chunksize=50000):
    
    ## processus chunk
    pass

Chaque objet chunk est un DataFrame Pandas, et nous pouvons le vérifier en utilisant la méthode type() de Python comme suit :

for chunk in pd.read_csv("dummy_dataset.csv", chunksize=50000):
    
    print(type(chunk))
<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>

Principaux enseignements/réflexions finales :

  1. Si le fichier CSV est trop volumineux pour être chargé et pour tenir dans la mémoire, utilise la méthode de découpage en morceaux pour charger des segments du CSV et les traiter l’un après l’autre.
  2. L’un des principaux inconvénients de cette méthode c’est qu’elle ne permet pas d’effectuer des opérations nécessitant l’intégralité du DataFrame. Par exemple, supposons que tu souhaites effectuer une opération groupby() sur une colonne. Dans ce cas, il est possible que les lignes correspondant à un groupe se trouvent dans des morceaux/chunks différents.

Pour conclure, dans cet article, j’ai discuté de 7 techniques incroyables d’optimisation de la mémoire avec Pandas, que tu peux directement exploiter dans tes prochains projets de Data Science.

À mon avis, les domaines que j’ai abordés dans cet article sont des moyens subtils d’optimiser l’utilisation de la mémoire, que l’on oublie souvent de chercher à optimiser. Néanmoins, j’espère que cet article t’aura permis de mieux comprendre ces fonctions quotidiennes de Pandas.

Merci de m’avoir lu !

Publications similaires

0 Commentaires
Le plus récent
Le plus ancien Le plus populaire
Commentaires en ligne
Afficher tous les commentaires