Si vous avez déjà utilisé Pandas avec des données tabulaires, vous connaissez peut-être le processus d'importation des données, de nettoyage et de transformation, puis de leur utilisation comme entrée dans le modèle. Cependant, lorsque vous devez faire évoluer et mettre votre code en production, votre pipeline Pandas commencera très probablement à planter et à fonctionner lentement. Dans cet article, je partagerai 2 conseils pour vous aider à accélérer l'exécution du code Pandas, à améliorer l'efficacité du traitement des données et à éviter les pièges courants.
Dans Pandas, les opérations de vectorisation sont un outil efficace qui peut traiter les colonnes de l'ensemble du bloc de données de manière plus concise sans boucler ligne par ligne.
La diffusion est un élément clé de la manipulation vectorisée, permettant de manipuler intuitivement des objets de formes différentes.
eg1 : Un tableau a avec 3 éléments est multiplié par un scalaire b, ce qui donne un tableau de même forme que Source.
eg2 : Lors de l'opération d'addition, ajoutez le tableau a avec la forme (4,1) et le tableau b avec la forme (3,). Le résultat sera un tableau avec la forme (4,3).
De nombreux articles ont abordé ce sujet, notamment dans le domaine de l'apprentissage profond où les multiplications matricielles à grande échelle sont courantes. Cet article présentera deux brefs exemples.
Tout d’abord, disons que vous souhaitez compter le nombre de fois qu’un entier donné apparaît dans une colonne. Voici 2 méthodes possibles.
"""计算DataFrame X 中 "column_1" 列中等于目标值 target 的元素个数。参数:X: DataFrame,包含要计算的列 "column_1"。target: int,目标值。返回值:int,等于目标值 target 的元素个数。"""# 使用循环计数def count_loop(X, target: int) -> int:return sum(x == target for x in X["column_1"])# 使用矢量化操作计数def count_vectorized(X, target: int) -> int:return (X["column_1"] == target).sum()
Supposons maintenant que vous ayez un DataFrame avec une colonne de date et que vous souhaitiez le décaler d'un nombre de jours donné. Le calcul utilisant des opérations vectorisées est le suivant :
def offset_loop(X, days: int) -> pd.DataFrame:d = pd.Timedelta(days=days)X["column_const"] = [x + d for x in X["column_10"]]return Xdef offset_vectorized(X, days: int) -> pd.DataFrame:X["column_const"] = X["column_10"] + pd.Timedelta(days=days)return X
La première et la plus intuitive façon d'itérer est d'utiliser une boucle for Python.
def loop(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:res = []i_remove_col = df.columns.get_loc(remove_col)i_words_to_remove_col = df.columns.get_loc(words_to_remove_col)for i_row in range(df.shape[0]):res.append(remove_words(df.iat[i_row, i_remove_col], df.iat[i_row, i_words_to_remove_col]))return result
def apply(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return df.apply(func=lambda x: remove_words(x[remove_col], x[words_to_remove_col]), axis=1).tolist()
À chaque itération de df.apply, la fonction appelable fournie obtient une série dont l'index est df.columns et dont les valeurs sont des lignes. Cela signifie que les pandas doivent générer la séquence dans chaque boucle, ce qui coûte cher. Pour réduire les coûts, il est préférable d'appeler apply sur un sous-ensemble de df que vous savez que vous utiliserez, comme ceci :
def apply_only_used_cols(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return df[[remove_col, words_to_remove_col]].apply(func=lambda x: remove_words(x[remove_col], x[words_to_remove_col]), axis=1)
Il est définitivement préférable d'itérer en utilisant des itertuples combinés avec des listes. itertuples génère des tuples (nommés) avec des données de ligne.
def itertuples_only_used_cols(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return [remove_words(x[0], x[1])for x in df[[remove_col, words_to_remove_col]].itertuples(index=False, name=None)]
zip accepte un objet itérable et génère un tuple, où le i-ième tuple contient tous les i-ième éléments de l'objet itérable donné dans l'ordre.
def zip_only_used_cols(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return [remove_words(x, y) for x, y in zip(df[remove_col], df[words_to_remove_col])]
def to_dict_only_used_columns(df: pd.DataFrame) -> list[str]:return [remove_words(row[remove_col], row[words_to_remove_col])for row in df[[remove_col, words_to_remove_col]].to_dict(orient="records")]
En plus des techniques itératives dont nous avons parlé, deux autres méthodes peuvent aider à améliorer les performances du code : la mise en cache et la parallélisation. La mise en cache est particulièrement utile si vous appelez plusieurs fois une fonction pandas avec les mêmes paramètres. Par exemple, si remove_words est appliqué à un ensemble de données comportant de nombreuses valeurs en double, vous pouvez utiliser functools.lru_cache pour stocker les résultats de la fonction et éviter de les recalculer à chaque fois. Pour utiliser lru_cache, ajoutez simplement le décorateur @lru_cache à la déclaration de remove_words, puis appliquez la fonction à votre ensemble de données en utilisant votre méthode d'itération préférée. Cela peut améliorer considérablement la vitesse et l’efficacité de votre code. Prenons le code suivant comme exemple :
@lru_cachedef remove_words(...):... # Same implementation as beforedef zip_only_used_cols_cached(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return [remove_words(x, y) for x, y in zip(df[remove_col], df[words_to_remove_col])]
L'ajout de ce décorateur génère une fonction qui "se souvient" de la sortie de l'entrée précédemment rencontrée, éliminant ainsi le besoin de réexécuter tout le code.
Le dernier atout est d'utiliser pandarallel pour paralléliser nos appels de fonction sur plusieurs blocs df indépendants. L'outil est facile à utiliser : il vous suffit de l'importer et de l'initialiser, puis de remplacer tous les .applys par .parallel_applys.
from pandarallel import pandarallelpandarallel.initialize(nb_workers=min(os.cpu_count(), 12))def parapply_only_used_cols(df: pd.DataFrame, remove_col: str, words_to_remove_col: str) -> list[str]:return df[[remove_col, words_to_remove_col]].parallel_apply(lambda x: remove_words(x[remove_col], x[words_to_remove_col]), axis=1)
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!