iterrows แพนด้ามีปัญหาด้านประสิทธิภาพหรือไม่?


96

ฉันสังเกตเห็นประสิทธิภาพที่แย่มากเมื่อใช้ iterrows จากแพนด้า

นี่คือสิ่งที่เป็นประสบการณ์ของผู้อื่นหรือไม่? มันเฉพาะเจาะจงสำหรับ iterrows และควรหลีกเลี่ยงฟังก์ชั่นนี้สำหรับข้อมูลบางขนาด (ฉันกำลังทำงานกับ 2-3 ล้านแถว)

การสนทนาเกี่ยวกับ GitHub นี้ทำให้ฉันเชื่อว่าเกิดจากการผสม dtypes ใน dataframe แต่ตัวอย่างง่ายๆด้านล่างแสดงให้เห็นว่ามีอยู่แม้จะใช้ dtype เดียว (float64) ใช้เวลา 36 วินาทีบนเครื่องของฉัน:

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

เหตุใดการดำเนินการที่เป็นเวกเตอร์จึงใช้งานได้เร็วกว่ามาก ฉันคิดว่าจะต้องมีการทำซ้ำทีละแถวเกิดขึ้นที่นั่นด้วย

ฉันคิดไม่ออกว่าจะไม่ใช้ iterrows ในกรณีของฉันอย่างไร (ฉันจะบันทึกไว้เป็นคำถามในอนาคต) ดังนั้นฉันจะยินดีที่ได้ยินหากคุณสามารถหลีกเลี่ยงการทำซ้ำนี้ได้อย่างสม่ำเสมอ ฉันกำลังคำนวณตามข้อมูลในดาต้าเฟรมแยกกัน ขอบคุณ!

--- แก้ไข: มีการเพิ่มเวอร์ชันที่เรียบง่ายของสิ่งที่ฉันต้องการเรียกใช้ด้านล่าง -

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ไม่ได้เป็นเวกเตอร์ iterrowsยิ่งแย่ลงไปอีกเมื่อมันวางกล่องทุกอย่างไว้ (นั่นคือ 'ความแตกต่างกับความสมบูรณ์แบบapply) คุณควรใช้iterrowsในสถานการณ์ที่น้อยมากเท่านั้น IMHO ไม่เคย แสดงว่าคุณกำลังทำอะไรiterrowsอยู่
เจฟฟ์

2
ปัญหาที่คุณเชื่อมโยงไปแทนจะทำอย่างไรกับมวยที่DatetimeIndexเข้ามาTimestamps(ได้ดำเนินการในพื้นที่หลาม) และนี้ได้รับการปรับปรุงให้ดีขึ้นมากในระดับปริญญาโท
เจฟฟ์

1
ดูปัญหานี้สำหรับการสนทนาแบบเต็มเพิ่มเติมได้ที่: github.com/pydata/pandas/issues/7194
เจฟฟ์

ลิงก์ไปยังคำถามเฉพาะ (คำถามนี้จะยังคงอยู่ทั่วไป): stackoverflow.com/questions/24875096/…
KieranPC

โปรดอย่าแนะนำให้ใช้ iterrows () มันเป็นตัวเปิดการต่อต้านรูปแบบที่เลวร้ายที่สุดในประวัติศาสตร์ของแพนด้า
cs95

คำตอบ:


188

โดยทั่วไปiterrowsควรใช้ในกรณีที่เฉพาะเจาะจงมากเท่านั้น นี่คือลำดับความสำคัญโดยทั่วไปสำหรับประสิทธิภาพของการดำเนินการต่างๆ:

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)

การใช้รูทีน Cython แบบกำหนดเองมักจะซับซ้อนเกินไปดังนั้นขอข้ามไปก่อน

1) Vectorization คือตัวเลือกแรกและดีที่สุดเสมอ อย่างไรก็ตามมีกรณีเล็ก ๆ น้อย ๆ (โดยปกติจะเกี่ยวข้องกับการเกิดซ้ำ) ซึ่งไม่สามารถทำเป็นเวกเตอร์ได้อย่างชัดเจน นอกจากนี้ในกรณีที่มีขนาดเล็กDataFrameอาจใช้วิธีอื่นได้เร็วกว่า

