Thursday, August 19, 2010

Python Programming

Reverse complement สาย DNA ด้วย Python (ภาค 2)

ประพัฒน์ สุริยผล


ครั้งที่แล้วเราได้โปรแกรมที่ใช้งานจริงไปเรียบร้อยแล้ว แต่ในแง่ของตัวโปรแกรมเอง ยังไม่น่าพอใจสักเท่าไหร่ แถมตัวโปรแกรมเองยาวตั้ง 25 บรรทัด ซึ่งงานในลักษณะนี้ ภาษา python ไม่น่าจะต้องเขียนยาวขนาดนั้น เสียยี่ห้อ python หมด


ครั้งนี้ เราจะมาเรียนรู้กันว่า ถ้าเขียนโปรแกรมทำ reverse complement แบบ python ควรจะเป็นอย่างไร


Reverse_sequence function revisit


จุดแรก เรามาดูกันที่ฟังก์ชัน reverse_sequence กันก่อนนะครับ ซึ่งครั้งที่แล้วเราเขียนเอาไว้ 5 บรรทัดดังนี้


def reverse_sequence(dna):

____result = ""

____for base in dna:

________result = base+result

____return result

จุดประสงค์คือคืนค่า sequence ที่ reverse แล้ว โดยเราจะวนลูปเองทีละเบส แต่ครั้งนี้เราจะเรียกใชัฟังก์ชันที่ python เตรียมเอาไว้ให้เราอยู่แล้ว ชื่อว่า reverse ที่ออกแบบมาใช้กับตัวแปรประเภท list ดังตัวอย่าง


ผมลองสาธิตตัวอย่างคำสั่งนี้ด้วยการพิมพ์คำสั่งที่ python command line ถ้าหากสนใจจะลองเปิดโปรแกรม python command line แล้วพิมพ์ตามได้ครับ บรรทัดที่พิมพ์คือบรรทัดที่ขึ้นต้นด้วย >>> ส่วนบรรทัดที่ไม่มีสัญลักษณ์ >>> จะเป็นสิ่งที่ python ตอบเรากลับมา


>>> a = [1,2,3,4]

>>> a.reverse()

>>> a

[4, 3, 2, 1]

จะเห็นว่าคำสั่ง reverse จะ reverse ข้อมูลใน list ให้เรา โดยที่เราไม่ต้องเขียนลูปเอง แต่ถ้าเราใช้คำสั่ง


>>> a = "atgc"

>>> a.reverse()

Traceback (most recent call last):

File "", line 1, in

AttributeError: 'str' object has no attribute 'reverse'


โปรแกรม python จะโวยวายออกมาว่า เราไม่สามารถใช้คำสั่ง reverse กับตัวแปร string ได้ ทั้งที่ในความจริงแล้ว ตัวแปร string ก็คือ list ของตัวอักษรนั่นเอง เราจะแก้ปัญหานี้ด้วยการบอก python ว่าเราต้องการให้มอง string เป็น list

>>> a = "atgc"

>>> list(a)

['a', 't', 'g', 'c']


จะเห็นว่า ถ้าเราส่งตัวแปร string ไปให้ฟังก์ชัน list เราก็จะได้ list ของเบสต่างๆ ออกมา แต่เราไม่ได้เขียนฟังก์ชัน list ขึ้นมา ทำไมเราถึงเรียกใช้ได้ คำตอบคือ ฟังก์ชัน list เป็น built-in function ที่ python เตรียมเอาไว้ให้เราเรียกใช้ได้เลย เราไม่ต้องเขียนเองเพิ่ม แต่งานของเรายังไม่จบครับ เพราะว่าการทำงานของฟังก์ชันเดิม โปรแกรมจะคืนค่าเป็น string ที่ reverse แล้ว แต่ถ้าเราใช้คำสั่ง reverse เราจะยังได้ข้อมูลเป็น list อยู่ โจทย์ถัดไปที่เราจะต้องหาคำตอบให้ได้คือ ทำอย่างไรจึงสร้าง string จาก list ได้


วิธีตรงไปตรงก็มา ก็คือใช้ลูป

dna_string = ""

for base in my_list:

____dna_string = dna_string+base


พอลูปครบถ้วน เราก็จะได้ตัวแปร string ที่มาจาก list


แต่ถ้าเราทำแบบนี้ โปรแกรมครั้งนี้ของเราก็ไม่ได้สั้นกว่าครั้งที่แล้วเลย ยาวกว่า และออกจะน่าเกลียดกว่าด้วย


python มีวิธีที่ดีกว่านี้มาให้เราแล้ว


คำสั่ง Join เพื่อเชื่อมแต่ละ items ใน list


เราจะใช้คำสั่งอีกตัวหนึ่งคือ join เพื่อเชื่อมโยงแต่ละข้อมูลใน list ดังตัวอย่าง


