Pandas iterrows có vấn đề về hiệu suất không?


96

Tôi đã nhận thấy hiệu suất rất kém khi sử dụng thuốc mọc từ gấu trúc.

Đây có phải là điều mà những người khác đã trải qua? Nó có dành riêng cho ngứa và có nên tránh chức năng này đối với dữ liệu có kích thước nhất định (tôi đang làm việc với 2-3 triệu hàng) không?

Cuộc thảo luận này trên GitHub khiến tôi tin rằng nó được gây ra khi trộn các loại dtype trong dataframe, tuy nhiên, ví dụ đơn giản bên dưới cho thấy nó có ở đó ngay cả khi sử dụng một loại dtype (float64). Quá trình này mất 36 giây trên máy của tôi:

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

Tại sao các thao tác vectơ như áp dụng nhanh hơn nhiều? Tôi tưởng tượng cũng phải có một số lần lặp lại từng hàng ở đó.

Tôi không thể tìm ra cách để không sử dụng iterrows trong trường hợp của mình (điều này tôi sẽ để dành cho một câu hỏi trong tương lai). Vì vậy, tôi sẽ đánh giá cao sự lắng nghe nếu bạn luôn có thể tránh được sự lặp lại này. Tôi đang tính toán dựa trên dữ liệu trong các khung dữ liệu riêng biệt. Cảm ơn bạn!

--- Chỉnh sửa: phiên bản đơn giản của những gì tôi muốn chạy đã được thêm vào bên dưới ---

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
applyKHÔNG được vector hóa. iterrowsthậm chí còn tệ hơn vì nó đóng hộp mọi thứ (đó là 'sự khác biệt hoàn hảo với apply). Bạn chỉ nên sử dụng iterrowstrong rất ít trường hợp. IMHO không bao giờ. Hiển thị những gì bạn đang thực sự làm với iterrows.
Jeff

2
Thay vào đó, vấn đề mà bạn đã liên kết liên quan đến quyền anh DatetimeIndexthành Timestamps(đã được triển khai trong không gian python) và điều này đã được cải thiện nhiều trong bản chính.
Jeff

1
Xem vấn đề này để có cuộc thảo luận đầy đủ hơn: github.com/pydata/pandas/issues/7194 .
Jeff

Liên kết với các câu hỏi cụ thể (cái này sẽ ở chung): stackoverflow.com/questions/24875096/...
KieranPC

Vui lòng không khuyến nghị sử dụng iterrows (). Đó là một kẻ tung hoành trắng trợn kiểu chống đối tồi tệ nhất trong lịch sử loài gấu trúc.
cs95

Câu trả lời:


187

Nói chung, iterrowschỉ nên được sử dụng trong những trường hợp rất, rất cụ thể. Đây là thứ tự ưu tiên chung để thực hiện các hoạt động khác nhau:

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)

Sử dụng một thói quen Cython tùy chỉnh thường quá phức tạp, vì vậy hãy bỏ qua điều đó ngay bây giờ.

1) Vectorization LUÔN LUÔN, LUÔN là sự lựa chọn đầu tiên và tốt nhất. Tuy nhiên, có một số trường hợp nhỏ (thường liên quan đến sự tái phát) không thể được vectơ hóa theo những cách rõ ràng. Hơn nữa, đối với một con nhỏ DataFrame, có thể nhanh hơn nếu sử dụng các phương pháp khác.

3) apply thường có thể được xử lý bởi một trình lặp trong không gian Cython. Điều này được xử lý nội bộ bởi gấu trúc, mặc dù nó phụ thuộc vào những gì đang diễn ra bên trong applybiểu thức. Ví dụ, df.apply(lambda x: np.sum(x))sẽ được thực thi khá nhanh chóng, mặc dù tất nhiên, df.sum(1)thậm chí còn tốt hơn. Tuy nhiên, một cái gì đó giống như df.apply(lambda x: x['b'] + 1)sẽ được thực thi trong không gian Python, và do đó chậm hơn nhiều.

4) itertupleskhông đóng hộp dữ liệu vào a Series. Nó chỉ trả về dữ liệu dưới dạng các bộ giá trị.

5) iterrowsDOES đóng hộp dữ liệu vào a Series. Trừ khi bạn thực sự cần điều này, hãy sử dụng phương pháp khác.

6) Cập nhật khung trống một-một-hàng-tại một thời điểm. Tôi đã thấy phương pháp này được sử dụng WAY quá nhiều. Nó là chậm nhất cho đến nay. Nó có thể là một nơi phổ biến (và khá nhanh đối với một số cấu trúc python), nhưng a DataFramethực hiện một số lần kiểm tra hợp lý trong việc lập chỉ mục, vì vậy điều này sẽ luôn rất chậm để cập nhật một hàng tại một thời điểm. Tốt hơn nhiều để tạo cấu trúc mới và concat.


1
Có, tôi đã sử dụng số 6 (và 5). Tôi có một số việc học để làm. Nó có vẻ như là sự lựa chọn rõ ràng cho một người mới bắt đầu tương đối.
KieranPC

