J'ai remarqué des performances très médiocres lors de l'utilisation d'iterrows de pandas.
Est-ce quelque chose qui est vécu par d'autres? Est-ce spécifique à iterrows et cette fonction doit-elle être évitée pour des données d'une certaine taille (je travaille avec 2 à 3 millions de lignes)?
Cette discussion sur GitHub m'a amené à croire que cela est causé par le mélange de types dans le cadre de données, mais l'exemple simple ci-dessous montre qu'il est présent même lors de l'utilisation d'un type (float64). Cela prend 36 secondes sur ma machine:
import pandas as pd
import numpy as np
import time
s1 = np.random.randn(2000000)
s2 = np.random.randn(2000000)
dfa = pd.DataFrame({'s1': s1, 's2': s2})
start = time.time()
i=0
for rowindex, row in dfa.iterrows():
i+=1
end = time.time()
print end - start
Pourquoi les opérations vectorisées comme s’appliquent-elles tellement plus rapidement? J'imagine qu'il doit également y avoir une itération rangée par rangée.
Je n'arrive pas à comprendre comment ne pas utiliser iterrows dans mon cas (j'économiserai ceci pour une question ultérieure). Par conséquent, j'aimerais savoir si vous avez toujours pu éviter cette itération. Je fais des calculs basés sur des données dans des cadres de données séparés. Merci!
--- Edit: la version simplifiée de ce que je veux exécuter a été ajoutée ci-dessous ---
import pandas as pd
import numpy as np
#%% Create the original tables
t1 = {'letter':['a','b'],
'number1':[50,-10]}
t2 = {'letter':['a','a','b','b'],
'number2':[0.2,0.5,0.1,0.4]}
table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)
#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=[0])
#%% Iterate through filtering relevant data, optimizing, returning info
for row_index, row in table1.iterrows():
t2info = table2[table2.letter == row['letter']].reset_index()
table3.ix[row_index,] = optimize(t2info,row['number1'])
#%% Define optimization
def optimize(t2info, t1info):
calculation = []
for index, r in t2info.iterrows():
calculation.append(r['number2']*t1info)
maxrow = calculation.index(max(calculation))
return t2info.ix[maxrow]
Généralement, iterrows
ne devrait être utilisé que dans des cas très spécifiques. Voici l'ordre de priorité général pour l'exécution de diverses opérations:
1) vectorization
2) using a custom cython routine
3) apply
a) reductions that can be performed in cython
b) iteration in python space
4) itertuples
5) iterrows
6) updating an empty frame (e.g. using loc one-row-at-a-time)
L'utilisation d'une routine cython personnalisée est généralement trop compliquée, sautons cela pour le moment.
1) La vectorisation est toujours TOUJOURS le premier et le meilleur choix. Cependant, il existe un petit nombre de cas qui ne peuvent pas être vectorisés de manière évidente (impliquant principalement une récurrence). En outre, sur un cadre plus petit, il peut être plus rapide de faire d’autres méthodes.
3) Appliquer implique peut généralement être effectué par un itérateur dans l'espace Cython (ceci est fait en interne dans les pandas) (c'est un cas).
Cela dépend de ce qui se passe dans l'expression apply. par exemple. df.apply(lambda x: np.sum(x))
sera exécuté assez rapidement (bien sûr, df.sum(1)
est encore meilleur). Cependant, quelque chose comme: df.apply(lambda x: x['b'] + 1)
sera exécuté dans python space) et sera donc plus lent.
4) itertuples
ne met pas les données dans une série, mais les renvoie sous forme de tuple
5) iterrows
ENCADRE les données dans une série. Sauf si vous en avez vraiment besoin, utilisez une autre méthode.
6) mettre à jour un cadre vide une seule ligne à la fois. J'ai vu cette méthode trop utilisée. C'est de loin le plus lent. C'est probablement un lieu commun (et raisonnablement rapide pour certaines python), mais un DataFrame effectue un bon nombre de contrôles sur l'indexation; il sera donc toujours très lent pour mettre à jour une ligne à la fois. Il est préférable de créer de nouvelles structures et concat
.
Les opérations vectorielles dans Numpy et pandas sont beaucoup plus rapides que les opérations scalaires dans Vanilla Python = pour plusieurs raisons:
Recherche de type amorti: Python est un langage typé dynamiquement, il existe donc une surcharge d'exécution pour chaque élément d'un tableau. Cependant, Numpy (et donc les pandas) effectuent des calculs dans C (souvent via Cython). Le type du tableau n’est déterminé qu’au début de l’itération; cette économie est à elle seule l’un des gains les plus importants.
Meilleure mise en cache: Itérer sur un tableau C est compatible avec le cache et donc très rapide. A pandas DataFrame est un "tableau orienté colonne", ce qui signifie que chaque colonne est en réalité un tableau. Ainsi, les actions natives que vous pouvez effectuer sur un DataFrame (comme la somme de tous les éléments d'un colonne) vont avoir peu de cache cache.
Plus de possibilités de parallélisme: Un tableau C simple peut être utilisé via les instructions SIMD. Certaines parties de Numpy activent SIMD, en fonction de votre CPU et du processus d’installation. Les avantages du parallélisme ne seront pas aussi dramatiques que le typage statique et une meilleure mise en cache, mais ils restent une victoire solide.
Morale de l'histoire: utilisez les opérations vectorielles dans Numpy et les pandas. Elles sont plus rapides que les opérations scalaires dans Python pour la simple raison que ces opérations sont exactement ce que le programmeur C aurait écrit à la main de toute façon. (Sauf que la notion de tableau est beaucoup plus facile à lire que explicite boucles avec instructions SIMD intégrées.)
Voici le moyen de résoudre votre problème. Ceci est tout vectorisé.
In [58]: df = table1.merge(table2,on='letter')
In [59]: df['calc'] = df['number1']*df['number2']
In [60]: df
Out[60]:
letter number1 number2 calc
0 a 50 0.2 10
1 a 50 0.5 25
2 b -10 0.1 -1
3 b -10 0.4 -4
In [61]: df.groupby('letter')['calc'].max()
Out[61]:
letter
a 25
b -1
Name: calc, dtype: float64
In [62]: df.groupby('letter')['calc'].idxmax()
Out[62]:
letter
a 1
b 2
Name: calc, dtype: int64
In [63]: df.loc[df.groupby('letter')['calc'].idxmax()]
Out[63]:
letter number1 number2 calc
1 a 50 0.5 25
2 b -10 0.1 -1
Une autre option consiste à utiliser to_records()
, qui est plus rapide que itertuples
et iterrows
.
Mais dans votre cas, d’autres améliorations sont possibles.
Voici ma version finale optimisée
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
t2info = table2.to_records()
for index, letter, n1 in table1.to_records():
t2 = t2info[grouped.groups[letter].values]
# np.multiply is in general faster than "x * y"
maxrow = np.multiply(t2.number2, n1).argmax()
# `[1:]` removes the index column
ret.append(t2[maxrow].tolist()[1:])
global table3
table3 = pd.DataFrame(ret, columns=('letter', 'number2'))
Test de référence:
-- iterrows() --
100 loops, best of 3: 12.7 ms per loop
letter number2
0 a 0.5
1 b 0.1
2 c 5.0
3 d 4.0
-- itertuple() --
100 loops, best of 3: 12.3 ms per loop
-- to_records() --
100 loops, best of 3: 7.29 ms per loop
-- Use group by --
100 loops, best of 3: 4.07 ms per loop
letter number2
1 a 0.5
2 b 0.1
4 c 5.0
5 d 4.0
-- Avoid multiplication --
1000 loops, best of 3: 1.39 ms per loop
letter number2
0 a 0.5
1 b 0.1
2 c 5.0
3 d 4.0
Code complet:
import pandas as pd
import numpy as np
#%% Create the original tables
t1 = {'letter':['a','b','c','d'],
'number1':[50,-10,.5,3]}
t2 = {'letter':['a','a','b','b','c','d','c'],
'number2':[0.2,0.5,0.1,0.4,5,4,1]}
table1 = pd.DataFrame(t1)
table2 = pd.DataFrame(t2)
#%% Create the body of the new table
table3 = pd.DataFrame(np.nan, columns=['letter','number2'], index=table1.index)
print('\n-- iterrows() --')
def optimize(t2info, t1info):
calculation = []
for index, r in t2info.iterrows():
calculation.append(r['number2'] * t1info)
maxrow_in_t2 = calculation.index(max(calculation))
return t2info.loc[maxrow_in_t2]
#%% Iterate through filtering relevant data, optimizing, returning info
def iterthrough():
for row_index, row in table1.iterrows():
t2info = table2[table2.letter == row['letter']].reset_index()
table3.iloc[row_index,:] = optimize(t2info, row['number1'])
%timeit iterthrough()
print(table3)
print('\n-- itertuple() --')
def optimize(t2info, n1):
calculation = []
for index, letter, n2 in t2info.itertuples():
calculation.append(n2 * n1)
maxrow = calculation.index(max(calculation))
return t2info.iloc[maxrow]
def iterthrough():
for row_index, letter, n1 in table1.itertuples():
t2info = table2[table2.letter == letter]
table3.iloc[row_index,:] = optimize(t2info, n1)
%timeit iterthrough()
print('\n-- to_records() --')
def optimize(t2info, n1):
calculation = []
for index, letter, n2 in t2info.to_records():
calculation.append(n2 * n1)
maxrow = calculation.index(max(calculation))
return t2info.iloc[maxrow]
def iterthrough():
for row_index, letter, n1 in table1.to_records():
t2info = table2[table2.letter == letter]
table3.iloc[row_index,:] = optimize(t2info, n1)
%timeit iterthrough()
print('\n-- Use group by --')
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
for index, letter, n1 in table1.to_records():
t2 = table2.iloc[grouped.groups[letter]]
calculation = t2.number2 * n1
maxrow = calculation.argsort().iloc[-1]
ret.append(t2.iloc[maxrow])
global table3
table3 = pd.DataFrame(ret)
%timeit iterthrough()
print(table3)
print('\n-- Even Faster --')
def iterthrough():
ret = []
grouped = table2.groupby('letter', sort=False)
t2info = table2.to_records()
for index, letter, n1 in table1.to_records():
t2 = t2info[grouped.groups[letter].values]
maxrow = np.multiply(t2.number2, n1).argmax()
# `[1:]` removes the index column
ret.append(t2[maxrow].tolist()[1:])
global table3
table3 = pd.DataFrame(ret, columns=('letter', 'number2'))
%timeit iterthrough()
print(table3)
La version finale est presque 10 fois plus rapide que le code original. La stratégie est la suivante:
groupby
pour éviter la comparaison répétée des valeurs.to_records
pour accéder aux objets bruts numpy.records.Oui, Pandas itertuples () est plus rapide que iterrows (). Vous pouvez vous référer à la documentation: https://pandas.pydata.org/pandas-docs/stable/reference/ api/pandas.DataFrame.iterrows.html
"Pour conserver les types lors de l'itération sur les lignes, il est préférable d'utiliser itertuples () qui renvoie les échantillons nommés des valeurs et qui est généralement plus rapide que iterrows."