สรุปการเขียน code ให้มีประสิทธิภาพเหมือนชาว pythonic writing-efficient-python-code

Image placeholder
แวะมาทักทายกันได้


สรุปการเขียน code ให้มีประสิทธิภาพเหมือนชาว pythonic ตอนที่ 1


1️⃣0️⃣0️⃣ เพื่อเป็นกำลังใจและค่า web server แก่ผู้เขียน อยากให้ช่วยกด google ads ทั้งด้านข้างและระหว่างบทสัก 2-3 click ให้ด้วยนะครับ กดฟรีไม่เสียเงิน —> เหตุผลที่ต้อง click google ads ให้ผู้เขียน 



1️⃣ สำหรับโลกของการพัฒนาซอฟต์แวร์นั้นมีหลายภาษามากมาย และ ในแต่ละภาษาก็ล้วนมีเอกลักษณ์เฉพาะตัว มีการใช้งานที่แตกต่างกัน ซึ่ง Mobile Application ก็ภาษาหนึ่ง การเขียน Website ก็ภาษาหนึ่ง หรือแม้แต่การทำ Server Computer ก็อีกภาษาหนึ่ง แต่อย่างไรก็ตาม มีสิ่งหนึ่งที่ไม่ได้ต่างกันนั้นคือ “กระบวนการคิด”

2️⃣ ไม่ต้องกังวลใจไป แม้ว่าจะหลากหลายภาษา หลายหลาย Brand หรือ หลากหลาย Platform สิ่งที่ไม่ต่างกันนั้นคือ “กระบวนการคิด” ภาษาแต่ละภาษาจะมีคู่มือ และ คำแนะนำต่างๆ ในเรื่องวิธีการเขียนให้เพียงแค่เราศึกษา syntax หรือ คำสั่งต่างๆที่ คู่มือจัดเตรียมเอาไว้ให้ก็ไม่ต้องใช้ความจำมากเกินไป สงสัยตรงไหน ก็สามารถที่จะเปิดคู่มือดูได้ทันที ยิ่งในยุคนี้ เข้าสู่ยุค AI เราสามารถที่ตั้งคำถามเพื่อสอบถาม syntax ได้ทันทีพร้อมยกตัวอย่างมาให้ด้วย สะดวกกว่าสมัยก่อนมาก

3️⃣ แต่ถึงอย่างไร ความสะดวกด้วย AI ก็ไม่สะดวกเท่าเราเข้าใจในเรื่องของกระบวนการคิดว่า จะเขียนอย่างไรให้มีประสิทธิภาพ ดังนั้น สิ่งที่เราจะได้เรียนรู้กันต่อไปนี้ คือ วิธีการเขียน Code ให้มีประสิทธิภาพของภาษา Python

  • ——————— *

  • 1️⃣0️⃣0️⃣ หากใครไม่อยากพลาด *

  • โพสต์เรื่องเกี่ยวกับ *

  • data analyst, data science, *

  • data engineer และ programming *

  • ในโพสต์ถัดๆไป ฝากแชร์ *

  • กดติดตาม profile กันไว้ด้วยนะครับ *

  • https://www.sklsongkiat.com *

  • *——————— **

4️⃣ ไม่ว่าจะทำงาน Data สายไหนก็ตาม Data Engineer หรือ Data Science มักจะใช้เวลาส่วนใหญ่ไปกับการรวบรวมข้อมูลเชิงลึก ทำความสะอาด ปรับแต่งข้อมูลให้อยู่ใน fotmat ที่เหมาะสม สิ่งเหล่านี้ล้วนใช้เวลาในการทำค่อนข้างมากเพื่อนำข้อมูลไปใช้งานได้จริง

5️⃣ เพื่อไม่ให้เวลานั้นเสียไปอย่างเปล่าประโยชน์ จะต้องเรียนรู้วิธีการเขียน Code ที่สะอาด (clean) รวดเร็ว (fasted) และ มีประสิทธิภาพมากขึ้น (performance) ด้วยการเรียนรู้เพื่อกำจัดจุดติดขัดต่างๆใน code Python


6️⃣ ก่อนอื่นมาทำความเข้าใจกับคำว่า Code ที่มีประสิทธิภาพกันก่อน

7️⃣ ”ประสิทธิภาพ" หมายถึง โค้ดที่ทำตามหลัก 2 ประการ คือ

  • ประการที่ 1 code ที่มีประสิทธิภาพจะต้องรวดเร็ว และ ใช้เวลาในการประมวลผลและส่งค่าคืนกลับมาน้อยมาก
  • ประการที่ 2 code ที่มีประสิทธิภาพ จะต้องจัดสรรทรัพยากร เช่น Ram เป็นอย่างดี และ ไม่เสียค่าใช้จ่าย (overhead) ที่ไม่จำเป็น

แม้ว่า runtime จะทำงานได้รวดเร็วแค่ไหนก็ตาม และ ใช้หน่วยความจำน้อย ซึ่งอาจขึ้นอยู่กับงานที่คุณกำลังทำ แต่เป้าหมาย ในการเขียนโค้ดที่มีประสิทธิภาพนั้น คือ การลดเวลาในการทำงานของโปรแกรม และค่าใช้จ่าย (overhead) ลง

8️⃣ overhead หมายถึง ค่าใช้จ่ายหรือภาระที่ไม่จำเป็นที่ต้องเสียไปในการทำงานของโปรแกรม ค่าใช้จ่ายหรือภาระนี้อาจรวมถึงการทำงานที่ซ้ำซ้อน เช่น การคัดลอกข้อมูลซ้ำๆ การเรียกใช้ฟังก์ชันที่ไม่จำเป็น เป็นต้น

9️⃣ โปรแกรมจะทำงานช้าลง โดยทั่วไปแล้ว overhead ของโปรแกรมจะเพิ่มขึ้นตามขนาดและซับซ้อนของโปรแกรม วิธีลด overhead ในการเขียนโปรแกรม ได้แก่:

  • ใช้โครงสร้างข้อมูลและอัลกอริทึมที่มีประสิทธิภาพ
  • ใช้ภาษาโปรแกรมที่มีประสิทธิภาพ
  • เขียนโค้ดที่กระชับและอ่านง่าย

