เมื่อใดที่ฉันควรใช้ pandas apply () ในรหัสของฉัน


115

applyฉันได้เห็นหลายคำตอบโพสต์คำถามในกองมากเกินเกี่ยวข้องกับการใช้วิธีแพนด้า ฉันยังเห็นผู้ใช้แสดงความคิดเห็นใต้พวกเขาโดยบอกว่า " applyช้าและควรหลีกเลี่ยง"

ฉันได้อ่านบทความมากมายในหัวข้อประสิทธิภาพที่อธิบายว่าapplyช้า ฉันยังได้เห็นข้อจำกัดความรับผิดชอบในเอกสารว่าapplyเป็นเพียงฟังก์ชันอำนวยความสะดวกในการส่ง UDFs (ดูเหมือนจะไม่พบในตอนนี้) ดังนั้นฉันทามติทั่วไปคือapplyควรหลีกเลี่ยงถ้าเป็นไปได้ อย่างไรก็ตามสิ่งนี้ทำให้เกิดคำถามต่อไปนี้:

  1. ถ้าapplyแย่มากแล้วทำไมถึงอยู่ใน API?
  2. ฉันควรสร้างรหัสapplyฟรีอย่างไรและเมื่อใด
  3. เคยมีสถานการณ์ใด ๆ ที่applyเป็นสิ่งที่ดี (ดีกว่าการแก้ปัญหาที่เป็นไปได้อื่น ๆ )?

