φυβλαςのβλογ
phyblas的博客



จัดการข้อมูลด้วย pandas เบื้องต้น บทที่ ๕: การจัดการกับข้อมูลที่ว่าง (NaN)
เขียนเมื่อ 2016/09/25 13:52
แก้ไขล่าสุด 2021/09/28 16:42
ข้อมูลว่าง หรือสัญลักษณ์ที่แทนการไม่มีข้อมูลใน numpy และ pandas จะเรียกว่า NaN ซึ่งเทียบเท่ากับ None ในไพธอนมาตรฐาน แต่ไม่ได้เหมือนกันเสียทีเดียว

NaN มักจะปรากฏขึ้นมาในกรณีต่างๆที่ข้อมูลมีความผิดพลาดหรือขาดหาย และมีความพิเศษกว่าข้อมูลตัวอื่น มีเมธอดหลายตัวที่ใช้จัดการเกี่ยวกับ NaN โดยเฉพาะ ซึ่งจะพูดถึงในบทนี้



การเกิด NaN
เวลาที่สร้างซีรีส์หรือเดตาเฟรมขึ้นมา ถ้าหากมีข้อมูลส่วนไหนที่ขาดหายไปก็ให้ใส่ None, np.NaN หรือ np.nan ลงไป จะกลายเป็น NaN ไปทันที

ตัวอย่าง ท่าของฟุชิงิดาเนะ ที่เรียนรู้ได้ในเลเวลต่างๆ ๔ ท่าแรก ซึ่งในจำนวนนั้นบางท่าไม่ใช่ท่าโจมตีจึงไม่มีพลังโจมตีดังนั้นช่องพลังโจมตีก็เลยปล่อยว่าง
import numpy as np
import pandas as pd
tha = pd.DataFrame([
        ['ปะทะตัว','ธรรมดา',35,1],
        ['เสียงร้อง','ธรรมดา',None,1],
        ['เมล็ดยาโดริงิ','ธรรมดา',np.NaN,7],
        ['แส้เถาวัลย์','พืช',35,13]],
    columns=['ชื่อ','ชนิด','พลังโจมตี','เลเวลที่ได้'])
print(tha)

ได้
  ชื่อ ชนิด พลังโจมตี เลเวลที่ได้
0 ปะทะตัว ธรรมดา 35.0 1
1 เสียงร้อง ธรรมดา NaN 1
2 เมล็ดยาโดริงิ ธรรมดา NaN 7
3 แส้เถาวัลย์ พืช 35.0 13

จะเห็นว่าข้อมูลตรงที่ใส่ None หรือ np.NaN ไปผลที่ออกมาก็ได้เป็น NaN

และภายในคอลัมน์ "พลังโจมตี" ค่าภายในช่องที่ไม่ใช่ NaN ถูกเปลี่ยนเป็นจำนวนจริงที่มีทศนิยมทั้งๆที่ตอนแรกใส่เป็นจำนวนเต็มไป

ที่เป็นแบบนี้เพราะคอลัมน์ที่มี NaN ปนอยู่จะเป็นจำนวนเต็มไม่ได้ เลยถูกเปลี่ยนเป็นจำนวนจริง

หรือในกรณีที่ดึงข้อมูลจากไฟล์เมื่อเจอบางคำเช่นคำว่า NA หรือ nan ก็จะกลายเป็น NaN รายละเอียดตรงนี้ให้อ่านในบทที่ ๓ ซึ่งเขียนถึงไปแล้ว

นอกจากนี้ยังมีกรณีอย่างเช่นใช้ .loc แล้วใส่ดัชนีเป็นชื่อแถวหรือชื่อคอลัมน์ที่ไม่มีตัวตนอยู่ แบบนั้นก็จะได้ NaN ตลอดแถว ดังที่กล่าวไว้ในบทที่ ๔

และอื่นๆอีกมากมาย



การกำจัดข้อมูลที่มี NaN
ในการคัดกรองข้อมูลทั่วไปเมื่อต้องการกำจัดตัวที่ไม่ต้องการเราอาจใช้วิธีการแบบที่ใช้ในบทที่ ๔ โดยตั้งเงื่อนไขเป็น
เดตาเฟรม[เดตาเฟรม[ชื่อคอลัมน์]!=ตัวที่ไม่ต้องการ]

แต่สำหรับ NaN แล้วเราจะเขียนเป็น
เดตาเฟรม[เดตาเฟรม[ชื่อคอลัมน์]!=np.NaN]

แบบนี้ไม่ได้