3
Theo kinh nghiệm của tôi, sự khác biệt giữa 3, 4 và 5 được giới hạn tùy thuộc vào trường hợp sử dụng.
IanS

8
Tôi đã cố gắng kiểm tra thời gian chạy trong sổ ghi chép này . Bằng cách nào đó itertupleslà nhanh hơn so với apply:(
Dimgold

1
pd.DataFrame.applythường chậm hơn itertuples. Ngoài ra, cần xem xét khả năng hiểu danh sách, maptên kém np.vectorizenumba(không theo thứ tự cụ thể) cho các phép tính không thể vecto , ví dụ như xem câu trả lời này .
jpp

2
@Jeff, tò mò, tại sao bạn không thêm phần hiểu danh sách vào đây? Mặc dù đúng là chúng không xử lý việc căn chỉnh chỉ mục hoặc thiếu dữ liệu (trừ khi bạn sử dụng một hàm với try-catch), chúng tốt cho nhiều trường hợp sử dụng (nội dung chuỗi / regex) trong đó các phương thức pandas không có vectơ hóa ( theo nghĩa chân thực nhất của từ này) triển khai. Bạn có nghĩ rằng điều đáng nói là LC là một giải pháp thay thế nhanh hơn, chi phí thấp hơn so với áp dụng gấu trúc và nhiều hàm chuỗi gấu trúc không?
cs95

17

Các phép toán vectơ trong Numpy và pandas nhanh hơn nhiều so với các phép toán vô hướng trong vani Python vì một số lý do:

  • Tra cứu kiểu phân bổ : Python là một ngôn ngữ được nhập động, do đó, có chi phí thời gian chạy cho mỗi phần tử trong một mảng. Tuy nhiên, Numpy (và do đó là gấu trúc) thực hiện các phép tính bằng C (thường thông qua Cython). Kiểu của mảng chỉ được xác định khi bắt đầu lặp; riêng khoản tiết kiệm này là một trong những chiến thắng lớn nhất.

  • Bộ nhớ đệm tốt hơn : Lặp lại trên một mảng C thân thiện với bộ nhớ cache và do đó rất nhanh. DataFrame gấu trúc là một "bảng hướng cột", có nghĩa là mỗi cột thực sự chỉ là một mảng. Vì vậy, các hành động gốc mà bạn có thể thực hiện trên DataFrame (như tổng hợp tất cả các phần tử trong một cột) sẽ có một số lần bỏ sót bộ nhớ cache.

  • Nhiều cơ hội hơn cho song song : Một mảng C đơn giản có thể được vận hành thông qua hướng dẫn SIMD. Một số phần của Numpy kích hoạt SIMD, tùy thuộc vào CPU và quá trình cài đặt của bạn. Lợi ích của tính năng song song sẽ không ấn tượng bằng việc nhập tĩnh và bộ nhớ đệm tốt hơn, nhưng chúng vẫn là một chiến thắng vững chắc.

Đạo đức của câu chuyện: sử dụng các phép toán vector trong Numpy và gấu trúc. Chúng nhanh hơn các phép toán vô hướng trong Python vì lý do đơn giản là các phép toán này chính xác là những gì một lập trình viên C sẽ viết bằng tay. (Ngoại trừ việc khái niệm mảng dễ đọc hơn nhiều so với các vòng lặp rõ ràng có hướng dẫn SIMD được nhúng.)


11

Đây là cách để giải quyết vấn đề của bạn. Tất cả đều được vector hóa.

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

Câu trả lời rất rõ ràng cảm ơn. Tôi sẽ thử hợp nhất nhưng tôi nghi ngờ vì sau đó tôi sẽ có 5 tỷ hàng (2,5 triệu * 2000). Để giữ cho vị tướng Q này, tôi đã tạo một Q. cụ thể. Tôi rất vui khi thấy một giải pháp thay thế để tránh cái bàn khổng lồ này, nếu bạn biết về một cái: tại đây: stackoverflow.com/questions/24875096/…
KieranPC

1
điều này không tạo ra sản phẩm Descartes - nó là một không gian nén và khá hiệu quả về bộ nhớ. những gì bạn đang làm là một vấn đề rất chuẩn. hãy thử đi. (câu hỏi liên quan của bạn có một soln rất giống nhau)
Jeff

7

Một tùy chọn khác là sử dụng to_records(), nhanh hơn cả itertuplesiterrows.

Nhưng đối với trường hợp của bạn, có nhiều chỗ cho các loại cải tiến khác.

Đây là phiên bản tối ưu hóa cuối cùng của tôi

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

Kiểm tra điểm chuẩn:

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

Mã đầy đủ:

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)

Phiên bản cuối cùng nhanh hơn gần 10 lần so với mã gốc. Chiến lược là:

  1. Sử dụng groupbyđể tránh so sánh lặp lại các giá trị.
  2. Sử dụng to_recordsđể truy cập các đối tượng numpy.records thô.
  3. Không hoạt động trên DataFrame cho đến khi bạn đã tổng hợp tất cả dữ liệu.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.