บล็อกเพื่อแลกเปลี่ยนเรียนรู้ สำหรับนักพัฒนาฟังก์ชันสำหรับ R ในประเทศไทย
สนับสนุนโดย สำนักงานกองทุนสนับสนุนการวิจัย (สกว)

03 พฤษภาคม 2548

การเขียน function ใน R

function หรือ คำสั่ง ใน R คือวัตถุชนิดหนึ่งที่ใช้สำหรับการทำงานต่างๆ เช่นคำสั่ง help เป็นต้น ในการออกคำสั่งให้ function ทำงาน ทำได้โดยการเขียนชื่อ function นั้น ตามด้วยวงเล็บ ซึ่งอาจจะมี argument หรือไม่มี argument อยูภายในก็ได้ เช่น help.start() ทำงานได้โดยไม่ต้องใส่ argument ใดๆ แต่ help() จะต้องมีชื่อของคำสั่งที่ต้องการดูความช่วยเหลือเป็น argument ด้วย เช่น help(pack)

การเขียน function ใช้เองใน R นั้นสามารถทำได้ง่าย เพียงแต่เรียนรู้สิ่งที่เกี่ยวข้องกับ function เพิ่มเติมอีกเล็กน้อยเท่านั้น ลองศึกษาจากขั้นตอนต่อไปนี้

1. function เป็นวัตถุ จึงต้องมีชื่อ และถูกสร้างด้วยคำสั่ง function ตามด้วยวงเล็บหนึ่งคู่ และวงเล็บปีกกาอีกหนึ่งคู่ ดังตัวอย่างการสร้าง myfun ดังนี้

> myfun <- function () {}

ตอนนี้เราจะได้ function ชื่อ myfun ที่ไม่ได้ทำงานอะไร และสามารถเรียกใช้ได้โดยเรียก myfun() บน R console ซึ่งจะได้ผลลัพธ์เป็น NULL คือไม่มีอะไรเป็นผลลัพธ์ ดังนี้

> myfun()
NULL

2. เขียนขั้นตอนการทำงานของ function ไว้ในวงเล็บปีกกา ดังตัวอย่างข้างต้น ต้องการให้ myfun พิมพ์ประโยคว่า This is my first function. ก็เขียนกำหนด myfun ใหม่ดังนี้

> myfun <- function () {
+ cat("This is my first function.\n")
+ }

สังเกตว่าเมื่อแยกเขียนวงเล็บปีกกาเปิดไว้ แล้วเคาะ Enter เพื่อจะเขียนข้อความบรรทัดต่อไป promt จะเปลี่ยนเป็นเครื่องหมาย + ในบรรทัดที่ 2 เขียนประโยค cat("This is my first function.\n") เพื่อเป็นการบอกให้พิมพ์ประโยค This is my first function. บน R console สังเกตว่าในที่นี้ปิดท้ายประโยคด้วย '\n' หมายความว่าให้ขึ้นบรรทัดใหม่หลังจากแสดงข้อความเสร็จแล้ว จากนั้นเคาะ Enter อีกครั้ง แล้วปิดด้วยวงเล็บปีกกาปิด คราวนี้เมื่อเรียกใช้งาน myfun จะได้ผลลัพธ์ดังนี้

> myfun()
This is my first function.

3. เขียนคำสั่งใน script file ดีกว่าเขียนตรงๆ บน R console เนื่องจากหากพิมพ์ผิดพลาด การกลับไปแก้จะยุ่งยาก ดังนั้นสร้างแฟ้ม myfun.R ไว้ใน working directory ของ R ที่กำลังทำงานอยู่ แล้วพิมพ์ส่วนของ function ไว้ในแฟ้มนั้น ดังนี้

myfun <- function () {
cat("This is my first function.\n")
}

เมื่อจะเรียกใช้งาน function ก็เรียกโดยการใช้คำสั่ง source โดยพิมพ์ใน R console ดังตัวอย่างข้างล่าง myfun จะถูกเรียกเข้ามาในหน่วยความจำของ R โดยไม่แสดงแต่อย่างใด แต่หลังจากถูกเรียกเข้ามาแล้ว ก็สามารถเรียกใช้ myfun() ได้ ดังตัวอย่าง

