Guide complet pour optimiser la mémoire de Pandas : types de données, chunking, variables catégorielles et techniques efficaces pour les grands datasets.
Optimisation des performances de Pandas : Maîtriser la réduction de l'utilisation de la mémoire
Pandas est une puissante bibliothèque Python pour l'analyse de données, offrant des structures de données flexibles et des outils d'analyse. Cependant, lors du travail avec de grands ensembles de données, l'utilisation de la mémoire peut devenir un goulot d'étranglement important, impactant les performances et pouvant même provoquer le plantage de vos programmes. Ce guide complet explore diverses techniques pour optimiser l'utilisation de la mémoire de Pandas, vous permettant de gérer des ensembles de données plus grands de manière plus efficace et efficiente.
Comprendre l'utilisation de la mémoire de Pandas
Avant de plonger dans les techniques d'optimisation, il est crucial de comprendre comment Pandas stocke les données en mémoire. Pandas utilise principalement des tableaux NumPy pour stocker les données au sein des DataFrames et des Series. Le type de données de chaque colonne affecte considérablement l'empreinte mémoire. Par exemple, une colonne `int64` consommera deux fois plus de mémoire qu'une colonne `int32`.
Vous pouvez vérifier l'utilisation de la mémoire d'un DataFrame en utilisant la méthode .memory_usage() :
import pandas as pd
data = {
'col1': [1, 2, 3, 4, 5],
'col2': ['A', 'B', 'C', 'D', 'E'],
'col3': [1.1, 2.2, 3.3, 4.4, 5.5]
}
df = pd.DataFrame(data)
memory_usage = df.memory_usage(deep=True)
print(memory_usage)
L'argument deep=True est essentiel pour calculer avec précision l'utilisation de la mémoire des colonnes d'objets (chaînes de caractères).
Techniques de réduction de l'utilisation de la mémoire
1. Choisir les bons types de données
Le choix du type de données approprié pour chaque colonne est l'étape la plus fondamentale pour réduire l'utilisation de la mémoire. Pandas infère automatiquement les types de données, mais il utilise souvent par défaut des types plus gourmands en mémoire que nécessaire. Par exemple, une colonne contenant des entiers entre 0 et 100 pourrait être assignée au type `int64`, même si `int8` ou `uint8` suffirait.
Exemple : Conversion à la baisse des types numériques
Vous pouvez convertir à la baisse les types numériques vers des représentations plus petites en utilisant la fonction pd.to_numeric() avec le paramètre downcast :
def reduce_mem_usage(df):
"""Iterate through all the columns of a dataframe and modify the data type
to reduce memory usage.
"""
start_mem = df.memory_usage().sum() / 1024**2
print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
for col in df.columns:
if df[col].dtype == 'object':
continue # Skip strings, handle them separately
col_type = df[col].dtype
if col_type in ['int64','int32','int16']:
c_min = df[col].min()
c_max = df[col].max()
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
df[col] = df[col].astype(np.int32)
else:
df[col] = df[col].astype(np.int64)
elif col_type in ['float64','float32']:
c_min = df[col].min()
c_max = df[col].max()
if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
df[col] = df[col].astype(np.float16)
elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
df[col] = df[col].astype(np.float64)
end_mem = df.memory_usage().sum() / 1024**2
print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
return df
Exemple : Conversion de chaînes en types catégoriels
Si une colonne contient un nombre limité de valeurs de chaîne uniques, la convertir en un type catégoriel peut réduire considérablement l'utilisation de la mémoire. Les types catégoriels stockent les valeurs uniques une seule fois et représentent chaque élément de la colonne comme un code entier référençant les valeurs uniques.
df['col2'] = df['col2'].astype('category')
Prenons l'exemple d'un ensemble de données de transactions clients pour une plateforme de commerce électronique mondiale. La colonne 'Country' (Pays) pourrait contenir seulement quelques centaines de noms de pays uniques, tandis que l'ensemble de données contient des millions de transactions. La conversion de la colonne 'Country' en un type catégoriel réduirait considérablement la consommation de mémoire.
2. Découpage en blocs et itération
Lorsque vous traitez des ensembles de données extrêmement volumineux qui ne peuvent pas tenir en mémoire, vous pouvez traiter les données par blocs en utilisant le paramètre chunksize dans pd.read_csv() ou pd.read_excel(). Cela vous permet de charger et de traiter les données en morceaux plus petits et gérables.
for chunk in pd.read_csv('large_dataset.csv', chunksize=100000):
# Process the chunk (e.g., perform calculations, filtering, aggregation)
print(f"Processing chunk with {len(chunk)} rows")
# Optionally, append results to a file or database.
Exemple : Traitement de fichiers journaux volumineux
Imaginez le traitement d'un fichier journal massif provenant d'une infrastructure réseau mondiale. Le fichier journal est trop volumineux pour tenir en mémoire. En utilisant le découpage en blocs, vous pouvez parcourir le fichier journal, analyser chaque bloc pour des événements ou des modèles spécifiques, et agréger les résultats sans dépasser les limites de mémoire.
3. Sélectionner uniquement les colonnes nécessaires
Souvent, les ensembles de données contiennent des colonnes qui ne sont pas pertinentes pour votre analyse. Charger uniquement les colonnes nécessaires peut réduire considérablement l'utilisation de la mémoire. Vous pouvez spécifier les colonnes souhaitées en utilisant le paramètre usecols dans pd.read_csv().
df = pd.read_csv('large_dataset.csv', usecols=['col1', 'col2', 'col3'])
Exemple : Analyse de données de ventes
Si vous analysez des données de ventes pour identifier les produits les plus performants, vous pourriez n'avoir besoin que des colonnes 'Product ID' (ID produit), 'Sales Quantity' (Quantité vendue) et 'Sales Revenue' (Revenus des ventes). Le chargement de ces seules colonnes réduira la consommation de mémoire par rapport au chargement de l'ensemble du jeu de données, qui pourrait inclure les données démographiques des clients, les adresses de livraison et d'autres informations non pertinentes.
4. Utiliser des structures de données éparses
Si votre DataFrame contient de nombreuses valeurs manquantes (NaNs) ou des zéros, vous pouvez utiliser des structures de données éparses pour représenter les données plus efficacement. Les DataFrames éparses ne stockent que les valeurs non manquantes ou non nulles, réduisant considérablement l'utilisation de la mémoire lors du traitement de données éparses.
sparse_series = df['col1'].astype('Sparse[float]')
sparse_df = sparse_series.to_frame()
Exemple : Analyse des évaluations clients
Considérons un ensemble de données d'évaluations de clients pour un grand nombre de produits. La plupart des clients n'évalueront qu'un petit sous-ensemble de produits, ce qui entraînera une matrice d'évaluations éparse. L'utilisation d'un DataFrame éparse pour stocker ces données réduira considérablement la consommation de mémoire par rapport à un DataFrame dense.
5. Éviter de copier des données
Les opérations Pandas peuvent parfois créer des copies de DataFrames, ce qui entraîne une augmentation de l'utilisation de la mémoire. Modifier un DataFrame sur place (lorsque cela est possible) peut aider à éviter les copies inutiles.
Par exemple, au lieu de :
df = df[df['col1'] > 10]
Considérez l'utilisation de :
df.drop(df[df['col1'] <= 10].index, inplace=True)
L'argument `inplace=True` modifie le DataFrame directement sans créer de copie.
6. Optimiser le stockage des chaînes de caractères
Les colonnes de chaînes de caractères peuvent consommer une mémoire significative, surtout si elles contiennent de longues chaînes ou de nombreuses valeurs uniques. La conversion des chaînes en types catégoriels, comme mentionné précédemment, est une technique efficace. Une autre approche consiste à utiliser des représentations de chaînes plus petites si possible.
Exemple : Réduire la longueur des chaînes
Si une colonne contient des identifiants stockés sous forme de chaînes de caractères mais pouvant être représentés comme des entiers, leur conversion en entiers peut économiser de la mémoire. Par exemple, les identifiants de produit actuellement stockés sous forme de chaînes comme "PROD-1234" pourraient être mappés à des ID entiers.
7. Utiliser Dask pour les ensembles de données plus grands que la mémoire
Pour les ensembles de données qui sont vraiment trop volumineux pour tenir en mémoire, même avec le découpage en blocs, envisagez d'utiliser Dask. Dask est une bibliothèque de calcul parallèle qui s'intègre bien avec Pandas et NumPy. Elle vous permet de travailler avec des ensembles de données plus grands que la mémoire en les divisant en plus petits blocs et en les traitant en parallèle sur plusieurs cœurs ou même plusieurs machines.
import dask.dataframe as dd
ddf = dd.read_csv('large_dataset.csv')
# Perform operations on the Dask DataFrame (e.g., filtering, aggregation)
result = ddf[ddf['col1'] > 10].groupby('col2').mean().compute()
La méthode compute() déclenche le calcul réel et renvoie un DataFrame Pandas contenant les résultats.
Bonnes pratiques et considérations
- Profilez votre code : Utilisez des outils de profilage pour identifier les goulots d'étranglement de la mémoire et concentrez vos efforts d'optimisation sur les zones les plus impactantes.
- Testez différentes techniques : La technique optimale de réduction de la mémoire dépend des caractéristiques spécifiques de votre ensemble de données. Expérimentez différentes approches pour trouver la meilleure solution pour votre cas d'utilisation.
- Surveillez l'utilisation de la mémoire : Gardez une trace de l'utilisation de la mémoire pendant le traitement des données pour vous assurer que vos optimisations sont efficaces et éviter les erreurs de mémoire insuffisante.
- Comprenez vos données : Une compréhension approfondie de vos données est cruciale pour choisir les types de données et les techniques d'optimisation les plus appropriés.
- Considérez les compromis : Certaines techniques d'optimisation de la mémoire peuvent introduire une légère surcharge de performance. Pesez les avantages d'une utilisation réduite de la mémoire par rapport à tout impact potentiel sur les performances.
- Documentez vos optimisations : Documentez clairement les techniques d'optimisation de la mémoire que vous avez implémentées pour garantir que votre code est maintenable et compréhensible par d'autres.
Conclusion
L'optimisation de l'utilisation de la mémoire de Pandas est essentielle pour travailler avec de grands ensembles de données de manière efficace et efficiente. En comprenant comment Pandas stocke les données, en sélectionnant les bons types de données, en utilisant le découpage en blocs et en employant d'autres techniques d'optimisation, vous pouvez réduire considérablement la consommation de mémoire et améliorer les performances de vos flux de travail d'analyse de données. Ce guide a fourni un aperçu complet des techniques clés et des meilleures pratiques pour maîtriser la réduction de l'utilisation de la mémoire dans Pandas. N'oubliez pas de profiler votre code, de tester différentes techniques et de surveiller l'utilisation de la mémoire pour obtenir les meilleurs résultats pour votre cas d'utilisation spécifique. En appliquant ces principes, vous pouvez libérer tout le potentiel de Pandas et relever même les défis d'analyse de données les plus exigeants.
En maîtrisant ces techniques, les scientifiques et analystes de données du monde entier peuvent gérer des ensembles de données plus volumineux, améliorer les vitesses de traitement et obtenir des informations plus approfondies de leurs données. Cela contribue à une recherche plus efficace, à des décisions commerciales mieux informées et, en fin de compte, à un monde plus axé sur les données.