Pandalar yinelemelerinde performans sorunları var mı?


96

Pandaların yinelemelerini kullanırken çok düşük performans fark ettim.

Bu başkaları tarafından deneyimlenen bir şey mi? Yinelemelere özel mi ve belirli bir boyuttaki veriler için bu işlevden kaçınılmalı mı (2-3 milyon satırla çalışıyorum)?

GitHub'daki bu tartışma , bunun veri çerçevesindeki dtype'ları karıştırırken ortaya çıktığına inanmamı sağladı, ancak aşağıdaki basit örnek, bir dtype (float64) kullanırken bile orada olduğunu gösteriyor. Bu, makinemde 36 saniye sürüyor:

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

Vektörize edilmiş işlemler neden bu kadar hızlı uygulanır? Orada da bir dizi satır yineleme olduğunu hayal ediyorum.

Benim durumumda yinelemeleri nasıl kullanmayacağımı çözemiyorum (bu, gelecekteki bir soru için saklayacağım). Bu nedenle, bu yinelemeden sürekli olarak kaçınabildiyseniz, duymaktan memnun olurum. Ayrı veri çerçevelerindeki verilere dayalı hesaplamalar yapıyorum. Teşekkür ederim!

--- Düzenleme: çalıştırmak istediğim şeyin basitleştirilmiş versiyonu aşağıya eklendi ---

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]

7
applyVektörize DEĞİLDİR. iterrowsdaha da kötüdür, çünkü her şeyi kutuya alır (bu 'performans farkı ile apply). Sadece iterrowsçok az durumda kullanmalısınız. IMHO asla. Gerçekte ne yaptığınızı gösterin iterrows.
Jeff

2
Bunun yerine bağlantı verdiğiniz konu, a'nın DatetimeIndexiçine kutulanması ile ilgilidir Timestamps(python alanında uygulanmıştır) ve bu, ana bilgisayarda çok iyileştirilmiştir.
Jeff

1
Daha kapsamlı bir tartışma için bu sayıya bakın: github.com/pydata/pandas/issues/7194 .
Jeff

Spesifik soruya bağlantı (bu genel olarak kalacaktır): stackoverflow.com/questions/24875096/…
KieranPC

Lütfen yinelemelerin () kullanılmasını önermeyin. Pandalar tarihindeki en kötü anti-modelin bariz bir etkinleştiricisidir.
cs95

Yanıtlar:


188

Genel olarak, iterrowsyalnızca çok, çok özel durumlarda kullanılmalıdır. Bu, çeşitli işlemlerin gerçekleştirilmesi için genel öncelik sırasıdır:

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)

Özel bir Cython rutini kullanmak genellikle çok karmaşıktır, bu yüzden şimdilik bunu atlayalım.

1) Vektörizasyon HER ZAMAN, DAİMA ilk ve en iyi seçenektir. Bununla birlikte, bariz yollarla vektörleştirilemeyen küçük bir dizi vaka (genellikle nüksü içeren) vardır. Dahası, küçücük bir şekilde DataFrame, diğer yöntemleri kullanmak daha hızlı olabilir.

3) apply genellikle Cython uzayında bir yineleyici tarafından ele alınabilir. Bu, applyifadenin içinde ne olduğuna bağlı olsa da, pandalar tarafından dahili olarak ele alınır . Örneğin, df.apply(lambda x: np.sum(x))oldukça hızlı bir şekilde yürütülecek, elbette df.sum(1)daha da iyi. Ancak buna benzer bir şey df.apply(lambda x: x['b'] + 1)Python alanında yürütülecektir ve bu nedenle çok daha yavaştır.

4) itertuplesverileri bir Series. Verileri tuple biçiminde döndürür.

5) iterrowsVerileri bir Series. Buna gerçekten ihtiyacınız olmadıkça başka bir yöntem kullanın.

6) Boş bir çerçeveyi her seferinde tek satır güncellemek. Bu yöntemin çok fazla WAY kullandığını gördüm. Açık farkla en yavaş olanıdır. Muhtemelen yaygın bir yerdir (ve bazı python yapıları için oldukça hızlıdır), ancak a DataFrame, indekslemede oldukça fazla sayıda kontrol yapar, bu nedenle bu, bir seferde bir satırı güncellemek için her zaman çok yavaş olacaktır. Yeni yapılar oluşturmak çok daha iyi ve concat.


1
Evet, 6 (ve 5) numarayı kullandım. Biraz öğrenmem gereken şeyler var. Göreceli bir acemi için bariz bir seçim gibi görünüyor.
KieranPC