>>> a = ["a","b","c","d"]

>>> '*'.join(a)

'a*b*c*d'


ถ้าเรามีตัวอักษรอยู่ใน list แล้วเราใช้คำสั่ง join ดังตัวอย่าง python จะนำเอาตัวอักษรที่อยู่ก่อน join ไปแทรกอยู่ระหว่างข้อมูลแต่ละ items ใน list ซึ่งเราสามารถในไปใช้งานได้หลายอย่างเช่น


>>> a = ["apple", "banana", "cow"]

>>> 'and'.join(a)

'appleandbananaandcow'

ซึ่งอ่านไม่รู้เรื่อง เราแก้ไขด้วยการเพิ่ม space (เว้นวรรค) หน้าและหลัง and


>>> ' and '.join(a)

'apple and banana and cow'

ก็จะได้ string ที่อ่านออก เราสามารถทำ comma-delimited text ง่ายๆ ด้วยคำสั่ง join เช่นกัน

>>> ', '.join(a)

'apple, banana, cow'


ให้สังเกตการใช้คำสั่ง join ให้ดีนะครับ จะเริ่มต้นด้วย string ที่เราต้องการใช้เชื่อมแต่ละ items ใน list แล้วตามด้วยจุดและคำสั่ง join และส่งค่า string เป็นพารามิเตอร์ (parameter) ไปให้กลับ function


ถ้าหากเราต้องการเชื่อม items ใน list เฉยๆ เราก็สามารถทำได้ด้วยการใช้ empty string ในการ join ดังตัวอย่าง


>>> ''.join(a)

'applebananacow'


ซึ่งก็จะเป็นสิ่งที่เราต้องการพอดี


ถึงตรงนี้ เราก็มีทุกอย่างพร้อมแล้วสำหรับการเรียกใช้ฟังก์ชันที่มากับ python แทนที่จะต้องมาเขียนเอง (ในเมื่อ python มีมาให้แล้ว เราจะเขียนเองให้ยุ่งยากทำไม จริงไหมครับ)

def reverse_sequence(dna):

____result = list(dna)

____result.reverse()

____result = ''.join(result)

____return result

Chain of functions


จะเห็นว่าฟังก์ชันใหม่ของเรา จะเรียกใช้ฟังก์ชันของ python ที่มีอยู่แล้วทั้งหมด คราวนี้เรามาเรียนรู้ concept อีกอย่างหนึ่ง ซึ่งจะทำให้เราเขียนโปรแกรมให้กะทัดรัดขึ้น แต่อาจจะทำให้มือใหม่งุนงงได้พอสมควร นั่นคือ concept เรื่อง chain of functions ครับ


เรามาดูตัวอย่างง่ายๆ กันก่อน สมมติว่าเรามี 2 functions ดังนี้ครับ

def function_addone(x):

____return x+1

def function_timetwo(x):

____return x*2

คิดว่าคงเข้าใจได้ไม่ยากนะครับ function_addone จะคืนค่าที่ส่งไปบวกหนึ่ง ส่วน function_timetwo จะคืนค่าที่ส่งไปคูณสอง


ลองมาดูตัวอย่างการใช้งานนะครับ


>>> function_addone(2)

3

>>> function_timetwo(2)

4


คราวนี้เราเอาผลลัพธ์มาใส่ไว้ในตัวแปร x ก่อน แล้วเอาผลลัพธ์ที่ได้ ไปใช้ใน function_timetwo ต่อ แล้วพิมพ์ผลลัพธ์ออกมาดู


>>> x = function_addone(3)

>>> x = function_timetwo(x)

>>> print x

8


จะเห็นว่าค่าของ x เป็นค่าที่ได้จากการคืนค่าของ function_addone แล้วค่านั้นก็จะถูกส่งต่อไปที่ function_timetwo ทันที เท่ากับว่าเรียกใช้ฟังก์ชันเพื่อให้ได้คำตอบของสมการ (3+1)*2 = 8 เราจึงอาจจะเขียนได้ดังด้านล่าง

>>> x = function_timetwo(function_addone(3))

>>> print x

8


ซึ่งเราจะได้คำตอบเดียวกัน ถ้าหากเราอ่านโปรแกรม python คุ้นเคยแล้ว การเขียนแบบหลังจะทำให้อ่านได้สะดวกและง่ายกว่า เพราะว่าเราจะทราบได้ทันทีว่า ตอนแรกเรียกใช้ function_addone แล้วผลที่ได้ส่งต่อไปให้ function_ timetwo เลย (ให้อ่านจากตัวที่อยู่ในวงเล็บในสุด แล้วค่อยๆ ขยายออกมาด้านนอก)


เพราะฉะนั้น ถ้าเมื่อไหร่เราเห็นการเขียนโปรแกรมในลักษณะที่ส่งค่าต่อไปเรื่อยๆ เราสามารถย่อโปรแกรมได้ด้วยการใช้ chain of functions