1
returns.add(1).apply(np.log)เทียบกับnp.log(returns.add(1)เป็นกรณีที่applyโดยทั่วไปจะเร็วขึ้นเล็กน้อยซึ่งเป็นกล่องสีเขียวล่างขวาในแผนภาพของ jpp ด้านล่าง
Alexander

@ อเล็กซานเดอร์ขอบคุณ ไม่ได้ชี้ให้เห็นสถานการณ์เหล่านี้อย่างละเอียดถี่ถ้วน แต่มีประโยชน์ที่จะรู้!
cs95

คำตอบ:


112

applyฟังก์ชันอำนวยความสะดวกที่คุณไม่เคยต้องการ

เราเริ่มต้นด้วยการตอบคำถามใน OP ทีละคำถาม

" ถ้าสมัครไม่ดีทำไมถึงอยู่ใน API "

DataFrame.applyและSeries.applyเป็นฟังก์ชันอำนวยความสะดวกที่กำหนดบนวัตถุ DataFrame และ Series ตามลำดับ applyยอมรับฟังก์ชันที่ผู้ใช้กำหนดซึ่งใช้การแปลง / การรวมบน DataFrame applyเป็นกระสุนเงินอย่างมีประสิทธิภาพที่ทำสิ่งที่ฟังก์ชันแพนด้าที่มีอยู่ไม่สามารถทำได้

บางสิ่งapplyสามารถทำได้:

  • เรียกใช้ฟังก์ชันที่ผู้ใช้กำหนดเองบน DataFrame หรือ Series
  • ใช้ฟังก์ชัน row-wise ( axis=1) หรือ column-wise ( axis=0) บน DataFrame
  • ดำเนินการจัดตำแหน่งดัชนีในขณะที่ใช้ฟังก์ชัน
  • ทำการรวมกับฟังก์ชันที่ผู้ใช้กำหนดเอง (อย่างไรก็ตามเรามักจะชอบaggหรือtransformในกรณีเหล่านี้)
  • ทำการแปลงองค์ประกอบอย่างชาญฉลาด
  • ถ่ายทอดผลลัพธ์รวมไปยังแถวเดิม (ดูresult_typeอาร์กิวเมนต์)
  • ยอมรับอาร์กิวเมนต์ตำแหน่ง / คีย์เวิร์ดเพื่อส่งผ่านไปยังฟังก์ชันที่ผู้ใช้กำหนดเอง

...ท่ามกลางคนอื่น ๆ. สำหรับข้อมูลเพิ่มเติมโปรดดูแอปพลิเคชันฟังก์ชันแถวหรือคอลัมน์ในเอกสารประกอบ

ดังนั้นด้วยคุณสมบัติทั้งหมดนี้ทำไมถึงapplyไม่ดี? มันเป็นเพราะapplyเป็น ช้า นุ่นไม่ได้ตั้งสมมติฐานเกี่ยวกับลักษณะของฟังก์ชันของคุณดังนั้นจึงใช้ฟังก์ชันของคุณซ้ำ ๆ กับแต่ละแถว / คอลัมน์ตามความจำเป็น นอกจากนี้การจัดการทั้งหมดของสถานการณ์ข้างต้นหมายถึงการapplyเกิดขึ้นบางส่วนค่าใช้จ่ายที่สำคัญในแต่ละซ้ำ นอกจากนี้ยังapplyใช้หน่วยความจำมากขึ้นซึ่งเป็นความท้าทายสำหรับแอปพลิเคชันที่มีขอบเขตหน่วยความจำ

มีสถานการณ์น้อยมากที่applyเหมาะสมที่จะใช้ (ดูข้อมูลเพิ่มเติมด้านล่าง) หากคุณไม่แน่ใจว่าควรใช้applyหรือไม่คุณอาจไม่ควรใช้


มาตอบคำถามต่อไป

" ฉันควรทำให้รหัสของฉันใช้ฟรีได้อย่างไรและเมื่อใด "

ใช้ถ้อยคำที่นี่มีบางสถานการณ์ทั่วไปที่คุณจะต้องได้รับการกำจัดของสายใด ๆ applyที่จะ

ข้อมูลตัวเลข

หากคุณกำลังทำงานกับข้อมูลตัวเลขเป็นไปได้ว่ามีฟังก์ชัน cython แบบเวกเตอร์ที่ทำสิ่งที่คุณพยายามทำอยู่แล้ว (หากไม่เป็นเช่นนั้นโปรดถามคำถามใน Stack Overflow หรือเปิดคำขอคุณลักษณะบน GitHub)

เปรียบเทียบประสิทธิภาพของapplyการใช้งานเพิ่มเติมอย่างง่าย

df = pd.DataFrame({"A": [9, 4, 2, 1], "B": [12, 7, 5, 4]})
df

   A   B
0  9  12
1  4   7
2  2   5
3  1   4

df.apply(np.sum)

A    16
B    28
dtype: int64

df.sum()

A    16
B    28
dtype: int64

ประสิทธิภาพที่ชาญฉลาดไม่มีการเปรียบเทียบการเทียบเท่าของ cythonized นั้นเร็วกว่ามาก ไม่จำเป็นต้องใช้กราฟเพราะความแตกต่างนั้นชัดเจนแม้กระทั่งข้อมูลของเล่น

%timeit df.apply(np.sum)
%timeit df.sum()
2.22 ms ± 41.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
471 µs ± 8.16 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

แม้ว่าคุณจะเปิดใช้งานการส่งผ่านอาร์เรย์ดิบด้วยrawอาร์กิวเมนต์ แต่ก็ยังช้าเป็นสองเท่า

%timeit df.apply(np.sum, raw=True)
840 µs ± 691 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

ตัวอย่างอื่น:

df.apply(lambda x: x.max() - x.min())

A    8
B    8
dtype: int64

df.max() - df.min()

A    8
B    8
dtype: int64

%timeit df.apply(lambda x: x.max() - x.min())
%timeit df.max() - df.min()

2.43 ms ± 450 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
1.23 ms ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

โดยทั่วไปให้ค้นหาทางเลือกที่เป็นเวกเตอร์หากเป็นไปได้

สตริง / Regex

Pandas มีฟังก์ชันสตริง "vectorized" ในสถานการณ์ส่วนใหญ่ แต่มีบางกรณีที่ไม่เกิดขึ้นบ่อยนักที่ฟังก์ชันเหล่านั้นไม่ ... "ใช้" เพื่อที่จะพูด

ปัญหาทั่วไปคือการตรวจสอบว่ามีค่าในคอลัมน์อยู่ในคอลัมน์อื่นของแถวเดียวกันหรือไม่

df = pd.DataFrame({
    'Name': ['mickey', 'donald', 'minnie'],
    'Title': ['wonderland', "welcome to donald's castle", 'Minnie mouse clubhouse'],
    'Value': [20, 10, 86]})
df

     Name  Value                       Title
0  mickey     20                  wonderland
1  donald     10  welcome to donald's castle
2  minnie     86      Minnie mouse clubhouse

สิ่งนี้ควรส่งคืนแถวที่สองและแถวที่สามเนื่องจาก "donald" และ "minnie" อยู่ในคอลัมน์ "Title" ตามลำดับ

โดยใช้ Apply สิ่งนี้จะทำได้โดยใช้

df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)