3
Deneyimlerime göre, 3, 4 ve 5 arasındaki fark, kullanım durumuna bağlı olarak sınırlıdır.
IanS

8
Bu defterdeki çalışma zamanlarını kontrol etmeye çalıştım . Her nasılsa itertuplesapply
şundan

1
pd.DataFrame.applygenellikle daha yavaştır itertuples. Ek olarak, mapkötü adlandırılmış np.vectorizeve numba(belirli bir sıra olmadan) vektörelleştirilemeyen hesaplamalar için liste anlamalarını dikkate almaya değer , örneğin bu yanıta bakın .
jpp

2
@Jeff, meraktan neden buraya liste anlayışlarını eklemedin? Dizin hizalamasını veya eksik verileri işlemedikleri doğru olsa da (try-catch ile bir işlev kullanmadığınız sürece), pandas yöntemlerinin vektörize edilmediği birçok kullanım durumu (dize / regex şeyler) için iyidirler ( kelimenin tam anlamıyla) uygulamaları. LC'lerin pandaların uygulanmasına ve birçok pandas dizesi işlevine göre daha hızlı, daha düşük bir ek yük alternatifi olduğundan bahsetmeye değer mi?
cs95

17

Numpy ve pandalarda vektör işlemleri, çeşitli nedenlerle vanilya Python'daki skaler işlemlerden çok daha hızlıdır :

  • Amortize edilmiş tür araması : Python dinamik olarak yazılmış bir dildir, bu nedenle bir dizideki her öğe için çalışma zamanı ek yükü vardır. Bununla birlikte, Numpy (ve dolayısıyla pandalar) C'de (genellikle Cython aracılığıyla) hesaplamalar yapar. Dizinin türü yalnızca yinelemenin başlangıcında belirlenir; Bu tasarruf tek başına en büyük kazançlardan biridir.

  • Daha iyi önbelleğe alma : Bir C dizisi üzerinde yineleme yapmak önbellek dostudur ve bu nedenle çok hızlıdır. Pandas DataFrame, "sütun yönelimli bir tablodur", yani her sütunun gerçekten sadece bir dizi olduğu anlamına gelir. Dolayısıyla, bir DataFrame üzerinde gerçekleştirebileceğiniz yerel eylemler (bir sütundaki tüm öğeleri toplamak gibi) birkaç önbellek eksikliğine neden olacaktır.

  • Paralellik için daha fazla fırsat : Basit bir C dizisi SIMD komutlarıyla çalıştırılabilir. Numpy'nin bazı bölümleri, CPU'nuza ve kurulum sürecine bağlı olarak SIMD'yi etkinleştirir. Paralelizmin faydaları, statik yazma ve daha iyi önbelleğe alma kadar dramatik olmayacak, ancak yine de sağlam bir kazanç.

Hikayenin ahlaki: Numpy ve pandalarda vektör işlemlerini kullanın. Python'daki skaler işlemlerden daha hızlıdırlar, çünkü bu işlemlerin bir C programcısının zaten elle yazacağı gibi olması basittir. (Dizi kavramının, gömülü SIMD komutları içeren açık döngülerden çok daha kolay okunması dışında.)


11

İşte probleminizi çözmenin yolu. Bunların hepsi vektörleştirilmiş.

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

Çok net cevap teşekkürler. Birleştirmeyi deneyeceğim ama şüphelerim var çünkü o zaman 5 milyar satırım olacak (2.5million * 2000). Bu Q genelini korumak için belirli bir Q oluşturdum. Bu dev tablodan kaçınmak için bir alternatif görmekten memnuniyet duyarım, eğer birini biliyorsanız: burada: stackoverflow.com/questions/24875096/…
KieranPC

1
bu Kartezyen ürünü yaratmaz - sıkıştırılmış bir alandır ve bellek açısından oldukça verimlidir. yaptığınız şey çok standart bir problem. bir dene. (bağlantılı sorunuz çok benzer bir çözüme sahip)
Jeff

7

Diğer bir seçenek de kullanmaktır to_records(), bu ikisinden de daha hızlıdır itertuplesve iterrows.

Ancak sizin durumunuz için, diğer iyileştirme türleri için çok yer var.

İşte benim son optimize edilmiş sürümüm

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'))

Karşılaştırma testi:

-- 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

Tam kod:

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)

Son sürüm, orijinal koddan neredeyse 10 kat daha hızlıdır. Strateji şudur:

  1. groupbyDeğerlerin tekrar tekrar karşılaştırılmasını önlemek için kullanın .
  2. to_recordsHam numpy.records nesnelere erişmek için kullanın .
  3. Tüm verileri derlemeden DataFrame üzerinde işlem yapmayın.


Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.