Data Analyst › pandas

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 ต่อทันที โดยไม่ต้องแยกเขียนอีกบรรทัด

ตัวอย่างแก้โจทย์

กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้

จำนวนลูกค้าต่อเมือง เรียงจากมากไปน้อย

ทีมการตลาดอยากรู้ว่าเมืองไหนมีลูกค้าเยอะที่สุด

  1. .groupby('city').size() นับจำนวนแถวในแต่ละกลุ่ม

    Python

เมืองไหนมีลูกค้ามากกว่า 300 คน (กรองผลลัพธ์หลังจัดกลุ่ม)

ต่อจากตัวอย่างที่แล้ว อยากเห็นเฉพาะเมืองที่มีลูกค้าค่อนข้างเยอะ (มากกว่า 300 คน) เท่านั้น

  1. นับจำนวนลูกค้าต่อเมืองก่อนเหมือนเดิม เก็บผลไว้ในตัวแปร

    Python

เติมช่องว่าง

แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ

ยังไม่ได้ทำ

ราคาเฉลี่ยของสินค้าแต่ละหมวด ปัดเป็นจำนวนเต็ม เรียงจากแพงไปถูก เก็บไว้ในตัวแปร result เติม ___ ให้ครบ

แบบฝึกท้ายบท

บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม

🟢 พื้นฐาน

🟢 พื้นฐานยังไม่ได้ทำ

จำนวนออเดอร์แต่ละสถานะ เก็บไว้ในตัวแปร result

🟢 พื้นฐานยังไม่ได้ทำ

ราคาต่ำสุดและสูงสุดของสินค้าแต่ละหมวด แสดงคอลัมน์ lo, hi เก็บไว้ในตัวแปร result

🟢 พื้นฐานยังไม่ได้ทำ

จำนวนลูกค้าที่สมัครในแต่ละปี (ใช้ pd.to_datetime ดึงปีก่อน) เก็บไว้ในตัวแปร result

🟢 พื้นฐานยังไม่ได้ทำ

จำนวนออเดอร์แต่ละวิธีจ่ายเงิน เก็บไว้ในตัวแปร result

🟡 ประยุกต์

🟡 ประยุกต์ยังไม่ได้ทำ

หมวดสินค้าที่มีจำนวนสินค้าน้อยกว่า 20 ชิ้น (กรองผลลัพธ์หลังจัดกลุ่ม) เก็บไว้ในตัวแปร result

🟡 ประยุกต์ยังไม่ได้ทำ

วิธีจ่ายเงินที่มีออเดอร์อย่างน้อย 1000 รายการ เรียงจากมากไปน้อย เก็บไว้ในตัวแปร result

🟡 ประยุกต์ยังไม่ได้ทำ

ส่วนลดเฉลี่ย (ปัด 1 ตำแหน่ง) ของออเดอร์ที่มีส่วนลด แยกตามวิธีจ่ายเงิน เก็บไว้ในตัวแปร result

🔴 ท้าทาย

🔴 ท้าทายยังไม่ได้ทำ

เมืองที่สัดส่วนลูกค้าจากช่องทาง organic มากกว่า 46% (ปัด 1 ตำแหน่งเป็น %) เก็บไว้ในตัวแปร result

Quiz

1. pandas มีคำสั่งเทียบเท่า HAVING ของ SQL ไหม
2. .agg(ชื่อ=('col', 'func')) มีประโยชน์ยังไง
3. groupby('x').size() > 300 ได้ผลลัพธ์แบบไหน

สรุป

  • 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 ใหม่ต้องเรียนรู้