1️⃣0️⃣  หลังจากที่เราได้เข้าใจความหมายของ โค้ดที่มีประสิทธิภาพ และบริบทในบทความกันแล้วก็ขอเน้นย้ำอีกครั้งว่า

  • ในบทความนี้ จะเน้นการเขียนโค้ดที่มีประสิทธิภาพด้วยการใช้ภาษา Python
  • ซึ่งภาษา Python เป็นภาษาที่เน้นในเรื่องของการอ่านโค้ด (focus on readability) และ ด้วยเหตุนี้มันมาพร้อมชุดสำนวน และ วิธีการที่ดีที่เป็นของมัน (best practice)
  • การเขียนโค้ด Python ในลักษณะที่ได้รับการออกแบบมักถูกเรียกว่า "Pythonic code" คำนี้ หมายถึงการเขียนโค้ดที่เป็นไปตามแนวทางปฏิบัติที่ดีที่สุด (best practice) และ กำหนดรายละเอียดของ Python โค้ด Pythonic tend เร็ว และ ง่ายต่อการตีความ

1️⃣1️⃣ ขอยกตัวอย่างระหว่าง Non-Pythonic และ Pythonic ว่าแตกต่างกันอย่างไร

1️⃣2️⃣ เขียนแบบปกติทั่วไป # Non Pythonic code

# Print the list created using the Non-Pythonic approach
i = 0
new_list= []
while i < len(names):
    if len(names[i]) >= 6:
        new_list.append(names[i])
    i += 1
print(new_list)

##

['Kramer', 'Elaine', 'George', 'Newman']

1️⃣2️⃣ output แบบเดียวกัน แต่เขียนแบบ Pythonic

# Print the list created by using list comprehension
best_list = [name for name in names if len(name) >= 6]
print(best_list)

##

['Kramer', 'Elaine', 'George', 'Newman']

1️⃣3️⃣ ทำไมดูง่ายยย ก็เพราะก่อนหน้านี้เราไม่รู้ แต่ตอนนี้เรารู้แล้ว

1️⃣4️⃣ สิ่งหนึ่งของ library python ที่หลายๆคนอาจจะยังไม่รู้ คือ หลักการที่ดีของการเขียนแบบ มีเตรียมเอาไว้ให้ แค่เรียกออกมาที่ command line หรือกดที่ link นี้ PEP20.

Terminal : import this

The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

1️⃣5️⃣  Standard Library ของ Python ซึ่ง component ต่างๆเตรียมมาไว้ให้ ทำให้ใช้งานได้ง่าย Python ที่ถูกใช้งานอยู่บ่อย ๆ อย่างที่ได้เรียนไปในบทความก่อนหน้านี้ เช่น

  • Build-in type : รายการ (lists), ทูเพิล (tuples), เซต (sets), และ ดิกชันนารี (dictionaries)
  • Build-in function : ฟังก์ชันที่มีมาให้ในตัว สามารถในการแก้ปัญหาหลากหลาย เช่น range(), enumerate(), map(), round(), len(), print(), zip()
  • Build-in Modules : โมดูลต่างๆที่จะต้อง import ก่อนการใช้งาน เช่น os, sys, itertools, collections, math เป็นต้น

1️⃣6️⃣ มาดูการยกตัวอย่างของ การเขียนแบบ Non-Pythonic และ Pythonic ************ของการเรียกใช้ function ต่างๆ แต่ละตัวกัน

1️⃣7️⃣ Pythonic with range()

#Non Pythonic

nums_list1 = list(range(2,11,2))
print(nums_list1)
##

[2, 4, 6, 8, 10]

#Pythonic

nums_list2 = [*range(2,11,2)]
print(nums_list2)

##

[2, 4, 6, 8, 10]

1️⃣8️⃣ Pythonic with enumerate()

print(names)

['Jerry', 'Kramer', 'Elaine', 'George', 'Newman']

#Non Pythonic

indexed_names = []
for i,name in enumerate(names):
    index_name = (i,name)
    indexed_names.append(index_name) 
print(indexed_names)

##

[(0, 'Jerry'), (1, 'Kramer'), (2, 'Elaine'), (3, 'George'), (4, 'Newman')]

#Pythonic

indexed_names_unpack = [*enumerate(names, 0)]
print(indexed_names_unpack)

##

[(0, 'Jerry'), (1, 'Kramer'), (2, 'Elaine'), (3, 'George'), (4, 'Newman')]

1️⃣9️⃣ Pythonic with map()

print(names)

['Jerry', 'Kramer', 'Elaine', 'George', 'Newman']

#Non Pythonic

names_uppercase = []

for name in names:
  names_uppercase.append(name.upper())

['JERRY', 'KRAMER', 'ELAINE', 'GEORGE', 'NEWMAN']

#Pythonic

names_map  = map(str.upper, names)
names_uppercase = [*names_map]
print(names_uppercase)

##

['JERRY', 'KRAMER', 'ELAINE', 'GEORGE', 'NEWMAN']

2️⃣0️⃣ ตัวอย่างการเขียนแบบ Pythonic ที่เป็นตัวเลข ด้วย numpy

# Create a list of arrival times
arrival_times = [*range(10,60,10)]

# Convert arrival_times to an array and update the times
arrival_times_np = np.array(arrival_times)
new_times = arrival_times_np - 3

# Use list comprehension and enumerate to pair guests to new times
guest_arrivals = [(names[i],time) for i,time in enumerate(new_times)]

# Map the welcome_guest function to each (guest,time) pair
welcome_map = map(welcome_guest, guest_arrivals)

guest_welcomes = [*welcome_map]
print(*guest_welcomes, sep='\\n')

##

Welcome to Festivus Jerry... You're 7 min late.
Welcome to Festivus Kramer... You're 17 min late.
Welcome to Festivus Elaine... You're 27 min late.
Welcome to Festivus George... You're 37 min late.
Welcome to Festivus Newman... You're 47 min late.


