Comment accélérer la méthode d'application de Pandas avec une seule phrase (avec calcul de vérification)

Conclusion

Ajoutez simplement la méthode plus rapide avant la méthode d'application Pandas

Exemple concret

import pandas as pd
import numpy as np
import swifter

#Créez un DataFrame adapté
df = pd.DataFrame({'col': np.random.normal(size=10000000)})

#Ajoutez une méthode plus rapide avant la méthode Apply.
%time df['col2'] = df['col'].swifter.apply(lambda x: x**2)
# Wall time: 50 ms

#Pour comparaison (les pandas normaux appliquent la méthode)
%time df['col2'] = df['col'].apply(lambda x: x**2)
# Wall time: 3.48 s

Comment installer

Pour pip


$ pip install -U pandas # upgrade pandas
$ pip install swifter

Pour conda


$ conda update pandas # upgrade pandas
$ conda install -c conda-forge swifter

Que fait plus vite

Les pandas postulent lentement

Le montant de calcul de la méthode d'application de Pandas est O (N). Peu importe si le DataFrame a environ 10000 lignes, Le traitement de grands DataFrames peut être assez pénible. Heureusement, il existe plusieurs façons d'accélérer les pandas.

Méthode d'accélération des pandas

  1. [Vectorisation](# Qu'est-ce que la vectorisation)
  2. Utilisez Cython et Numba [^ 1]
  3. Traitement parallèle par Dask [^ 2]

Par exemple, lorsque la vectorisation n'est pas possible, un traitement parallèle par Dask est effectué. Cependant, le traitement parallèle sur un DataFrame qui n'a pas beaucoup de lignes peut ralentir le processus. Pour les gens comme moi qui trouvent compliqué de choisir la meilleure méthode d'accélération au cas par cas, ** plus rapide ** est le meilleur choix.

swifter Selon le document officiel [^ 3], swifter fait ce qui suit:

  1. Vectorisation Si possible, vectorisez.
  2. Si la vectorisation n'est pas possible, le plus rapide du traitement parallèle de Dask et de l'application de Pandas est automatiquement sélectionné.

Il est très pratique de sélectionner automatiquement la meilleure méthode. Comme je le montrerai plus tard, dans de nombreux cas, plus rapide est plus rapide que les Pandas, donc N'est-il pas mal de toujours utiliser swfiter?

Vérification

Ci-dessous, je voudrais vérifier à quelle vitesse est plus rapide que Dask, Pandas, etc. Puisque swifter se comporte différemment selon qu'il peut être vectorisé ou non, nous vérifierons chaque cas. Les spécifications du PC utilisé sont Intel Core i5-8350U à 1,70 GHz et la mémoire est de 16 Go.

Lorsque vectorisable

Etant donné que plus rapide est vectorisé lorsqu'il peut être vectorisé, le temps de calcul de plus rapide est le même que lorsqu'il est simplement vectorisé. Devrait être à peu près égal. Vérifions ça.

Lorsque vectorisable


import pandas as pd
import numpy as np
import dask.dataframe as dd
import swifter
import multiprocessing
import gc

pandas_time_list = []
dask_time_list = []
vector_time_list = []
swifter_time_list = []

#Fonction vectorisable
def multiple_func(df):
    return df['col1']*df['col2']

def apply_func_to_df(df):
    return df.apply(multiple_func, axis=1)

for num in np.logspace(2, 7, num=7-2+1, base=10, dtype='int'):
    df = pd.DataFrame()
    df['col1'] = np.random.normal(size=num)
    df['col2'] = np.random.normal(size=num)
    ddf = dd.from_pandas(df, npartitions=multiprocessing.cpu_count())

    pandas_time = %timeit -n2 -r1 -o -q df.apply(multiple_func, axis=1)
    dask_time = %timeit -n2 -r1 -o -q ddf.map_partitions(apply_func_to_df).compute(scheduler='processes')
    vector_time = %timeit -n2 -r1 -o -q df['col1']*df['col2']
    swifter_time = %timeit -n2 -r1 -o -q df.swifter.apply(multiple_func, axis=1)
    
    pandas_time_list.append(pandas_time.average)
    dask_time_list.append(dask_time.average)
    vector_time_list.append(vector_time.average)
    swifter_time_list.append(swifter_time.average)

    del df, ddf
    gc.collect()

vect.png

L'axe horizontal de la figure correspond au nombre de lignes du DataFrame et l'axe vertical correspond au temps écoulé. Notez qu'il s'agit d'un double graphe logarithmique.

Le temps écoulé de swifter </ font> est proche du temps écoulé de vectorisation </ font>, vous pouvez donc voir qu'il est vectorisé. ..

Pour les DataFrames de moins de 100 000 lignes, un seul noyau de Pandas </ font> est plus rapide que le traitement parallèle de Dask </ font>. Puisque le temps écoulé de Dask </ font> de 100 000 lignes ou moins est constant, on peut en déduire que cela est dû à la surcharge comme le partage de mémoire dû au traitement parallèle. (Temps de calcul de la fonction <Temps de copie des données pour le partage de la mémoire)

Lorsque la vectorisation n'est pas possible

Examinons ensuite le cas où la vectorisation n'est pas possible. S'il ne peut pas être vectorisé, plus rapide devrait choisir entre le traitement parallèle et le traitement monocœur, selon ce qui est le mieux.

Lorsque la vectorisation n'est pas possible


pandas_time_list_non_vectorize = []
dask_time_list_non_vectorize = []
swifter_time_list_non_vectorize = []

#Fonctions non vectorisées
def compare_func(df):
    if df['col1'] > df['col2']:
        return 1
    else:
        return -1

def apply_func_to_df(df):
    return df.apply(compare_func, axis=1)

for num in np.logspace(2, 7, num=7-2+1, base=10, dtype='int'):
    df = pd.DataFrame()
    df['col1'] = np.random.normal(size=num)
    df['col2'] = np.random.normal(size=num)
    ddf = dd.from_pandas(df, npartitions=multiprocessing.cpu_count())

    pandas_time = %timeit -n2 -r1 -o -q df.apply(compare_func, axis=1)
    dask_time = %timeit -n2 -r1 -o -q ddf.map_partitions(apply_func_to_df).compute(scheduler='processes')
    swifter_time = %timeit -n2 -r1 -o -q df.swifter.apply(compare_func, axis=1)
    
    pandas_time_list_non_vectorize.append(pandas_time.average)
    dask_time_list_non_vectorize.append(dask_time.average)
    swifter_time_list_non_vectorize.append(swifter_time.average)

    del df, ddf
    gc.collect()

non_vect.png

swifter </ font> est traité par un seul cœur lorsque le traitement parallèle n'est pas obtenu. Si le traitement parallèle est supérieur au single core, vous pouvez voir que le traitement parallèle est sélectionné.

Résumé

swifter est un excellent module qui sélectionne automatiquement la méthode d'accélération optimale en fonction de la situation. Pour éviter de perdre un temps précieux, utilisez plus rapidement lorsque vous utilisez la méthode d'application de Pandas.

prime

Qu'est-ce que la vectorisation?

Une fonction de vectorisation est une fonction qui s'applique automatiquement à tous les éléments sans écrire une boucle for explicite. Je pense que c'est plus facile à comprendre si vous regardez un exemple.

Si non vectorisé


array_sample = np.random.normal(size=1000000)

def non_vectorize(array_sample):
    result = []
    for i in array_sample:
        result.append(i*i)
    return np.array(result)

%time non_vectorize_result = non_vectorize(array_sample)
# Wall time: 350 ms

Lorsque vectorisé


def vectorize(array_sample):
    return array_sample*array_sample

%time vectorize_result = vectorize(array_sample)
# Wall time: 4.09 ms

En vectorisant, c'est environ 80 fois plus rapide. Vérifiez que les deux résultats correspondent.

Vérifiez s'ils correspondent


np.allclose(non_vectorize_result, vectorize_result)
# True

Recommended Posts