groupby และ agg
ประมาณ 25 นาที
เป้าหมายของบท
- สรุปข้อมูลเป็นกลุ่มด้วย groupby() ร่วมกับ .size()/.agg()
- ตั้งชื่อคอลัมน์ผลลัพธ์ด้วย named aggregation (.agg(ชื่อ=('คอลัมน์', 'ฟังก์ชัน')))
- กรอง "กลุ่ม" หลังสรุปแล้วด้วยการกรอง Series/DataFrame ผลลัพธ์ต่อ (pandas ไม่มี HAVING)
สรุปข้อมูลเป็นกลุ่มด้วย groupby
customers.groupby('city').size()
.groupby('city') จัดกลุ่มแถวตามค่าในคอลัมน์ city แล้ว .size() นับจำนวนแถวในแต่ละกลุ่ม
ได้ผลเป็น Series ที่มี index เป็นชื่อเมือง
agg() และการตั้งชื่อคอลัมน์เอง
products.groupby('category').agg(lo=('price', 'min'), hi=('price', 'max'))
.agg(ชื่อ=('คอลัมน์', 'ฟังก์ชัน')) (เรียกว่า named aggregation) ให้ตั้งชื่อคอลัมน์ผลลัพธ์เองได้
อ่านง่ายกว่าการปล่อยให้ pandas ตั้งชื่อ default ให้ (ซึ่งมักจะยาวและอ่านยาก)
กรองหลังจัดกลุ่ม — pandas ไม่มี HAVING
SQL มี HAVING แยกจาก WHERE ชัดเจน แต่ pandas ไม่มีคำสั่งพิเศษแบบนั้น — วิธี “กรองหลังจัดกลุ่ม” ใน pandas
คือเอาผลลัพธ์จาก groupby() (ซึ่งเป็นแค่ Series/DataFrame ธรรมดา) ไปกรองต่อด้วย [...] เหมือนกรองตารางทั่วไป:
counts = customers.groupby('city').size()
counts[counts > 300]
หรือเขียนในบรรทัดเดียวด้วย .loc[lambda s: เงื่อนไข]:
customers.groupby('city').size().loc[lambda s: s > 300]
assign() — เพิ่มคอลัมน์ชั่วคราวก่อน groupby
customers.assign(y=pd.to_datetime(customers['signup_date']).dt.year).groupby('y').size()
.assign(ชื่อคอลัมน์=ค่า) เพิ่มคอลัมน์ใหม่แล้วคืน DataFrame ใหม่ (ไม่แก้ตัวเดิม) มีประโยชน์มากตอนอยากสร้าง
คอลัมน์ชั่วคราวไว้ใช้ groupby ต่อทันที โดยไม่ต้องแยกเขียนอีกบรรทัด
ตัวอย่างแก้โจทย์
กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้
จำนวนลูกค้าต่อเมือง เรียงจากมากไปน้อย
ทีมการตลาดอยากรู้ว่าเมืองไหนมีลูกค้าเยอะที่สุด
.groupby('city').size()นับจำนวนแถวในแต่ละกลุ่ม
เมืองไหนมีลูกค้ามากกว่า 300 คน (กรองผลลัพธ์หลังจัดกลุ่ม)
ต่อจากตัวอย่างที่แล้ว อยากเห็นเฉพาะเมืองที่มีลูกค้าค่อนข้างเยอะ (มากกว่า 300 คน) เท่านั้น
นับจำนวนลูกค้าต่อเมืองก่อนเหมือนเดิม เก็บผลไว้ในตัวแปร
เติมช่องว่าง
แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ
ราคาเฉลี่ยของสินค้าแต่ละหมวด ปัดเป็นจำนวนเต็ม เรียงจากแพงไปถูก เก็บไว้ในตัวแปร result
เติม ___ ให้ครบ
แบบฝึกท้ายบท
บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม
🟢 พื้นฐาน
จำนวนออเดอร์แต่ละสถานะ เก็บไว้ในตัวแปร result
ราคาต่ำสุดและสูงสุดของสินค้าแต่ละหมวด แสดงคอลัมน์ lo, hi เก็บไว้ในตัวแปร result
จำนวนลูกค้าที่สมัครในแต่ละปี (ใช้ pd.to_datetime ดึงปีก่อน) เก็บไว้ในตัวแปร result
จำนวนออเดอร์แต่ละวิธีจ่ายเงิน เก็บไว้ในตัวแปร result
🟡 ประยุกต์
หมวดสินค้าที่มีจำนวนสินค้าน้อยกว่า 20 ชิ้น (กรองผลลัพธ์หลังจัดกลุ่ม) เก็บไว้ในตัวแปร result
วิธีจ่ายเงินที่มีออเดอร์อย่างน้อย 1000 รายการ เรียงจากมากไปน้อย เก็บไว้ในตัวแปร result
ส่วนลดเฉลี่ย (ปัด 1 ตำแหน่ง) ของออเดอร์ที่มีส่วนลด แยกตามวิธีจ่ายเงิน เก็บไว้ในตัวแปร result
🔴 ท้าทาย
เมืองที่สัดส่วนลูกค้าจากช่องทาง organic มากกว่า 46% (ปัด 1 ตำแหน่งเป็น %) เก็บไว้ในตัวแปร result
Quiz
สรุป
- groupby(col).agg(...)/.size() สรุปข้อมูลเป็นกลุ่ม ตั้งชื่อคอลัมน์เองได้ด้วย named aggregation
- pandas ไม่มีคำสั่งเทียบเท่า HAVING ต้องกรองผลลัพธ์ต่อด้วย [...] หลังจัดกลุ่มเสร็จแล้ว
- .assign() เพิ่มคอลัมน์ใหม่ชั่วคราวได้โดยไม่แก้ DataFrame เดิม มีประโยชน์มากก่อน groupby
คำถามสัมภาษณ์
อธิบายวิธี "กรองหลังจัดกลุ่ม" ใน pandas เทียบกับ HAVING ใน SQL
SQL มี HAVING แยกจาก WHERE ชัดเจน (WHERE กรองก่อนจัดกลุ่ม, HAVING กรองหลังจัดกลุ่ม)
ส่วน pandas ไม่มีคำสั่งพิเศษสำหรับกรองหลังจัดกลุ่ม ใช้หลักการเดียวกับการกรอง DataFrame ทั่วไป
คือเอาผลลัพธ์จาก groupby(...).agg(...) หรือ .size() (ซึ่งเป็น Series/DataFrame ธรรมดา)
ไปกรองต่อด้วย boolean indexing (result[result > ค่า]) เหมือนกรองตารางทั่วไป ไม่มี syntax ใหม่ต้องเรียนรู้