1️⃣0️⃣0️⃣ โดยสรุป การเขียนอย่างมีประสิทธิภาพ ช่วยให้ Code ของเราดูสะอาดและมีประสิทธิภาพมากขึ้น


1️⃣0️⃣0️⃣ ขอขอบคุณผู้อ่านทุกท่านที่ติดตาม โปรดติดตามตอนต่อไป และสนันสนุนผู้เขียน เพื่อเป็นกำลังใจและค่า web server แก่ผู้เขียน ด้วยการกด google ads ทั้งด้านข้างและระหว่างบทสัก 2-3 click ให้ด้วยนะครับ กดฟรีไม่เสียเงิน —> เหตุผลที่ต้อง click google ads ให้ผู้เขียน



สรุปการเขียน code ให้มีประสิทธิภาพด้วยเวลาเหมือนชาว pythonic ตอนที่ 2


1️⃣ จากตอนที่ 1 ได้เขียนเกี่ยวกับ คำนิยามของคำว่า ประสิทธิภาพของการเขียน code ในบทความนี้เป็นภาษา Python รวมถึงเทคนิควิธีการเขียน Code ที่ดี (Best Practice) ในรูปแบบที่เรียกว่า “pythonic” สามารถอ่านย้อนหลัง ตอนที่ 1 ด้านบน


2️⃣ สำหรับตอนที่ 2 นี้ เมื่อเรารู้วิธีการเขียนแบบ pythonic กันไปแล้ว เราจะเชื่อได้อย่างไรว่ามีประสิทธิภาพจริง นักวิทยาศาสตร์ หรือ วิศวกร จะเชื่ออะไรง่ายๆไม่ได้ จะต้องต้องมีข้อมูลที่เชื่อถือได้ ในอาชีพของพวกเราทักษะที่สำคัญที่สุด คือ ทักษะการตั้งคำถาม คำถามที่ถูกต้องมักจะพาเราไปถูกทาง

  • ——————— *

  • 1️⃣0️⃣0️⃣ หากใครไม่อยากพลาด *

  • โพสต์เรื่องเกี่ยวกับ *

  • data analyst, data science, *

  • data engineer และ programming *

  • ในโพสต์ถัดๆไป ฝากแชร์ *

  • กดติดตาม profile กันไว้ด้วยนะครับ *

  • *——————— **

3️⃣ หลายๆ คำถามระหว่างที่เขียนโปรแกรม อย่างเช่น

  • คำถามเกี่ยวกับโจทย์หรือปัญหาที่ต้องการแก้ไข เช่น
    • ปัญหาคืออะไร?
    • ต้องการผลลัพธ์อะไร?
    • ข้อมูลที่จำเป็นมีอะไรบ้าง?
    • ข้อจำกัดของปัญหาคืออะไร?
  • คำถามเกี่ยวกับ code โปรแกรม เช่น
    • code โปรแกรมของเราทำงานอย่างไร?
    • code โปรแกรมของเราสามารถปรับปรุงได้หรือไม่?
    • code โปรแกรมของเรามีความปลอดภัยหรือไม่?

4️⃣ สำหรับคำถามในเรื่องของ ประสิทธิภาพในการเขียน code ซึ่งจะใช้เวลา (runtime) เป็นเครื่องมือวัดผล

5️⃣ ทำไมถึงควรจับเวลากับ code ของเรา

  • การทำงานของ computer จะมี timer ใน cpu ทำงานกับวงจรไฟฟ้าที่ซับซ้อน ซึ่ง code เป็นตัวกำหนดการทำงานของวงจร ให้ประมวลผล แสดงผลลัพท์ ตัวอักษร ตัวเลข รูปภาพ วิดีโอ เสียง ให้กับเรา ในรูปแบบที่ตาเห็น หูได้ยิน

6️⃣ ด้วยเหตุผลข้างต้นนั้น เราจำเป็นต้องวัดเวลาของโปรแกรมของเรา วิธีวัดผล คือ การนำ code 2 ชุดมาเปรียบเทียบกัน แล้ว ใช้เครื่องมือช่วยในการคำนวนหรือ ดักจับ การทำงานของแต่ละขั้นตอน จากนั้นก็นำ code ที่มีประสิทธิภาพกว่ามาใช้

7️⃣ เราจะจับเวลา code ของเราได้อย่างไร

Python มาพร้อมกับ Library คำสั่งที่ใช้สำหรับวัดผลโดยเฉพาะ เช่น

  • การใช้ %timeit
  • การใช้ %lprun
  • การใช้ %mprun

8️⃣ การใช้ %timeit

%timeit คือการวัดค่าเฉลี่ย ค่าเบี่ยงเบนมาตรฐาน เป็นค่าสถิติเวลา ในการทำงานของ code แต่ละบรรทัด ยกตัวอย่างการใช้งาน

9️⃣ สมมติว่าเรามี code อยู่ 2 ชุด

1️⃣0️⃣ code ที่เขียนด้วย list comprehension

# Create a list of integers (0-50) using list comprehension
nums_list_comp = [num for num in range(51)]
print(nums_list_comp)

##

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]

1️⃣1️⃣ และ code ที่เขียนด้วย list by unpacking range

# Create a list of integers (0-50) by unpacking range
nums_unpack = [*list(range(0,51))]
print(nums_unpack)

##

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]

1️⃣2️⃣ คำถามคือ ระหว่าง 2 code นี้ code ชุดไหน เร็วกว่ากัน

1️⃣3️⃣ ให้ลอง วัดค่าเวลาความเร็วด้วย %timeit ดู แล้วบอกผู้เขียนหน่อยว่า code ชุดไหนเร็วกว่ากัน ระหว่าง nums_list_comp และ nums_unpack ลอง comment ใน facebook profile ผมหน่อยนะครับ

%timeit nums_list_comp = [num for num in range(51)]
2.57 us +- 79.8 ns per loop (mean +- std. dev. of 7 runs, 100000 loops each)

และ

%timeit nums_unpack = [*list(range(0,51))]
1.42 us +- 32.9 ns per loop (mean +- std. dev. of 7 runs, 1000000 loops each)