> source("myfun.R")
> myfun()
This is my first function.

ในที่นี้ ใช้รูปแบบการย่อหน้าที่นิยมในการเขียนภาษา C ซึ่งเป็นส่วนหนึ่งของที่มาหรือต้นกำเนิดของภาษา R คือเมื่อจะเขียนเครื่องหมายวงเล็บปีกกาเปิดและปิดแยกกัน ให้เขียนวงเล็บปีกกาเปิดไว้ท้ายประโยค หรือขึ้นบรรทัดใหม่ที่ตำแหน่งเดียวกันกับตัวอักษรแรกของชื่อ function และปิดวงเล็บปีกกาโดยขึ้นบรรทัดใหม่เปล่า โดยวางไว้ที่ตำแหน่งตรงกับตัวอักษรแรกของชื่อ function

myfun <- function () {
cat("This is my first function.\n")
}

หรือ

myfun <- function () 
{
cat("This is my first function.\n")
}

ที่นิยมกันเช่นนี้เนื่องจากในการเขียน function ที่ทำงานซับซ้อน เรามักจำเป็นต้องใช้เครื่องหมาย { } ซ้อนกันหลายครั้ง การเขียนในลักษณะเช่นนี้จะทำให้ติดตามได้ว่าวงเล็บปีกกาปิดอันไหนคู่กับวงเล็บปีกกาเปิดอันไหน จะได้ไม่สับสน จำไว้ว่ายอมเสียบรรทัดมากขึ้นดีกว่าสับสนว่าปิดวงเล็บครบทุกอันหรือยัง

ความนิยมอีกอย่างหนึ่งคือ ข้อความบรรทัดต่างๆ ภายในวงเล็บปีกกา จะย่อหน้าเข้าไปจากตำแหน่งของวงเล็บปีกกา 1 tab (3 ถึง 5 ตัวอักษร แล้วแต่จะตั้ง) และหากมีการใช้ { } ซ้อนอีก ก็ย่อหน้าเข้าไปอีก ดังตัวอย่างข้างล่าง ซึ่งจะทำให้การเขียนคำสั่งสวยงาม และติดตาม block ของคำสั่งได้ง่าย

do <- function (file, prompt.echo = ">>", from = 1, to = -1) 
{
script.version <- function(lns) {
if (substr(lns[1], 1, 1) != "#")
result <- try(eval(parse(text = lns[1]), envir = .GlobalEnv),
silent = TRUE)
if ((substr(lns[1], 1, 5) == "clean" | substr(lns[1],
1, 2) == "rm") & substr(lns[2], 1, 1) != "#")
result <- try(eval(parse(text = lns[2]), envir = .GlobalEnv),
silent = TRUE)
if (exists(".ec.version", env = .GlobalEnv)) {
version <- get(".ec.version", env = .GlobalEnv)
}
return(version)
}
...
if (any(match("package:epican12",search()))) detach(package:epican12)
library(epican)
assign(".ec.version", "1.3", env = .GlobalEnv)
}

4. ส่งผ่าน argument ในวงเล็บ ซึ่งหากมี argument หลายตัวก็แยกด้วยจุลภาค ',' ดังตัวอย่างข้างต้น เราอาจเปลี่ยน myfun ให้รับข้อความไปแสดงออกบน R console แล้วขึ้นบรรทัดใหม่ทันทีก็ได้ โดยแก้ไข myfun ในแฟ้ม myfun.R ดังนี้

myfun <- function (x) {
cat(x, "\n")
}

จากนั้นเรียก source("myfun.R") ใหม่อีกครั้ง myfun เดิมจะถูกแทนที่ด้วยคำสั่งที่แก้ไขใหม่แล้วนั้น จากนั้นเราสามารถส่งผ่านข้อความใน myfun ได้ดังนี้