ลองดูตัวอย่าง สร้างตารางรายชื่อท่าของเซนิงาเมะ ที่เรียนรู้ได้ในเวลต่างๆ ๔ ท่าแรก จากนั้นลองทำการกำจัด NaN
tha = pd.DataFrame([
        ['ปะทะตัว','ธรรมดา',35,1],
        ['สั่นหาง','ธรรมดา',None,1],
        ['ฟองน้ำ','น้ำ',20,8],
        ['กระสุนน้ำ','น้ำ',40,15]],
    columns=['ชื่อ','ชนิด','พลังโจมตี','เลเวลที่ได้'],
    index=[1,2,3,4])
print(tha[tha['พลังโจมตี']!=np.nan])

ได้
  ชื่อ ชนิด พลังโจมตี เลเวลที่ได้
1 ปะทะตัว ธรรมดา 35.0 1
2 สั่นหาง ธรรมดา NaN 1
3 ฟองน้ำ น้ำ 20.0 8
4 กระสุนน้ำ น้ำ 40.0 15

จะเห็นว่าแถวที่มี NaN ก็ยังอยู่ไม่ได้หายไปไหน

กรณีนี้ต้องใช้เมธอดชื่อ notnull แทนเพื่อตรวจสอบว่าตัวไหนไม่เป็น NaN ถ้าเป็น NaN จะได้ False ถ้าไม่เป็นจะได้ True
print(tha['พลังโจมตี'].notnull())
print('---------------------------')
print(tha[tha['พลังโจมตี'].notnull()])

ได้
1     True
2    False
3     True
4     True
Name: พลังโจมตี, dtype: bool
---------------------------
  ชื่อ ชนิด พลังโจมตี เลเวลที่ได้
1 ปะทะตัว ธรรมดา 35.0 1
3 ฟองน้ำ น้ำ 20.0 8
4 กระสุนน้ำ น้ำ 40.0 15

ในทางตรงข้าม มีเมธอด isnull ที่จะได้ True เมื่อเป็น NaN และ False เมื่อไม่ใช่
print(tha[tha['พลังโจมตี'].isnull()])

ได้
  ชื่อ ชนิด พลังโจมตี เลเวลที่ได้
2 สั่นหาง ธรรมดา NaN 1

ในกรณีที่ต้องการลบข้อมูลทั้งแถวออกเมื่อมีคอลัมน์ใดคอลัมน์หนึ่งเป็น NaN จะมีวิธีที่สะดวกกว่านั้น คือมีเมธอดที่ใช้โดยเฉพาะ นั่นคือ dropna

เมธอด dropna จะทำการลบแถวที่มี NaN อยู่ เช่น print(tha[tha['พลังโจมตี'].notnull()]) ในตัวอย่างข้างต้นอาจเขียนเป็น
print(tha.dropna())

แบบนี้ผลที่ได้ก็จะเหมือนกัน

แต่ dropna มีการใช้ที่ยืดหยุ่นกว่านั้นโดยอาจใส่คีย์เวิร์ดเพิ่มเติมปรับเปลี่ยนผลลัพธ์ให้เป็นตามที่ต้องการ

เช่นแทนที่จะลบแถวที่มี NaN อยู่ในคอลัมน์ใดคอลัมน์หนึ่ง ก็อาจเปลี่ยนเป็นลบคอลัมน์ที่มี NaN อยู่ที่แถวใดแถวหนึ่ง ซึ่งทำได้โดยเติมคีย์เวิร์ด axis เข้าไปเป็น axis=1 ความหมายก็คือพิจารณาเป็นคอลัมน์ๆไป

ตัวอย่าง เดตาเฟรมอันเดิมถ้าต้องการลบคอลัมน์ที่มี NaN ซึ่งในที่นี้คือ "พลังโจมตี" จะเขียนได้ว่า
print(tha.dropna(axis=1))

ได้
  ชื่อ ชนิด เลเวลที่ได้
1 ปะทะตัว ธรรมดา 1
2 สั่นหาง ธรรมดา 1
3 ฟองน้ำ น้ำ 8
4 กระสุนน้ำ น้ำ 15

อนึ่ง ปกติเมธอดนี้จะแค่คืนเดตาเฟรมใหม่ที่ลบ NaN ออกแล้ว แต่ตัวเดตาเฟรมเก่าก็ยังอยู่ ต้องเอาตัวแปรมารับค่าที่ได้ใหม่ ถ้าจะเขียนทับก็ใช้ตัวแปรตัวเดิม