0    False
1     True
2     True
dtype: bool

df[df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)]

     Name                       Title  Value
1  donald  welcome to donald's castle     10
2  minnie      Minnie mouse clubhouse     86

อย่างไรก็ตามมีทางออกที่ดีกว่าโดยใช้การทำความเข้าใจรายการ

df[[y.lower() in x.lower() for x, y in zip(df['Title'], df['Name'])]]

     Name                       Title  Value
1  donald  welcome to donald's castle     10
2  minnie      Minnie mouse clubhouse     86

%timeit df[df.apply(lambda x: x['Name'].lower() in x['Title'].lower(), axis=1)]
%timeit df[[y.lower() in x.lower() for x, y in zip(df['Title'], df['Name'])]]

2.85 ms ± 38.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
788 µs ± 16.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

สิ่งที่ควรทราบก็คือกิจวัตรซ้ำ ๆ เกิดขึ้นเร็วกว่าapplyเนื่องจากค่าใช้จ่ายที่ต่ำกว่า หากคุณต้องการจัดการ NaN และ dtypes ที่ไม่ถูกต้องคุณสามารถสร้างสิ่งนี้โดยใช้ฟังก์ชันที่กำหนดเองจากนั้นคุณสามารถเรียกด้วยอาร์กิวเมนต์ภายในความเข้าใจรายการ

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเวลาที่ควรพิจารณาความเข้าใจในรายการเป็นตัวเลือกที่ดีโปรดดูบทความของฉัน: สำหรับลูปที่มีหมีแพนด้า - ฉันควรดูแลเมื่อใด .

หมายเหตุ
การดำเนินการวันที่และวันที่และเวลายังมีเวอร์ชัน vectorized ดังนั้นสำหรับตัวอย่างเช่นคุณจะชอบpd.to_datetime(df['date'])มากกว่า, df['date'].apply(pd.to_datetime)กล่าวว่า

อ่านเพิ่มเติมได้ที่ เอกสาร

ข้อผิดพลาดทั่วไป: การระเบิดคอลัมน์ของรายการ

s = pd.Series([[1, 2]] * 3)
s

0    [1, 2]
1    [1, 2]
2    [1, 2]
dtype: object

apply(pd.Series)คนอยากจะใช้ นี่เป็นเรื่องที่น่ากลัวในแง่ของประสิทธิภาพ

s.apply(pd.Series)

   0  1
0  1  2
1  1  2
2  1  2

ตัวเลือกที่ดีกว่าคือแสดงรายการคอลัมน์และส่งต่อไปยัง pd.DataFrame

pd.DataFrame(s.tolist())

   0  1
0  1  2
1  1  2
2  1  2

%timeit s.apply(pd.Series)
%timeit pd.DataFrame(s.tolist())

2.65 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
816 µs ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

สุดท้ายนี้

" มีสถานการณ์ไหน apply บ้างที่ดี "

สมัครเป็นฟังก์ชั่นอำนวยความสะดวกเพื่อให้มีเป็นสถานการณ์ที่ค่าใช้จ่ายก็พอเพียงเล็กน้อยที่จะให้อภัย จริงๆมันขึ้นอยู่กับจำนวนครั้งที่เรียกใช้ฟังก์ชัน

ฟังก์ชันที่เป็น Vectorized สำหรับ Series แต่ไม่ใช่ DataFrames
จะเกิดอะไรขึ้นถ้าคุณต้องการใช้การดำเนินการแบบสตริงกับหลายคอลัมน์ จะเกิดอะไรขึ้นถ้าคุณต้องการแปลงหลายคอลัมน์เป็นวันที่และเวลา? ฟังก์ชันเหล่านี้เป็นแบบเวกเตอร์สำหรับซีรี่ส์เท่านั้นดังนั้นจึงต้องใช้กับแต่ละคอลัมน์ที่คุณต้องการแปลง / ดำเนินการ

df = pd.DataFrame(
         pd.date_range('2018-12-31','2019-01-31', freq='2D').date.astype(str).reshape(-1, 2), 
         columns=['date1', 'date2'])
df

       date1      date2
0 2018-12-31 2019-01-02
1 2019-01-04 2019-01-06
2 2019-01-08 2019-01-10
3 2019-01-12 2019-01-14
4 2019-01-16 2019-01-18
5 2019-01-20 2019-01-22
6 2019-01-24 2019-01-26
7 2019-01-28 2019-01-30

