Versions¶
In [1]:
#numpy
import numpy
numpy.__version__
Out[1]:
'2.2.2'
In [2]:
#pandas
import pandas
pandas.__version__
Out[2]:
'2.2.3'
Générer les données¶
In [3]:
#initaliser le générateur
rng = numpy.random.default_rng(seed=1)
In [4]:
#générer une matrice de dim (n, p)
n = 1000
p = 50
mat = rng.random(size=(n,p))
#vérif.
print(mat.shape)
(1000, 50)
In [5]:
#transformer en data frame pandas
import pandas
df = pandas.DataFrame(mat,columns=["V"+str(i+1) for i in range(p)])
print(df.shape)
print(df.columns)
(1000, 50) Index(['V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10', 'V11', 'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19', 'V20', 'V21', 'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'V29', 'V30', 'V31', 'V32', 'V33', 'V34', 'V35', 'V36', 'V37', 'V38', 'V39', 'V40', 'V41', 'V42', 'V43', 'V44', 'V45', 'V46', 'V47', 'V48', 'V49', 'V50'], dtype='object')
Fonction de transformation¶
In [6]:
#transformation
#remplacer la valeur par le quartile le plus proche
#x est une Serie d'un data frame
def ftrans(x):
#calculer les quartiles
q = x.quantile(q=[0.25,0.5,0.75])
#remplacer la valeur par le quartile le plus proche
res = x.apply(lambda v:q.iloc[((v-q)**2).argmin()])
#renvoyer la serie
return res
Application séquentielle¶
In [7]:
#boucle
def fun_boucle(D):
#liste vide
lst = []
#boucler sur les colonnes
for x in D.columns:
lst.append(ftrans(D[x]))
#transformer en data frame
res_df = pandas.DataFrame(lst).transpose()
return res_df
In [8]:
%%time
#appel
res_boucle = fun_boucle(df)
#dimension
res_boucle.shape
CPU times: total: 6.52 s Wall time: 6.91 s
Out[8]:
(1000, 50)
In [9]:
#moyennes par colonne - juste pour contrôle
avg_boucle = res_boucle.mean(axis=0)
print(avg_boucle[:5])
V1 0.517097 V2 0.502991 V3 0.486669 V4 0.527154 V5 0.515145 dtype: float64
Utilisation de "apply" de Pandas¶
In [10]:
#utilisation de apply de Pandas
#très proche dans son expression de celui de R
def fun_apply(D):
#appel avec passage de ftrans en callback
#par défaut : axis = 0, calcul par colonne
res = D.apply(func=ftrans)
#renvoyer direct - déjà un data frame
return res
In [11]:
%%time
#appel - en réalité : plus pratique mais pas plus performant qu'une boucle
res_apply = fun_apply(df)
#dim.
print(res_apply.shape)
(1000, 50) CPU times: total: 6.47 s Wall time: 6.85 s
In [12]:
#vérif. -- ok
numpy.sum((avg_boucle-res_apply.mean(axis=0))**2)
Out[12]:
np.float64(7.157371970925172e-28)
In [13]:
# /!\ REMARQUE
# on peut utiliser un "engine" numba
# cf. https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.apply.html
# cf. https://www.youtube.com/watch?v=BkJoHz0NGlE
# mais bien sûr il faut installer le package
# il faut aussi redéfinir ftrans() avec les directives adéquates
Parallélisation avec "Modin"¶
In [14]:
#choix du "moteur" (engine) - Dask
#qui a été installé dans l'environnement
import os
os.environ["MODIN_ENGINE"] = "dask"
In [15]:
# importation du package
# il faut avoir installé "dask" - https://www.youtube.com/watch?v=qOSFKtJPASg
# /!\ il faut aussi installer manuellement le package "distributed"
import modin.pandas as pdm
#fonction avec apply de modin
def fun_modin(D):
#appel avec passage de ftrans en callback
#cast du data frame pandas en df modin
res = pdm.DataFrame(D).apply(ftrans)
#renvoyer direct - déjà un data frame
return res
In [17]:
%%time
#appel
res_modin = fun_modin(df)
#dim.
res_modin.shape
CPU times: total: 1.09 s Wall time: 5.53 s
Out[17]:
(1000, 50)
In [18]:
#vérification
numpy.sum((avg_boucle-res_modin.mean(axis=0))**2)
UserWarning: __array_ufunc__ is not currently supported by PandasOnDask, defaulting to pandas implementation. Please refer to https://modin.readthedocs.io/en/stable/supported_apis/defaulting_to_pandas.html for explanation.
Out[18]:
np.float64(7.157926638749156e-28)
In [19]:
# dask a aussi sa propre version des data frame
# cf. https://www.youtube.com/watch?v=yDHzpSobUro
# mais il ne sait utiliser apply qu'avec axis = 1
# il faudrait réaliser une transposition 2 fois
# pas probant par rapport à "modin"
Parallélisation avec "pandarallel"¶
In [20]:
from pandarallel import pandarallel
#fonction avec apply
def fun_pandarallel(D):
#initialisation - spécifier le nb de "workers"
#pas verbose
pandarallel.initialize(nb_workers=5, verbose=0)
#appel avec passage de ftrans en callback
res = D.parallel_apply(ftrans)
#renvoyer direct - déjà un data frame
return res
In [21]:
%%time
#appel
res_pdrl = fun_pandarallel(df)
#dim.
res_pdrl.shape
CPU times: total: 375 ms Wall time: 3.81 s
Out[21]:
(1000, 50)
In [22]:
#vérification
numpy.sum((avg_boucle-res_pdrl.mean(axis=0))**2)
Out[22]:
np.float64(7.157371970925172e-28)
Comparaison des temps de traitement¶
In [23]:
#apply de pandas
%timeit -r 10 -n 1 fun_apply(df)
8.87 s ± 194 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)
In [24]:
#apply de modin
%timeit -r 10 -n 1 fun_modin(df)
5.34 s ± 128 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)
In [25]:
#apply de pandarallel
%timeit -r 10 -n 1 fun_pandarallel(df)
3.9 s ± 72.8 ms per loop (mean ± std. dev. of 10 runs, 1 loop each)