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



จัดการข้อมูลด้วย pandas เบื้องต้น บทที่ ๙: การจัดการข้อมูลพร้อมกันทั้งตาราง
เขียนเมื่อ 2016/09/25 14:33
แก้ไขล่าสุด 2021/09/28 16:42
ในภาษาไพธอนปกติเวลามีคำสั่งอะไรบางอย่างที่ต้องการทำซ้ำๆเดิมกับออบเจ็กต์กลุ่มหนึ่งๆที่อยู่ภายในลิสต์ก็มักจะใช้ for

แต่ในบางกรณีก็มีฟังก์ชันที่สะดวกกว่านั้นเมื่อแค่ต้องการเปลี่ยนค่าในลิสต์หนึ่งๆแบบพร้อมกันทีเดียวแล้วคืนลิสต์ใหม่กลับมา ฟังก์ชันที่ใช้ในกรณีนี้คือ map

ใน pandas เองก็มีเมธอดที่ใช้กับซีรีส์และเดตาเฟรมซึ่งมีความสามารถทำนองเดียวกับฟังก์ชัน map ในไพธอนมาตรฐาน

ก่อนที่จะเข้าใจเนื้อหาของบทนี้ได้อาจต้องเข้าใจเรื่อง map และ lambda ที่เขียนไว้ในเนื้อหาไพธอนพื้นฐานบทที่ ๒๑



การใช้ map ในซีรีส์
ซีรีส์มีเมธอด map ซึ่งเอาไว้ทำอะไรบางอย่างเหมือนๆกันกับสมาชิกทุกตัวในซีรีส์แล้วคืนค่ากลับออกมาเป็นซีรีส์ใหม่ที่มีจำนวนสมาชิกเท่าเดิม

ตัวอย่าง ลองสร้างซีรีส์ที่เก็บชื่อของโปเกมอนขึ้นมาแล้วสร้างฟังก์ชันสำหรับเอาแค่อักษรสองตัวแรกมาเติมคำต่อท้าย
import pandas as pd
def chan(p):
    return p[0:2]+'จัง'
pokemon = pd.Series(['ฟุชิงิดาเนะ','ฮิโตคาเงะ','เซนิงาเมะ','คาเตอร์ปี','บีเดิล'])
# 0    ฟุชิงิดาเนะ
# 1      ฮิโตคาเงะ
# 2      เซนิงาเมะ
# 3      คาเตอร์ปี
# 4         บีเดิล

print(pokemon.map(chan))



ได้
0    ฟุจัง
1    ฮิจัง
2    เซจัง
3    คาจัง
4    บีจัง
dtype: object

สามารถเขียนสั้นๆโดยใช้ lambda เพื่อให้ไม่ต้องใช้ def เพื่อสร้างฟังก์ชันขึ้นมาก่อนได้
pokemon.map(lambda x:x[0:2]+'จัง')

การใช้ lambda ค่อนข้างสะดวกกะทัดรัดกว่ามาก ดังนั้นส่วนใหญ่จะใช้แบบนี้มากกว่า หากฟังก์ชันที่ใช้นั้นไม่ได้ซับซ้อนอะไรมาก

ที่จริงจะใช้ฟังก์ชัน map แล้วค่อยนำผลที่ได้มาสร้างซีรีส์ใหม่แบบนี้ก็ได้
pd.Series(map(lambda x:x[0:2]+'จัง',pokemon))

แต่ยังไงก็ไม่สะดวกเท่าใช้เมธอด map ที่มีอยู่ในตัวซีรีส์อยู่แล้ว

ส่วนของดัชนีเองก็สามารถใช้เมธอด map ได้เช่นกัน เช่นลองเปลี่ยนค่าดัชนีของซีรีส์เดิมในตัวอย่างที่แล้วที่เดิมเป็น 0 ถึง 4
pokemon.index = pokemon.index.map(lambda x: x*3+1)
print(pokemon)

ได้
1     ฟุชิงิดาเนะ
4       ฮิโตคาเงะ
7       เซนิงาเมะ
10      คาเตอร์ปี
13         บีเดิล
dtype: object

เมธอด map นี้นอกจากจะใช้กับฟังก์ชันแล้วยังใช้กับดิกชันนารีได้ด้วย โดยในกรณีนี้จะเป็นการเปลี่ยนค่าจากที่มีอยู่ในคีย์ให้เป็นค่าในดักชันนารี
p = {151:'มิว',152:'ชิโครีตา',153:'เบย์ลีฟ',154:'เมกาเนียม',155:'ฮิโนอาราชิ',
     156:'แม็กมาราชิ',157:'บักฟูน',158:'วานิโนโกะ',159:'อาลิเกตซ์',160:'ออร์ไดล์'}
pokemon = pd.Series([152,155,158,161])
print(pokemon.map(p))



ได้
0      ชิโครีตา
1    ฮิโนอาราชิ
2     วานิโนโกะ
3           NaN
dtype: object

จะเห็นว่าถ้าหากค่าในซีรีส์ไม่มีอยู่ในคีย์ของดิกชันนารีก็จะคืนค่า NaN

