φυβλαςのβλογ
บล็อกของ phyblas



จัดการข้อมูลด้วย pandas เบื้องต้น บทที่ ๑๕: การใช้ฟังก์ชันจัดการกับข้อมูลที่จัดกลุ่มแล้ว
เขียนเมื่อ 2016/09/25 16:04
แก้ไขล่าสุด 2021/09/28 16:42
เนื้อหาต่อจากบทที่แล้วที่ได้อธิบายการใช้ groupby เพื่อจัดกลุ่มข้อมูลไป ในบทนี้จะพูดถึงการใช้ฟังก์ชันต่างๆสำหรับจัดการกับข้อมูลที่จัดกลุ่มแล้ว



การใช้เมธอด apply กับข้อมูลที่จัดกลุ่มแล้ว
ข้อมูลที่จัดกลุ่มแล้วก็มีเมธอด apply เช่นเดียวกับซีรีส์หรือเดตาเฟรมธรรมดาดังที่ได้กล่าวไปในบทที่ ๙

เพียงแต่เมธอด apply ที่ทำกับออบเจ็กต์ข้อมูลที่จัดกลุ่มแล้วนี้จะเป็นการทำซ้ำโดยแยกทำตามกลุ่มที่แยก (ไม่ได้ทำโดยแยกแถวหรือคอลัมน์เหมือนอย่างใน apply ที่ทำกับเดตาเฟรมโดยตรง)

โดยเมื่อป้อนฟังก์ชันเข้าไปโดยใช้ apply ข้อมูลเป็นเดตาเฟรมที่ได้จากการแยกในแต่ละกลุ่มจะถูกส่งเข้าไปเป็นอาร์กิวเมนต์ของฟังก์ชันนั้น

ตัวอย่าง เริ่มจากสร้างตารางโปเกมอนที่เหมือนกับในบทที่แล้ว
import pandas as pd
pokemon = pd.DataFrame([
        ['ฟุชิงิดาเนะ','พืช/พิษ',0.7,6.9],
        ['ฟุชิงิโซว','พืช/พิษ',1.0,13.0],
        ['ฟุชิงิบานะ','พืช/พิษ',2.4,155.5],
        ['ฮิโตคาเงะ','ไฟ',0.6,8.5],
        ['ลิซาร์โด','ไฟ',1.1,19.0],
        ['ลิซาร์ดอน','ไฟ/บิน',1.7,101.5],
        ['เซนิงาเมะ','น้ำ',0.5,9.0],
        ['คาเมล','น้ำ',1.0,22.5],
        ['คาเม็กซ์','น้ำ',1.6,101.1]],
    columns=['สายพันธุ์','ชนิด','ส่วนสูง','น้ำหนัก'],
    index=[1,2,3,4,5,6,7,8,9])
print(pokemon)



ได้
  สายพันธุ์ ชนิด ส่วนสูง น้ำหนัก
1 ฟุชิงิดาเนะ พืช/พิษ 0.7 6.9
2 ฟุชิงิโซว พืช/พิษ 1.0 13.0
3 ฟุชิงิบานะ พืช/พิษ 2.4 155.5
4 ฮิโตคาเงะ ไฟ 0.6 8.5
5 ลิซาร์โด ไฟ 1.1 19.0
6 ลิซาร์ดอน ไฟ/บิน 1.7 101.5
7 เซนิงาเมะ น้ำ 0.5 9.0
8 คาเมล น้ำ 1.0 22.5
9 คาเม็กซ์ น้ำ 1.6 101.1

จากนั้นลองป้อนฟังก์ชัน type ลงไปโดยใช้ apply
print(pokemon.groupby('ชนิด').apply(type))

ได้
ชนิด
น้ำ        <class 'pandas.core.frame.DataFrame'>
พืช/พิษ    <class 'pandas.core.frame.DataFrame'>
ไฟ         <class 'pandas.core.frame.DataFrame'>
ไฟ/บิน     <class 'pandas.core.frame.DataFrame'>
dtype: object