df.dtypes

date1    object
date2    object
dtype: object

นี่เป็นกรณีที่ยอมรับได้สำหรับapply:

df.apply(pd.to_datetime, errors='coerce').dtypes

date1    datetime64[ns]
date2    datetime64[ns]
dtype: object

โปรดทราบว่ามันก็สมเหตุสมผลstackหรือแค่ใช้การวนซ้ำที่ชัดเจน ตัวเลือกทั้งหมดนี้เร็วกว่าการใช้เล็กน้อยapplyแต่ความแตกต่างนั้นเล็กน้อยพอที่จะให้อภัยได้

%timeit df.apply(pd.to_datetime, errors='coerce')
%timeit pd.to_datetime(df.stack(), errors='coerce').unstack()
%timeit pd.concat([pd.to_datetime(df[c], errors='coerce') for c in df], axis=1)
%timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')

5.49 ms ± 247 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.94 ms ± 48.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
3.16 ms ± 216 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
2.41 ms ± 1.71 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

คุณสามารถสร้างกรณีที่คล้ายกันสำหรับการดำเนินการอื่น ๆ เช่นการดำเนินการสตริงหรือการแปลงเป็นหมวดหมู่

u = df.apply(lambda x: x.str.contains(...))
v = df.apply(lambda x: x.astype(category))

v / s

u = pd.concat([df[c].str.contains(...) for c in df], axis=1)
v = df.copy()
for c in df:
    v[c] = df[c].astype(category)

และอื่น ๆ ...

การแปลงซีรี่ส์เป็นstr: astypeกับapply

ดูเหมือนจะเป็นความแปลกประหลาดของ API โดยใช้applyการแปลงจำนวนเต็มในชุดสตริงก็เปรียบได้ (และบางครั้งเร็วกว่า) astypeกว่าการใช้

ป้อนคำอธิบายภาพที่นี่ กราฟถูกพล็อตโดยใช้perfplotไลบรารี

import perfplot

perfplot.show(
    setup=lambda n: pd.Series(np.random.randint(0, n, n)),
    kernels=[
        lambda s: s.astype(str),
        lambda s: s.apply(str)
    ],
    labels=['astype', 'apply'],
    n_range=[2**k for k in range(1, 20)],
    xlabel='N',
    logx=True,
    logy=True,
    equality_check=lambda x, y: (x == y).all())

ด้วยลอยฉันเห็นเป็นอย่างสม่ำเสมอให้เร็วที่สุดเท่าหรือเร็วกว่าเล็กน้อยastype applyสิ่งนี้เกี่ยวข้องกับข้อเท็จจริงที่ว่าข้อมูลในการทดสอบเป็นประเภทจำนวนเต็ม

GroupBy การดำเนินการกับการเปลี่ยนแปลงที่ถูกล่ามโซ่

GroupBy.applyยังไม่ได้รับการกล่าวถึงจนถึงขณะนี้ แต่GroupBy.applyยังเป็นฟังก์ชันอำนวยความสะดวกแบบวนซ้ำเพื่อจัดการกับสิ่งที่GroupByฟังก์ชันที่มีอยู่ไม่มี

ข้อกำหนดทั่วไปประการหนึ่งคือการดำเนินการ GroupBy จากนั้นการดำเนินการหลักสองอย่างเช่น "lagged cumsum":

df = pd.DataFrame({"A": list('aabcccddee'), "B": [12, 7, 5, 4, 5, 4, 3, 2, 1, 10]})
df

   A   B
0  a  12
1  a   7
2  b   5
3  c   4
4  c   5
5  c   4
6  d   3
7  d   2
8  e   1
9  e  10

คุณต้องมีการโทรแบบกลุ่มต่อเนื่องสองครั้งที่นี่:

df.groupby('A').B.cumsum().groupby(df.A).shift()

0     NaN
1    12.0
2     NaN
3     NaN
4     4.0
5     9.0
6     NaN
7     3.0
8     NaN
9     1.0
Name: B, dtype: float64

โดยใช้applyคุณสามารถย่อให้สั้นลงเป็นการโทรครั้งเดียว

df.groupby('A').B.apply(lambda x: x.cumsum().shift())