แต่ว่านอกจากนั้นแล้วเราอาจใส่คีย์เวิร์ด inplace=True ลงไปเพื่อทำให้เมธอดนี้เป็นการเปลี่ยนแปลงแทนที่เดตาเฟรมตัวเก่าด้วยตัวที่ลบ NaN ออกไปแล้วทันที

กล่าวคือเขียนเป็น
tha.dropna(inplace=True)

แบบนี้จะมีค่าเท่ากับเขียน
tha = tha.dropna()



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

ตัวอย่าง รายชื่อท่าชนิดไฟฟ้าท่าต่างๆของโปเกมอน บางท่าไม่ใช่ท่าโจมตี พลังโจมตีจึงเป็น NaN และบางท่าไม่มีวาซะแมชชีนก็เป็น NaN
tha = pd.DataFrame([
        [40,100,np.NaN],
        [np.NaN,100,'45'],
        [95,100,'24'],
        [120,70,'25']],
    columns=['พลังโจมตี','ความแม่นยำ','วาซะแมชชีน'],
    index=['ช็อตไฟฟ้า','คลื่นไฟฟ้า','ไฟฟ้าแสนโวลต์','ฟ้าผ่า'])
print(tha)

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
ช็อตไฟฟ้า 40.0 100 NaN
คลื่นไฟฟ้า NaN 100 45
ไฟฟ้าแสนโวลต์ 95.0 100 24
ฟ้าผ่า 120.0 70 25

ลองใช้ dropna
print(tha.dropna())

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
ไฟฟ้าแสนโวลต์ 95.0 100 24
ฟ้าผ่า 120.0 70 25

แต่ถ้าจะให้ลบแค่เมื่อพลังโจมตีเป็น NaN ก็ทำได้โดย
print(tha.dropna(subset=['พลังโจมตี']))

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
ช็อตไฟฟ้า 40.0 100 NaN
ไฟฟ้าแสนโวลต์ 95.0 100 24
ฟ้าผ่า 120.0 70 25

หรือถ้าใช้คู่กับ axis=1 ก็ให้ระบุชื่อแถวที่ต้องการพิจารณา
print(tha.dropna(axis=1,subset=['ช็อตไฟฟ้า','ฟ้าผ่า']))

ได้
  พลังโจมตี ความแม่นยำ
ช็อตไฟฟ้า 40.0 100
คลื่นไฟฟ้า NaN 100
ไฟฟ้าแสนโวลต์ 95.0 100
ฟ้าผ่า 120.0 70

นอกจากนี้ยังมีคีย์เวิร์ด how สำหรับระบุว่าจะให้ลบทิ้งเมื่อเป็น NaN แค่ตัวเดียวหรือเมื่อเป็น NaN ทั้งหมด

โดยปกติถ้าไม่ระบุจะเป็น how='any' คือมี NaN แค่ตัวเดียวก็ลบทิ้งแล้ว แต่ถ้าใส่ how='all' จะลบทิ้งต่อเมื่อข้อมูลของทั้งแถวเป็น NaN หมดเลยเท่านั้น



การเติมเต็มส่วนที่เป็น NaN
ในบางครั้งเราอาจไม่ได้อยากจะลบข้อมูลที่ขาดหายทิ้งไปเลยแต่อาจจะอยากเติมด้วยค่าอะไรบางอย่าง กรณีแบบนั้นให้ใช้ fillna

เมื่อใช้ fillna จะต้องใส่ค่าที่จะเติมให้ข้อมูลที่ขาดไปด้วย เช่นถ้าจะเติมด้วย 0 ก็ใส่ fillna(0)

ตัวอย่าง ท่าชนิดพืชของโปเกมอน
tha = pd.DataFrame([
        [55,95,np.NaN],
        [np.NaN,100,np.NaN],
        [40,100,'21'],
        [120,100,'22']],
    columns=['พลังโจมตี','ความแม่นยำ','วาซะแมชชีน'],
    index=['คัตเตอร์ใบไม้','สปอร์เห็ด','เมกาเดรน','โซลาร์บีม'])
print(tha)

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
คัตเตอร์ใบไม้ 55.0 95 NaN
สปอร์เห็ด NaN 100 NaN
เมกาเดรน 40.0 100 21
โซลาร์บีม 120.0 100 22

จากนั้นใช้ fillna
print(tha.fillna(0))

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
คัตเตอร์ใบไม้ 55.0 95 0
สปอร์เห็ด 0.0 100 0
เมกาเดรน 40.0 100 21
โซลาร์บีม 120.0 100 22

