บางครั้งข้อมูลที่เราพิจารณาก็อาจมีบางส่วนที่ซ้ำซ้อน ซึ่งบางครั้งเราอาจลบทิ้งหรือแก้ไขให้ไม่ซ้ำ
ใน pandas มีเมธอดสำหรับจัดการกับข้อมูลที่ซ้ำซ้อนโดยเฉพาะอยู่หลายอันซึ่งสะดวกที่จะใช้งานได้เป็นอย่างดี
ดัชนีซ้ำ ก่อนอื่นขอกล่าวถึงกรณีที่ค่าในดัชนีมีการซ้ำเกิดขึ้นก่อน
แม้จะยังไม่เคยกล่าวถึงในบทก่อนๆ แต่จริงๆแล้วดัชนีของซีรีส์และเดตาเฟรมสามารถกำหนดให้ซ้ำกันได้
เช่นลองดูซีรีส์ที่ตั้งดัชนีไว้ซ้ำกัน
import pandas as pd
pokemon = pd.Series(['ฟุชิงิดาเนะ','ชิโครีตา','คิโมริ','นาเอเทิล'],index=[1,2,3,1])
print(pokemon)
ได้
1 ฟุชิงิดาเนะ
2 ชิโครีตา
3 คิโมริ
1 นาเอเทิล
dtype: object
พอเป็นแบบนี้เวลาที่อ้างอิงถึงโดยใช้ดัชนีที่ซ้ำก็จะได้ผลออกมาเป็นซีรีส์เหมือนเดิม เช่น
print(pokemon[1])
ได้
1 ฟุชิงิดาเนะ
1 นาเอเทิล
dtype: object
แล้วพอใส่ [1] ต่อท้ายไปอีกก็ยังจะได้ตัวเดิม จะใส่กี่ตัวก็ยังจะได้ผลเหมือนเดิมต่อไปเรื่อยๆ ไม่สิ้นสุด เช่น
print(pokemon[1][1][1][1][1][1][1][1][1][1][1][1][1][1])
ดังนั้นการจะเข้าถึงสมาชิกข้างในตัวใดตัวหนึ่งจึงไม่มีทางทำได้ด้วยการใส่แค่ดัชนีเฉยๆ อาจต้องใช้วิธีอื่นเช่น .iloc เพื่อจะพิจารณาจากลำดับที่
print(pokemon.iloc[0]) # ได้ ฟุชิงิดาเนะ
print(pokemon.iloc[3]) # ได้ นาเอเทิล
ในขณะที่ถ้าใส่ดัชนีที่ไม่ซ้ำก็จะได้ผลออกมาเป็นสมาชิกข้างในตัวนั้นทันทีโดยไม่ใช่ซีรีส์ เช่น pokemon[2] ได้ ชิโครีตา
กรณีของเดตาเฟรมก็ให้ผลในทำนองเดียวกัน แต่เดตาเฟรมนอกจากดัชนีแล้วยังมีคอลัมน์ด้วย ซึ่งคอลัมน์เองก็สามารถมีชื่อซ้ำกันได้เช่นกัน เช่น
pokemon = pd.DataFrame([
[4,'ฮิโตคาเงะ','Hitokage'],
[155,'ฮิโนอาราชิ','Hinoarashi'],
[255,'อาชาโม','Achamo'],
[390,'ฮิโกะซารุ','Hikozaru']],
columns=['หมายเลข','สายพันธุ์','สายพันธุ์'],
index=[1,2,3,1])
print(pokemon)
ได้
|
หมายเลข |
สายพันธุ์ |
สายพันธุ์ |
1 |
4 |
ฮิโตคาเงะ |
Hitokage |
2 |
155 |
ฮิโนอาราชิ |
Hinoarashi |
3 |
255 |
อาชาโม |
Achamo |
1 |
390 |
ฮิโกะซารุ |
Hikozaru |
แบบนี้หากอ้างอิงถึงคอลัมน์ที่ชื่อซ้ำก็จะได้ผลเป็นเดตาเฟรมเหมือนเดิม ในขณะที่ปกติแล้วถ้าคอลัมน์ไม่ซ้ำควรจะได้เป็นซีรีส์
print(pokemon['สายพันธุ์'])
ได้
|
สายพันธุ์ |
สายพันธุ์ |
1 |
ฮิโตคาเงะ |
Hitokage |
2 |
ฮิโนอาราชิ |
Hinoarashi |
3 |
อาชาโม |
Achamo |
1 |
ฮิโกะซารุ |
Hikozaru |
เพื่อที่จะดูว่าดัชนีหรือคอลัมน์มีการซ้ำหรือเปล่าอาจดูที่แอตทริบิวต์ is_unique ที่ตัว index และ columns
print(pokemon.index.is_unique) # ได้ False
print(pokemon.columns.is_unique) # ได้ False
ที่จริงแล้วถ้าเป็นไปได้ดัชนีและชื่อคอลัมน์เป็นสิ่งที่ไม่ควรจะให้ซ้ำกัน เพราะเป็นสิ่งที่เอาไว้อ้างอิงถึงข้อมูลในแต่ละส่วน ถ้าซ้ำกันก็จะเกิดปัญหาอย่างที่เห็น ดังนั้นถ้าพบกว่ามีการซ้ำกันก็อาจควรจะทำการแก้ไข
การกำจัดข้อมูลที่ซ้ำซ้อน มีเมธอดที่สะดวกสำหรับใช้ตรวจดูว่าข้อมูลซ้ำกันหรือเปล่าก็คือ duplicated
เมธอดนี้ใช้ได้ทั้งในซีรีส์และในเดตาเฟรม แต่จะต่างกันนิดหน่อย ในซีรีส์จะเป็นการดูว่าข้อมูลนั้นซ้ำกับตัวก่อนหน้าหรือเปล่า ถ้าซ้ำก็จะได้ค่า True
ตัวอย่าง
pokemon = pd.Series(['เซนิงาเมะ','วานิโนโกะ','วานิโนโกะ','มิซึโงโรว','พจจามะ','เซนิงาเมะ','มิจุมารุ'])
print(pokemon.duplicated())
ได้
0 False
1 False
2 True
3 False
4 False
5 True
6 False
dtype: bool
จะเห็นว่าเป็น True เฉพาะตัวหลังที่ซ้ำกับตัวก่อนหน้าเท่านั้น ตัวแรกจะยังเป็น False อยู่
และถ้าต้องการกำจัดตัวซ้ำ ก็แค่ใช้ซีรีส์บูลที่ได้มานี้เป็นดัชนี
print(pokemon[pokemon.duplicated()==0])
ได้
0 เซนิงาเมะ
1 วานิโนโกะ
3 มิซึโงโรว
4 พจจามะ
6 มิจุมารุ
dtype: object
กรณีที่ใช้กับเดตาเฟรมจะได้ True เมื่อสมาชิกในทุกคอลัมน์เหมือนกันหมด
pokemon = pd.DataFrame([
['ฟูจัง','ฟุชิงิโซว',30],
['ฟูจัง','ฟุชิงิโซว',30],
['ฟูจัง','ฟูดิน',38],
['ดีจัง','ดิกดา',10],
['กาจัง','การ์ดี',35],
['กาจัง','การะการะ',44]],
columns=['ชื่อ','สายพันธุ์','เลเวล'])
print(pokemon)
print('-----------------')
print(pokemon.duplicated())
ได้
|
ชื่อ |
สายพันธุ์ |
เลเวล |
0 |
ฟูจัง |
ฟุชิงิโซว |
30 |
1 |
ฟูจัง |
ฟุชิงิโซว |
30 |
2 |
ฟูจัง |
ฟูดิน |
38 |
3 |
ดีจัง |
ดิกดา |
10 |
4 |
กาจัง |
การ์ดี |
35 |
5 |
กาจัง |
การะการะ |
44 |
-----------------
0 False
1 True
2 False
3 False
4 False
5 False
dtype: bool
จะเห็นว่านอกจากแถวที่ซ้ำกันทุกตัวแล้ว แถวอื่นได้ False หมด
หากต้องการให้พิจารณาแค่บางแถวก็อาจใส่คีย์เวิร์ด subset โดยใส่รายชื่อคอลัมน์ที่ต้องการให้พิจารณา เช่น
print(pokemon.duplicated(subset='ชื่อ'))
ได้
0 False
1 True
2 True
3 False
4 False
5 True
dtype: bool
ปกติแล้วจะให้ค่า True กับตัวที่ซ้ำกับตัวก่อนหน้าเท่านั้น ในขณะที่ตัวหน้ายังเป็น False แต่สามารถทำให้กลับกันได้โดยเพิ่มคีย์เวิร์ด keep
หากต้องการไล่จากท้าย แล้วให้ตัวท้ายเป็น False ตัวก่อนเป็น True ก็ใส่ keep='last' เช่น
print(pokemon.duplicated(subset='ชื่อ',keep='last'))
ได้
0 True
1 True
2 False
3 False
4 True
5 False
dtype: bool
หรือถ้าจะให้เป็น True หมดไม่ว่าจะตัวแรกหรือตัวหลังก็ใส่ keep=0
print(pokemon.duplicated(subset='ชื่อ',keep=0))
ได้
0 True
1 True
2 True
3 False
4 True
5 True
dtype: bool
เมื่อได้ซีรีส์บูลมาจากเมธอด duplicated แล้วก็เอาซีรีส์นี้มาใช้เพื่อเป็นดัชนีเพื่อคัดกรองเอาตัวที่มีค่าซ้ำออกได้
เมธอด duplicated สามารถใช้เพื่อตรวจดูการซ้ำกันของค่าดัชนีได้เช่นกัน เพื่อคัดกรองแถวที่มีดัชนีซ้ำกันทิ้งไป
ตัวอย่าง ท่าทั้งหมดที่เรียนรู้ได้ที่เลเวลต่างๆของพิคาชู
จากเกมโปเกมอนภาคแรก สร้างข้อมูลขึ้นมาให้มีดัชนีซ้ำกันจากนั้นก็ทำการคัดออกให้เหลือแค่ตัวหลังเท่านั้น
tha = pd.DataFrame([
['เสียงร้อง',np.NaN,'ธรรมดา',1],
['ช็อตไฟฟ้า',40,'ไฟฟ้า',1],
['คลื่นไฟฟ้า',np.NaN,'ไฟฟ้า',9],
['จู่โจมสายฟ้าแลบ',40,'ไฟฟ้า',16],
['สปีดสตาร์',60,'ธรรมดา',26],
['เคลื่อนไหวเร็ว',np.NaN,'พลังจิต',33],
['ฟ้าผ่า',120,'ไฟฟ้า',43]],
columns=['ชื่อ','พลังโจมตี','ชนิด','เลเวลที่ได้'],
index=[1,2,3,4,1,4,2])
print(tha)
print(tha.index.duplicated(keep='last'))
print(tha[tha.index.duplicated(keep='last')==0])
ได้
|
ชื่อ |
พลังโจมตี |
ชนิด |
เลเวลที่ได้ |
1 |
เสียงร้อง |
NaN |
ธรรมดา |
1 |
2 |
ช็อตไฟฟ้า |
40.0 |
ไฟฟ้า |
1 |
3 |
คลื่นไฟฟ้า |
NaN |
ไฟฟ้า |
9 |
4 |
จู่โจมสายฟ้าแลบ |
40.0 |
ไฟฟ้า |
16 |
1 |
สปีดสตาร์ |
60.0 |
ธรรมดา |
26 |
4 |
เคลื่อนไหวเร็ว |
NaN |
พลังจิต |
33 |
2 |
ฟ้าผ่า |
120.0 |
ไฟฟ้า |
43 |
[ True True False True False False False]
|
ชื่อ |
พลังโจมตี |
ชนิด |
เลเวลที่ได้ |
3 |
คลื่นไฟฟ้า |
NaN |
ไฟฟ้า |
9 |
1 |
สปีดสตาร์ |
60.0 |
ธรรมดา |
26 |
4 |
เคลื่อนไหวเร็ว |
NaN |
พลังจิต |
33 |
2 |
ฟ้าผ่า |
120.0 |
ไฟฟ้า |
43 |
เมธอด duplicated นั้นแค่ให้ผลเป็นซีรีส์บูลแล้วเราก็ต้องเอามันมาใช้เป็นดัชนีอีกทีเพื่อคัดกรองแถวที่ต้องการ
แต่มีเมธอดที่สะดวกกว่านั้น คือ drop_duplicates ซึ่งจะทำการลบแถวที่ซ้ำกันออกไปเลย
คีย์เวิร์ดใน drop_duplicates นั้นจะเหมือนกับ duplicated คือใช้ keep กับ subset ได้
เพียงแต่ว่า drop_duplicates จะใช้กับคอลัมน์ธรรมดาเท่านั้น ไม่สามารถใช้กับคอลัมน์ของดัชนี ดังนั้นในกรณีนั้นยังคงต้องใช้ duplicated
ตัวอย่างการใช้ ลองดูท่าของโปเกมอนพาราส
จากโปเกมอนภาคแรก
tha = pd.DataFrame([
[40,'ธรรมดา',1],
[np.NaN,'พืช',13],
[20,'แมลง',20],
[np.NaN,'พืช',27],
[70,'ธรรมดา',34],
[np.NaN,'ธรรมดา',41]],
columns=['พลังโจมตี','ชนิด','เลเวลที่ได้'],
index=['ข่วน','ผงชา','ดูดเลือด','สปอร์เห็ด','ข่วนยับ','เติบโต'])
print(tha)
print('~~~~~~~~~~~~~~')
print(tha.drop_duplicates(subset='ชนิด',keep='last'))
ได้
|
พลังโจมตี |
ชนิด |
เลเวลที่ได้ |
ข่วน |
40.0 |
ธรรมดา |
1 |
ผงชา |
NaN |
พืช |
13 |
ดูดเลือด |
20.0 |
แมลง |
20 |
สปอร์เห็ด |
NaN |
พืช |
27 |
ข่วนยับ |
70.0 |
ธรรมดา |
34 |
เติบโต |
NaN |
ธรรมดา |
41 |
~~~~~~~~~~~~~~
|
พลังโจมตี |
ชนิด |
เลเวลที่ได้ |
ดูดเลือด |
20.0 |
แมลง |
20 |
สปอร์เห็ด |
NaN |
พืช |
27 |
เติบโต |
NaN |
ธรรมดา |
41 |
แบบนี้จะมีค่าเท่ากับ
print(tha[tha.duplicated(subset='ชนิด',keep='last')==0])
ปกติ drop_duplicates จะสร้างข้อมูลใหม่โดยไม่ทับตัวเก่า แต่ก็สามารถใส่คีย์เวิร์ด inplace=True เพื่อให้ตัวเก่าถูกทับไปเลย เช่น
tha.drop_duplicates(subset='ชนิด',keep='last',inplace=True)
แบบนี้จะมีค่าเท่ากับเขียน
tha = tha.drop_duplicates(subset='ชนิด',keep='last')
กำจัดตัวซ้ำพร้อมแปลงเป็นอาเรย์ นอกจากจะใช้ drop_duplicates ได้แล้ว สำหรับซีรีส์ยังมีเมธอด unique ซึ่งใช้ลบตัวที่สมาชิกมีค่าซ้ำกันได้เช่นกัน ข้อแตกต่างก็คือ unique จะให้ผลเป็นอาเรย์ ไม่ใช่ซีรีส์ ดังนั้นดัชนีจะหายไปหมด
ตัวอย่าง
pokemon = pd.Series(['ฟุชิงิบานะ','เมกาเนียม','จูไคน์','เมกาเนียม','จูไคน์','ฟุชิงิบานะ','โดไดโทส','ฟุชิงิบานะ'])
print(pokemon.unique())
# หรือ print(pokemon.drop_duplicates().values)
ได้
['ฟุชิงิบานะ' 'เมกาเนียม' 'จูไคน์' 'โดไดโทส']
อ้างอิง