apply ใน pandas ผมใช้สำหรับรันฟังก์ชันกับแต่ละแถวหรือแต่ละคอลัมน์ใน DataFrame หรือรันกับแต่ละค่าใน Series ตอนที่ต้องการ logic ซับซ้อนที่ vectorized operations ทำไม่ได้ครับ
df.apply(func, axis)
df.apply(func, axis)
Series
ผลลัพธ์เป็น Series ถ้าฟังก์ชันคืนค่า scalar ต่อแถว/คอลัมน์ หรือเป็น DataFrame ถ้าฟังก์ชันคืน Series (axis=1) ครับ ส่วนใหญ่ตอน apply บน Series หรือ axis=1 แบบง่ายจะได้ Series กลับมา
| Argument | Type | Required | Default | Description |
|---|---|---|---|---|
| func | callable | Yes | ฟังก์ชันที่จะรัน รับได้ทั้ง lambda, def function, หรือ numpy function เช่น np.sqrt | |
| axis | int | str | Optional | 0 | 0 หรือ ‘index’ = รันต่อคอลัมน์ (ค่า default), 1 หรือ ‘columns’ = รันต่อแถว ผมใช้ axis=1 บ่อยกว่าครับตอนอยากคำนวณข้ามคอลัมน์ |
| raw | bool | Optional | False | False = ส่ง Series เข้าฟังก์ชัน, True = ส่ง ndarray แทน เร็วกว่าถ้าใช้ numpy functions ที่รับ array โดยตรง |
| result_type | str | None | Optional | None | ใช้ได้เฉพาะ axis=1: ‘expand’ แปลง list เป็นหลายคอลัมน์, ‘reduce’ บีบเป็น Series, ‘broadcast’ คงรูปต้นฉบับ |
| args | tuple | Optional | () | arguments เพิ่มเติมที่จะส่งเข้าฟังก์ชัน ตามหลัง argument หลักที่ apply ส่งให้อัตโนมัติ |
s.apply(lambda v: v * 2)s.apply(lambda v: v * 2)
0 20
1 40
2 60
3 80
dtype: int64
s.apply(categorize)s.apply(categorize)
0 ราคาถูก
1 ราคากลาง
2 ราคาแพง
3 ราคาถูก
4 ราคาแพง
dtype: str
df.apply(lambda row: row['base_price'] * (1 - row['discount_pct'] / 100), axis=1)df.apply(lambda row: row['base_price'] * (1 - row['discount_pct'] / 100), axis=1)
0 90.0
1 190.0
2 240.0
dtype: float64
df.apply(lambda col: col.max() - col.min())df.apply(lambda col: col.max() - col.min())
score_A 20
score_B 35
dtype: int64
ช้ากว่าเยอะมากครับ 😅 apply วน loop ใน Python ทีละแถว ถ้าข้อมูล 1 ล้านแถว vectorized operations เสร็จในหลัก milliseconds แต่ apply อาจใช้เวลาหลายวินาทีเลย ผมเลยใช้ apply เฉพาะตอนที่ logic ซับซ้อนจริงๆ เช่น ต้องเงื่อนไข if-else หลายชั้น หรือ call ฟังก์ชัน external ที่ไม่รับ array ครับ
apply ส่ง Series เข้าฟังก์ชัน (ทั้งแถวหรือทั้งคอลัมน์) ครับ ส่วน map ใน Series และ applymap (เปลี่ยนชื่อเป็น map ใน pandas 2.1+) ส่งค่าแต่ละ cell เข้าทีละค่าเลย ผมใช้ .map() ตอนอยากเปลี่ยนค่าแต่ละ cell อย่างเดียวโดยไม่ต้องเห็นแถวรอบข้าง และใช้ apply ตอนต้องคำนวณข้ามคอลัมน์ในแถวเดียวกันครับ
ได้เลยครับ ใช้ parameter args=(val1, val2) เพื่อส่ง positional args เพิ่ม หรือส่งเป็น **kwargs ก็ได้ เช่น df[‘price’].apply(add_tax, args=(0.07,)) ถ้าเขียนฟังก์ชัน add_tax(price, tax_rate) ไว้ครับ
apply เป็นเหมือนคำสั่ง “วิ่งทีละแถว” ของ pandas ครับ ทำงานในโหมด Split-Apply-Combine คล้ายกับ groupby เลย แต่แทนที่จะแบ่งกลุ่มก่อน มันจะส่งแต่ละแถว (axis=1) หรือแต่ละคอลัมน์ (axis=0) เป็น Series เข้าไปหาฟังก์ชันที่เราเขียน แล้วรวมผลลัพธ์กลับมา ถ้าใช้กับ Series ธรรมดา (ไม่ใช่ DataFrame) มันจะส่งแต่ละค่าทีละตัวเข้าไปเลย
ที่เจ๋งคือ apply รับฟังก์ชันอะไรก็ได้ ทั้ง lambda แบบสั้น, ฟังก์ชันที่เราเขียนเอง หรือฟังก์ชันจาก library อื่นก็ได้ ทำให้มัน flexible มากครับ อยากทำ logic อะไรที่แปลกๆ ก็ทำได้หมด ✨
ส่วนตัวผมใช้ apply เป็น “ทางเลือกสุดท้าย” ครับ 😎 เพราะมัน loop ทีละแถวใน Python ซึ่งช้ากว่า vectorized operations อย่าง `df[‘col’] * 2` หรือ `np.sqrt(df[‘col’])` เยอะมาก ถ้า logic ง่ายๆ ให้ใช้ arithmetic operators หรือ numpy ก่อน apply ค่อยเข้ามาตอนที่ logic ซับซ้อนจริงๆ