นอกจากจะใช้ดิกชันนารีแล้วจะใช้เป็นซีรีส์ก็ได้ เช่นในที่นี้ดิก p อาจเขียนใหม่เป็นซีรีส์ เป็น
p = pd.Series(['มิว','ชิโครีตา','เบย์ลีฟ','เมกาเนียม','ฮิโนอาราชิ',
              'แม็กมาราชิ','บักฟูน','วานิโนโกะ','อาลิเกตซ์','ออร์ไดล์'],
              index=range(151,161))



การใช้ applymap ในเดตาเฟรม
สำหรับเดตาเฟรมจะไม่มีเมธอดที่ชื่อว่า map แต่จะมีเมธอดชื่อ applymap ซึ่งมีลักษณะการทำงายใกล้เคียงกัน

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

เพียงแต่ applymap จะใช้ได้แค่ฟังก์ชันเท่านั้น ใช้ดิกชันนารีหรือซีรีส์ไม่ได้

ตัวอย่าง เปลี่ยนข้อมูลน้ำหนักส่วนสูงของโปเกมอนให้เป็นสายอักขระที่แสดงผลทศนิยม ๔ ตัว
pokemon = pd.DataFrame(
    [[0.6,7.8],[0.8,13.3],[1.4,61.5]],
    index=['เมรีป','โมโกโกะ','เดนริว'],
    columns=['ส่วนสูง','น้ำหนัก'],
    dtype='float32')
print(pokemon.applymap(lambda x:'%.4f'%x))



ได้
  ส่วนสูง น้ำหนัก
เมรีป 0.6000 7.8000
โมโกโกะ 0.8000 13.3000
เดนริว 1.4000 61.5000

applymap จะทำกับสมาชิกในทุกแถวทุกคอลัมน์ แต่หากต้องการใช้แค่แถวใดแถวหนึ่งหรือคอลัมน์ใดคอลัมน์หนึ่งก็ให้เข้าถึงแถวหรือคอลัมน์นั้นแล้วใช้ map เอา



การใช้เมธอด apply ในเดตาเฟรม
ในขณะที่ applymap จะทำกับสมาชิกทั้งหมดในเดตาเฟรมอย่างเท่าเทียมกันทั้งหมด แต่หากต้องการทำอะไรบางอย่างกับข้อมูลพร้อมกันทั้งแถวในคอลัมน์หนึ่ง หรือทั้งคอลัมน์ในแถวหนึ่ง กรณีแบบนั้นจะใช้เมธอด apply

เมธอด apply นั้นที่จริงแล้วสามารถใช้กับซีรีส์ได้ด้วย ซึ่งผลที่ได้จะเหมือนกับใช้ map เพียงแต่จะไม่สามารถใช้ดิกชันนารีหรือซีรีส์ ใช้ได้แค่ฟังก์ชันเท่านั้น

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

หากไม่ใส่คีย์เวิร์ด axis เลยหรือใส่ axis=0 จะเป็นการทำทั้งแถวในแต่ละคอลัมน์ แต่ถ้าใส่ axis=1 จะเป็นการทำทั้งคอลัมน์ในแต่ละแถว

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

ตัวอย่าง สร้างเดตาเฟรมเก็บค่าน้ำหนักส่วนสูงของโปเกมอน
pokemon = pd.DataFrame(
    [[1.9,178.0],[2.1,198.0],[2.0,187.0],[5.2,216.0],[3.8,199.0]],
    columns=['น้ำหนัก','ส่วนสูง'],
    index=['ไรโคว','เอนเทย์','ซุยคูน','ลูเกีย','โฮวโอว'])
print(pokemon)



ได้
  น้ำหนัก ส่วนสูง
ไรโคว 1.9 178.0
เอนเทย์ 2.1 198.0
ซุยคูน 2.0 187.0
ลูเกีย 5.2 216.0
โฮวโอว 3.8 199.0

แล้วทำการหาค่าเฉลี่ยและผลรวม
print(pokemon.apply(lambda x:'รวม = '+str(x.sum())+' เฉลี่ย = '+str(x.mean())))

ได้
น้ำหนัก       รวม = 15.0 เฉลี่ย = 3.0
ส่วนสูง    รวม = 978.0 เฉลี่ย = 195.6
dtype: object

กรณี axis=1 จะแยกแต่ละแถวออกมาเป็นแถวละซีรีส์ ผลที่ได้ก็จะออกมาในรูปของซีรีส์ของผลลัพธ์ในแต่ละแถว

ตัวอย่าง หาค่าน้ำหนักต่อส่วนสูงของโปเกมอนแต่ละตัว
print(pokemon.apply(lambda x:str(x['น้ำหนัก']/x['ส่วนสูง'])+' kg/m',axis=1))

ได้
ไรโคว      0.0106741573034 kg/m
เอนเทย์    0.0106060606061 kg/m
ซุยคูน     0.0106951871658 kg/m
ลูเกีย     0.0240740740741 kg/m
โฮวโอว     0.0190954773869 kg/m
dtype: object

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