แต่ว่าบางครั้งข้อมูลในแต่ละคอลัมน์อาจมีค่าที่ต้องการเติมไม่เหมือนกัน แบบนี้อาจใส่ค่าในรูปดิกชันนารีแทนโดยระบุว่าคอลัมน์ไหนต้องการเติมด้วยอะไร
print(tha.fillna({'พลังโจมตี':0,'วาซะแมชชีน':'ไม่มี'}))

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
คัตเตอร์ใบไม้ 55.0 95 ไม่มี
สปอร์เห็ด 0.0 100 ไม่มี
เมกาเดรน 40.0 100 21
โซลาร์บีม 120.0 100 22

หรือจะใช้เดตาเฟรมที่มีขนาดเท่ากันมาเป็นตัวเติมก็ได้ แบบนั้นค่าจากเดตาเฟรมที่เติมจะไปแทนในส่วนที่เป็น NaN ส่วนตรงที่มีค่าอยู่แล้วก็จะเป็นค่าเดิม เช่น
tha0 = pd.DataFrame([
        [0,555,'~'],
        [-1,555,'='],
        [-2,555,'@'],
        [-3,555,'#']],
    columns=['พลังโจมตี','ความแม่นยำ','วาซะแมชชีน'],
    index=['คัตเตอร์ใบไม้','สปอร์เห็ด','เมกาเดรน','โซลาร์บีม'])
print(tha.fillna(tha0))

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
คัตเตอร์ใบไม้ 55.0 95 ~
สปอร์เห็ด -1.0 100 =
เมกาเดรน 40.0 100 21
โซลาร์บีม 120.0 100 22

หรือบางครั้งเราอาจจะต้องการเติมส่วนขาดโดยอาศัยข้อมูลที่มีอยู่ กรณีแบบนี้อาจใช้คีย์เวิร์ด method แทน

หากต้องการเติมข้อมูลที่ขาดโดยอาศัยข้อมูลที่อยู่ด้านหน้าให้ใส่เป็น method='ffill' หรือ method='pad' เช่น
print(tha.fillna(method='ffill'))

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
คัตเตอร์ใบไม้ 55.0 95 NaN
สปอร์เห็ด 55.0 100 NaN
เมกาเดรน 40.0 100 21
โซลาร์บีม 120.0 100 22

ซึ่งจะเห็นว่าข้อมูลตรงไหนที่ไม่มีตัวข้างหน้าก็จะไม่ถูกเติม

ในทางตรงข้ามถ้าจะเติมด้วยข้อมูลด้านหลังก็ใส่ method='bfill' หรือ method='backfill'
print(tha.fillna(method='bfill'))

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
คัตเตอร์ใบไม้ 55.0 95 21
สปอร์เห็ด 40.0 100 21
เมกาเดรน 40.0 100 21
โซลาร์บีม 120.0 100 22

หากไม่ต้องการให้เติมทั้งหมดแต่จำกัดจำนวนที่มีการเติมก็อาจใส่คีย์เวิร์ด limit เช่น
print(tha.fillna(method='bfill',limit=1))

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
คัตเตอร์ใบไม้ 55.0 95 NaN
สปอร์เห็ด 40.0 100 21
เมกาเดรน 40.0 100 21
โซลาร์บีม 120.0 100 22

แบบนี้ 21 จะถูกเติมไปที่ช่องก่อนหน้าเพียงช่องเดียว แต่ถัดไปอีกก็ยังคงเป็น NaN อยู่

กรณีข้างต้นนี้เป็นการเติมโดยอาศัยข้อมูลในต่างแถวที่อยู่ในคอลัมน์เดียวกัน แต่หากต้องการเติมด้วยข้อมูลที่อยู่ในแถวเดียวกันจากต่างคอลัมน์ก็ให้ใส่คีย์เวิร์ด axis=1 เช่น
print(tha.fillna(method='ffill',axis=1))

ได้
  พลังโจมตี ความแม่นยำ วาซะแมชชีน
คัตเตอร์ใบไม้ 55 95 95
สปอร์เห็ด NaN 100 100
เมกาเดรน 40 100 21
โซลาร์บีม 120 100 22

และเช่นเดียวกับ dropna เลยคือ fillna จะสร้างเดตาเฟรมอันใหม่โดยไม่เขียนทับอันเก่า หากต้องการให้เขียนทับก็ใส่คีย์เวิร์ด inplace=True ลงไป



อ้างอิง


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


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

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

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

หมวดหมู่

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

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

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



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

  查看日志

  推荐日志

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