จะเห็นว่าข้อมูลในแต่ละกลุ่มถูกส่งเข้าไปเป็นอาร์กิวเมนต์ของฟังก์ชัน type ในรูปของเดตาเฟรม ในที่นี้ทำการคืนค่าคลาสซึ่งจะเห็นว่าเป็นเดตาเฟรม

วิธีที่มีประสิทธิภาพในการใช้ apply ก็คือใช้คู่กับ lambda เช่น ถ้าต้องการดัชนีของแต่ละแถวในกลุ่ม
print(pokemon.groupby('ชนิด').apply(lambda x:x.index.values))

ได้
ชนิด
น้ำ        [7, 8, 9]
พืช/พิษ    [1, 2, 3]
ไฟ            [4, 5]
ไฟ/บิน           [6]
dtype: object

หากผลจากฟังก์ชันซึ่งได้มาในแต่ละกลุ่มอยู่ในรูปของซีรีส์ มันก็จะหลอมรวมกันเป็นเดตาเฟรม เช่นถ้าใช้เมธอด max
print(pokemon.groupby('ชนิด').apply(lambda x:x.max()))

max ในที่นี้ดูจะให้ผลคล้ายกับใช้ .max() โดยตรงโดยไม่ผ่าน apply เพียงแต่ข้อมูลทั้งตารางจะถูกส่งมาดังนั้นคอลัมน์ "ชนิด" ก็ปรากฏซ้ำในนี้ด้วย
  สายพันธุ์ ชนิด ส่วนสูง น้ำหนัก
ชนิด        
น้ำ เซนิงาเมะ น้ำ 1.6 101.1
พืช/พิษ ฟุชิงิโซว พืช/พิษ 2.4 155.5
ไฟ ฮิโตคาเงะ ไฟ 1.1 19.0
ไฟ/บิน ลิซาร์ดอน ไฟ/บิน 1.7 101.5

แต่ในบางกรณีก็อาจรวมกันเป็นซีรีส์ที่มีดัชนีซ้อน เช่น
print(pokemon.groupby('ชนิด').apply(lambda x:x['สายพันธุ์']))

ได้
ชนิด
น้ำ      7      เซนิงาเมะ
         8          คาเมล
         9       คาเม็กซ์
พืช/พิษ  1    ฟุชิงิดาเนะ
         2      ฟุชิงิโซว
         3     ฟุชิงิบานะ
ไฟ       4      ฮิโตคาเงะ
         5       ลิซาร์โด
ไฟ/บิน   6      ลิซาร์ดอน
Name: สายพันธุ์, dtype: object

การใช้ apply แม้อาจจะดูซับซ้อนสักหน่อย แต่ก็ยืดหยุ่นสามารถเอาไปประยุกต์ใช้ทำอะไรต่างๆได้มากมาย



การใช้เมธอด agg
มีเมธอดอีกอันที่คล้ายกับ apply คือเอาไว้ป้อนฟังก์ชันเข้าไปให้กับข้อมูลในแต่ละกลุ่ม นั่นคือ agg

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

เรื่องนี้ค่อนข้างซับซ้อน การอธิบายเป็นคำพูดน่าจะเข้าใจยากมาก เพื่อให้เห็นภาพชัดจำเป็นจะต้องยกตัวอย่าง น่าจะพอช่วยได้

ตัวอย่างเช่นใช้ฟังก์ชัน lambda x:x.shape ซึ่งเป็นฟังก์ชันที่จะคืนรูปร่างขนาดของอาเรย์หรือเดตาเฟรม

เมื่อใช้กับ apply (เดตาเฟรมยังคงใช้ข้อมูลเดิมจากตัวอย่างก่อนๆ)
print(pokemon.groupby('ชนิด').apply(lambda x:x.shape))

ได้
ชนิด
น้ำ        (3, 4)
พืช/พิษ    (3, 4)
ไฟ         (2, 4)
ไฟ/บิน     (1, 4)
dtype: object