1️⃣4️⃣ การใช้ %lprun

สำหรับ %timeit ทำงานได้ดีกับ code ขนาดเล็ก แต่ถ้าเราต้องการจับเวลา code ขนาดใหญ่หรือ หลายบรรทัด เพื่อดูเวลาการทำงานแบบบรรทัดต่อบรรทัดภายในฟังก์ชันได้อย่างไร

1️⃣5️⃣ เราใช้ %lprun ในการวิเคราะห์ code เป็นเทคนิคที่ใช้เพื่ออธิบายว่าแต่ละส่วนของโปรแกรมที่ทำงานนานเท่าไร และทำงานบ่อยแค่ไหน ข้อดีของเครื่องมือวิเคราะห์โค้ดคือความสามารถในการรวบรวมสถิติสรุปเกี่ยวกับส่วนแต่ละของ code ของเรา

1️⃣6️⃣ ก่อนเราจะใช้ %lprun จะต้อง load external package เข้ามาก่อน คือ line_profiler โดยเขียน แบบนี้

%load_ext line_profiler 

1️⃣7️⃣ เราจึงจะเรียกใช้ %lprun ได้และ option -f ระบุเพื่อเป็นตัวบอกว่ากำลังจะวิเคราะห์เวลาของ function

%lprun -f function_name(arguments)

1️⃣8️⃣ ตัวอย่างเช่น วิเคราะห์ function ตามภาพ



1️⃣9️⃣ การใช้ %mprun

code ที่มีประสิทธิภาพ ถูกกำหนดด้วยค่าของเวลา และ memory (หน่วยความจำคอมพิวเตอร์) ซึ่ง code จะมีการเก็บข้อมูลเอาไว้ในตัวแปรต่างๆ ตัวแปรเหล่านั้นจะใช้ Ram หรือ Memory ซึ่งแน่นอนว่า เกี่ยวข้องกับเวลาที่จะต้องประมวลผลใน Ram มีเครื่องมือวัดค่าของหน่วยความจำที่ใช้ด้วยเช่นกัน ก็คือ %mprun

2️⃣0️⃣ เช่นเดียวกันกับ %lprun จะต้อง load external มาเพื่อเรียกใช้งาน memory_profiler ซึ่งคล้ายกับ line_profiler

%load_ext memory_profiler

2️⃣1️⃣ ยกตัวอย่าง

%load_ext memory_profiler
%mprun -f get_publisher_heroes get_publisher_heroes(heroes, publishers, 'George Lucas')

2️⃣2️⃣ ผลลัพท์ที่ได้จากการใช้ %mprun



1️⃣0️⃣0️⃣ โดยสรุป การทดสอบและวัดผลเป็นสิ่งสำคัญ เพื่อให้รู้ผลลัพท์ที่จะเกิดขึ้นก่อนใช้งานจริง ในการเขียน code ไม่ว่าจะภาษาใดก็ตามจะวัดค่าประสิทธิภาพด้วยเวลา


1️⃣0️⃣0️⃣ ขอขอบคุณผู้อ่านทุกท่านที่ติดตาม โปรดติดตามตอนต่อไป และสนันสนุนผู้เขียน เพื่อเป็นกำลังใจและค่า web server แก่ผู้เขียน ด้วยการกด google ads ทั้งด้านข้างและระหว่างบทสัก 2-3 click ให้ด้วยนะครับ กดฟรีไม่เสียเงิน —> เหตุผลที่ต้อง click google ads ให้ผู้เขียน



สรุปการเขียน code วนลูปให้มีประสิทธิภาพเหมือนชาว pythonic ตอนที่ 3


1️⃣ จากตอนที่ 1 และ ตอนที่ 2 ได้เกริ่นเกี่ยวกับ เทคนิคการเขียน code ที่ดี หรือที่เรียกว่า best practice เปรียบเทียบกับ code ทั่วๆไป ที่ผลลัพท์ได้เหมือนกัน ถึงแม้ว่าจะได้ผลลัพท์ที่ได้จะเหมือนกันก็จริงแต่ว่าประสิทธิภาพที่เสียไปกับเวลาที่คอมพิวเตอร์ทำงาน หรือค่าใช้จ่าย (cost) ที่เกิดขึ้นแย่กว่าแบบ Best Practice วิธีที่จะทำให้รู้ว่า code สองชุดแตกต่างกันหรือไม่ใช้เครื่องมือวัดอย่าง %timeit วัดค่าด้วยเวลาเพื่อเปรียบเทียบกัน สามารถอ่านย้อนหลัง ตอนที่ 1 ตอนที่ 2 ด้านบน


2️⃣ สำหรับตอนที่ 3 นี้จะเริ่มนำ code บางส่วนของตอนที่ 1 และ 2 มาใช้กับ code ในตอนที่ 3 เพื่อเพิ่มประสิทธิภาพให้กับการเขียนวนลูป loop เมื่อพูดถึงการเขียน code วนลูป เป็น code พบมากที่สุดในบรรทัดการทำงานของ code เพราะมันคือการจัดการ code ซ้ำๆ หลายๆ รอบ ถ้ารูปแบบทั่วๆไป cost ที่เกิดขึ้นจะเกิดจากการเขียน code วนลูปจะมาก ดังนั้น เราจะมาเรียนรู้วิธีการเขียน code วนลูป หรือ iterator ให้มีประสิทธิภาพและเป็น Best Practice ที่เรียกว่า pythonic

  • ——————— *

  • 1️⃣0️⃣0️⃣ หากใครไม่อยากพลาด *

  • โพสต์เรื่องเกี่ยวกับ *

  • data analyst, data science, *

  • data engineer และ programming *

  • ในโพสต์ถัดๆไป ฝากแชร์ *

  • กดติดตาม profile กันไว้ด้วยนะครับ *

  • *——————— **