ตัวอย่าง เอาซีรีส์ที่ได้จากแต่ละคอลัมน์มาคูณ 1000 แล้วเปลี่ยนชนิดข้อมูลเป็น int
print(pokemon.apply(lambda x:(x*1000).astype(int)))

ได้
  น้ำหนัก ส่วนสูง
ไรโคว 1900 178000
เอนเทย์ 2100 198000
ซุยคูน 2000 187000
ลูเกีย 5200 216000
โฮวโอว 3800 199000

หากใช้ apply แล้ว map ด้านในก็จะมีค่าเท่ากับการใช้เมธอด applymap และนั่นก็เป็นที่มาของชื่อ applymap ด้วย

ตัวอย่าง เปลี่ยนค่าในตารางให้กลายเป็นสายอักขระที่แสดงค่าด้วยเลขทศนิยมถึง ๓ ตำแหน่ง
print(pokemon.apply(lambda x:x.map(lambda x:'%.3f'%x)))
print(pokemon.applymap(lambda x:'%.3f'%x))

สองอันนี้ได้ผลเหมือนกันเป็น
  น้ำหนัก ส่วนสูง
ไรโคว 1.900 178.000
เอนเทย์ 2.100 198.000
ซุยคูน 2.000 187.000
ลูเกีย 5.200 216.000
โฮวโอว 3.800 199.000

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

ตัวอย่างเปรียบเทียบความแตกต่าง
print(pokemon.apply(type))
print('------------------------')
print(pokemon.apply(type,raw=1))

ได้
น้ำหนัก    <class 'pandas.core.series.Series'>
ส่วนสูง    <class 'pandas.core.series.Series'>
dtype: object
------------------------
น้ำหนัก    <class 'numpy.ndarray'>
ส่วนสูง    <class 'numpy.ndarray'>
dtype: object



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

ตัวอย่าง คัดกรองโปเกมอนที่เป็นชนิดพิษ
pokemon = pd.DataFrame(
    [['ซูแบ็ต','พิษ,บิน'],
     ['นาโซโนะคุสะ','พืช,พิษ'],
     ['พาราส','แมลง,พืช'],
     ['คงปัง','แมลง,พิษ'],
     ['ดิกดา','ดิน']],
    columns=['สายพันธุ์','ชนิด'],
    index=[41,43,46,48,50])
print(pokemon)
print(pokemon['ชนิด'].map(lambda x: 'พิษ' in x))



ได้
  สายพันธุ์ ชนิด
41 ซูแบ็ต พิษ,บิน
43 นาโซโนะคุสะ พืช,พิษ
46 พาราส แมลง,พืช
48 คงปัง แมลง,พิษ
50 ดิกดา ดิน
41     True
43     True
46    False
48     True
50    False
Name: ชนิด, dtype: bool

นำมาใช้คัดกรอง
print(pokemon[pokemon['ชนิด'].map(lambda x: 'พิษ' in x)])

ได้
  สายพันธุ์ ชนิด
41 ซูแบ็ต พิษ,บิน
43 นาโซโนะคุสะ พืช,พิษ
48 คงปัง แมลง,พิษ

หากใช้ apply(axis=1) จะสามารถใช้ข้อมูลจากสองคอลัมน์ขึ้นไปมาเป็นเงื่อนไขในการคัดกรองได้

ตัวอย่างสร้างตารางข้อมูลโปเกมอนขึ้นมา
pokemon = pd.DataFrame(
    [['ฮาเน็กโกะ',0.4,0.5],
     ['โปโปกโกะ',0.6,1.0],
     ['วาตักโกะ',0.8,3.0],
     ['เอย์ปาม',0.8,11.5],
     ['ฮิมานัตส์',0.3,1.8],
     ['คิมาวาริ',0.8,8.5]],
    columns=['สายพันธุ์','ส่วนสูง','น้ำหนัก'],
    index=[187,188,189,190,191,192])
print(pokemon)



ได้
  สายพันธุ์ ส่วนสูง น้ำหนัก
187 ฮาเน็กโกะ 0.4 0.5
188 โปโปกโกะ 0.6 1.0
189 วาตักโกะ 0.8 3.0
190 เอย์ปาม 0.8 11.5
191 ฮิมานัตส์ 0.3 1.8
192 คิมาวาริ 0.8 8.5

แล้วคัดกรองโปเกมอนที่ค่าน้ำหนัก (กก.) มากกว่าส่วนสูง (ม.) ๑๐ เท่า
print(pokemon.apply(lambda x:x['น้ำหนัก']/x['ส่วนสูง'],axis=1))
print(pokemon[pokemon.apply(lambda x:x['น้ำหนัก']/x['ส่วนสูง']>10,axis=1)])

ได้
187     1.250000
188     1.666667
189     3.750000
190    14.375000
191     6.000000
192    10.625000
dtype: float64
  สายพันธุ์ ส่วนสูง น้ำหนัก
190 เอย์ปาม 0.8 11.5
192 คิมาวาริ 0.8 8.5



อ้างอิง


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


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

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

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

หมวดหมู่

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

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

สารบัญ

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

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

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



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

  ค้นหาบทความ

  บทความแนะนำ

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

ไทย

日本語

中文