ผลที่ได้จะแบ่งเป็นแต่ละกลุ่ม ซึ่งในแต่ละกลุ่มจะมีหนึ่งเดตาเฟรมเอามาเข้าฟังก์ชันเพื่อหาค่า shape เดตาเฟรมมีสองมิติจึงได้ค่าออกมาเป็นเลข ๒ ตัว ซึ่งคือจำนวนข้อมูลในแต่ละกลุ่มและจำนวนคอลัมน์

แต่เมื่อใช้กับ agg
print(pokemon.groupby('ชนิด').agg(lambda x:x.shape))

ได้
  สายพันธุ์ ส่วนสูง น้ำหนัก
ชนิด      
น้ำ (3,) (3,) (3,)
พืช/พิษ (3,) (3,) (3,)
ไฟ (2,) (2,) (2,)
ไฟ/บิน (1,) (1,) (1,)

จะเห็นว่า agg นั้นนอกจากจะคิดแยกย่อยแต่ละกลุ่มแล้วยังแบ่งย่อยเป็นคอลัมน์ โดยแต่ละตัวที่แบ่งออกมาจะเป็นซีรีส์ ซึ่งมีมิติเดียว จึงได้ค่า shape เป็นเลขตัวเดียว

นอกจากนี้ยังจะเห็นได้ว่าคอลัมน์ "ชนิด" ซึ่งใช้เป็นตัวจัดกลุ่มจะไม่ถูกรวมอยู่ในนี้ด้วย ซึ่งต่างจาก apply ที่จะมาหมดทุกคอลัมน์

นอกจากใส่ชื่อฟังก์ชันแล้ว บางฟังก์ชันยังสามารถใส่ในรูปสายอักขระได้ ไม่จำเป็นต้องใส่ตัวฟังก์ชัน เช่นถ้าจะหาค่าเฉลี่ยก็ใส่ mean หาผลรวมก็ใส่ sum เป็นต้น นอกจากนั้นก็ยังมีอีกหลายตัวเช่น min, max, std, var, describe, ฯลฯ

เช่นใช้ mean (หมายเหตุ mean ไม่สามารถใช้กับสายอักขระได้ ดังนั้นคอลัมน์ "สายพันธุ์" จะไม่ปรากฏ)
print(pokemon.groupby('ชนิด').agg('mean'))
# มีค่าเท่ากับ print(pokemon.groupby('ชนิด').agg(lambda x:x.mean()))

ได้
  ส่วนสูง น้ำหนัก
ชนิด    
น้ำ 1.033333 44.200000
พืช/พิษ 1.366667 58.466667
ไฟ 0.850000 13.750000
ไฟ/บิน 1.700000 101.500000

สามารถใช้หลายฟังก์ชันพร้อมกันได้ด้วยโดยใส่เป็นลิสต์ของฟังก์ชัน (หรือสายอักขระชื่อฟังก์ชันนั้น) ผลที่ได้จะออกมาเป็นคอลัมน์ซ้อน เช่น
print(pokemon.groupby('ชนิด').agg(['sum',len]))

ได้
  สายพันธุ์ ส่วนสูง น้ำหนัก
  sum len sum len sum len
ชนิด            
น้ำ เซนิงาเมะคาเมลคาเม็กซ์ 3 3.1 3.0 132.6 3.0
พืช/พิษ ฟุชิงิดาเนะฟุชิงิโซวฟุชิงิบานะ 3 4.1 3.0 175.4 3.0
ไฟ ฮิโตคาเงะลิซาร์โด 2 1.7 2.0 27.5 2.0
ไฟ/บิน ลิซาร์ดอน 1 1.7 1.0 101.5 1.0

จะเห็นว่าชื่อคอลัมน์ที่ซ้อนอยู่นั้นจะเป็นไปตามชื่อฟังก์ชัน