3️⃣ ในการเขียน code วนลูปให้มีประสิทธิภาพ มีหลายสถานการณ์ที่จะต้องใช้อย่างเช่น

  • การ merge ชุดข้อมูล List เข้าด้วยกัน
  • การเปรียบเทียบข้อมูลด้วยชุดข้อมูลที่เป็น Set ด้วย Set Theory
  • วิธีหลีกเลี่ยงขั้นตอนบางบรรทัดที่อยู่ในลูปโดยไม่จำเป็น

4️⃣ การ merge ชุดข้อมูล List เข้าด้วยกัน

มายกตัวอย่างของ code ที่เขียนแบบทั่วๆไป กันสักเล็กน้อยด้วยชุดข้อมูลเกม Pokemon สมมติว่าเรามี ชุดข้อมูลที่เป็น List อยู่ 2 ชุด และต้องการจะรวม List ทั้ง 2 ชุดเข้าด้วยกันวิธีทั่วไป ก็ใช้ loop ในการจัดการ เช่น code ด้านล่าง

List1 = ['Bulbasaur', 'Charmander', 'Squirtle']
List2 = [45, 39, 44]

combined = []

for i, pokemon in enumerate(List1):
	combined.append((pokemon, List2[i]))

print(combined)

### Result ###

[('Bulbasaur', 45), ('Charmander', 39), ('Squirtle', 44)]

5️⃣ คราวนี้มาดูวิธีที่เป็น Best Practice กันบ้าง

List1 = ['Bulbasaur', 'Charmander', 'Squirtle']
List2 = [45, 39, 44]

combined_zip = zip(List1, List2)
combined_zip_list = [*combined_zip]

print(combined_zip_list)

### Result ###

[('Bulbasaur', 45), ('Charmander', 39), ('Squirtle', 44)]

6️⃣ จากนั้นก็เอา code สองชุดนี้มาวัดค่าด้วยคำสั่ง %timeit เพื่อดูว่า code ชุดไหนเร็วกว่ากัน

7️⃣ การเปรียบเทียบข้อมูลด้วยชุดข้อมูลที่เป็น Set ด้วย Set Theory

บ่อยครั้งที่เราจะต้องใช้วิธีการเปรียบเทียบข้อมูลเพื่อสำรวจสิ่งที่ต้องการ หรือ ดูลักษณะความเหมือนและแตกต่าง

8️⃣ เมื่อเราต้องการจะทำการเปรียบเทียบเราควรจะนำทฤษฏี Set เข้ามาช่วยในเรื่องนี้ เพราะ Set มีฟังก์ชันที่มีประโยชน์ในเรื่องนี้โดยเฉพาะ เราควรพิจารณาว่าควรเก็บข้อมูลเป็น Set หรือไม่

9️⃣ คุณสมบัติของ Set มีความสามารถที่จะตรวจสอบค่าต่างๆที่เป็นสมาชิกอยู่ อย่าง เช่น คำสั่ง “in” มันคล้ายกับคำสั่งค้นหา

1️⃣0️⃣ ยกตัวอย่างการใช้ Loop เพื่อเปรียบเทียบข้อมูล 2 ชุด เพื่อดูความเหมือน

List1 = ['Bulbasaur', 'Charmander', 'Squirtle']
List2 = ['Caterpia', 'Pidgey', 'Squirtle']

in_common = []

for pokemon_1 in List1:
	for pokemon_2 in List2:
		if pokemon_1 == pokemon_2:
			in_common.append(pokemon_1)

print(in_common)

## Result ##

['Squirtle']

1️⃣1️⃣ จะเห็นว่า เขียน Code ลูป แบบซ้อนลูปหลายชั้น คราวนี้มาดู เทคนิคที่ใช้ Set กันบ้าง

ash_pokedex  :  ['Pikachu', 'Bulbasaur', 'Koffing', 'Spearow', 'Vulpix', 'Wigglytuff', 'Zubat', 'Rattata', 'Psyduck', 'Squirtle'] 
misty_pokedex:  ['Krabby', 'Horsea', 'Slowbro', 'Tentacool', 'Vaporeon', 'Magikarp', 'Poliwag', 'Starmie', 'Psyduck', 'Squirtle']

ash_set = set(ash_pokedex)
misty_set = set(misty_pokedex)

both = ash_set.intersection(misty_set)
print(both)

## Result ##

{'Psyduck', 'Squirtle'}

1️⃣2️⃣ จะเห็นว่าวิธีการหาความเหมือนกันของข้อมูลไม่จำเป็นต้องใช้ loop เลยด้วยซ้ำ หรือ จะต้องใช้คำสั่ง in ดูบ้าง

# Convert Brock's Pokédex to a set
brock_pokedex_set = set(brock_pokedex)
print(brock_pokedex_set)

## Result ##
{'Geodude', 'Machop', 'Vulpix', 'Golem', 'Zubat', 'Omastar', 'Onix', 'Dugtrio', 'Tauros', 'Kabutops'}

# Check if Psyduck is in Ash's list and Brock's set
print('Psyduck' in ash_pokedex)
print('Psyduck' in brock_pokedex_set)

## Result ##
True
False

1️⃣3️⃣ ลองวัดค่าเปรียบเทียบระหว่าง Set กับ List ดูว่าวิธีไหน เร็วกว่ากัน

แบบใช้ List

%timeit 'Psyduck' in ash_pokedex
161 ns +- 4.31 ns per loop (mean +- std. dev. of 7 runs, 10000000 loops each)

1️⃣4️⃣ แบบใช้ Set

%timeit 'Psyduck' in brock_pokedex_set
42.6 ns +- 2.21 ns per loop (mean +- std. dev. of 7 runs, 10000000 loops each)

1️⃣5️⃣ จากตัวอย่างนี้จะเห็นว่า แบบ Set จะมีความเร็วกว่า

1️⃣6️⃣ วิธีหลีกเลี่ยงขั้นตอนบางบรรทัดที่อยู่ในลูปโดยไม่จำเป็น

