Apakah iterrow panda memiliki masalah kinerja?


96

Saya telah memperhatikan kinerja yang sangat buruk saat menggunakan iterrow dari panda.

Apakah ini sesuatu yang dialami oleh orang lain? Apakah ini khusus untuk iterrow dan haruskah fungsi ini dihindari untuk data dengan ukuran tertentu (saya bekerja dengan 2-3 juta baris)?

Diskusi di GitHub ini membuat saya percaya bahwa ini disebabkan saat mencampurkan dtypes di dataframe, namun contoh sederhana di bawah ini menunjukkan bahwa itu ada bahkan ketika menggunakan satu dtype (float64). Ini membutuhkan 36 detik di mesin saya:

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

Mengapa operasi vektorisasi seperti diterapkan jauh lebih cepat? Saya membayangkan pasti ada beberapa perulangan baris demi baris yang terjadi di sana juga.

Saya tidak tahu bagaimana tidak menggunakan iterrow dalam kasus saya (ini akan saya simpan untuk pertanyaan mendatang). Oleh karena itu, saya akan sangat menghargai jika Anda secara konsisten dapat menghindari pengulangan ini. Saya membuat perhitungan berdasarkan data dalam kerangka data terpisah. Terima kasih!

--- Edit: versi sederhana dari apa yang ingin saya jalankan telah ditambahkan di bawah ---

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
apply TIDAK vektorisasi. iterrowsbahkan lebih buruk karena kotak semuanya (yaitu 'the perf diff with apply). Anda sebaiknya hanya menggunakan iterrowssangat sedikit situasi. IMHO tidak pernah. Tunjukkan apa yang sebenarnya Anda lakukan iterrows.
Jeff

2
Masalah yang Anda tautkan justru berkaitan dengan tinju DatetimeIndexke Timestamps(diimplementasikan dalam ruang python), dan ini telah jauh ditingkatkan di master.
Jeff

1
Lihat masalah ini untuk diskusi yang lebih lengkap: github.com/pydata/pandas/issues/7194 .
Jeff

Tautan ke pertanyaan spesifik (yang ini akan tetap umum): stackoverflow.com/questions/24875096/…
KieranPC

Mohon tidak merekomendasikan penggunaan iterrows (). Ini adalah pemungkin yang terang-terangan dari anti-pola terburuk dalam sejarah panda.
cs95

Jawaban:


188

Umumnya, iterrowssebaiknya hanya digunakan dalam kasus yang sangat, sangat spesifik. Ini adalah urutan prioritas umum untuk kinerja berbagai operasi:

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)

Menggunakan rutinitas Cython khusus biasanya terlalu rumit, jadi mari kita lewati itu untuk saat ini.

1) Vektorisasi SELALU, SELALU pilihan pertama dan terbaik. Namun, ada sekumpulan kecil kasus (biasanya melibatkan kekambuhan) yang tidak dapat di-vektorisasi dengan cara yang jelas. Selanjutnya, pada yang bertubuh kecil DataFrame, mungkin lebih cepat menggunakan metode lain.

3) apply biasanya dapat ditangani oleh iterator di ruang Cython. Ini ditangani secara internal oleh panda, meskipun bergantung pada apa yang terjadi di dalam applyekspresi. Misalnya, df.apply(lambda x: np.sum(x))akan dieksekusi dengan cukup cepat, meskipun tentu saja, df.sum(1)lebih baik. Namun sesuatu seperti itu df.apply(lambda x: x['b'] + 1)akan dijalankan dalam ruang Python, dan akibatnya jauh lebih lambat.

4) itertuplestidak mengotakkan data menjadi a Series. Itu hanya mengembalikan data dalam bentuk tupel.

5) iterrowsMELAKUKAN kotak data menjadi a Series. Kecuali Anda benar-benar membutuhkan ini, gunakan metode lain.

6) Memperbarui bingkai kosong satu baris satu per satu. Saya telah melihat metode ini menggunakan JAUH terlalu banyak. Sejauh ini, ini paling lambat. Ini mungkin tempat yang umum (dan cukup cepat untuk beberapa struktur python), tetapi a DataFramemelakukan cukup banyak pemeriksaan pada pengindeksan, jadi ini akan selalu sangat lambat untuk memperbarui baris dalam satu waktu. Jauh lebih baik untuk membuat struktur baru dan concat.


1
Ya, saya menggunakan nomor 6 (dan 5). Aku harus belajar. Sepertinya pilihan yang jelas untuk pemula yang relatif.
KieranPC

