Cara menggunakan ITERTUPLES pada Python

Cara menggunakan ITERTUPLES pada Python


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]






Jawaban:


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.







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


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




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.