บางครั้ง เวลาที่เขียน code ที่มีขนาดใหญ่ มากๆ การดูเรื่องที่เป็นประสิทธิภาพจำเป็นอย่างมาก จึงได้มีวิธีการนำเรื่องของ Clean Code เพื่อทำ DRY (Don't Repeat Yourself) อย่าใช้ Code ซ้ำๆ แบบ copy วาง ถึงแม้ว่าจะมี Best Practice แนะนำเอาไว้แต่ในโลกของความเป็นจริงมักจะเกิด Debt หรือต้นทุนทางเทคนิค ถ้าไม่จัดการ code ให้ดีแต่แรก

1️⃣7️⃣ ผู้เขียนจะสื่อว่า ถ้าจะทำให้ code มีประสิทธิภาพเพิ่มขึ้นเราจะต้องกลับมา review code และทำการ refactory code เสมอ

1️⃣8️⃣ มาดู code ที่ควรจะ refactory กันดีกว่า

import numpy as np

names = ['Absol', 'Aron', 'Jynx', 'Natu', 'Onix']
attacts = np.array([130, 79,50,50,45])

for pokemon, attack in zip(names, attacks):
	total_attack_avg = attacks.mean()
	if attack > total_attack_avg:
		print(
				"{}'s attack: {} > average: {}!"
				.format(pokemon, attack, total_attack_avg)
		)

## Result ##

Absol's attact: 130 > average: 69.0!
Aron's attact: 70 > average: 69.0!

1️⃣9️⃣ จาก code ด้านบนจะเห็นว่า บรรทัด total_attack_avg = attacks.mean() ไม่จำเป็นต้องอยู่ใน loop เมื่อเราเอาออกมาอยู่นอก loop ก็จะได้ผลลัพท์เหมือนกัน

import numpy as np

names = ['Absol', 'Aron', 'Jynx', 'Natu', 'Onix']
attacts = np.array([130, 79,50,50,45])

total_attack_avg = attacks.mean()

for pokemon, attack in zip(names, attacks):
	
	if attack > total_attack_avg:
		print(
				"{}'s attack: {} > average: {}!"
				.format(pokemon, attack, total_attack_avg)
		)

## Result ##

Absol's attact: 130 > average: 69.0!
Aron's attact: 70 > average: 69.0!

2️⃣0️⃣ มาลองวัดค่าเวลาเปรียบเทียบระหว่าง ก่อน refactory และ หลัง refactory กัน

ก่อน refactory

%%timeit

for pokemon, attack in zip(names, attacks):
	total_attack_avg = attacks.mean()
	if attack > total_attack_avg:
		print(
				"{}'s attack: {} > average: {}!"
				.format(pokemon, attack, total_attack_avg)
		)

74.9 us +- 4.31 us per loop (mean +- std. dev. of 7 runs, 10000000 loops each)

2️⃣1️⃣ หลัง refactory

%%timeit

total_attack_avg = attacks.mean()

for pokemon, attack in zip(names, attacks):

	if attack > total_attack_avg:
		print(
				"{}'s attack: {} > average: {}!"
				.format(pokemon, attack, total_attack_avg)
		)

37.5 us +- 4.31 us per loop (mean +- std. dev. of 7 runs, 10000000 loops each)


1️⃣0️⃣0️⃣ โดยสรุปแล้ว การเขียน code วนลูปจะพบมากที่สุด ควรจะพิจารณาวิธีการเขียนเพื่อลด cost ในการทำงานของคอมพิวเตอร์


1️⃣0️⃣0️⃣ ขอขอบคุณผู้อ่านทุกท่านที่ติดตาม โปรดติดตามตอนต่อไป และสนันสนุนผู้เขียน เพื่อเป็นกำลังใจและค่า web server แก่ผู้เขียน ด้วยการกด google ads ทั้งด้านข้างและระหว่างบทสัก 2-3 click ให้ด้วยนะครับ กดฟรีไม่เสียเงิน —> เหตุผลที่ต้อง click google ads ให้ผู้เขียน



สรุปการเขียน Code จัดการ Dataframe ให้มีประสิทธิภาพเหมือนชาว pythonic ตอนที่ 4 (จบ)


1️⃣ จากตอนที่ 1 ตอนที่ 2 และ ตอนที่ 3 ได้เรียนรู้ เทคนิคการเขียน code ที่ดี หรือที่เรียกว่า best practice เปรียบเทียบกับ code ทั่วๆไป และ การเขียน Loop ให้มีประสิทธิภาพ เพราะ การเขียน Loop จะพบมากที่สุดในการเขียน Code สามารถอ่านย้อนหลัง ตอนที่ 1 ตอนที่ 2 และ ตอนที่ 3 ได้ ที่ด้านบน


2️⃣ สำหรับตอนที่ 4 ตอนสุดท้ายนี้ จะ advance เพิ่มขึ้นมาสักเล็กน้อยในการใช้เครื่องมือการวิเคราะห์ข้อมูลอย่าง Pandas (แพนด้าส) ผู้สอนบางท่าน อ่านว่า แพนด้า


3️⃣ สำหรับเพื่อนๆที่อาจจะยังไม่รู้ว่า pandas คืออะไร

pandas เป็นเครื่องมือวิเคราะห์ข้อมูลยอดนิยมที่ใช้งานในภาษา Python ที่มีโครงสร้างข้อมูล 2 มิติ มี Row และ Column คล้ายกับ Table หรือ ตาราง เรียกโครงสร้างข้อมูลนี้ว่า Dataframe นี้คือภาพตัวอย่างของ Dataframe



4️⃣ Dataframe มี function ต่างๆ หลายอย่างที่สามารถจัดการ Dataframe ได้ จะไม่ได้ลงลึก pandas ในบทความนี้ สามารถนำ keyword ของ pandas ไปค้นคว้าต่อได้ แต่ถ้าใครอยากอ่านที่ผมเขียนเพิ่มเติม โปรด comment ว่า “สนใจบทความ pandas”

5️⃣ อย่างที่ได้เกริ่นไปตามหัวข้อ Data frame เป็นโครงสร้างที่ใช้วิเคราะห์และเป็นที่นิยมมากในการใช้กับ ภาษา Python ซึ่งปัญหาส่วนใหญ่ของ Code ที่ไม่มีประสิทธิภาพ มาจากการเขียนลูปที่ไม่ดี ใน Dataframe ก็เช่นเดียวกัน ดังนั้นเราจะมาเรียนรู้กันในการเขียน Code จัดการ Dataframe ให้มีประสิทธิภาพ

6️⃣ ในการวนลูปของ Dataframe มีหลายวิธี แต่จะต้องเลือกใช้ให้เหมาะสมตามสถานการณ์ต่างๆ ไม่ว่าจะเป็น

  • การวน Loop Dataframe โดย .iloc
  • การวน Loop Dataframe โดย .iterrows
  • การวน Loop Dataframe โดย .itertuples
  • การวน Loop Dataframe โดยใช้ Numpy
  • หลีกเลี่ยงการใช้ Loop โดยใช้ .apply
  • ——————— *

  • 1️⃣0️⃣0️⃣ หากใครไม่อยากพลาด *

  • โพสต์เรื่องเกี่ยวกับ *

  • data analyst, data science, *

  • data engineer และ programming *

  • ในโพสต์ถัดๆไป ฝากแชร์ *

  • กดติดตาม profile กันไว้ด้วยนะครับ *

  • *——————— **

7️⃣ การวน Loop Dataframe โดย .iloc

มาตัวอย่างการ loop แบบนี้กัน โดยใช้ Dataset สถิติการแข่งขันเบสบอล Major League Baseball ของทุกทีมตั้งแต่ปี 1962 ถึง 2012

8️⃣ โจทย์มีอยู่ว่า ต้องการจะคำนวนเปอร์เซ็นผู้ชนะการแข่งขันของแต่ละทีม สิ่งที่ทำก็คือ การสร้าง Column ใหม่ขึ้นมา เพื่อเก็บค่าการคำนวนเปอร์เซ็นการชนะของแต่ละทีมใน List แล้วนำ List ไปใส่ใน Dataframe โดยมีขั้นตอนดังนี้

  • สร้าง ตัวแปร List สำหรับเก็บข้อมูลที่คำนวน
  • วน Loop ข้อมูล Dataframe ของแต่ละแถว
  • ใช้ method iloc เพื่อใช้ index name ดึงข้อมูลในแต่ละแถว
  • นำค่าที่ได้มาคำนวน ผ่านการใช้ method ที่สร้างขึ้น
  • นำค่าที่คำนวนได้ไปเก็บเอาไว้ที่ตัวแปร
  • นำ List ไป merge ใส่ลง Dataframe ที่ Column ใหม่

9️⃣ จากโจทย์จะต้องสร้าง method หรือ function สำหรับคำนวนเปอร์เซ็นผู้ชนะ เสียก่อนเพื่อเรียกใช้ต่ออีกที ยกตัวอย่างการใช้ คือ ชนะ 50 ครั้ง จาก จำนวน 100 ครั้ง เปอร์เซ็นการชนะ คือ 0.5 หรือ 50%

import numpy as np

def calc_with_perc(wins, games_played):
	win_perc = wins / games_played
	return np.round(win_perc,2)

## Example ##

win_perc = calc_with_perc(50, 100)
print(win_perc)

## Result ## 
0.5

1️⃣0️⃣ code ของ Loop แบบ iloc

win_perc_list = []
for i in range(len(baseball_df)):
	row = baseball_df.iloc[i]
	wins = row['W']
	games_played = row['G']
	win_perc = calc_win_perc(wins, game_played)
	win_perc_list.append(win_perc)

baseball_df['WP'] = win_perc_list

print(baseball_df.head())



1️⃣1️⃣ เมื่อลองเอามาดูประสิทธิภาพด้านความเร็ว พบว่าใช้เวลาไป 186 ms เรายังไม่รู้ว่าประสิทธิภาพของวิธีเขียนแบบนี้ดีหรือไม่ จนกว่าจะมี code เปรียบเทียบ งั้นเราไปต่อการด้วยวิธีการเขียน Loop Dataframe ด้วย .iterrows

186 ms +- 6.05 ms per loop (mean +- std. dev. of 7 runs, 1000 loops each)

1️⃣2️⃣ ใน Dataframe มี method ที่ชื่อ iterrows ลักษณะการใช้ก็คล้ายกับ iloc แต่ต่างกันที่การ return ค่าออกมาจะเป็น Dataframe ลักษณะที่เป็นแบบนี้ (index, pandas series)

# Iterate over pit_df and print each row
for i, row in baseball_df.iterrows():
    print(row)

## Result ## 

Team         PIT
League        NL
Year        2012
RS           651
RA           674
W             79
G            162
Playoffs       0
Name: 0, dtype: object

## ...

1️⃣3️⃣ เพื่อให้ง่าย จะกำหนด pandas_series แทนด้วยชื่อ row ในตัวแปร

win_perc_list = []
for i, row in baseball_df.iterrows():
	wins = row['W']
	games_played = row['G']
	win_perc = calc_win_perc(wins, game_played)
	win_perc_list.append(win_perc)

baseball_df['WP'] = win_perc_list

1️⃣4️⃣ ได้ผลลัพท์เหมือนกัน แต่มาดูเรื่องประสิทธิภาพด้านเวลาแล้ว ได้ 95.3 ms ซึ่งเร็วกว่า แบบ iloc

95.3 ms +- 3.57 ms per loop (mean +- std. dev. of 7 runs, 1000 loops each)

1️⃣5️⃣ การวน Loop Dataframe โดย .itertuples

เช่นเดียวกับ iterrows แต่ output ที่ return ค่าออกมาไม่ใช่ Pandas Series แต่เป็น Nametuple แบบนี้ จะเห็นว่าเป็น Tuple

for row in team_wins_df.itertuples():
  print(row)

## Result ##

Pandas(Index=0, Team='TEX', Year=2012, W=93)
Pandas(Index=1, Team='TEX', Year=2011, W=96)
Pandas(Index=2, Team='TEX', Year=2010, W=90)

1️⃣6️⃣ ลองเปรียบเทียบเวลากับ iterrows

%%timeit
for row in team_wins_df.iterrows():
  print(row)

## Result ##

527 ms +- 41.1 ms per loop (mean +- std. dev. of 7 runs, 1000 loops each)

1️⃣7️⃣  เปรียบเทียบกับ itertuples

%%timeit
for row in team_wins_df.itertuples():
  print(row)

## Result ##

7.48 ms +- 243 us per loop (mean +- std. dev. of 7 runs, 1000 loops each)

1️⃣8️⃣ จะเห็นว่า itertuples มีประสิทธิภาพมากกว่า iterrows อยู่มากพอสมควร เนื่องจากวิธีจัดเก็บผลลัพท์ของ itertuples ซึ่งเป็นวิธีที่ดี ต่อประสิทธิภาพเมื่อเจอกับ Dataframe ที่มีขนาดใหญ่

1️⃣9️⃣ การวน Loop Dataframe โดยใช้ Numpy

การวน Loop ทำงานซ้ำๆของ DataFrame เพื่อทำการคำนวณนั้นใน pandas เป็น Library ที่ถูกสร้างขึ้นบน Numpy หมายความว่าเราสามารถใช้ฟังก์ชันของ Numpy จาก Dataframe ได้

2️⃣0️⃣ การที่เราแปลงค่าจาก Dataframe มาเป็น Numpy Array นั้นมันจะแปลงโครงสร้างของข้อมูลจาก Dataframe มาเป็น Series Array หรือให้เห็นภาพง่าย ก็คือ ไม่มี column จะเป็นค่าแค่แถวเดียวเท่านั้น

2️⃣1️⃣ ยกตัวอย่าง สมมติว่ามี Dataframe baseball_df

print(baseball_df)



2️⃣2️⃣ เอามาแปลงเป็น Numpy ผ่านฟังก์ชัน calc_win_perc ที่เขียนเอาไว้ในตอนต้น การแปลง column W และ G ให้เป็น Numpy Array ด้วย .values

win_percs_np = calc_win_perc(baseball_df['W'].values, baseball_df['G'].values)
print(win_percs_np)

## Result ##

array([0.5 , 0.58, 0.57, ..., 0.62, 0.52, 0.37])

2️⃣3️⃣ ลองทดสอบประสิทธิภาพของเวลา

%%timeit

win_percs_np = calc_win_perc(baseball_df['W'].values, baseball_df['G'].values)
baseball_df['WP'] = win_percs_np

151 us +- 12.3 us per loop (mean +- std. dev. of 7 runs, 10000 loops each)

2️⃣4️⃣ ลองย้อนไปดูย่อหน้าที่ 1️⃣1️⃣ จะเห็นว่าผลลัพท์เหมือนกัน และมีความเร็วในการคำนวนเร็วกว่าทุกวิธีที่ผ่านมา

2️⃣5️⃣ หลีกเลี่ยงการใช้ Loop โดยใช้ .apply

ไม่ว่าจะมีวิธีการ Loop ที่ดีที่สุด แต่ถ้าหลีกเลี่ยงการใช้ Loop ได้ก็ควรจะหลีกเลี่ยง เพื่อเพิ่มประสิทธิภาพให้กับ Code ของเรา เหตุผลอย่างที่ได้เขียนไปในตอนที่ 3 ประสิทธิภาพจะลดลงเมื่อใช้ Loop ไม่ถูกวิธีและเสีย Cost ค่อนข้างมาก

2️⃣6️⃣ ทางเลือกหนึ่งในการหลีกเลี่ยง เพื่อวนซ้ำผ่าน DataFrame คือการใช้ .apply method ของ pandas ฟังก์ชันนี้ทำหน้าที่เหมือนกับฟังก์ชัน map และใช้งานร่วมกับ คำสั่ง lambda เป็นคำสั่ง short function ให้กับ code อีกด้วย

2️⃣7️⃣ ตัวอย่างการใช้ apply method โดยใช้ function sum

# Gather sum of all columns
stat_totals = rays_df.apply(sum, axis=0)
print(stat_totals)

## Result ##

RS          3783
RA          3265
W            458
Playoffs       3
dtype: int64

2️⃣8️⃣ ลองใช้ apply กับ function calc_win_perc ที่สร้างเอาไว้ ร่วมกับ lambda

# Create a win percentage Series 
win_percs = dbacks_df.apply(lambda row: calc_win_perc(row['W'], row['G']), axis=1)
print(win_percs, '\\n')

## Result ## 

0     0.50
1     0.58
2     0.40
3     0.43
4     0.51
5     0.56
6     0.47
7     0.48
8     0.31
9     0.52
10    0.60
11    0.57
12    0.52
13    0.62
14    0.40
dtype: float64

2️⃣9️⃣ ลองวัดประสิทธิภาพเวลาทำงานของการใช้ apply ที่ได้ผลลัพท์แบบเดียวกัน พบว่า เร็วกว่า iterrows และ itertuple แต่ยังช้ากว่า วิธีของ numpy

%%timeit

win_perc_preds_apply = baseball_df.apply(lambda row: predict_win_perc(row['RS'], row['RA']), axis=1)

33.6 ms +- 4.96 ms per loop (mean +- std. dev. of 7 runs, 10 loops each)


1️⃣0️⃣0️⃣ โดยสรุป การจัดการ Loop กับ Dataframe มีหลากหลายวิธี สามารถเลือกให้เหมาะสมกับสถานการณ์ ร่วมถึงพิจารณา cost ต่างๆที่เกิดขึ้นด้วย จะช่วยให้ประสิทธิภาพของ code ดีขึ้น


1️⃣0️⃣0️⃣ ขอขอบคุณผู้อ่านทุกท่านที่ติดตาม โปรดติดตามตอนต่อไป และสนันสนุนผู้เขียนเพื่อเป็นกำลังใจและค่า web server แก่ผู้เขียน ด้วยการกด google ads ทั้งด้านข้างและระหว่างบทสัก 2-3 click ให้ด้วยนะครับ กดฟรีไม่เสียเงิน —> เหตุผลที่ต้อง click google ads ให้ผู้เขียน 






แวะมาทักทายกันได้
donate