3) apply โดยปกติแล้วตัววนซ้ำสามารถจัดการได้ในช่องว่าง Cython สิ่งนี้ถูกจัดการโดยแพนด้าภายในแม้ว่ามันจะขึ้นอยู่กับสิ่งที่เกิดขึ้นภายในapplyนิพจน์ ตัวอย่างเช่นdf.apply(lambda x: np.sum(x))จะถูกดำเนินการอย่างรวดเร็วแม้ว่าแน่นอนว่าdf.sum(1)จะดีกว่า อย่างไรก็ตามdf.apply(lambda x: x['b'] + 1)จะมีการดำเนินการบางอย่างใน Python space และส่งผลให้ช้าลงมาก

4) itertuplesไม่ใส่กล่องข้อมูลลงในไฟล์Series. เพียงแค่ส่งคืนข้อมูลในรูปแบบของสิ่งทู

5) iterrowsใส่ข้อมูลลงในไฟล์Series. หากคุณไม่ต้องการสิ่งนี้จริงๆให้ใช้วิธีอื่น

6) การอัปเดตเฟรมว่างทีละแถวทีละภาพ ฉันเคยเห็นวิธีนี้ใช้ WAY มากเกินไป มันช้าที่สุด อาจเป็นสถานที่ทั่วไป (และเร็วพอสมควรสำหรับโครงสร้าง python บางตัว) แต่การDataFrameตรวจสอบการจัดทำดัชนีเป็นจำนวนที่ยุติธรรมดังนั้นการอัปเดตทีละแถวจะช้ามาก ดีกว่ามากในการสร้างโครงสร้างใหม่และconcat.


1
ใช่ฉันใช้หมายเลข 6 (และ 5) ฉันได้เรียนรู้ที่จะทำ ดูเหมือนว่าตัวเลือกที่ชัดเจนสำหรับผู้เริ่มต้นที่สัมพันธ์กัน
KieranPC

3
จากประสบการณ์ของฉันความแตกต่างระหว่าง 3, 4 และ 5 มีข้อ จำกัด ขึ้นอยู่กับกรณีการใช้งาน
IanS