0     NaN
1    12.0
2     NaN
3     NaN
4     4.0
5     9.0
6     NaN
7     3.0
8     NaN
9     1.0
Name: B, dtype: float64

เป็นการยากมากที่จะหาปริมาณประสิทธิภาพเนื่องจากขึ้นอยู่กับข้อมูล แต่โดยทั่วไปapplyเป็นวิธีแก้ปัญหาที่ยอมรับได้หากเป้าหมายคือการลดการgroupbyโทร (เนื่องจากgroupbyมีราคาค่อนข้างแพงด้วย)


ข้อควรระวังอื่น ๆ

นอกเหนือจากคำเตือนที่กล่าวถึงข้างต้นแล้วยังควรกล่าวถึงด้วยว่าapplyทำงานในแถวแรก (หรือคอลัมน์) สองครั้ง ทำเพื่อตรวจสอบว่าฟังก์ชันนี้มีผลข้างเคียงหรือไม่ หากไม่เป็นเช่นapplyนั้นอาจสามารถใช้เส้นทางลัดในการประเมินผลลัพธ์ได้มิฉะนั้นจะกลับไปใช้งานช้า

df = pd.DataFrame({
    'A': [1, 2],
    'B': ['x', 'y']
})

def func(x):
    print(x['A'])
    return x

df.apply(func, axis=1)

# 1
# 1
# 2
   A  B
0  1  x
1  2  y

พฤติกรรมนี้ยังพบได้ในGroupBy.applyเวอร์ชันแพนด้า <0.25 (แก้ไขที่ 0.25 ดูข้อมูลเพิ่มเติมได้ที่นี่)


ฉันคิดว่าเราต้องระวัง .. %timeit for c in df.columns: df[c] = pd.to_datetime(df[c], errors='coerce')แน่นอนว่าหลังจากการทำซ้ำครั้งแรกมันจะเร็วกว่ามากเมื่อคุณเปลี่ยนdatetimeเป็น ... datetime?
jpp

@jpp ฉันมีความกังวลเหมือนกัน แต่คุณยังต้องทำการสแกนเชิงเส้นไม่ว่าจะด้วยวิธีใดการเรียก to_datetime บนสตริงนั้นเร็วพอ ๆ กับการเรียกใช้บนวัตถุวันที่และเวลาหากไม่เร็วกว่า การกำหนดเวลาสนามเบสบอลเหมือนกัน อีกทางเลือกหนึ่งคือใช้ขั้นตอนการคัดลอกล่วงหน้าสำหรับทุกโซลูชันที่ตั้งเวลาไว้ซึ่งห่างจากประเด็นหลัก แต่เป็นความกังวลที่ถูกต้อง
cs95

"การเรียกto_datetimeสตริงเร็วพอ ๆ กับ ... datetimeวัตถุ" .. จริงหรือ? ฉันรวมการสร้างดาต้าเฟรม (ต้นทุนคงที่) ในการกำหนดเวลาapplyเทียบกับforลูปและความแตกต่างนั้นน้อยกว่ามาก
jpp

@jpp นั่นคือสิ่งที่ฉันได้รับจากการทดสอบ (ยอมรับได้อย่าง จำกัด ) ฉันแน่ใจว่ามันขึ้นอยู่กับข้อมูล แต่แนวคิดทั่วไปก็คือเพื่อจุดประสงค์ในการแสดงภาพประกอบความแตกต่างคือ "จริงจังไม่ต้องกังวล"
cs95

1
@ cs95 สวัสดีปีใหม่!
jpp

52

ทั้งหมดไม่applyเหมือนกัน

แผนภูมิด้านล่างแสดงให้เห็นว่าเมื่อใดควรพิจารณาapply1 . สีเขียวหมายถึงอาจมีประสิทธิภาพ หลีกเลี่ยงสีแดง

ป้อนคำอธิบายภาพที่นี่

บางส่วนนี้ใช้งานง่าย: pd.Series.applyเป็นลูปที่ชาญฉลาดระดับ Python, ditto pd.DataFrame.applyrow-wise ( axis=1) การนำไปใช้ในทางที่ผิดมีมากมายและหลากหลาย โพสต์อื่น ๆ เกี่ยวข้องกับพวกเขาในเชิงลึกมากขึ้น วิธีแก้ปัญหาที่ได้รับความนิยมคือการใช้วิธีการแบบ vectorised รายการความเข้าใจ (ถือว่าข้อมูลสะอาด) หรือเครื่องมือที่มีประสิทธิภาพเช่นตัวpd.DataFrameสร้าง (เช่นเพื่อหลีกเลี่ยงapply(pd.Series))

