3 เคล็ดลับ Polars เพื่อการจัดการข้อมูลประสิทธิภาพสูง

Polars ได้รับความเร็วมาจากสองส่วนหลัก คือ Expression Engine ที่เขียนและประมวลผลด้วย Rust บนทุกคอร์ที่มี และ Query Optimizer ที่จะเขียนลำดับการทำงานของคุณใหม่ก่อนเริ่มทำงานจริง
สคริปต์ Polars ที่ทำงานช้าเกือบทุกตัวมักจะขาดคุณสมบัติอย่างใดอย่างหนึ่งในสองอย่างนี้ สิ่งที่น่าลำบากใจคือเวอร์ชันที่ทำงานเร็วและเวอร์ชันที่ทำงานช้าอาจดูคล้ายกันมากบนหน้ากระดาษ และนี่คือ 3 จุดสำคัญที่มักส่งผลต่อประสิทธิภาพอย่างชัดเจน
ตัวอย่างเหล่านี้จะใช้ข้อมูลการเดินทางของ NYC Yellow Taxi เป็นเวลาหนึ่งเดือน ซึ่งเผยแพร่โดย TLC ในรูปแบบ Parquet โดยคุณสามารถดาวน์โหลดข้อมูลมาทดสอบก่อนได้ดังนี้:
curl -O https://d37ci6vzurychx.cloudfront.net/trip-data/yellow_tripdata_2026-01.parquetข้อมูลด้านล่างทั้งหมดได้รับการตรวจสอบความถูกต้องกับ Polars เวอร์ชัน 1.44.2 เรียบร้อยแล้ว
เคล็ดลับที่ 1: การ Scan ไฟล์แทนการ Read
ฟังก์ชัน pl.read_parquet จะดึงเนื้อหาของไฟล์ทั้งหมดเข้าสู่หน่วยความจำแล้วจึงทำการกรองข้อมูล แต่ pl.scan_parquet จะส่งคืน LazyFrame กลับมาแทน ซึ่งจะบันทึกคำสั่งที่คุณต้องการโดยยังไม่ลงมือทำจริง
ช่องว่างนี้เองที่ทำให้ Optimizer แสดงฝีมือ โดยมันจะดันการกรอง (Filter) และการเลือกคอลัมน์ลงไปที่ขั้นตอนการ Scan โดยตรง ทำให้การคัดกรองข้อมูลเกิดขึ้นขณะกำลังอ่านไฟล์ แถวที่ไม่ต้องการจะไม่ถูกถอดรหัส (Decoded) ช่วยประหยัดพลังประมวลผลมหาศาล
import polars as pl
q = (
pl.scan_parquet("yellow_tripdata_2026-01.parquet")
.filter(pl.col("fare_amount") > 50)
.select("PULocationID", "tip_amount")
.group_by("PULocationID")
.agg(pl.col("tip_amount").mean())
)
# นี่คือการอธิบายแผนการทำงาน ไม่ใช่ข้อมูล
print(q.explain())
df = q.collect()จะไม่มีการประมวลผลจริงจนกว่าจะถึงคำสั่ง collect() ส่วนคำสั่ง explain() จะแสดงแผนการทำงานที่ Optimizer สร้างขึ้น ซึ่งคุณจะเห็นว่ามีการผลัก Predicate (pushed-down) และการตัดคอลัมน์ที่ไม่จำเป็นออกไปแล้ว:
AGGREGATE[maintain_order: false]
[col("tip_amount").mean()] BY [col("PULocationID")]
FROM
simple π 2/2 ["PULocationID", "tip_amount"]
Parquet SCAN [yellow_tripdata_2026-01.parquet]
PROJECT 3/20 COLUMNS
SELECTION: col("fare_amount") > 50.0
ESTIMATED ROWS: 3724889นิสัยที่ควรเลิกคือการเรียก collect() ระหว่างกลางของเชนคำสั่ง เพราะทุกครั้งที่เรียก collect() จะเปรียบเสมือนการสร้างกำแพงที่ Optimizer ไม่สามารถมองทะลุผ่านไปปรับแต่งการทำงานได้
เคล็ดลับที่ 2: ค่ารายกลุ่ม (Per-Group Values) โดยไม่ต้องวนรอบ Group-By
การหาค่าเฉลี่ยหรือยอดรวมรายกลุ่มกลับมาใส่ในทุกแถวมักถูกทำด้วยการ group_by และ agg แล้วนำมา Join กลับ ซึ่งวิธีนี้ทำให้ต้องประมวลผลสองรอบและวุ่นวายกับการจัดการ Key ที่ใช้ Join แต่การใช้ .over() สามารถจบงานได้ใน Expression เดียวภายในรอบเดียว โดยยังคงลำดับแถวเดิมไว้
ตัวอย่างการคำนวณสัดส่วนค่าโดยสารในแต่ละโซน:
df = pl.DataFrame({
"pickup_zone": ["A", "A", "B", "B"],
"fare_amount": [30.0, 70.0, 25.0, 75.0],
})
out = df.with_columns(
(pl.col("fare_amount") / pl.col("fare_amount").sum().over("pickup_zone"))
.alias("share_of_zone")
)
print(out)ผลลัพธ์ที่ได้จะแสดงสัดส่วนของแต่ละแถวเทียบกับผลรวมของโซนนั้นๆ ทันที นอกจากนี้ over ยังรองรับ order_by ทำให้การทำผลรวมสะสม (Running Total) รายกลุ่มทำได้ง่ายขึ้นเพียงบรรทัดเดียว โดยไม่ต้องเสียเวลา Sort และ Join ใหม่
เคล็ดลับที่ 3: การเอา Python ออกจาก Loop
ฟังก์ชัน map_elements จะส่งค่าในคอลัมน์ไปให้ Python ประมวลผลทีละค่า ซึ่งช้ากว่า Expression API แบบปกติมาก จน Polars จะแจ้งเตือน PolarsInefficientMapWarning หากพบว่าสามารถใช้การทำงานแบบ Native แทนได้ โดยเฉพาะการทำงานแบบมีเงื่อนไขที่ควรใช้ when/then/otherwise แทน
ตัวอย่างการจัดกลุ่มช่วงข้อมูล (Banding) แบบ Native:
banded = df.with_columns(
pl.when(pl.col("fare_amount") > 50)
.then(pl.lit("high"))
.when(pl.col("fare_amount") > 20)
.then(pl.lit("medium"))
.otherwise(pl.lit("low"))
.alias("fare_band")
)
print(banded)วิธีนี้ได้ผลลัพธ์เหมือนกับการใช้ map_elements แต่ CPU จะประมวลผลได้รวดเร็วกว่าเนื่องจาก Polars จะคำนวณทุกเงื่อนไขแบบขนานภายใน Engine ของตัวเองโดยไม่ต้องพึ่งพาตัวแปลภาษา Python
บทสรุป
หัวใจสำคัญของทั้งสามเคล็ดลับคือการรักษาการทำงานให้อยู่ภายใน Engine ของ Polars ให้ได้นานที่สุด แทนที่จะส่งงานกลับไปให้ Python หรือดึงข้อมูลลงหน่วยความจำโดยไม่จำเป็น หากสคริปต์ของคุณทำงานช้า ให้ลองตรวจสอบว่าคุณได้ใช้ Scan แทน Read หรือใช้ Expression แทนการ Loop แล้วหรือยัง
ความคิดเห็น (0)
เข้าสู่ระบบเพื่อร่วมแสดงความเห็น
สมัครสมาชิกมาเป็นคนแรกที่แสดงความเห็นกันเลยโบร