8
ฉันได้ลองตรวจสอบเวลาทำงานในสมุดบันทึกนี้แล้ว อย่างใดitertuplesจะเร็วกว่าapply:(
Dimgold

1
pd.DataFrame.applyitertuplesมักจะช้ากว่า นอกจากนี้ยังควรพิจารณาถึงความเข้าใจในรายการชื่อที่mapไม่ดีnp.vectorizeและnumba(ไม่เรียงตามลำดับ) สำหรับการคำนวณแบบเวกเตอร์ที่ไม่สามารถใช้งานได้เช่นดูคำตอบนี้
jpp

2
@ เจฟฟ์ด้วยความอยากรู้ทำไมคุณไม่เพิ่มความเข้าใจในรายการที่นี่? แม้ว่าจะเป็นความจริงที่พวกเขาไม่จัดการการจัดแนวดัชนีหรือข้อมูลที่ขาดหายไป (เว้นแต่คุณจะใช้ฟังก์ชันที่มีการลองจับ) แต่ก็เหมาะสำหรับกรณีการใช้งานจำนวนมาก (สิ่งที่เป็นสตริง / regex) โดยที่วิธีการของแพนด้าไม่มี vectorized ( ในความหมายที่แท้จริงที่สุดของคำ) การใช้งาน คุณคิดว่าควรค่าแก่การกล่าวถึง LC เป็นทางเลือกที่เร็วกว่าและมีค่าใช้จ่ายต่ำกว่าสำหรับการใช้แพนด้าและฟังก์ชันสตริงของแพนด้าจำนวนมากหรือไม่?
cs95

17

การดำเนินการเวกเตอร์ใน Numpy และแพนด้านั้นเร็วกว่าการดำเนินการสเกลาร์ใน vanilla Python ด้วยเหตุผลหลายประการ:

  • การค้นหาประเภทค่าตัดจำหน่าย : Python เป็นภาษาที่พิมพ์แบบไดนามิกดังนั้นจึงมีค่าใช้จ่ายรันไทม์สำหรับแต่ละองค์ประกอบในอาร์เรย์ อย่างไรก็ตาม Numpy (และแพนด้า) จะทำการคำนวณใน C (มักใช้ Cython) ประเภทของอาร์เรย์จะถูกกำหนดเมื่อเริ่มต้นการทำซ้ำเท่านั้น การออมเพียงอย่างเดียวถือเป็นหนึ่งในชัยชนะที่ยิ่งใหญ่ที่สุด

  • การแคชที่ดีขึ้น : การทำซ้ำบนอาร์เรย์ C นั้นเป็นมิตรกับแคชและเร็วมาก แพนด้า DataFrame คือ "ตารางเชิงคอลัมน์" ซึ่งหมายความว่าแต่ละคอลัมน์เป็นเพียงอาร์เรย์เท่านั้น ดังนั้นการกระทำดั้งเดิมที่คุณสามารถทำได้บน DataFrame (เช่นการสรุปองค์ประกอบทั้งหมดในคอลัมน์) จะมีแคชน้อย

  • โอกาสมากขึ้นสำหรับการขนาน : อาร์เรย์ C แบบธรรมดาสามารถทำงานได้ผ่านคำแนะนำของ SIMD บางส่วนของ Numpy เปิดใช้งาน SIMD ขึ้นอยู่กับ CPU และกระบวนการติดตั้งของคุณ ประโยชน์ของการขนานจะไม่น่าทึ่งเท่ากับการพิมพ์แบบคงที่และการแคชที่ดีขึ้น แต่ก็ยังคงเป็นชัยชนะที่มั่นคง

คุณธรรมของเรื่องราว: ใช้การดำเนินการเวกเตอร์ใน Numpy และหมีแพนด้า เร็วกว่าการดำเนินการสเกลาร์ใน Python ด้วยเหตุผลง่ายๆว่าการดำเนินการเหล่านี้เป็นสิ่งที่โปรแกรมเมอร์ C จะเขียนด้วยมืออยู่แล้ว (ยกเว้นว่าแนวคิดของอาร์เรย์นั้นอ่านง่ายกว่าลูปที่ชัดเจนพร้อมคำแนะนำ SIMD ในตัว)


11

นี่คือวิธีแก้ปัญหาของคุณ นี่คือ vectorized ทั้งหมด

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

ขอบคุณคำตอบที่ชัดเจนมาก ฉันจะลองรวม แต่ฉันมีข้อสงสัยเพราะฉันจะมี 5 พันล้านแถว (2.5 ล้าน * 2000) เพื่อรักษา Q ทั่วไปนี้ไว้ฉันได้สร้าง Q เฉพาะขึ้นฉันยินดีที่จะเห็นทางเลือกอื่นเพื่อหลีกเลี่ยงตารางยักษ์นี้ถ้าคุณรู้จัก: ที่นี่: stackoverflow.com/questions/24875096/…
KieranPC

1
สิ่งนี้ไม่ได้สร้างผลิตภัณฑ์คาร์ทีเซียน - เป็นพื้นที่บีบอัดและหน่วยความจำค่อนข้างมีประสิทธิภาพ สิ่งที่คุณกำลังทำอยู่เป็นปัญหามาตรฐานมาก ลองดู (คำถามที่เชื่อมโยงของคุณมีคำตอบที่คล้ายกันมาก)
เจฟฟ์

7

อีกทางเลือกหนึ่งคือการใช้to_records()ซึ่งเร็วกว่าทั้งสองอย่างitertuplesและiterrows.

แต่สำหรับกรณีของคุณมีช่องว่างมากมายสำหรับการปรับปรุงประเภทอื่น ๆ

นี่คือเวอร์ชันที่ปรับให้เหมาะสมขั้นสุดท้ายของฉัน

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

การทดสอบเกณฑ์มาตรฐาน:

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

รหัสเต็ม:

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)

เวอร์ชันสุดท้ายเร็วกว่าโค้ดเดิมเกือบ 10 เท่า กลยุทธ์คือ:

  1. ใช้groupbyเพื่อหลีกเลี่ยงการเปรียบเทียบค่าซ้ำ ๆ
  2. ใช้to_recordsเพื่อเข้าถึงวัตถุ numpy.records ดิบ
  3. อย่าดำเนินการบน DataFrame จนกว่าคุณจะรวบรวมข้อมูลทั้งหมด

0

ใช่ Pandas itertuples () เร็วกว่า iterrows () คุณสามารถอ้างอิงเอกสาร: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.iterrows.html

"ในการรักษา dtypes ในขณะที่ทำซ้ำในแถวนั้นควรใช้ itertuples () ซึ่งจะส่งคืนค่า nametuples ของค่าซึ่งโดยทั่วไปเร็วกว่า iterrows"


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.