หากคุณใช้pd.DataFrame.applyrow-wise การระบุraw=True(ถ้าเป็นไปได้) มักจะเป็นประโยชน์ ในขั้นตอนnumbaนี้มักเป็นทางเลือกที่ดีกว่า

GroupBy.apply: เป็นที่ชื่นชอบโดยทั่วไป

การทำซ้ำgroupbyเพื่อหลีกเลี่ยงapplyจะส่งผลเสียต่อประสิทธิภาพ GroupBy.applyโดยปกติจะใช้ได้ดีที่นี่หากวิธีการที่คุณใช้ในฟังก์ชันที่กำหนดเองของคุณเป็นแบบเวกเตอร์ บางครั้งไม่มีวิธีดั้งเดิมของ Pandas สำหรับการรวมกลุ่มที่คุณต้องการใช้ ในกรณีนี้สำหรับกลุ่มเล็ก ๆ ที่applyมีฟังก์ชันแบบกำหนดเองอาจยังคงให้ประสิทธิภาพที่สมเหตุสมผล

pd.DataFrame.apply คอลัมน์ที่ชาญฉลาด: ถุงผสม

pd.DataFrame.applycolumn-wise ( axis=0) เป็นกรณีที่น่าสนใจ สำหรับแถวจำนวนน้อยเมื่อเทียบกับคอลัมน์จำนวนมากมักจะมีราคาแพง สำหรับแถวจำนวนมากที่สัมพันธ์กับคอลัมน์ในกรณีที่พบบ่อยบางครั้งคุณอาจเห็นการปรับปรุงประสิทธิภาพที่สำคัญโดยใช้apply:

# Python 3.7, Pandas 0.23.4
np.random.seed(0)
df = pd.DataFrame(np.random.random((10**7, 3)))     # Scenario_1, many rows
df = pd.DataFrame(np.random.random((10**4, 10**3))) # Scenario_2, many columns

                                               # Scenario_1  | Scenario_2
%timeit df.sum()                               # 800 ms      | 109 ms
%timeit df.apply(pd.Series.sum)                # 568 ms      | 325 ms

%timeit df.max() - df.min()                    # 1.63 s      | 314 ms
%timeit df.apply(lambda x: x.max() - x.min())  # 838 ms      | 473 ms

%timeit df.mean()                              # 108 ms      | 94.4 ms
%timeit df.apply(pd.Series.mean)               # 276 ms      | 233 ms

1มีข้อยกเว้น แต่สิ่งเหล่านี้มักจะเล็กน้อยหรือผิดปกติ ตัวอย่างสองสามตัวอย่าง:

  1. df['col'].apply(str)df['col'].astype(str)อาจจะดีกว่าเล็กน้อย
  2. df.apply(pd.to_datetime)การทำงานกับสตริงปรับขนาดได้ไม่ดีกับแถวเมื่อเทียบกับforลูปปกติ

2
ขอบคุณที่เข้าร่วมขอบคุณมุมมองที่หลากหลาย :) +1
cs95

1
@coldspeed ขอบคุณโพสต์ของคุณไม่มีอะไรผิดพลาดมากนัก (นอกเหนือจากการเปรียบเทียบที่ขัดแย้งกับของฉัน แต่อาจเป็นข้อมูลป้อนเข้าหรือตั้งค่าตาม) รู้สึกว่ามีวิธีที่แตกต่างออกไปในการมองปัญหา
jpp

@jpp ฉันมักจะใช้คุณผังงานที่ดีเยี่ยมกำลังเป็นคำแนะนำจนเมื่อผมเห็นในวันนี้ว่าแถวที่ชาญฉลาดapplyอย่างมีนัยสำคัญได้เร็วกว่าวิธีการแก้ปัญหาของฉันanyด้วย มีความคิดเห็นเกี่ยวกับเรื่องนี้ไหม
Stef

1
@jpp: คุณสิทธิกำลัง: สำหรับ 1mio แถว x 100 cols anyประมาณ 100 applyครั้งเร็วกว่า เป็นการทดสอบครั้งแรกของฉันด้วย 2,000 แถว x 1,000 cols และที่นี่applyเร็วเป็นสองเท่าany
Stef