3
Menurut pengalaman saya, perbedaan antara 3, 4, dan 5 terbatas tergantung pada kasus penggunaan.
IanS

8
Saya sudah mencoba memeriksa runtime di notebook ini . Entah bagaimana itertupleslebih cepat dari apply:(
Redup

1
pd.DataFrame.applyseringkali lebih lambat dari itertuples. Selain itu, ada baiknya mempertimbangkan pemahaman daftar``map dinamai dengan buruk np.vectorizedan numba(tanpa urutan tertentu) untuk kalkulasi yang tidak dapat vektor , misalnya lihat jawaban ini .
jpp

2
@Jeff, karena penasaran, mengapa Anda tidak menambahkan pemahaman daftar di sini? Meskipun benar bahwa mereka tidak menangani penyelarasan indeks atau data yang hilang (kecuali jika Anda menggunakan fungsi dengan coba-tangkap), mereka baik untuk banyak kasus penggunaan (string / regex) di mana metode panda tidak memiliki vektorisasi ( dalam arti sebenarnya dari kata) implementasi. Apakah menurut Anda perlu disebutkan LC adalah alternatif overhead yang lebih cepat dan lebih rendah untuk penggunaan panda dan banyak fungsi string panda?
cs95

17

Operasi vektor di Numpy dan pandas jauh lebih cepat daripada operasi skalar di vanilla Python karena beberapa alasan:

  • Pencarian jenis diamortisasi : Python adalah bahasa yang diketik secara dinamis, jadi ada overhead waktu proses untuk setiap elemen dalam larik. Namun, Numpy (dan karenanya pandas) melakukan kalkulasi dalam C (seringkali melalui Cython). Jenis larik ditentukan hanya di awal iterasi; tabungan ini sendiri adalah salah satu kemenangan terbesar.

  • Caching yang lebih baik : Iterasi melalui array C ramah-cache dan karenanya sangat cepat. DataFrame pandas adalah "tabel berorientasi kolom", yang berarti bahwa setiap kolom sebenarnya hanya sebuah array. Jadi tindakan asli yang dapat Anda lakukan pada DataFrame (seperti menjumlahkan semua elemen dalam kolom) akan memiliki beberapa cache yang terlewat.

  • Lebih banyak peluang untuk paralelisme : Array C sederhana dapat dioperasikan melalui instruksi SIMD. Beberapa bagian dari Numpy mengaktifkan SIMD, tergantung pada CPU dan proses instalasi Anda. Manfaat paralelisme tidak akan sedramatis pengetikan statis dan caching yang lebih baik, tetapi ini masih merupakan kemenangan yang solid.

Moral dari cerita: gunakan operasi vektor di Numpy dan pandas. Mereka lebih cepat daripada operasi skalar dengan Python karena alasan sederhana bahwa operasi ini persis seperti yang ditulis oleh programmer C dengan tangan. (Kecuali bahwa gagasan array jauh lebih mudah dibaca daripada loop eksplisit dengan instruksi SIMD yang disematkan.)


11

Inilah cara untuk menyelesaikan masalah Anda. Ini semua vektorisasi.

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

Jawaban yang sangat jelas terima kasih. Saya akan mencoba menggabungkan tetapi saya ragu karena saya akan memiliki 5 miliar baris (2,5 juta * 2000). Untuk menjaga Q umum ini, saya telah membuat Q spesifik. Saya akan senang melihat alternatif untuk menghindari tabel raksasa ini, jika Anda mengetahuinya: di sini: stackoverflow.com/questions/24875096/…
KieranPC

1
ini tidak menciptakan produk Cartesian - ini adalah ruang terkompresi dan cukup hemat memori. apa yang Anda lakukan adalah masalah yang sangat standar. cobalah. (pertanyaan terkait Anda memiliki soln yang sangat mirip)
Jeff

7

Pilihan lainnya adalah menggunakan to_records(), yang lebih cepat dari keduanya itertuplesdan iterrows.

Namun untuk kasus Anda, ada banyak ruang untuk jenis perbaikan lainnya.

Ini versi terakhir saya yang dioptimalkan

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

Tes benchmark:

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

Kode lengkap:

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)

Versi terakhir hampir 10x lebih cepat dari kode aslinya. Strateginya adalah:

  1. Gunakan groupbyuntuk menghindari perbandingan nilai yang berulang.
  2. Gunakan to_recordsuntuk mengakses objek numpy.records mentah.
  3. Jangan beroperasi pada DataFrame sampai Anda telah mengumpulkan semua data.


Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.