> source("myfun.R")
> myfun("Here it is.")
Here it is.
>

5. function ใน R สามารถรับ argument ชนิดต่างๆ กันได้ โดยไม่จำกัด และไม่ต้องระบุชนิดเหมือนภาษาโปรแกรมอื่นบางภาษา ลองดูผลการส่งผ่านวัตถุชนิดต่างๆ เป็น argument เช่น ตัวเลข หรือเวกเตอร์ ดูผลลัพธ์ดังข้างล่าง

> myfun(2*5)
10
> x <- c(1,2,3,4,5,6)
> myfun(x)
1 2 3 4 5 6

6. เราสามารถระบุการทำงานของ function ให้แตกต่างกันตามคลาสของ argument ตัวแรกได้ ในข้อ 5 ข้างต้น function ทำงานโดยไม่พิจารณาถึงความแตกต่างของคลาสของ argument แต่เราสามารถทำให้ function ทำงานต่างกันตามคลาสของ argument ตัวแรกได้ คุณสมบัตินี้เรียกว่า polymorphism ของ function โดยมีขั้นตอนในการทำต่อไปนี้

ขั้นแรก ระบุวิธีการส่งผ่านร่วม โดยเขียนคำสั่ง UseMethod โดยระบุ string ของชื่อ function ร่วมนั้น (เขียนไว้ในเครื่องหมายคำพูด) ท้ายบรรทัดกำหนด function หรือจะขึ้นบรรทัดใหม่ก็ได้ จากนั้นเขียน function ใหม่โดยใช้ชื่อ function เดิมนั้น ตามด้วย '.' และชื่อคลาสของ argument โดยเขียนวิธีการทำงานให้เหมาะสมกับคลาสของ argument นั้นๆ และบางครั้ง หากการจัดการกับ argument ในคลาสอื่นนอกเหนือจากนั้นมีการทำงานที่เฉพาะออกไป ก็จำเป็นต้องมี function ที่เป็น '.default' เพื่อจัดการด้วย ดังตัวอย่าง

myfun <- function (x) 
UseMethod("myfun")

myfun.character <- function (x)
{
cat("The STRING is:", x, "\n")
}

myfun.numeric <- function (x)
{
cat("The NUMBER is:", x, "\n")
}

myfun.default <- function (x)
{
cat("The OBJECT is:", x, "\n")
}

จะเห็นได้ว่าเมื่อเรียก source("myfun.R") ใหม่อีกครั้ง myfun จะทำงานต่างกันตามแต่ argument ที่ส่งผ่านเข้าไป ดังนี้ี้

> myfun("Here it is.")
The STRING is: Here it is.
> myfun(3*4)
The NUMBER is: 12

7. เราสามารถระบุชื่อ function หลายชื่อให้ทำงานอย่างเดียวกันได้ สิ่งนี้มีประโยชน์ในการเรียก function ด้วยชื่อย่อ เพื่อให้พิมพ์คำสั่งเพียงสั้นๆ การทำงานทำได้โดยใช้ UseMethod อ้างไปถึง function เดียวกัน ดังตัวอย่าง
myfun <- function (x) 
UseMethod("myfun")

myf <- function (x)
UseMethod("myfun")

my <- function (x)
UseMethod("myfun")

myfun.character <- function (x)
{
cat("The STRING is:", x, "\n")
}
...
ซึ่งมีความหมายว่าเรากำหนดให้ชื่อ myfun, myf และ my เป็น function ที่ไปเรียกชุดวิธีการทำงานเดียวกัน คือ myfun.character, myfun.numeric และ myfun.default ดังนั้นเราสามารถเรียกคำสั่ง myfun ได้ด้วยตัวย่อได้อีกเป็น myf หรือ my ตามที่กำหนดนั่นเอง

สนับสนุนโดย สำนักงานกองทุนสนับสนุนการวิจัย (สกว)

Comments: แสดงความคิดเห็น

<< Home

This page is powered by Blogger. Isn't yours?