1
@jpp ฉันต้องการใช้ภาพของคุณในการนำเสนอ / บทความ คุณโอเคไหม? ฉันจะพูดถึงแหล่งที่มาอย่างชัดเจน ขอบคุณ
Erfan

4

สำหรับaxis=1(เช่นฟังก์ชัน row-wise) คุณสามารถใช้ฟังก์ชันต่อไปนี้แทนapply. ฉันสงสัยว่าทำไมนี่ไม่ใช่pandasพฤติกรรม (ยังไม่ได้ทดสอบด้วยดัชนีผสม แต่ดูเหมือนว่าจะเร็วกว่ามากapply)

def faster_df_apply(df, func):
    cols = list(df.columns)
    data, index = [], []
    for row in df.itertuples(index=True):
        row_dict = {f:v for f,v in zip(cols, row[1:])}
        data.append(func(row_dict))
        index.append(row[0])
    return pd.Series(data, index=index)

ฉันประหลาดใจมากที่พบว่าสิ่งนี้ทำให้ฉันมีประสิทธิภาพที่ดีขึ้นในบางกรณี มันมีประโยชน์อย่างยิ่งเมื่อฉันต้องทำหลาย ๆ สิ่งโดยแต่ละรายการมีค่าคอลัมน์ย่อยต่างกัน คำตอบ "ใช้ทั้งหมดไม่เหมือนกัน" อาจช่วยให้ทราบได้ว่าเมื่อใดมีแนวโน้มที่จะช่วยได้ แต่การทดสอบตัวอย่างข้อมูลของคุณไม่ใช่เรื่องยาก
denson

1
คำแนะนำบางประการ: เพื่อประสิทธิภาพการทำความเข้าใจรายการจะทำได้ดีกว่าสำหรับลูป zip(df, row[1:])เพียงพอที่นี่; ในขั้นตอนนี้ให้พิจารณาnumbaว่า func เป็นการคำนวณตัวเลขหรือไม่ ดูคำตอบนี้สำหรับคำอธิบาย
jpp

@jpp - ถ้าคุณมีฟังก์ชั่นที่ดีกว่าโปรดแบ่งปัน ฉันคิดว่านี่ค่อนข้างใกล้เคียงกับที่ดีที่สุดจากการวิเคราะห์ของฉัน ใช่numbaเร็วกว่าfaster_df_applyมีไว้สำหรับผู้ที่ต้องการสิ่งที่เทียบเท่า แต่เร็วกว่าDataFrame.apply(ซึ่งช้าอย่างประหลาด)
Pete Cacioppi

สิ่งนี้ใกล้เคียงกับวิธี.applyการใช้งานจริง ๆ แต่มันทำสิ่งหนึ่งที่ทำให้ช้าลงอย่างเห็นได้ชัดโดยพื้นฐานแล้ว: row = pd.Series({f:v for f,v in zip(cols, row[1:])})ซึ่งจะเพิ่มการลากมาก ฉันเขียนคำตอบที่อธิบายถึงการใช้งานแม้ว่าฉันคิดว่ามันล้าสมัย แต่เวอร์ชันล่าสุดได้พยายามใช้ประโยชน์จาก Cython .applyแล้วฉันเชื่อว่า (อย่าอ้างฉันในเรื่องนั้น)
juanpa.arrivillaga

2

เคยมีสถานการณ์ใดบ้างที่applyดี? ใช่บางเวลา.

งาน: ถอดรหัสสตริง Unicode

import numpy as np
import pandas as pd
import unidecode

s = pd.Series(['mañana','Ceñía'])
s.head()
0    mañana
1     Ceñía


s.apply(unidecode.unidecode)
0    manana
1     Cenia

ปรับปรุง
ผมก็ไม่ได้หมายความว่าเรียกร้องให้ใช้applyเพียงแค่คิดตั้งแต่ไม่สามารถรับมือกับสถานการณ์ดังกล่าวข้างต้นจะได้รับผู้สมัครที่ดีสำหรับNumPy pandas applyแต่ฉันลืมความเข้าใจในรายการ ol แบบธรรมดาด้วยการแจ้งเตือนโดย @jpp


ดีไม่ วิธีนี้ดีกว่า[unidecode.unidecode(x) for x in s]หรือlist(map(unidecode.unidecode, s))?
jpp

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