สำหรับคนทำงานสาย IT ช่วงหลายปีหลังมานี้น่าจะไม่มีใครไม่เคยได้ยินคำว่าข้อมูลขนาดใหญ่ (big data) แม้หลายคนจะคิดว่าคำดังกล่าวเป็นเพียงแค่คำแฟชั่นเท่ๆ (buzzword) เท่านั้น แต่ก็ปฏิเสธไม่ได้ว่าโลก IT ก้าวมาถึงขั้นที่ให้ความสำคัญกับการขุดหาความรู้ (data mining) จากข้อมูลที่มีอยู่กันซักพักแล้ว ส่วนก้าวต่อไปในโลก IT คงหนีไม่พ้นเครื่องจักรที่เรียนรู้ได้ (machine learning) และปัญญาประดิษฐ์ (artificial intelligence) การจะเข้าใจในศาสตร์เหล่านี้ได้ แค่เขียนโปรแกรมเป็นอย่างเดียวนั้นไม่เพียงพออีกต่อไป แต่ยังต้องรู้จักและจัดการกับข้อมูลเป็นอีกด้วย บทความนี้จะพาไปสัมผัสกับภาษา R ที่แม้จะมีรากฐานมาจากการใช้งานทางสถิติ แต่ภายหลังก็ปรับตัวมารองรับการคำนวณข้อมูลที่ซับซ้อนยิ่งขึ้น จนกลายเป็นหนึ่งในภาษาที่มาแรงไม่ควรมองข้าม ประวัติโดยย่อ R เป็นภาษาแบบเปิดซอร์ซที่ได้รับแรงบันดาลใจมาจากภาษา S โดยมีจุดประสงค์สำหรับการคำนวณและนำเสนอกราฟิกทางสถิติ มันถูกสร้างและเผยแพร่เป็นครั้งแรกในปี 1993 โดย Ross Ihaka และ Robert Gentleman ปัจจุบันตัวภาษาเดินทางมาถึงรุ่นที่ 3.4 แล้ว ด้วยความเฉพาะทางของมัน R จึงแตกต่างจากภาษาโปรแกรมสำหรับงานทั่วไป (general-purpose programming language) อย่างเช่น C, Python ถึงกระนั้น การจะเรียนรู้ R ก็ไม่จำเป็นต้องทิ้งความรู้จากภาษาอื่นๆ ไปเสียทั้งหมด เพียงแค่ให้ระวังความแตกต่างอันเป็นเอกลักษณ์จากภาษาใหม่นี้ก็พอแล้ว ซึ่งเอกลักษณ์สำคัญอย่างหนึ่งของภาษา R ก็คือการเป็นภาษาโปรแกรมเชิงอาเรย์ (array language) ที่เน้นการคำนวณข้อมูลพร้อมกันเป็นกลุ่ม แนวคิดนี้อาจฟังดูแปลกและไกลตัว แต่อันที่จริงแล้วมันอาจเป็นเรื่องใกล้ตัวที่หลายคนเคยผ่านหูผ่านตาจนชินด้วยซ้ำ นั่นก็คือ การคำนวณเซลล์หลายเซลล์ด้วยสูตรเดียวกันบนโปรแกรม Excel นั่นเอง เริ่มต้นใช้งานผ่าน REPL สำหรับใครที่คุ้นเคยแต่กับการเขียนภาษาที่ต้องคอมไพล์ก่อน เช่น C, Java จะพบว่าการทดลองคำนวณเล็กน้อยๆ ก่อนการเขียนโปรแกรมจริงนั้นเป็นเรื่องที่ยุ่งยากพอสมควร ในภาษาแบบสคริปต์หลายๆ ภาษาจึงมักมีเครื่องมือคำนวณแบบโต้ตอบที่เรียกว่า REPL (read-eval-print loop) แถมมาให้ โดยเครื่องมือนี้มีหลักการทำงานง่ายๆ 3 ขั้นตอนตามชื่อของมัน คือ อ่านคำสั่ง คำนวณค่าตามคำสั่งนั้นๆ แล้วแสดงผลลัพธ์กลับมาให้ผู้ใช้งานเห็นทันที ก่อนจะวนกลับไปรออ่านคำสั่งถัดไปซ้ำเรื่อยๆ นั่นเอง เครื่องมือดังกล่าวนอกจากจะช่วยให้ผู้ใช้สามารถเรียนรู้ภาษานั้นๆ ได้อย่างรวดเร็วแล้ว มันยังมีค่าเปรียบได้ดั่งกระดาษทด ที่เราสามารถจดบันทึกแนวคิดต่างๆ พร้อมคำนวณผลลัพธ์ออกมาดูความถูกต้องได้ทันทีทันใดอีกด้วย เราสามารถทดลองใช้ REPL ของภาษา R ได้ผ่านเว็บ R-Fiddle หรือ Jupyter ได้ทันที หรือถ้าใครติดตั้งโปรแกรมไว้ในเครื่องแล้ว ก็เพียงแค่เปิดโปรแกรม terminal/cmd ขึ้นมา พิมพ์ R (ใช้ตัวอักษรตัวใหญ่ ต่างจากคำสั่งโดยทั่วไปที่มักใช้ตัวเล็กทั้งหมด) แล้วกดปุ่ม <Enter> เมื่อเข้ามาแล้วเราจะพบกับข้อความต้อนรับที่บอกรายระเอียดว่ากำลังใช้โปรแกรมเวอร์ชันใด พร้อมทั้งแสดงคำสั่งพื้นฐานสำหรับช่วยเหลืออีกเล็กน้อย เลื่อนลงมาดูที่ด้านล่างสุดของหน้าจอ จะพบกับเครื่องหมาย > ที่ตามด้วยเคอร์เซอร์กะพริบรอรับคำสั่งที่เราพิมพ์เข้าไป เราอาจทดลองคำนวณการบวกเลขง่ายๆ เช่น 1+1 ได้ดังนี้ > 1+1 [1] 2 สังเกตว่าที่ด้านหน้าของผลลัพธ์ (เลข 2) จะมีตัวเลขในวงเล็บปีกแข็ง ([1]) แสดงอยู่ นี่เป็นตัวเลขที่จะแสดงเฉพาะในโหมด REPL เท่านั้น มันมีไว้เพื่อช่วยให้เรานับได้อย่างสะดวกว่า ค่าผลลัพธ์ที่เห็นเป็นตัวแรกในแถวนี้เป็นผลลัพธ์ตัวที่เท่าไหร่จากผลลัพธ์ทั้งหมด เราอาจทดลองคำนวณค่าอื่นๆ ด้วยตัวดำเนินการพื้นฐานทางคณิตศาสตร์ที่เราคุ้นเคยในภาษาทั่วไป อันได้แก่ บวก (+) ลบ (-) คูณ (*) หาร (/) หรือบางตัวก็แปลกกว่าภาษาอื่นบ้าง เช่น ยกกำลัง (^) หารเอาเศษ (%%) และหารปัดเศษทิ้ง (%/%) สังเกตว่า R จะมองว่าตัวเลขต่างๆ เป็นตัวเลขแบบจุดทศนิยมลอยตัว (floating point) โดยปริยาย ดังนั้นการคำนวณตัวเลขแปลกๆ มักจะไม่ส่งผลให้โปรแกรมต้องหยุดทำงานเนื่องจากมีข้อผิดพลาดเกิดขึ้น เช่น > 1/0 [1] Inf แต่นั่นก็ไม่ได้หมายความภาษา R จะเป็นภาษาที่เขียนโปรแกรมได้โดยไม่มีข้อผิดพลาดแต่อย่างใด เช่น ข้อผิดพลาดนี้ที่เกิดจากการพิมพ์คำสั่งผิด (syntax error) > (1+)*3 Error: unexpected ')' in "(1+)" เมื่อเจอข้อผิดพลาดเหล่านี้ก็ไม่ต้องตกใจ ขอเพียงแค่ค่อยๆ ทำความเข้าใจว่าข้อความดังกล่าวบอกว่าเราผิดพลาดตรงไหน แล้วก็แก้ไขข้อผิดพลาดนั้น โปรแกรมก็จะทำงานต่อไปได้แล้ว ส่วนในการทำงานจริง เราอาจลืมไปว่าฟังก์ชันที่ต้องการใช้นั้นเรียกใช้งานอย่างไร หรือประเภทข้อมูลที่ใช้มีสมบัติใดบ้าง เราสามารถเรียกเอกสารขึ้นมาอ่านได้ทันทีผ่านฟังก์ชัน help() (หรือจะใช้ทางลัดเป็นเครื่องหมาย ? เพียงตัวเดียวนำหน้าหัวข้อที่ต้องการก็ได้) help() # หน้าแรกเอกสาร help("+") # เอกสารเกี่ยวกับการดำเนินการตัวเลขพื้นฐาน help(numeric) # เอกสารเกี่ยวกับข้อมูลประเภทตัวเลข เมื่อต้องการออกจาก REPL สามารถพิมพ์ฟังก์ชัน q() จากที่ได้เกริ่นไปข้างต้นว่าการใช้งานโปรแกรม REPL นี้เปรียบได้ดั่งกระดาษทด โปรแกรมจะถามว่าเราต้องการเก็บบันทึกตัวแปรและคำสั่งต่างๆ บนกระดาษทดครั้งนี้ไว้เผื่อใช้ซ้ำในครั้งถัดไปหรือไม่ หากตอบว่าใช่ (y) ทุกครั้งนับจากนี้ที่เปิดโปรแกรม REPL จากตำแหน่งแฟ้มเดิมขึ้นมา เราจะสามารถเรียกใช้ตัวแปรที่เคยสร้างและย้อนดูคำสั่งที่เคยเรียกจากครั้งนี้ได้เสมอ ตัวแปรและประเภทข้อมูล แม้ภาษา R จะรับแนวคิดการเขียนโปรแกรมเชิงวัตถุ (object-oriented programming) ที่ทำให้เราสามารถดัดแปลงและสร้างประเภทข้อมูลชนิดใหม่ๆ ขึ้นมาได้ แต่โดยส่วนใหญ่เราจะวนเวียนอยู่กับข้อมูลพื้นฐานเพียงไม่กี่ประเภทเท่านั้น การทดสอบว่าข้อมูลดังกล่าวถูกจัดอยู่ในประเภทใด สามารถใช้ฟังก์ชัน class() เพื่อตรวจสอบประเภทของข้อมูลนั้นได้ เช่น > class("hello") [1] "character" แต่ก่อนที่เราจะไปดูว่าภาษา R มีประเภทข้อมูลที่น่าสนใจใดบ้าง จะขอกล่าวถึงเรื่องพื้นฐานที่น่าสนใจไม่แพ้กันอย่างการประกาศตัวแปรเสียก่อน ประกาศตัวแปร การประกาศตัวแปรในภาษา R จะใช้สัญลักษณ์ที่แตกต่างจากภาษาโปรแกรมโดยทั่วไป แต่ดูคุ้นเคยเมื่อเขียนเป็นสมการคณิตศาสตร์ ซึ่งก็คือสัญลักษณ์ลูกศรซ้าย (<-) นั่นเอง ดังเช่นตัวอย่างการประกาศตัวแปรเหล่านี้ > x <- 42 > y <- "We \u2764 R-lang" อย่างไรก็ตาม R ยังยอมให้ใช้สัญลักษณ์เท่ากับ (=) เพื่อประกาศตัวแปรได้เช่นกัน (แม้หลายที่เช่น Google จะไม่แนะนำวิธีนี้) ความแตกต่างคือสัญลักษณ์เท่ากับถูกออกแบบมาเพื่อใช้ประกาศพารามิเตอร์/อาร์กิวเมนต์ของฟังก์ชันเป็นหลัก การใช้สัญลักษณ์ลูกศรซ้ายเพื่อประกาศพารามิเตอร์/อาร์กิวเมนต์ฟังก์ชันอาจทำให้โปรแกรมทำงานผิดพลาดได้ อนึ่ง R ยังยินยอมให้ใช้สัญลักษณ์จุด . เป็นส่วนผสมในชื่อตัวแปรได้ สำหรับใครที่คุ้นชินกับภาษาอื่นที่ใช้จุดเพื่อคั่นแอตทรีบิวต์/เมธอด ให้ระวังว่าเมื่อพบตัวแปรมีจุดในภาษา R ไม่ได้หมายความว่าตัวแปรนั้นจะเป็นแอตทรีบิวต์ของวัตถุด้านหน้าจุดแต่อย่างใด เราสามารถขอดูตัวแปรทั้งหมดที่เราสร้างขึ้นมาได้ผ่านฟังก์ชัน ls() และทำลายตัวแปร ได้ผ่านฟังก์ชัน rm() ประเภทข้อมูลเดี่ยว ข้อมูลเดี่ยวคือข้อมูลขนาดเล็กที่สุดที่ไม่สามารถแบ่งย่อยลงไปได้อีก และไม่สามารถนำไปใช้คำนวณทางสถิติได้ ข้อมูลตัวเลข เนื่องจาก R เป็นภาษาที่เน้นการนำไปใช้งานทางสถิติเป็นหลัก การเก็บตัวเลขแบบจุดทศนิยมลอยตัว (ในภาษา R เรียกข้อมูลชนิดนี้ว่า numeric) จึงเป็นท่ามาตรฐานที่ R เลือกใช้ เพราะนำไปคำนวณต่อได้ง่าย อย่างไรก็ตาม R ยังมีข้อมูลตัวเลขอีกสองประเภทให้เลือกใช้เพิ่มเติมตามความเหมาะสมเช่นกัน ซึ่งก็คือ จำนวนเต็ม (integer) สร้างได้โดยเขียนห้อยตัว L ไว้ข้างหลังตัวเลข เช่น 100L จำนวนเต็มมีไว้สำหรับใช้ในงานที่ต้องการให้แน่ใจว่าการคำนวณต่างๆ เป็นแบบวิยุตคณิต (discrete mathematics) หรือใช้เพื่อบ่งบอกจำนวนการวนซ้ำ จำนวนเชิงซ้อน (complex) สร้างได้โดยเขียนห้อยตัว i ไว้ข้างหลังตัวเลข เช่น 1i, 3+4i จำนวนเชิงซ้อนมีไว้สำหรับงานทางด้านวิทยาศาสตร์-วิศวกรรมบางแขนง เช่น การคำนวณด้านแม่เหล็กไฟฟ้า พลศาสตร์ของไหล ลำดับของตัวเลขในภาษา R เรียงจากต่ำไปสูงได้แก่ จำนวนเต็ม จำนวนทศนิยม และจำนวนเชิงซ้อน หากมีการใช้งานตัวเลขคละประเภทกัน R จะแปลงตัวเลขที่อยู่ในชั้นต่ำกว่าขึ้นไปเป็นชั้นที่สูงกว่าเสมอ ข้อมูลตัวอักษร ภาษา R ไม่แบ่งแย่งข้อมูลตัวอักษรเป็นหลายระดับ (แบบภาษา C) แต่จะมองว่าตัวอักษรชุดหนึ่งไม่ว่าจะยาวเท่าใดคือหนึ่งข้อความเสมอ (แบบภาษา Python) และเรียกข้อมูลชนิดนี้ว่า character การประกาศข้อความสามารถใช้ได้ทั้งอัญประกาศเดี่ยว (') หรืออัญประกาศคู่ (") ล้อมรอบข้อความที่ต้องการโดยไม่มีความแตกต่างกันแต่อย่างใด ข้อมูลตรรกะ การแสดงข้อมูลตรรกะ (logical) ว่าถูกหรือผิด จะใช้คำว่า TRUE กับ FALSE ตามลำดับ ไม่สามารถใช้ตัวเลขทดแทนได้ อย่างไรก็ตาม หากต้องการประหยัดเวลาพิมพ์ในส่วนนี้ ก็สามารถป้อนแค่ตัวอักษรตัวแรก (T,F) เข้าไปได้เช่นกัน R ยังมีข้อมูลตรรกะแบบพิเศษอีกตัวหนึ่งเพิ่มเข้ามา คือ ข้อมูลว่าง (NA) ซึ่งจะเกิดขึ้นเมื่อพยายามเข้าถึงข้อมูลในตำแหน่งที่ไม่มีข้อมูล หรือพยายามคำนวณค่าทั้งที่ไม่มีข้อมูลอยู่ หมายเหตุว่า R จะไม่ถือว่าเกิดข้อผิดพลาดขึ้นจนต้องหยุดการคำนวณ แต่อาจขึ้นคำเตือนบางครั้งเมื่อพยายามทำงานบนข้อมูลที่ไม่สมบูรณ์นี้ ประเภทข้อมูลชุด ข้อมูลชุดคือข้อมูลที่เก็บรวบรวมข้อมูลเดี่ยวหลายๆ ตัวมาไว้ในที่เดียวกัน จึงทำให้สามารถนำไปวิเคราะห์เชิงสถิติต่อได้ เวกเตอร์ อย่างที่หลายคนน่าจะสังเกตเห็นตอนที่ทดลองคำสั่งบน REPL ไปแล้ว ว่าการคำนวณใดๆ ในภาษา R จะมีตัวเลขตำแหน่งกำกับที่ผลลัพธ์เสมอ นั่นหมายความว่า R ให้ความสำคัญกับการคำนวณตัวเลขหลายตัวพร้อมกันเป็นอย่างมาก อันที่จริงแล้วข้อมูลเดี่ยวทุกชนิดที่สร้างขึ้นในภาษา R จะถูกห่อหุ้มด้วยสิ่งที่เรียกว่าเวกเตอร์ (vector) ซึ่งเทียบในภาษาอื่นได้ว่าเป็นอาเรย์ที่มีความยาวไม่จำกัด แม้ว่าข้อมูลชุดนั้นจะมีความยาวเพียงแค่หนึ่งหน่วยก็ตาม การนำเวกเตอร์หลายชุดมารวมกัน จะเป็นการนำเวกเตอร์มาต่อกันเสมอ ซึ่งก็คือจะได้ผลลัพธ์เป็นเวกเตอร์เพียงชุดเดียวที่ไล่สมาชิกจากเวกเตอร์ตัวแรกไปจนจบ แล้วจึงไล่สมาชิกจากเวกเตอร์ตัวถัดไปเรื่อยๆ (ไม่ใช่ได้ผลลัพธ์เป็นเวกเตอร์ซ้อนเวกเตอร์แต่อย่างใด) โดยฟังก์ชันที่ทำหน้าที่ประกาศเวกเตอร์หรือนำเวกเตอร์มาต่อกันคือ c() ดังตัวอย่างต่อไปนี้ > x <- 1 > y <- c(2,3,4) > z <- c(x,y) > z [1] 1 2 3 4 การถามประเภทข้อมูลของเวกเตอร์ด้วยฟังก์ชัน class() จะไม่รับคำตอบกลับมาว่ามันคือเวกเตอร์ แต่จะได้รับคำตอบว่าสมาชิกทุกตัวในเวกเตอร์นี้เป็นข้อมูลเดี่ยวประเภทใด นี่ทำให้เมื่อนำเวกเตอร์ของข้อมูลคละชนิดกันมารวมกัน สมาชิกบางตัวจะถูกแปลงประเภทข้อมูลขึ้นไปเป็นแบบที่สามัญกว่าทันที > class(z) [1] "numeric" > mix <- c(1,"a",NA,T) > mix [1] "1" "a" NA "TRUE" > class(mix) [1] "character" การเข้าถึงสมาชิกในเวกเตอร์ สามารถใช้สัญลักษณ์วงเล็บปีกแข็งล้อมรอบตัวเลขตำแหน่งที่ต้องการ โดย R จะเริ่มนับสมาชิกตัวแรกด้วยเลขหนึ่ง (one-based indexing) และการเข้าถึงสมาชิกตัวที่สูงเกินกว่าขนาดของเวกเตอร์จะได้ผลลัพธ์เป็นข้อมูลว่าง เช่นนี้ > z[3] [1] 3 > z[10] [1] NA > length(z) [1] 4 นอกจากนี้ เรายังสามารถเข้าถึงข้อมูลย่อยพร้อมๆ กันหลายตัวได้ โดยส่งเวกเตอร์ของตำแหน่งทั้งหมดที่ต้องการเข้าไป ซึ่งสามารถเขียนย่อช่วงตำแหน่งที่ติดกันโดยใช้สัญลักษณ์โคลอน ) หรือจะส่งเวกเตอร์ตรรกะเพื่อบอกว่าจะเลือกสมาชิกตัวไหนบ้างก็ได้ ตามตัวอย่างต่อไปนี้ > z[c(2,4)] [1] 2 4 > z[1:3] [1] 1 2 3 > z[4:2] [1] 4 3 2 > z[c(T,F,F,T)] [1] 1 4 > z[c(T,F)] [1] 1 3 การคำนวณทางคณิตศาสตร์ระหว่างเวกเตอร์ จะเป็นการจับคู่สมาชิกแต่ละตัวในเวกเตอร์แล้วคำนวณตามเครื่องหมายคณิตศาสตร์ที่ให้ไว้ ถ้าเวกเตอร์มีขนาดไม่เท่ากัน เวกเตอร์ตัวที่สั้นกว่าจะถูกนำมาใช้จับคู่ซ้ำเรื่อยๆ นั่นเอง > z - 1 [1] 0 1 2 3 > z * c(1,-1) [1] 1 -2 3 -4 > z + c(4,5,6) [1] 5 7 9 8 Warning message: In z + c(4, 5, 6) : longer object length is not a multiple of shorter object length ข้อมูลชุดสำหรับคำนวณทางคณิตศาสตร์ หากเราต้องการเก็บข้อมูลที่เทียบเท่ากับอาเรย์สองมิติในภาษาอื่น ภาษา R จะเรียกข้อมูลชนิดนั้นว่าเมทริกซ์ (matrix) การจะสร้างข้อมูลชนิดนี้ได้ต้องเตรียมข้อมูลให้อยู่ในรูปของเวกเตอร์เสียก่อน แล้วจึงค่อยเปลี่ยนประเภทข้อมูลเป็นเมทริกซ์ ดังนี้ > A <- matrix(z, nrow=2, ncol=2) [,1] [,2] [1,] 1 3 [2,] 2 4 > B <- matrix(z, nrow=2, ncol=2, byrow=TRUE) [,1] [,2] [1,] 1 2 [2,] 3 4 สังเกตว่าที่ตัวเลขกำกับตำแหน่งผลลัพธ์ จะมีสัญลักษณ์ลูกน้ำ (,) เพิ่มเข้ามาด้วย นั่นบ่งบอกว่าการเข้าถึงสมาชิกในข้อมูลประเภทเมทริกซ์ จะต้องเขียนสัญลักษณ์ลูกน้ำกำกับด้วยเช่นกัน > A[1,2] [1] 3 > B[,2] [1] 2 4 > A[2:1,2:1] [,1] [,2] [1,] 4 2 [2,] 3 1 หากเรายังต้องการเก็บอาเรย์ที่มีมิติสูงขึ้นไปกว่านี้ ก็สามารถสร้างประเภทข้อมูลอาเรย์ได้ผ่านฟังก์ชัน array() โดยระบุขนาดในแต่ละมิติเข้าไป > array(1:24, c(4,3,2,1)) , , 1, 1 [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 7 11 [4,] 4 8 12 , , 2, 1 [,1] [,2] [,3] [1,] 13 17 21 [2,] 14 18 22 [3,] 15 19 23 [4,] 16 20 24 สังเกตว่าการดำเนินการทางคณิตศาสตร์โดยใช้เครื่องหมายธรรมดาทั่วไป (+,-,*,/) จะเป็นการนำสมาชิกในข้อมูลทั้งสองชุดมาจับคู่กันตัวต่อตัวแล้วดำเนินการทางคณิตศาสตร์แบบธรรมดาเท่านั้น หากต้องการดำเนินการทางคณิตศาสตร์แบบเมทริกซ์ ต้องใช้เครื่องหมายคณิตศาสตร์สำหรับเมทริกซ์โดยเฉพาะ (เช่น การคูณเมทริกซ์ %*%) ดังตัวอย่างต่อไปนี้ > A * B [,1] [,2] [1,] 1 6 [2,] 6 16 > A %*% B [,1] [,2] [1,] 10 14 [2,] 14 20 กรอบข้อมูล กรอบข้อมูล (data.frame) คือข้อมูลชุดแบบตารางเช่นเดียวกับตารางในฐานข้อมูล กล่าวคือ แต่ละหลักของตารางจะบอกว่าหลักนี้ใช้เก็บข้อมูลเรื่องใด ส่วนแต่ละแถวของตารางก็คือข้อมูลหนึ่งระเบียน (record) นั่นเอง วิธีสร้างกรอบข้อมูล เริ่มจากการเตรียมข้อมูลแต่ละหลักด้วยเวกเตอร์ โดยตรวจสอบให้แน่ใจว่าเวกเตอร์ทุกตัวมีความยาวเท่ากัน แล้วจึงนำมารวมกันด้วยฟังก์ชัน data.frame() ดังนี้ > handle <- c("Sheldon","Leonard","Penny") > year <- c(1980,1980,1985) > iq <- c(187,173,NA) > big.bang <- data.frame(handle, yob=year, iq) > big.bang handle yob iq 1 Sheldon 1980 187 2 Leonard 1980 173 3 Penny 1985 NA เนื่องจากรูปแบบการเก็บข้อมูลนี้ คล้ายคลึงกับการเก็บโดยเมทริกซ์ การเข้าถึงข้อมูลแต่ละแถว สามารถทำได้ในทำนองเดียวกันกับที่ทำบนเมทริกซ์ คือ > big.bang[3,] handle yob iq 3 Penny 1985 NA ส่วนการเข้าถึงข้อมูลแต่ละหลัก สามารถใช้เครื่องหมาย $ แล้วตามด้วยชื่อของเขตข้อมูลที่ต้องการได้เลย ซึ่งก็คือ > big.bang$yob [1] 1980 1980 1985 อนึ่ง หากข้อมูลในหลักซ้ายสุดของตารางมีค่าไม่ซ้ำกัน และเราต้องการค้นหาข้อมูลในแถวนั้นโดยใช้ค่าในหลักซ้ายสุดเป็นคำค้น (เทียบได้กับกุญแจหลักในฐานข้อมูล) ตอนสร้างกรอบข้อมูลให้กำหนดอาร์กิวเมนต์เพิ่มเข้าไปเล็กน้อย ดังนี้ > new.big.bang <- data.frame(row.names=handle, yob=year, iq) > new.big.bang yob iq Sheldon 1980 187 Leonard 1980 173 Penny 1985 NA > new.big.bang["Sheldon",] yob iq Sheldon 1980 187 นอกจากนี้ อย่าลืมว่าเราสามารถใช้เวกเตอร์ตรรกะซึ่งอาจได้มาจากการคำนวณ เพื่อกรองว่าต้องการใช้ข้อมูลแถวได้บ้างในกรอบข้อมูลนี้ ดังที่จะเห็นได้จากตัวอย่างถัดไป > use <- new.big.bang$yob == 1980 > use [1] TRUE TRUE FALSE > new.big.bang[use,] yob iq Sheldon 1980 187 Leonard 1980 173 สถิติและความน่าจะเป็นอย่างรวบรัด พูดอย่างหยาบๆ แล้ว สถิติคือวิชาที่เล่นกับข้อมูลจำนวนมาก ส่วนความน่าจะเป็นนั้นนำค่าทางสถิติคาดมาเดาอนาคต หลายต่อหลายครั้งสถิติและความน่าจะเป็นมักมาพร้อมกับการสุ่ม เช่น เมื่อเราเจอเหรียญที่ไม่สมดุลเหรียญหนึ่งจึงทดลองโยนดูค่า 100 ครั้ง พบว่าออกหัวถึง 80 ครั้ง เราอาจทำนายได้ว่าเมื่อโยนครั้งต่อไปควรคาดหวังว่าผลลัพธ์จะออกด้านหัวมากกว่าก้อย สิ่งสำคัญที่เรียกได้ว่าเป็นพระเอกของงานนี้ก็คือ "ข้อมูล" นั่นเอง หากขาดซึ่งข้อมูลแล้ว ไม่ว่าจะสร้างโมเดลทำนายความน่าจะเป็นมาดีขนาดไหนก็คงไร้ประโยชน์ ในหัวข้อนี้ เราอาจสร้างข้อมูลอย่างสุ่มขึ้นมาเพื่อทดลองหาค่าทางสถิติก่อนก็ได้ แต่จะขอแสดงวิธีนำเข้าข้อมูลจากภายนอก ซึ่งแม้บางขั้นตอนจะซับซ้อนอยู่บ้าง แต่ก็ดีกว่าค่อยๆ ป้อนตัวเลขเข้าไปทีละตัวเป็นแน่แท้ นำเข้าข้อมูล เนื่องจากการเข้าถึงไฟล์ข้อมูลทางสถิติต่างๆ ของประเทศไทยยังเป็นไปได้ยาก ตัวอย่างต่อไปนี้จะขอยืมข้อมูลจากต่างประเทศมาใช้ก่อน ซึ่งก็คือข้อมูลคะแนนเฉลี่ยของการสอบ SAT จากโรงเรียนระดับมัธยมในเมืองนิวยอร์ก (แนวคิดข้อสอบเทียบได้กับการสอบแอดมิชชั่นของไทย) โดยมีขั้นตอนดังนี้ เริ่มจากเข้าไปดาวน์โหลดข้อมูลดังกล่าวผ่าน Data.gov เลือกรูปแบบไฟล์ที่จะดาวน์โหลดเป็นแบบ CSV (comma separated values) เพื่อความสะดวกในการนำไปใช้ต่อ เมื่อดาวน์โหลดไฟล์ดังกล่าวเสร็จสิ้น อาจทดสอบความถูกต้องของข้อมูลด้วยการเปิดไฟล์ดังกล่าวผ่านโปรแกรมจำพวกสเปรดชีต เช่น Microsoft Excel, Google Sheets, LibreOffice Calc หรือจะเปิดผ่านโปรแกรมแก้ไขข้อความทั่วไปก็ได้ ต่อไปจะอ่านข้อมูลเข้ามาใช้ใน R โดยคร่าวๆ แล้วสามารถทำได้ดังนี้ (สำหรับ Windows ใช้เครื่องหมาย / เพื่อคั่นระหว่างโฟลเดอร์เช่นเดียวกัน) > data <- read.csv("PATH/TO/DOWNLOAD/SAT_Results.csv") อย่างไรก็ตาม หากเราลองดูข้อมูลตัวเลขที่อ่านเข้ามา จะพบว่าในหลักเดียวกันนั้น บางแถวก็ยังมีตัวอักษร "s" ปนอยู่ด้วย หากยังพอจำกันได้ การมีข้อมูลตัวอักษรปนกับข้อมูลตัวเลขนั้น จะทำให้ข้อมูลตัวเลขถูกแปลงไปเป็นตัวอักษรทันที ซึ่งอาจส่งผลให้วิเคราะห์ผิดพลาดในภายหลัง นี่เป็นสิ่งที่เราสามารถหลีกเลี่ยงได้โดยระบุว่าตัวอักษร "s" หมายถึงข้อมูลในช่องนั้นหายไป (ข้อมูลชุดอื่นๆ อาจใช้ตัวอักษรระบุค่าที่หายไปแตกต่างกัน เช่น "-", "n/a" เป็นต้น) > data <- read.csv("PATH/TO/DOWNLOAD/SAT_Results.csv", na.strings="s") นอกจากนี้แล้ว เรายังสามารถระบุเพิ่มเติมว่า ให้ใช้หลักแรกสุดเป็นกุญแจในการเข้าถึงข้อมูลในแถวนั้นๆ โดยเพิ่มอาร์กิวเมนต์ row.names เข้าไป > data <- read.csv("PATH/TO/DOWNLOAD/SAT_Results.csv", na.strings="s", row.names=1) สำหรับข้อมูลชุดนี้ การระบุอาร์กิวเมนต์ตามข้างต้นก็ถือว่าเพียงพอแล้ว เพราะเมื่อไม่สนใจค่า NA ทั้งหมดที่พบ R จะพยายามแปลงข้อมูลแต่ละแถวให้เป็นตัวเลข เพื่อให้นำไปใช้งานต่อในเชิงสถิติได้ทันที อย่างไรก็ตาม หากเราไม่มั่นใจว่า R จะแปลงประเภทข้อมูลได้ถูกต้อง เรายังสามารถบังคับประเภทของตัวแปรในแต่ละแถวได้เช่นกัน > data <- read.csv("PATH/TO/DOWNLOAD/SAT_Results.csv", na.strings="s", row.names=1, colClasses=c(rep("character",2), rep("numeric",4))) ท้ายสุด เราอาจเลือกลบแถวที่ข้อมูลขาดหาย (มีข้อมูลเป็น NA) ทิ้งไป เพื่อความสะดวกในการคำนวณข้อมูลในภายหลัง ซึ่งทำได้โดย > data <- na.omit(data) ภาพรวมข้อมูลทางสถิติ เมื่อต้องการเริ่มวิเคราะห์ข้อมูลชุดหนึ่งๆ การไล่ค่าทุกค่าในข้อมูลชุดนั้นออกมาแล้วทำความเข้าใจกับมันมักเป็นการกระทำที่เสียเวลาและไม่สมเหตุสมผล วิธีการที่มีประสิทธิภาพกว่าคือการดูข้อสรุปชิ้นเล็กๆ ที่ถูกสรุปออกมาจากข้อมูลชุดนั้น โดยหลักๆ แล้วเราอาจสนใจค่าสรุปอยู่ 2 แบบ ได้แก่ ตำแหน่งข้อมูล และการกระจายข้อมูล ตำแหน่งข้อมูล ตำแหน่งข้อมูลจะแสดงว่าข้อมูลดังกล่าวมีตำแหน่งสำคัญๆ ตรงไหนบ้าง เช่น ค่ามากสุด/น้อยสุด ค่ากลางข้อมูล และควอร์ไทล์ ค่ากลางข้อมูลคือค่าที่แสดงว่าข้อมูลชุดนั้นมีค่าลู่เข้าสู่ตรงกลางที่เท่าใด ซึ่งอาจแบ่งย่อยได้เป็นอีก 3 ประเภท คือ มัชฌิม (mean) หรือบางที่ก็เรียกว่าค่าเฉลี่ย (average) หาได้จากการนำทุกค่าในข้อมูลมาบวกรวมกัน แล้วจึงหารด้วยจำนวนข้อมูลที่ใช้ มัธยฐาน (median) หาได้จากการเรียงข้อมูลจากน้อยไปมากตามลำดับ แล้วดูว่าที่ตำแหน่งตรงกลางของข้อมูลมีค่าเป็นเท่าใด ฐานนิยม (mode) หาได้จากการนับว่าค่าใดปรากฏซ้ำมากที่สุดในข้อมูล ค่าทั้งสามนี้ไม่จำเป็นต้องมีค่าเท่ากัน ซึ่งก็ถือเป็นเรื่องปรกติเนื่องจากข้อมูลมักมีการกระจายตัวไม่สม่ำเสมอ ส่วนควอร์ไทล์จะใช้แนวคิดเดียวกันกับการหามัธยฐาน เพียงแต่แบ่งข้อมูลออกเป็นกลุ่มที่มีขนาดเท่ากันเพิ่มเป็น 4 กลุ่ม และสนใจค่าที่อยู่ตรงจุดเปลี่ยนระหว่างแต่ละกลุ่ม ดังนี้ ควอร์ไทล์ที่ 1 (1st quartile) คือค่าที่มีตำแหน่งอยู่ตรงกลางระหว่างค่าน้อยสุดและค่ามัธยฐาน ควอร์ไทล์ที่ 2 (2nd quartile) คือมัธยฐานนั่นเอง (และเรามักไม่ค่อยเรียกว่า "ควอร์ไทล์ที่ 2" เท่าใดนักเพราะสามารถเรียกชื่อที่พื้นฐานกว่าได้) ควอร์ไทล์ที่ 3 (3rd quartile) คือค่าที่มีตำแหน่งอยู่ตรงกลางระหว่างค่ามากที่สุดและค่ามัธยฐาน ใน R เราสามารถเรียกฟังก์ชัน summary() เพื่อดูภาพรวมตำแหน่งข้อมูลต่างๆ เหล่านี้ได้ทันที เช่น > summary(data$SAT.Math.Avg..Score) Min. 1st Qu. Median Mean 3rd Qu. Max. 312.0 371.0 395.0 413.4 437.0 735.0 สังเกตว่าผลลัพธ์ดังกล่าว ไม่ได้นำข้อมูลฐานนิยมมาแสดงด้วย เพราะโดยทั่วไปข้อมูลมักมีความแม่นยำของตำแหน่งทศนิยมสูงจนไม่มีข้อมูลชิ้นใดซ้ำกัน การหาฐานนิยมด้วยวิธีพื้นฐานมักไม่ให้ข้อสรุปที่เกิดประโยชน์เท่าไหร่ เราอาจเลี่ยงไปหาฐานนิยมด้วยการแบ่งข้อมูลเป็นช่วงๆ ผ่านฮิสโตแกรม ซึ่งจะกล่าวถึงในหัวข้อถัดไป การกระจายข้อมูล ข้อสรุปเดี่ยวๆ จากการวัดตำแหน่งตัวแทนข้อมูล อาจพอบอกแนวโน้มของข้อมูลส่วนใหญ่ได้บ้าง แต่สิ่งที่มันบอกไม่ได้เลยคือลักษณะการกระจายตัวของข้อมูล ลองพิจารณาตัวอย่างว่ามีร้านค้าอยู่สองร้าน ร้านทั้งสองมีจำนวนลูกค้าเข้าร้านเท่าๆ กันและมีรายได้จากการขายของใกล้เคียงกัน ดังนั้นร้านทั้งสองจึงขายของได้เฉลี่ยต่อลูกค้าเป็นจำนวนเงินไม่แตกต่างกันเท่าใดนัก มองผ่านๆ เราอาจคิดว่าร้านทั้งสองขายสินค้าคล้ายกันและตั้งราคาไม่หนีกัน แต่อันที่จริงแล้วข้อมูลเพียงเท่านี้ไม่สามารถบอกลักษณะความหลากหลายในช่วงราคาที่ลูกค้าจับจ่ายได้เลย เพราะร้านหนึ่งอาจขายของเฉพาะทางไม่กี่อย่างในช่วงราคาแคบๆ ส่วนอีกร้านอาจขายตั้งแต่สากกระเบือยันเรือดำน้ำก็เป็นได้ แต่การวัดตำแหน่งข้อมูลก็ไม่ได้ไร้ประโยชน์เสียทีเดียว เพราะเมื่อนำเอาข้อสรุปหลายๆ ชิ้นมารวมกัน ก็อาจพอบอกถึงการกระจายข้อมูลได้บ้าง เช่น พิสัย (range) บอกถึงขนาดของช่วงที่เล็กที่สุดที่สามารถครอบคลุมข้อมูลได้ทั้งหมด คำนวณจากการนำค่ามากที่สุดลบน้อยที่สุดได้เลย พิสัยระหว่างควอร์ไทล์ (interquartile range) คำนวณจากการนำค่าของควอร์ไทล์ที่ 3 ลบด้วยค่าของควอร์ไทล์ที่ 1 หรือกล่าวอีกนัยหนึ่งได้ว่า วิธีนี้คือเป็นหาพิสัยที่ครอบคลุมข้อมูลจำนวนครึ่งหนึ่งที่กระจายรอบมัธยฐานนั่นเอง นอกจากนี้ ยังมีการวัดการกระจายข้อมูลอีกวิธีหนึ่งที่เป็นที่นิยม ซึ่งก็คือส่วนเบี่ยงเบนมาตรฐาน (standard deviation, สัญลักษณ์ σ) จุดเด่นของวิธีนี้คือการใช้ข้อมูลทุกตัวมาคำนวณ แทนที่จะอ้างอิงจากค่าสรุปทางสถิติเพียงไม่กี่ค่า (ขอละการอธิบายสมการเนื่องจากมีรายละเอียดค่อนข้างมาก) การวัดการกระจายด้วยวิธีนี้จะใช้ได้ผลดีที่สุดเมื่อข้อมูลมีรูปร่างเป็นการแจกแจงปรกติ (normal distribution) ซึ่งบอกเป็นนัยว่ามีข้อมูลที่อยู่ห่างจากค่าเฉลี่ยไม่เกิน ±1σ อยู่ถึง 68% อย่างไรก็ตาม ข้อมูลที่มีการแจกแจงรูปแบบอื่นจะไม่สามารถสรุปขนาดของการแจกแจงเช่นนี้ได้ ฟังก์ชันสำหรับคำนวณการกระจายข้อมูลในภาษา R ได้แก่ > range(data$SAT.Math.Avg..Score) [1] 312 735 > IQR(data$SAT.Math.Avg..Score) [1] 66 > sd(data$SAT.Math.Avg..Score) [1] 64.68466 พล็อตข้อมูล จุดแข็งสำคัญของภาษาที่เน้นการใช้งานเชิงสถิติอย่าง R คงหนีไม่พ้นความเรียบง่ายแต่ก็ทรงพลังในการพล็อตข้อมูลขึ้นมาดู ยิ่งเมื่อคำนึงว่ามนุษย์เราสามารถรับรู้รูปทรงได้รวดเร็วกว่าการดูตัวเลขเป็นจำนวนมหาศาลแล้ว การพล็อตข้อมูลนอกจากจะช่วยให้เห็นภาพว่าข้อมูลโดยรวมมีหน้าตาเป็นยังไงแล้ว มันยังช่วยให้เราไม่เข้าใจข้อมูลผิดไปเพียงเพราะเลือกดูแค่ค่าเฉลี่ยที่สรุปมาจากข้อมูลทั้งหมดเท่านั้น ดังเช่นทวีตต่อไปนี้ที่แสดงให้เห็นว่าข้อมูลจริงสามารถแตกต่างกันได้มากแม้จะมีข้อสรุปทางสถิติไม่ต่างกัน New #chi2017 paper is up. Don't trust statistics alone, visualize your data!https://t.co/amnbAYvsq1 pic.twitter.com/1s6vkge6dl — Justin Matejka (@JustinMatejka) May 1, 2017 เพื่อความรวดเร็วและถูกต้องของข้อมูล ในหัวข้อนี้เราจะใช้ข้อมูลตัวอย่างที่ภาษา R แถมมา สำหรับสร้างภาพการพล็อตทั้งหมด พล็อตประชันทุกเขตข้อมูล เมื่อเราเริ่มทำงานกับข้อมูลชุดใหม่ที่ยังไม่เคยเห็นภาพมาก่อน เราอาจไม่รู้เลยว่าความสัมพันธ์ของแต่ละเขตข้อมูลเป็นอย่างไรบ้าง ดังนั้นสิ่งที่เราควรเริ่มทำเป็นอย่างแรก คือการมองภาพความสัมพันธ์ทั้งหมดอย่างหยาบๆ ก่อน การพล็อตประชันทุกเขตข้อมูล คือการจับเอาข้อมูลทุกๆ คู่ในแต่ละหลักของกรอบข้อมูลมาประชันกัน แล้วพล็อตแต่ละคู่ของข้อมูลแบบกระจาย (scatter plot) การพล็อตนี้สามารถทำได้ผ่านฟังก์ชันง่ายๆ คือ plot() ตัวอย่างต่อไปนี้จะยืมข้อมูลดอกไอริสของ Edgar Anderson มาใช้ โดยข้อมูลดังกล่าวสามารถเรียกใช้งานได้ทันทีเลยในภาษา R โดยไม่ต้องโหลดไลบรารีใดเพิ่ม > iris Sepal.Length Sepal.Width Petal.Length Petal.Width Species 1 5.1 3.5 1.4 0.2 setosa 2 4.9 3.0 1.4 0.2 setosa ... 150 5.9 3.0 5.1 1.8 virginica > plot(iris) ภาพที่ 1: พล็อตประชันทุกเขตข้อมูลของข้อมูลดอกไอริส พล็อตกระจาย (Scatter Plot) จากภาพที่ 1 เราอาจเริ่มต้นให้ความสนใจที่ความยาวกลีบดอก (Petal.Length) ต่อความกว้างกลีบดอก (Petal.Width) เนื่องจากเมื่อดูอย่างหยาบๆ แล้วน่าจะสร้างโมเดลได้ง่ายที่สุด เราจะเลือกพล็อตกระจายโดยใช้เขตข้อมูลเพียง 2 เขตนี้เท่านั้น เพื่อความสะดวกเราจะบอก R ก่อนว่ากำลังใช้ข้อมูลชุดใดเป็นหลักผ่านฟังก์ชัน attach() แล้วจึงสั่งพล็อตแบบเฉพาะเจาะจงด้วยสมการดังนี้ > attach(iris) > plot(Petal.Width ~ Petal.Length) ภาพที่ 2: พล็อตกระจายข้อมูลดอกไอริส โดยสนใจความกว้างต่อความยาวกลีบดอก หมายเหตุว่า Petal.Width ~ Petal.Length ไม่ใช่การนำตัวแปรสองตัวมาดำเนินการกันแต่อย่างใด แต่มันคือการประกาศตัวแปรแบบสมการในภาษา R ที่มีความมหัศจรรย์ตรงที่ไม่ต้องใช้เครื่องหมายอัญประกาศมาครอบเพื่อบอกขอบเขตของสมการเฉกเช่นการประกาศข้อความ อนึ่ง ในอนาคตเมื่อไม่ต้องการใช้ข้อมูลชุดนี้แล้ว (เช่น หลังจบหัวข้อนี้) ควรเรียกฟังก์ชัน detach() ในทำนองเดียวกันกับที่เรียก attach() ทุกครั้ง เพื่อไม่ให้เกิดความสับสนจากการที่มีข้อมูลจำนวนหลายชุดมากเกินกว่าที่ต้องการใช้งาน และเพื่อป้องกันกรณีที่มีข้อมูลต่างชุดกันแต่ใช้ชื่อเขตข้อมูลซ้ำกัน ลากโมเดลเชิงเส้น จากภาพที่ 2 เราอาจต้องการหาโมเดลเชิงเส้นของข้อมูลดังกล่าว เพื่อทำนายแนวโน้มว่าหากมีข้อมูลใหม่ๆ เพิ่มขึ้นมา ควรคาดหวังว่าข้อมูลเหล่านั้นจะเกิดบริเวณไหน การคำนวณดังกล่าวสามารถทำได้ผ่านฟังก์ชัน lm() ดังนี้ > lm(Petal.Width ~ Petal.Length) Call: lm(formula = Petal.Width ~ Petal.Length, data = iris) Coefficients: (Intercept) Petal.Length -0.3631 0.4158 หากต้องการลากเส้นดังกล่าวลงไปบนพล็อต ขอให้แน่ใจก่อนว่ามีภาพของพล็อตเดิมอยู่ก่อนแล้ว หลังจากนั้นจึงค่อยเรียกฟังก์ชัน abline() เพื่อลากเส้นตรงจากสัมประสิทธิ์ที่คำนวณได้ ดังนี้ > plot(Petal.Width ~ Petal.Length) > abline(lm(Petal.Width ~ Petal.Length)) ภาพที่ 3: โมเดลเชิงเส้นของความกว้างต่อความยาวกลีบดอก ลากโมเดลไม่เชิงเส้น อย่างไรก็ตาม หากข้อมูลที่สนใจมีแนวโน้มว่าจะไม่เป็นเชิงเส้น ภาษา R ก็มีวิธีการคำนวณโมเดลแบบต่างๆ ให้เลือกใช้ได้หลายวิธี สำหรับวิธีที่ง่ายต่อการเรียกใช้งานที่สุด คือการประมาณค่าถดถอยแบบท้องถิ่น (local regression หรือ LOESS) ซึ่งจะสร้างโมเดลสมการพหุนามโดยอาศัยข้อมูลแต่ละส่วนในบริเวณใกล้เคียงมาปรับค่า เราสามารถเรียกดูผลลัพธ์จุดตัดต่างๆ บนเส้นโค้งของโมเดลที่คำนวณได้ดังนี้ > loess.smooth(Petal.Length, Petal.Width) $x [1] 1.000000 1.120408 1.240816 1.361224 1.481633 ... $y [1] 0.06892099 0.11545407 0.16200259 0.20869061 0.25544166 ... ส่วนการพล็อตเส้นโค้งดังกล่าว จะเปลี่ยนมาเรียกผ่านฟังก์ชัน lines() ที่จะลากเส้นตรงเชื่อมระหว่างจุดตัดต่างๆ ตามที่ระบุ แต่เนื่องจากเรามีจุดตัดที่คำนวณไว้เป็นจำนวนมาก จึงได้ผลลัพธ์ออกมาเปรียบเสมือนเส้นโค้งนั่นเอง > plot(Petal.Width ~ Petal.Length) > lines(loess.smooth(Petal.Length, Petal.Width)) ภาพที่ 4: โมเดลไม่เชิงเส้นของความกว้างต่อความยาวกลีบดอก ขอให้สังเกตการสลับที่ของตัวแปร สำหรับบรรทัดบนที่ระบุสมการผ่านเครื่องหมาย ~ นั้น จะมีความหมายว่าฝั่งซ้ายของ ~ คือข้อมูลในแกนตั้ง หรืออาจมองเป็นสมการคณิตศาสตร์ที่คุ้นตาโดยเปลี่ยนเครื่องหมาย ~ เป็น = ในทำนองเดียวกันกับ y=x นั่นเอง ส่วนในบรรทัดล่างเนื่องจากเราไม่สามารถระบุสมการด้วยเครื่องหมาย ~ ได้ จึงต้องส่งค่าเข้าไปเป็นข้อมูลในแกนนอน (x) แล้วจึงตามด้วยแกนตั้ง (y) ตามลำดับ ลากโมเดลอิสระจากสมการที่กำหนดเอง หากว่าเรายังไม่พอใจกับโมเดลแบบต่างๆ ที่ภาษา R มีให้เลือก เราอาจคำนวณหาสมการด้วยตนเองแล้วนำไปพล็อตทดสอบกับข้อมูลจริงได้ดังนี้ > plot(Petal.Width ~ Petal.Length) > curve(exp(x-4/3*pi), add=TRUE) ภาพที่ 5: โมเดลจากสมการ ex-(4/3)π จุดที่น่าสนใจคือเราสามารถเขียนสมการที่ประกอบขึ้นมาจากพจน์ x แล้วส่งเป็นอาร์กิวเมนต์แรกของฟังก์ชัน curve() ได้ตรงๆ โดยที่เราไม่จำเป็นต้องมีตัวแปร x มาก่อน (หรือหากมีตัวแปรนี้อยู่ มันจะไม่ถูกใช้ในการคำนวณนี้แต่อย่างใด) นี่เป็นอีกหนึ่งในความมหัศจรรย์ของภาษา R ที่พยายามเอาใจผู้ใช้ที่มีพื้นฐานมาจากฝั่งคณิตศาสตร์/สถิติ ซึ่งอาจจะดูผิดปรกติไปบ้างเมื่อเทียบกับภาษาคอมพิวเตอร์อื่นๆ แต้มสีแยกกลุ่มข้อมูล อนึ่ง จากข้อมูลตั้งต้นของดอกไอริส นอกจากจะมีข้อมูลเชิงตัวเลขที่บ่งบอกลักษณะดอกไม้แล้ว ยังมีข้อมูลการแบ่งกลุ่มดอกไม้ตามสปีชีส์อีกด้วย ซึ่งข้อมูลการแบ่งกลุ่มนี้อาจไม่เหมาะสมในการพล็อตเทียบกับข้อมูลตัวเลขซักเท่าไหร่ (ดังจะเห็นได้จากแถวล่างสุด/แถบขวาสุดในภาพที่ 1) แต่จะเหมาะสมกว่าหากพล็อตข้อมูลตัวเลขเทียบกันเช่นเดิม แล้วเพิ่มการแบ่งกลุ่มเข้าไปเป็นอีกแกนหนึ่ง เช่น แต้มสีจุดของแต่ละกลุ่มให้แตกต่างกัน ซึ่งสามารถสั่งได้ดังนี้ > plot(Petal.Width ~ Petal.Length, col=Species) ภาพที่ 6: ข้อมูลดอกไอริสโดยแยกสีตามสปีชีส์ ทั้งนี้ทั้งนั้น หากข้อมูลตั้งต้นไม่ได้ถูกแบ่งกลุ่มไว้ก่อน แต่เรายังต้องการจัดกลุ่มข้อมูลอยู่ อาจเลือกใช้วิธี k-means ในการแบ่งกลุ่มข้อมูล โดยมีหลักการคร่าวๆ ดังนี้ สุ่มเลือกจุดศูนย์กลางของแต่ละกลุ่มมาทั้งหมด k จุด กำหนดว่าจุดข้อมูลต่างๆ อยู่ในกลุ่มใด โดยพิจารณาจากจุดข้อมูลนั้นอยู่ใกล้จุดศูนย์กลางใดมากที่สุด คำนวณย้ายค่าจุดศูนย์กลางของแต่ละกลุ่มให้เป็นค่าเฉลี่ยของทุกจุดข้อมูลในกลุ่ม ทำซ้ำขั้นตอนที่ 2-3 จนกว่าการจัดกลุ่มจะเสถียร เนื่องจาก R เตรียมฟังก์ชันดังกล่าวไว้ให้แล้ว วิธีเรียกใช้จึงไม่ยุ่งยากอะไร ขอเพียงแค่เตรียมข้อมูลสำหรับการจัดกลุ่ม และจำนวนกลุ่มที่ต้องการจัดไว้ให้เรียบร้อย แล้วจึงป้อนคำสั่งเหล่านี้ลงไป > group <- kmeans(iris[3:4], 3) > plot(Petal.Width ~ Petal.Length, col=group$cluster) ภาพที่ 7: ข้อมูลดอกไอริสที่จัดกลุ่มแยกสีด้วยวิธี k-means เป็นจำนวน 3 กลุ่ม หมายเหตุ เนื่องจากอัลกอริทึม k-means เริ่มต้นด้วยการสุ่ม อีกทั้งข้อมูลบางชุดก็ไม่สามารถแบ่งกลุ่มได้อย่างชัดเจน การแบ่งกลุ่มแต่ละครั้งด้วยวิธีดังกล่าวอาจให้ผลลัพธ์ที่แตกต่างกัน พล็อตข้อสรุปการแจกแจงข้อมูล เราได้เห็นการพล็อตกระจายที่พล็อตทุกๆ จุดจากข้อมูลที่มีไปแล้ว แต่บางครั้งเราก็ต้องการพล็อตแค่ภาพรวมของข้อมูลเท่านั้น วิธีการพล็อตที่ตอบโจทย์ดังกล่าวและเป็นที่นิยม คือ ฮิสโตแกรมและพล็อตกล่อง ฮิสโตแกรม (histogram) ฮิสโตแกรมเป็นหนึ่งในวิธีพื้นฐานสำหรับแสดงการแจกแจงข้อมูล โดยมันจะแสดงแท่งสี่เหลี่ยมที่ความสูงต่างกัน แต่ละแท่งบอกว่ามีข้อมูลจำนวนเท่าใดที่เกิดขึ้นในช่วงนั้นๆ > hist(Sepal.Width) ภาพที่ 8: ฮิสโตแกรมความกว้างกลีบเลี้ยงดอกไอริส จากภาพที่ 8 จะเห็นว่าแท่งที่สูงที่สุดคือแท่งที่ 5 จากทางซ้าย หรือเมื่อดูเป็นช่วงจะได้ว่าคือช่วงที่ข้อมูลมีค่ามากกว่า 2.8 แต่ไม่เกิน 3.0 ซึ่งจะได้ว่าช่วงนี้คือฐานนิยมนั่นเอง เราสามารถเปลี่ยนจำนวนรอยต่อระหว่างช่วงที่ต้องการได้ ด้วยการระบุอาร์กิวเมนต์ breaks เพิ่มเข้าไปดังนี้ > hist(Sepal.Width, breaks=4) ภาพที่ 9: ฮิสโตแกรมความกว้างกลีบเลี้ยงดอกไอริส โดยขีดเส้นแบ่งช่วง 4 ครั้ง หรือเราอาจระบุตำแหน่งทั้งหมดของรอยต่อระหว่างช่วงที่ต้องการแบ่งก็ได้ โดยเรามีอิสระที่ไม่จำเป็นต้องเลือกให้แต่ละช่วงมีขนาดเท่ากัน > hist(Sepal.Width, breaks=c(2,2.1,2.4,2.7,3.0,3.3,3.6,3.9,4.4)) ภาพที่ 10: ฮิสโตแกรมความกว้างกลีบเลี้ยงดอกไอริส โดยแบ่งขนาดช่วงไม่เท่ากัน หากแต่ละแท่งในฮิสโตแกรมมีความกว้างไม่เท่ากัน (เช่น ในภาพที่ 10) แกนแนวตั้งในพล็อตจะไม่ได้แสดงจำนวนการเกิดของข้อมูลอีกต่อไปแล้ว แต่มันคือค่าความหนาแน่นของข้อมูลในแต่ละช่วงแทน โดยใช้กฎว่าพื้นที่ของสี่เหลี่ยมทุกแท่ง (ขนาดช่วงคูณความหนาแน่น) รวมกันจะต้องได้เท่ากับหนึ่งหน่วยเสมอ ดังนั้นหากเราขยายขนาดของช่วงโดยที่มันยังครอบคลุมข้อมูลได้เป็นจำนวนเท่าเดิม แท่งข้อมูลแทนช่วงนั้นก็จะมีความสูงลดลงไปนั่นเอง (เช่น ขยายขนาดของช่วงสุดท้ายจาก 3.9-4.4 ไปเป็น 3.9-5.0) พล็อตกล่อง (box plot) พล็อตกล่องนั้นไม่ได้แสดงจำนวนการเกิดของข้อมูลเป็นช่วงๆ เหมือนอย่างฮิสโตแกรมแล้ว แต่จะแสดงข้อมูลด้วยค่าสรุปจากควอร์ไทล์แทน ลองพิจารณาตัวอย่างต่อไปนี้ > boxplot(Sepal.Length) ภาพที่ 11: พล็อตกล่องความกว้างกลีบเลี้ยงดอกไอริส ผลลัพธ์ที่ได้จากพล็อตกล่องในภาพที่ 11 มีลักษณะเป็นกล่องสี่เหลี่ยมที่ลอยอยู่และมีขายื่นออกมาข้างบนและล่าง เส้นหนาที่ลากผ่านกลางกล่องคือมัธยฐาน ขอบล่างของกล่องคือควอร์ไทล์ที่ 1 และขอบบนของกล่องคือควอร์ไทล์ที่ 3 สำหรับขาแต่ละข้างที่ยื่นออกมาจากตัวกล่องนั้น สุดปลายขาล่างคือค่าต่ำสุด และสุดปลายขาบนคือค่าสูงสุดนั่นเอง แม้ว่าพล็อตกล่องจะแสดงการกระจายได้ไม่ละเอียดเท่าฮิสโตแกรม แต่ข้อได้เปรียบของพล็อตกล่องก็คือมันสามารถแสดงข้อมูลหลายๆ ชุดเปรียบเทียบกันได้ง่ายกว่า ดังจะเห็นได้ในภาพถัดไป > boxplot(iris[1:4]) ภาพที่ 12: พล็อตกล่องข้อมูลตัวเลขทั้งหมดของดอกไอริส การเปรียบเทียบการกระจายในภาพที่ 12 แสดงให้เห็นชัดว่า ความกว้างกลีบเลี้ยง (Sepal.Width) มีการกระจายตัวต่ำที่สุด และในทางกลับกัน ความยาวกลีบดอก (Petal.Length) ก็มีการกระจายตัวมากที่สุด นอกจากนี้มันยังบอกตำแหน่งข้อมูลได้อีกด้วยว่า ความยาวกลีบเลี้ยง (Sepal.Length) มีค่ามากกว่าความกว้างกลีบดอก (Petal.Width) เสมอ จุดเด่นอีกอย่างของพล็อตกล่อง คือมันสามารถแยกให้เห็นค่าผิดปรกติ (outlier) ได้ง่าย โดยแสดงเป็นจุดที่อยู่ไกลเลยขากล่องไปนั่นเอง หลักเกณฑ์สำหรับคัดแยกค่าผิดปรกติ คือ ค่านั้นมีค่าน้อยกว่า/มากกว่าค่าจากควอร์ไทล์ที่ 1/ควอร์ไทล์ที่ 3 อยู่ 1.5 เท่าของพิสัยระหว่างควอร์ไทล์ตามลำดับ ซึ่งอาจตีความหมายได้ว่า ค่านั้นเกิดจากการจดบันทึกข้อมูลผิดเพี้ยน และควรลบมันทิ้งก่อนนำข้อมูลไปประมวลผลต่อไป เขียนโปรแกรมด้วย R จากหัวข้อก่อนๆ เราอาจสัมผัสได้ถึงพลังของ R ในการใช้งานคำนวณโต้ตอบแบบกระดาษทด (REPL) ไปแล้ว แต่ในการใช้งานที่ซับซ้อนขึ้นไปนั้น การพิมพ์คำสั่งใหม่ทุกครั้งที่ต้องการคำนวณอาจไม่สะดวกเท่าไหร่นัก การจัดเรียบเรียงคำสั่งต่างๆ เหล่านั้นลงในไฟล์ แล้วเรียกใช้งานเป็นโปรแกรมแยกที่ทำงานทุกอย่างด้วยตัวเอง หรือนำเข้าฟังก์ชันสำคัญจากไฟล์นั้นมาใช้งานต่อ จึงเป็นทางเลือกที่มีประสิทธิภาพกว่าการพิมพ์คำสั่งใหม่ทุกครั้งเสมอ เราจะแสดงการเขียนโปรแกรมในภาษา R โดยยกตัวอย่างผ่านปัญหานักสะสมคูปอง ที่ถามคำถามง่ายๆ แต่น่าสนใจว่า นักสะสมคูปองต้องเปิดซองเสี่ยงโชคกี่ครั้งถึงจะเก็บคูปองที่สุ่มแจกได้ครบทุกแบบ หมายเหตุว่าสำหรับหัวข้อนี้ โค้ดที่แสดงจะไม่มีสัญลักษณ์ > นำหน้า ซึ่งก็คือทุกคำสั่งต่อไปนี้ไม่ได้สั่งงานผ่าน REPL และรอดูคำตอบแล้ว แต่เป็นการเขียนคำสั่งต่างๆ เหล่านี้ลงไปในไฟล์สคริปต์แทน ใครที่ต้องการทดลองตามขอให้สร้างไฟล์ใหม่ชื่อ coupon.R แล้วคัดลอกโค้ดต่างๆ ที่กำลังจะปรากฏในหัวข้อนี้ลงไปยังไฟล์ดังกล่าว ประกาศฟังก์ชัน ฟังก์ชันในภาษา R นั้น เมื่อมองอย่างผิวเผินแล้วอาจดูไม่แตกต่างจากฟังก์ชันในภาษาอื่น การประกาศฟังก์ชันทำได้โดยใช้คำสำคัญว่า function ตามด้วยพารามิเตอร์ แล้วจบด้วยเนื้อหาในฟังก์ชันนั้นๆ สิ่งสำคัญที่ต่างออกไปจากภาษาอื่น คือการตั้งชื่อให้ฟังก์ชันจะต้องทำด้วยวิธีเดียวกันกับการประกาศตัวแปรเท่านั้น random.int <- function(n) { sample.int(n, 1) } โค้ดข้างต้นสร้างฟังก์ชัน random.int สำหรับสุ่มเลขจำนวนเต็มระหว่าง 1 ถึง n ขึ้นมาหนึ่งตัว ข้อสังเกตสำหรับการเขียนเนื้อหาฟังก์ชัน นั่นก็คือตอนจบฟังก์ชันไม่จำเป็นต้องสั่ง return เพื่อบอกว่าต้องการคืนค่าใดจากฟังก์ชัน แต่คำสั่งสุดท้ายในฟังก์ชันจะถูกส่งคืนค่าโดยอัตโนมัติ วนซ้ำจนกว่าจะผิดเงื่อนไข (while loop) การวนซ้ำจนกว่าจะผิดเงื่อนไข ประกาศโดยคำสำคัญ while และมีโครงสร้างเฉกเช่นภาษาทั่วไป random.coupon <- function(...) { count <- 0 have.coupon <- logical(...) while (!all(have.coupon)) { have.coupon[random.int(...)] <- TRUE count <- count + 1 } count } โค้ดข้างต้นสร้างฟังก์ชัน random.coupon สำหรับนับว่าต้องเปิดซองชิงโชคกี่ครั้ง ถึงจะได้คูปองครบ n แบบ โดยใช้เทคนิคการวนซ้ำแบบ while ในบรรทัดที่ 4 ถึง 7 ข้อสังเกตที่มาคู่กับการวนซ้ำเช่นนี้ คงหนีไม่พ้นการเพิ่มค่าตัวนับในแต่ละรอบที่วน ในภาษาทั่วไปอาจใช้เครื่องหมาย ++ หรือ += เพื่อเพิ่มค่านั้นก็ได้ แต่สำหรับภาษา R ที่ไม่มีตัวดำเนินการทั้งสองให้ใช้แล้ว จึงจำเป็นต้องเขียนเต็มยศ ดังเช่นที่เห็นได้จากบรรทัดที่ 6 ในโค้ดข้างต้น ข้อสังเกตอื่นๆ อยู่ตรงการประกาศฟังก์ชันที่ระบุพารามิเตอร์ว่า ... ซึ่งมีความหมายว่าฟังก์ชันนี้สามารถรับอาร์กิวเมนต์ได้ไม่จำกัด และจะส่งต่อค่าในอาร์กิวเมนต์ที่รับเข้ามาไปยังฟังก์ชันอื่นๆ ในเนื้อหาฟังก์ชันนี้ที่เรียกใช้ ... เป็นอาร์กิวเมนต์ (ตัวอย่างเช่น ถ้าเรียกใช้ฟังก์ชัน random.coupon(42) ค่า 42 นี้จะถูกส่งไปเป็นเรียกใช้งานฟังก์ชัน logical(42) ต่อไป) วนซ้ำสำหรับของแต่ละชิ้น (for loop) การวนซ้ำสำหรับของแต่ละชิ้น ประกาศโดยใช้คำสำคัญ for ตามด้วยตัวแปรสำหรับรับสิ่งของในแต่ละรอบ แล้วตามด้วยเวกเตอร์ของสิ่งของทั้งหมดที่ต้องการวนซ้ำ (เช่นเดียวกับ foreach ในหลายภาษา) sample.coupon <- function(n, size=10*n) { result <- NULL for (i in 1:size) { result <- c(result, random.coupon(n)) } result } โค้ดข้างต้นสร้างฟังก์ชัน sample.coupon สำหรับทดลองหลายๆ ครั้งว่าแต่ละครั้งต้องสุ่มเปิดซองชิงโชคกี่ครั้ง โดยใช้เทคนิคการวนซ้ำแบบ for ในบรรทัดที่ 3 ถึง 5 จุดที่น่าสนใจอยู่ที่การประกาศพารามิเตอร์โดยกำหนดอาร์กิวเมนต์ปริยาย (default argument) ที่สามารถอ้างอิงไปยังอาร์กิวเมนต์อื่นของฟังก์ชันนี้ได้ทันที ทำหรือไม่ตามแต่เงื่อนไขจะกำหนด (if-else) การตรวจสอบถ้า-แล้ว สามารถใช้คำสำคัญได้เช่นเดียวกันกับภาษาทั่วไป คือ if else if และ else if (!interactive()) { args <- commandArgs(TRUE) if (length(args) == 1) { filename <- "sample-coupon.png" } else if (length(args) == 2) { filename <- args[2] } else { stop("wrong number of argument!") } png(filename, width=800, height=800) arg.list <- list(x=as.numeric(args[1])) do.call(hist, list(substitute(sample.coupon(x), arg.list))) dev.off() } โค้ดข้างต้นจะตรวจสอบว่าโปรแกรมนี้ถูกเรียกใช้งานผ่าน REPL หรือไม่ ถ้าไม่ใช่แล้วจึงตรวจสอบต่อว่าการเรียกใช้งานมีอาร์กิวเมนต์ใดบ้าง หากอาร์กิวเมนต์ถูกต้องจะสร้างไฟล์ภาพฮิสโตแกรมของการทดลองหาคำตอบของคำถามนักสะสมคูปองขึ้นมา เรียกใช้โปรแกรม ถึงตอนนี้หากใครพิมพ์ตามโค้ดข้างต้นทั้งหมด ก็จะได้ไฟล์สคริปต์ภาษา R มาหนึ่งโปรแกรมแล้ว (ส่วนใครไม่ได้พิมพ์ตาม แต่ยังอยากทดลองต่อ สามารถดาวน์โหลดโค้ดข้างต้นได้ที่นี่) เราสามารถเรียกใช้โปรแกรมดังกล่าวผ่าน terminal/cmd ได้ดังนี้ $ Rscript coupon.R 30 หรือจะนำเข้าฟังก์ชันจากไฟล์ดังกล่าวเข้ามายัง REPL เพื่อประยุกต์ใช้แบบโต้ตอบก็ย่อมได้ > source("PATH/TO/SCRIPT/coupon.R") > hist(sample.coupon(30)) ไม่ว่าจะเรียกด้วยวิธีไหน ผลลัพธ์ที่ได้จะมีหน้าตาหนีไม่พ้นภาพที่ 13 ไปเท่าใดนัก (แม้ว่าอัลกอริทึมดังกล่าวจะอาศัยการสุ่มก็ตาม) ภาพที่ 13: ฮิสโตแกรมคำตอบจากการทดลองปัญหานักสะสมคูปอง เมื่อมีคูปองแตกต่างกัน 30 แบบ ดาวน์โหลดแพ็กเกจเสริมความสามารถ จากหัวข้อที่ผ่านๆ มา หลายคนอาจมองเห็นแนวทางอันหลากหลายที่จะประยุกต์ใช้ภาษา R เพื่อช่วยแก้ปัญหาที่ต้องข้องเกี่ยวกับข้อมูลกันไปแล้ว แต่ความสามารถเท่านั้นจากตัวภาษา R เพียงอย่างเดียว ก็ยังนับว่าน้อยนิดเมื่อเทียบกับแพ็กเกจเพิ่มความสามารถที่ชุมชนอุทิศให้ โดยสถานที่จัดเก็บแพ็กเกจหลักอย่างเป็นทางการของภาษาดังกล่าวมีชื่อว่า CRAN (Comprehensive R Archive Network) ซึ่งปัจจุบันได้โฮสต์แพ็กเกจเสริมความสามารถกว่าหนึ่งหมื่นแพ็กเกจให้เลือกใช้งาน วิธีการดาวน์โหลดแพ็กเกจเหล่านี้ก็ไม่ยาก เพียงแค่มีชื่อแพ็กเกจเหล่านั้นไว้ในใจ แล้วเปิด REPL ขึ้นมาเรียกฟังก์ชันต่อไปนี้ > install.pacakges("packageName") โดยเปลี่ยน "packageName" เป็นชื่อแพ็กเกจที่ต้องการ หลังจากนั้นจะมีตัวเลือก mirror ของประเทศต่างๆ ขึ้นมา เลือกประเทศที่อยู่ใกล้ที่สุดเพื่อความเร็วในการดาวน์โหลด แล้วรอโปรแกรมดาวน์โหลดและติดตั้งแพ็กเกจเหล่านั้น หลังจากติดตั้งเสร็จสิ้น เมื่อต้องการนำเข้าแพ็คเกจเหล่านั้นมาใช้งาน ก็ทำได้โดย > library(packageName) ต่อไปนี้จะขอแนะนำแพ็กเกจที่น่าสนใจจำนวนหนึ่ง (หากสั่ง library() แล้วพบข้อความแสดงความผิดพลาด อย่าลืมว่าต้องดาวน์โหลดแพ็กเกจเหล่านั้นก่อน) กราฟเครือข่าย กราฟเครือข่ายสามารถบ่งบอกความสัมพันธ์ในข้อมูลผ่านจุด (node) และเส้นเชื่อม (edge) ตัวอย่างข้อมูลที่เราคุ้นเคยกันดีคงหนีไม่พ้นความสัมพันธ์บนเครือข่ายสังคมออนไลน์ แต่ข้อมูลชนิดอื่นๆ ก็อาจถูกแปลงให้เป็นกราฟเครือข่ายได้ เช่น แผนที่ทางหลวง > library(igraph) > plot(make_graph("petersen")) ภาพที่ 14: กราฟเครือข่ายที่มีการเชื่อมโยงแบบกราฟปีเตอร์เซน ทำนายอนาคต สำหรับข้อมูลที่มีแกนหนึ่งเป็นแกนเวลา เช่น ข้อมูลดินฟ้าอากาศ จำนวนประชากร หรือกระทั่งราคาหุ้น หากข้อมูลเหล่านั้นมีรูปแบบที่เกิดซ้ำมากพอ เราก็อาจทำนายอนาคตของข้อมูลนั้นได้ > library(forecast) > plot(forecast(nottem)) ภาพที่ 15: พยากรณ์อุณหภูมิของเมืองนอตทิงแฮม (เส้นสีน้ำเงิน) แผนที่โลก นอกจากการพล็อตข้อมูลเชิงสถิติแล้ว R ยังสามารถพล็อตแผนที่โลกได้อีกด้วย > library(maps) > library(mapdata) > map("world2Hires", xlim=c(110,190), ylim=c(-50,0)) > points(quakes[2:1], pch=".", col="red") ภาพที่ 16: แผนที่โลกพร้อมตำแหน่งเกิดแผ่นดินไหว (จุดสีแดง) ใกล้ฟีจี สรุป บทความตอนนี้แสดงให้เห็นความสามารถสำคัญๆ ในภาษา R โดยไล่ตั้งแต่เรื่องพื้นฐานอย่างชนิดตัวแปร ไปจนถึงการดาวน์โหลดแพ็กเกจเพิ่มความสามารถและเขียนโปรแกรมมาแก้ไขปัญหาจริง ใครที่อดทนอ่านและทำความเข้าใจได้จนถึงบรรทัดนี้ ก็คงพูดไม่ผิดว่าคุณรู้จักและสามารถเขียนโค้ดในภาษา R ได้แล้ว อย่างไรก็ตาม บทความนี้ก็ไม่ได้กล่าวถึงทุกเรื่องที่ควรรู้ในภาษา R และตัวอย่างการคำนวณทางสถิติก็ยังอยู่ในระดับขั้นพื้นฐานเป็นอย่างมาก ใครที่ต้องการนำภาษา R ไปประยุกต์ใช้งานอย่างจริงจัง หรือวาดฝันไว้ว่าต้องการเป็นนักวิทยาศาสตร์ข้อมูล (data scientist) ก็คงต้องบอกว่าจงตั้งใจศึกษาตัวภาษาและวิชาสถิติเพิ่มเติม เพราะการเรียนรู้ไม่มีวันสิ้นสุด และการเรียนรู้จะบรรลุผลสูงสุดเมื่อสามารถประยุกต์ใช้ความรู้นั้นเพื่อสร้างสรรค์ประโยชน์ได้ ภาคผนวก ติดตั้งโปรแกรมบนเครื่อง การติดตั้ง R นั้นสามารถทำได้อย่างง่ายดาย เพียงแค่เข้าไปยังเว็บไซต์หลักของภาษา R แล้วตามลิงก์ที่เขียนคำว่า "download R" จะพบกับรายชื่อ mirror ต่างๆ ทั่วโลกที่ทำสำเนาโครงการภาษา R ไว้ เลือกตัวเลือกที่อยู่ใกล้เคียงตำแหน่งปัจจุบันมากที่สุดเพื่อความเร็วในการดาวน์โหลด (สำหรับประเทศไทย มี mirror ของมหาวิทยาลัยสงขลานครินทร์สำหรับสำเนาดังกล่าว) เมื่อเข้าไปแล้ว จะพบกับตัวเลือกให้ดาวน์โหลดโปรแกรมได้ทั้งบน Windows, OS X และ Linux โดยมีรายละเอียดต่างกันเล็กน้อย ดังนี้ Windows เลือกดาวน์โหลดไฟล์ติดตั้ง .exe จากแฟ้ม "base" มาติดตั้งก็เพียงพอแล้วสำหรับบทความนี้ OS X ดาวน์โหลดไฟล์ติดตั้ง .pkg รุ่นใหม่ล่าสุดจากหน้าดาวน์โหลดของ OS X มาติดตั้งได้เลย Linux เนื่องจากความหลากหลายทางดิสโทร การหาไฟล์ติดตั้งที่ถูกต้องจากหน้านี้อาจเป็นเรื่องยุ่งยากเกินจำเป็น เราอาจเลือกสั่งเพียงคำสั่งต่อไปนี้ผ่าน terminal แทน apt install r-base สำหรับดิสโทร Debian/Ubuntu yum install R สำหรับดิสโทร Red Hat/Fedora หลังจากติดตั้งโปรแกรมสำเร็จแล้ว ผู้ใช้ OS X/Linux ไม่ต้องทำอะไรต่อ ส่วนผู้ใช้ Windows ควรแก้ไขตัวแปร PATH โดยเพิ่มตำแหน่งที่ติดตั้งโปรแกรม R (เช่น C:\Program Files\R\R-3.4.1\bin\x64) ต่อท้ายตัวแปรนั้น เพื่อให้สามารถเรียกโปรแกรม R ผ่าน cmd ได้ในภายหลัง Topics: RProgrammingSpecial Report