ย้อนกลับมาที่โปรแกรมของเรา จะเห็นว่าเราน่าจะใช้ chain of functions ได้ ยกเว้นคำสั่ง reverse() ซึ่งไม่ได้คืนค่าใดกลับมา แต่คำสั่งนี้ไป reverse ข้อมูลของ list ทำให้เรายังไม่สามารถใช้ chain of functions ได้ เราสามารถแก้ไขได้ด้วยการใช้คำสั่งอีกหนึ่งคำสั่งที่คล้ายกันคือ


reversed() ให้สังเกตตัว d ที่อยู่ข้างหลัง ที่จะแตกต่างจากคำสั่ง reverse ที่เราใช้ก่อนหน้านี้


คำสั่ง reversed() จะคืนค่ากลับมา เราจึงเขียน function ได้ใหม่ ดังนี้

def reverse_sequence(dna):

____result = list(dna)

____result = reversed(result)

____result = ''.join(result)

____return result


ให้สังเกตการเรียกใช้ที่เปลี่ยนไป จะเห็นว่าคราวนี้ค่าสั่ง reversed จะคืนค่ากลับมาให้ เราจึงสามารถนำค่าที่ได้ใส่กลับลงไปที่ตัวแปร result ต่างจากที่เราใช้คำสั่ง reverse() ก่อนหน้านี้

เมื่อเราได้รูปแบบดังนี้แล้ว เราก็พร้อมจะใช้ chain of functions แล้วครับ โดยเราจะค่อยๆ ย้อนจากล่างขึ้นบน

def reverse_sequence(dna):

____result = list(dna)

____result = ''.join(reversed(result))

____return result


ผมเอา reversed(result) มาแทนที่ result ในบรรทัดที่ใช้คำสั่ง join เพราะเมื่อเราได้ผลลัพธ์จาก reversed(result) เราก็ส่งต่อค่านี้ไปที่ join ทันที จึงไม่จำเป็นต้องมาพักค่าเอาไว้ที่ตัวแปร result เราจะรวบคำสั่งต่อ ได้เป็น

def reverse_sequence(dna):

____result = ''.join(reversed(list(dna)))

____return result


ผมแทนที่ result ในบรรทัด join ด้วย list(dna) เพราะว่าตัวแปร result ที่จะมาที่บรรทัดนี้ก็คือผลลัพธ์ที่ได้จาก list(dna) จึงไม่จำเป็นต้องพักค่าเช่นกัน


สุดท้ายเราไม่จำเป็นต้องเก็บค่าในตัวแปร result อีกต่อไป เราสามารถ return ค่าได้เลย จึงได้ฟังก์ชันสุดท้ายเป็น

def reverse_sequence(dna):

____return ''.join(reversed(list(dna)))

ครั้งนี้เราจะจบที่ฟังก์ชันนี้ก่อนนะครับ เราได้เรียนคำสั่งเพิ่มเติมหลายคำสั่ง สามารถเปลี่ยน string ให้เป็น list และเปลี่ยน list ให้เป็น string ได้ และได้เรียนรู้ concept เรื่อง chain of functions ได้แล้ว ฟังก์ชันใหม่ที่เราได้ มีแค่ 1 บรรทัดเท่านั้น ครั้งหน้า เราจะมาดูกันว่า function complement ที่เราได้เขียนไปนั้น จะปรับปรุงให้ดีขึ้นได้อย่างไร


3 comments:

  1. สวัสดีครับ
    สำหรัับ PDF version นะครับ สามารถเข้าไป download ได้ที่

    http://thaibioinfo-vol12.4shared.com

    สำหรับท่านที่ต้องการ download magazine ในฉบับก่อนหน้านี้ก็สามารถเปลี่ยนตัวเลข vol ใน url ข้างบนได้เลยครับ เช่น อยาก download ฉบับที่ 7 ก็เปลี่ยนเป็น

    http://thaibioinfo-vol07.4shared.com

    แบบนี้เลยครับ

    ReplyDelete
  2. ลองใช้ script นี้สำหรับการ reverse สิครับ

    ถ้า
    a = 'ATGCT'

    และต้องการ reverse string ของ a
    reverse_a = a[::-1]

    ใช้ script นี้จะไม่มีปัญหาเมื่อกลับ string ยาวๆครับ

    ReplyDelete
  3. @ Tanawat

    ขอบคุณครับ ผมลืมนึกไปเลยนะเนี่ย
    code ที่คุณ Tanawat ส่งมานี่ pythonic สุดครับ ไม่ต้องใช้ chain of function เลย
    ผมลืมไปว่าเรากำหนด step เวลา slice string ได้ ถ้ามีอะไรแนะนำอีก ยินดีครับ

    ReplyDelete