เพียงแต่ว่ากรณีที่ใช้ lambda เพื่อเป็นฟังก์ชัน ฟังก์ชันนั้นจะไม่มีชื่อแต่จะออกมาเป็น หากมีฟังก์ชันแบบนี้อยู่ ๒ อันก็จะเกิดการซ้ำซ้อนและเกิดข้อผิดพลาดขึ้น

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

ตัวอย่าง
import numpy as np
f1 = lambda x:len('%s'%np.sum(x))
f2 = lambda x:'%s,%s'%(x.min(),x.max())
print(pokemon.groupby('ชนิด').agg([('ฟ1',f1),('ฟ2',f2)]))

ได้
  สายพันธุ์ ส่วนสูง น้ำหนัก
  ฟ1 ฟ2 ฟ1 ฟ2 ฟ1 ฟ2
ชนิด            
น้ำ 22 คาเมล,เซนิงาเมะ 3.0 0.5,1.6 5.0 9.0,101.1
พืช/พิษ 30 ฟุชิงิดาเนะ,ฟุชิงิโซว 3.0 0.7,2.4 5.0 6.9,155.5
ไฟ 17 ลิซาร์โด,ฮิโตคาเงะ 18.0 0.6,1.1 4.0 8.5,19.0
ไฟ/บิน 9 ลิซาร์ดอน,ลิซาร์ดอน 3.0 1.7,1.7 5.0 101.5,101.5



และยิ่งไปกว่านั้น agg ยังสามารถให้ฟังก์ชันทำกับแต่ละคอลัมน์ไม่เหมือนกันได้ด้วย กำหนดโดยใส่ในรูปของดิกชันนารีของ ชื่อคอลัมน์: ฟังก์ชันที่จะใช้กับคอลัมน์นั้น

โดยจะใส่แค่เฉพาะคอลัมน์ที่ต้องการก็ได้ คอลัมน์ที่ไม่ใส่ก็จะไม่ได้แสดง

ตัวอย่าง นำคอลัมน์ "ส่วนสูง" มาหาค่าผลรวม ส่วนคอลัมน์ "สายพันธุ์" ให้หาความยาวรวมของชื่อสายพันธุ์ทุกตัวในกลุ่ม ส่วนคอลัมน์ "น้ำหนัก" ไม่ใช้ก็ไม่ต้องใส่
f = lambda x:len(np.sum(x))
print(pokemon.groupby('ชนิด').agg({'สายพันธุ์':f,'ส่วนสูง':'sum'}))

ได้
  สายพันธุ์ ส่วนสูง
ชนิด    
น้ำ 22 3.1
พืช/พิษ 30 4.1
ไฟ 17 1.7
ไฟ/บิน 9 1.7

และซับซ้อนยิ่งขึ้นไปอีกคืออาจใช้หลายฟังก์ชันทำต่อแต่ละคอลัมน์พร้อมกันก็ยังได้ โดยให้ใส่เป็นดิกชันนารีของ ชื่อคอลัมน์: ลิสต์ของฟังก์ชันที่จะใช้กับคอลัมน์นั้น

ตัวอย่าง
f1 = ['sum',('lensum',lambda x:len(np.sum(x)))]
f2 = ['sum','mean']
print(pokemon.groupby('ชนิด').agg({'สายพันธุ์':f1,'ส่วนสูง':f2}))

ได้
  สายพันธุ์ ส่วนสูง
  sum lensum sum mean
ชนิด        
น้ำ เซนิงาเมะคาเมลคาเม็กซ์ 22 3.1 1.033333
พืช/พิษ ฟุชิงิดาเนะฟุชิงิโซวฟุชิงิบานะ 30 4.1 1.366667
ไฟ ฮิโตคาเงะลิซาร์โด 17 1.7 0.850000
ไฟ/บิน ลิซาร์ดอน 9 1.7 1.700000



อ้างอิง


<< บทที่แล้ว      บทถัดไป >>
หน้าสารบัญ


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python >> pandas

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文