ใน pandas มีฟังก์ชันสำหรับแบ่งข้อมูลออกเป็นส่วนๆตามช่วงของค่าตัวเลขมากน้อย ได้แก่ pd.cut และ pd.qcut
การแบ่งข้อมูลเป็นกลุ่มๆตามช่วงของค่าแบ่งที่กำหนด ถ้ามีชุดข้อมูลตัวเลขอยู่ชุดหนึ่ง อาจเป็นลิสต์, อาเรย์ หรือซีรีส์ก็ได้ แล้วต้องการทำการแบ่งกลุ่มตามค่ามากน้อยสามารถทำได้โดยใช้ฟังก์ชัน pd.cut
ตัวอย่าง มีข้อมูลน้ำหนักของโปเกมอนจำนวนหนึ่ง
import pandas as pd
saiphan = ['นาเอเทิล','ฮายาชิงาเมะ','โดไดโทส',
'ฮิโกะซารุ','โมวกะซารุ','โกวกะซารุ',
'พจจามะ','พตไทชิ','เอ็มเพิร์ต']
namnak = [10.2,97.2,310.0,6.2,22.0,55.0,5.2,23.0,84.5]
pokemon = pd.Series(namnak,index=saiphan,name='น้ำหนัก')
print(pokemon)
ได้
นาเอเทิล 10.2
ฮายาชิงาเมะ 97.2
โดไดโทส 310.0
ฮิโกะซารุ 6.2
โมวกะซารุ 22.0
โกวกะซารุ 55.0
พจจามะ 5.2
พตไทชิ 23.0
เอ็มเพิร์ต 84.5
Name: น้ำหนัก, dtype: float64
นำมาแบ่งกลุ่มตามช่วงน้ำหนัก โดยแบ่งที่ 50, 100 และ 300 ก็ให้ใส่ซีรีส์ที่ต้องการแบ่งเป็นอาร์กิวเมนต์ตัวแรก ส่วนตัวที่ ๒ เป็นลิสต์ของช่วงที่ต้องการ
print(pd.cut(pokemon,[0,50,100,300]))
ได้
นาเอเทิล (0, 50]
ฮายาชิงาเมะ (50, 100]
โดไดโทส NaN
ฮิโกะซารุ (0, 50]
โมวกะซารุ (0, 50]
โกวกะซารุ (50, 100]
พจจามะ (0, 50]
พตไทชิ (0, 50]
เอ็มเพิร์ต (50, 100]
Name: น้ำหนัก, dtype: category
Categories (3, object): [(0, 50] < (50, 100] < (100, 300]]
ผลที่ได้จะได้เป็นซีรีส์ซึ่งมีสมาชิกเป็นช่วงตัวเลขตามที่ถูกแบ่ง พร้อมกับมีข้อมูลของ Categories เสริมเข้ามาด้วย ซึ่งจะบอกว่าในนี้มีกลุ่มอะไรบ้าง
จำนวนกลุ่มที่แบ่งจะเท่ากับจำนวนลิสต์ของค่าแบ่ง ลบด้วย 1
ส่วนค่าที่เกินกว่าขอบเขตของตัวเลขในลิสต์จะไม่สังกัดอยู่ในกลุ่มไหนเลย และได้ค่าเป็น NaN
นอกจากจะแบ่งโดยกำหนดค่าที่ต้องการแบ่งแล้ว ยังอาจแบ่งโดยใช้จำนวนช่วงที่ต้องการแบ่งได้ด้วย ในกรณีนี้จะเป็นการแบ่งให้ค่าแต่ละช่วงเท่ากันโดยนับจากขอบเขตต่ำสุดและสูงสุดของข้อมูล เช่น
klum = pd.cut(pokemon,4)
print(pd.concat([pokemon,klum],axis=1))
ได้
|
น้ำหนัก |
น้ำหนัก |
นาเอเทิล |
10.2 |
(4.895, 81.4] |
ฮายาชิงาเมะ |
97.2 |
(81.4, 157.6] |
โดไดโทส |
310.0 |
(233.8, 310] |
ฮิโกะซารุ |
6.2 |
(4.895, 81.4] |
โมวกะซารุ |
22.0 |
(4.895, 81.4] |
โกวกะซารุ |
55.0 |
(4.895, 81.4] |
พจจามะ |
5.2 |
(4.895, 81.4] |
พตไทชิ |
23.0 |
(4.895, 81.4] |
เอ็มเพิร์ต |
84.5 |
(81.4, 157.6] |
นอกจากจะแสดงชื่อกลุ่มเป็นค่าช่วงแล้วเรายังสามารถกำหนดชื่อกลุ่มตามที่ต้องการได้ด้วยการใส่คีย์เวิร์ด labels เป็นลิสต์ของชื่อทั้งหมด โดยจะต้องมีจำนวนเท่ากับจำนวนส่วนที่แบ่ง
klum = pd.cut(pokemon,[0,50,100,500],labels=['เบา','ปานกลาง','หนัก'])
klum.name = 'ถือว่า'
print(pd.concat([pokemon,klum],axis=1))
ได้
|
น้ำหนัก |
ถือว่า |
นาเอเทิล |
10.2 |
เบา |
ฮายาชิงาเมะ |
97.2 |
ปานกลาง |
โดไดโทส |
310.0 |
หนัก |
ฮิโกะซารุ |
6.2 |
เบา |
โมวกะซารุ |
22.0 |
เบา |
โกวกะซารุ |
55.0 |
ปานกลาง |
พจจามะ |
5.2 |
เบา |
พตไทชิ |
23.0 |
เบา |
เอ็มเพิร์ต |
84.5 |
ปานกลาง |
แต่ถ้าหากไม่ต้องการให้มีชื่ออะไรเลย แม้แต่ค่าช่วงก็ไม่ต้องการก็อาจใส่ labels=False แบบนี้จะได้ค่าเป็นเลขดัชนีกลุ่ม 0,1,2,...
klum = pd.cut(pokemon,[0,50,100,500],labels=False)
klum.name = 'กลุ่ม'
print(pd.concat([pokemon,klum],axis=1))
ได้
|
น้ำหนัก |
กลุ่ม |
นาเอเทิล |
10.2 |
0 |
ฮายาชิงาเมะ |
97.2 |
1 |
โดไดโทส |
310.0 |
2 |
ฮิโกะซารุ |
6.2 |
0 |
โมวกะซารุ |
22.0 |
0 |
โกวกะซารุ |
55.0 |
1 |
พจจามะ |
5.2 |
0 |
พตไทชิ |
23.0 |
0 |
เอ็มเพิร์ต |
84.5 |
1 |
ปกติแล้วช่วงจะถูกแบ่งแบบ (a,b] นั่นคือแต่ละช่วงจะรวมถึงค่ามากสุด (b) แต่ไม่รวมถึงค่าน้อยสุด (a)
แต่สามารถเปลี่ยนให้กลายเป็น [a,b) แบบนี้ได้โดยใส่คีย์เวิร์ด right=0 (ถ้าไม่ใส่จะเป็นค่าตั้งต้น right=1)
ตัวอย่าง
klum1 = pd.cut(pokemon,[0,22,55,500],right=0)
klum1.name = 'right=0'
klum2 = pd.cut(pokemon,[0,22,55,500])
klum2.name = 'right=1'
print(pd.concat([pokemon,klum1,klum2],axis=1))
ได้
|
น้ำหนัก |
right=0 |
right=1 |
นาเอเทิล |
10.2 |
[0, 22) |
(0, 22] |
ฮายาชิงาเมะ |
97.2 |
[55, 500) |
(55, 500] |
โดไดโทส |
310.0 |
[55, 500) |
(55, 500] |
ฮิโกะซารุ |
6.2 |
[0, 22) |
(0, 22] |
โมวกะซารุ |
22.0 |
[22, 55) |
(0, 22] |
โกวกะซารุ |
55.0 |
[55, 500) |
(22, 55] |
พจจามะ |
5.2 |
[0, 22) |
(0, 22] |
พตไทชิ |
23.0 |
[22, 55) |
(22, 55] |
เอ็มเพิร์ต |
84.5 |
[55, 500) |
(55, 500] |
การแบ่งข้อมูลเป็นกลุ่มตามช่วงลำดับของข้อมูล นอกจากการแบ่งโดยกำหนดค่าที่เป็นตัวคั่นแล้ว ยังมีอีกวิธีในการแบ่งที่สามารถทำได้ นั่นคือการแบ่งตามค่าลำดับที่ของข้อมูล ซึ่งทำได้โดยใช้ฟังก์ชัน pd.qcut
pd.qcut จะคล้ายกับ pd.cut แต่จะแบ่งโดยใช้ค่าลำดับที่เป็นเกณฑ์ในการแบ่ง ค่าลำดับที่ในที่นี้คือค่าลำดับจากต่ำ โดยที่ตัวที่มีค่าน้อยสุดจะมีค่าลำดับเป็น 0 ตัวที่มากสุดจะเป็น 1 และตัวอื่นๆที่ค่าอยู่ระหว่างกลางก็จะมีค่าอยู่ระหว่าง 0 ถึง 1 ไล่กันไป
ค่าที่ต้องใส่ใน pd.qcut จะไม่ใช่ค่าแบ่งเหมือนอย่างของ pd.cut แต่จะใส่เป็นลิสต์ของค่าที่ไล่ตั้งแต่ 0 ถึง 1
ตัวอย่าง
klum = pd.qcut(pokemon,[0,0.25,0.5,0.75,1])
print(pd.concat([pokemon,klum],axis=1))
ได้
|
น้ำหนัก |
น้ำหนัก |
นาเอเทิล |
10.2 |
[5.2, 10.2] |
ฮายาชิงาเมะ |
97.2 |
(84.5, 310] |
โดไดโทส |
310.0 |
(84.5, 310] |
ฮิโกะซารุ |
6.2 |
[5.2, 10.2] |
โมวกะซารุ |
22.0 |
(10.2, 23] |
โกวกะซารุ |
55.0 |
(23, 84.5] |
พจจามะ |
5.2 |
[5.2, 10.2] |
พตไทชิ |
23.0 |
(10.2, 23] |
เอ็มเพิร์ต |
84.5 |
(23, 84.5] |
จะเห็นว่าช่วงที่ถูกแบ่งแต่ละช่วงจะแบ่งตามค่าของข้อมูลพอดี
จะกำหนดเป็นจำนวนช่วงที่จะแบ่งก็ได้เช่นกัน การใช้แบบนี้จะทำให้จำนวนในแต่ละกลุ่มเท่ากันพอดีหรือใกล้เคียงกันมากที่สุด
klum = pd.qcut(pokemon,3)
print(pd.concat([pokemon,klum],axis=1))
ได้
|
น้ำหนัก |
น้ำหนัก |
นาเอเทิล |
10.2 |
[5.2, 18.0667] |
ฮายาชิงาเมะ |
97.2 |
(64.833, 310] |
โดไดโทส |
310.0 |
(64.833, 310] |
ฮิโกะซารุ |
6.2 |
[5.2, 18.0667] |
โมวกะซารุ |
22.0 |
(18.0667, 64.833] |
โกวกะซารุ |
55.0 |
(18.0667, 64.833] |
พจจามะ |
5.2 |
[5.2, 18.0667] |
พตไทชิ |
23.0 |
(18.0667, 64.833] |
เอ็มเพิร์ต |
84.5 |
(64.833, 310] |
การใส่คีย์เวิร์ด labels เพื่อตั้งชื่อกลุ่มแทนค่าช่วงก็สามารถทำได้เช่นเดียวกัน
ใช้คู่กับ groupby ในการแบ่งกลุ่ม ข้อมูลที่ได้จาก pd.cut และ pd.qcut สามารถนำมาใช้เป็นเกณฑ์การแบ่งกลุ่มใน groupby ได้
ตัวอย่าง
klum = pd.cut(pokemon,[0,50,100,500])
print(pokemon.groupby(klum).groups)
print(pokemon.groupby(klum).apply(dict))
ได้
{'(100, 500]': ['โดไดโทส'], '(50, 100]': ['ฮายาชิงาเมะ', 'โกวกะซารุ', 'เอ็มเพิร์ต'], '(0, 50]': ['นาเอเทิล', 'ฮิโกะซารุ', 'โมวกะซารุ', 'พจจามะ', 'พตไทชิ']}
น้ำหนัก
(0, 50] นาเอเทิล 10.2
พจจามะ 5.2
พตไทชิ 23.0
ฮิโกะซารุ 6.2
โมวกะซารุ 22.0
(50, 100] ฮายาชิงาเมะ 97.2
เอ็มเพิร์ต 84.5
โกวกะซารุ 55.0
(100, 500] โดไดโทส 310.0
Name: น้ำหนัก, dtype: float64
อ้างอิง