Category: Web Development & Technology

  • ใช้ Google AI Studio แปลง PDF เป็น CSV

    ใช้ Google AI Studio แปลง PDF เป็น CSV


    ตั้งแต่อดีตจนถึงปัจจุบัน นักวิทยาศาสตร์ข้อมูล หรือ Data Scienctist ล้วนแล้วแต่ต้องเคยเจอปัญหาที่ว่าข้อมูลนั้นอยู่ในรูปกายภาพ(physical) แทนที่จะเป็นลักษณะดิจิตอล (digital) 

    ซึ่งข้อมูลเหล่านี้สร้างความรำคาญให้แก่พวกเขาเป็นอย่างมาก ในอดีต ก่อนที่จะมีนวัตกรรมอย่าง OCR (Optical Character Recognition) ที่เกิดจากการประยุกต์ใช้ Machine Learning เหล่า Data Scientist ต้องรับมือกับข้อมูลกายภาพด้วยการคีย์ข้อมูลด้วยมือ ไม่ว่าจะผ่านหน้าจอ Console, Web Application หรือ GUI ต่างๆ

    วิธีดังกล่าวนั้นมีโอกาสเกิด Human Error เป็นอย่างสูง ลองจินตนาการดูสิ คุณกำลังนั่งกรอกข้อมูลเหล่านี้ประมาณ ตี 2-3 อดหลับอดนอน นั่งกรอกตารางข้อมูลเหล่านี้มากกว่า 100 หน้ามาแล้ว 3 วัน 

    คุณที่กำลังขมักเขม้นกรอกข้อมูล

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

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

    แล้วถ้าเกิดว่าวันนึง มันดันมีเทคโนโลยีที่สามารถแก้ปัญหาตรงนี้ได้ละ เทคโนโลยีที่จะช่วยให้เราสแกนเอกสารและทำออกมาเป็นไฟล์พร้อมนำเข้าข้อมูล บนโปรแกรต่างๆไม่ว่าจะเป็น Spreadsheet, Excel หรือ โปรแกรมจากการเขียนโค้ดอย่าง python หรือ R คุณลองคิดดูสิว่าขยะเหล่านี้จะมีค่าแค่ไหน ? เพราะฉะนั้น วันนี้ผมจะพาทุกท่านมารู้จักกับ AI จากทาง Google อย่าง Gemini Flash 2.5 ที่เราสามารถทดสอบ บนGoogle AI Studio ซึ่งจะเข้ามาช่วยแก้ปัญหาและอุด Pain Point นี้กันครับ

    ในช่วงแรกที่เหล่า Data Scientists ต้องแก้ปัญหานี้ พวกเขาก็พยายามค้นหาวิธีการต่างๆ อาทิเช่น การ Scan ข้อมูลเหล่านั้นเป็น PDF Copy/Paste บ้างละ ใช้โปรแกรม online สำหรับแปลง PDF ไปเป็น CSV บ้างละ ไปจนถึงขั้นยอมเสียเงินจำนวนมากเพื่อซื้อโปรแกรมมาปิดจุดอ่อนนี้ 

    วิธีเหล่านั้นล้วนสามารถแก้ปัญหาขั้นเบื้องต้นได้แต่ก็มีจุดอ่อนในตัวเช่นกัน เช่นการ Copy/Paste นั้นในระยะยาวนั้นจะไม่เป็นผลดีต่อเวลาเป็นอย่างมาก การใช้โปรแกรมออนไลน์ก็ดูเป็นวิธีที่เวิร์ค แต่ถ้าเกิดมีไฟล์เป็นจำนวนมากก็คงจะไม่ไหว และการใช้ซอฟแวร์สำเร็จรุปนั้นก็อาจจะให้ผลลัพธ์ที่ไม่ดีหากข้อมูลของเรานั้นซับซ้อน

    ด้วยการมาถึงของยุคที่ Generative AI เป็นที่รู้จักแพร่หลาย บริษัท Google ก็ไม่น้อยหน้าปล่อย แพลทฟอร์ม ออกมาให้ผู้ใช้งานอย่างเราๆได้ทดลองใช้ฟรี โดยเราสามารถเข้าไปลองเล่น Model ต่างๆของทาง Google ผ่านทาง Google AI Studio 

    ทำไมต้อง Google AI Studio ทั้งๆ ที่เจ้าอื่นก็มีแพลตฟอร์มทดลองให้ใช้เหมือนกัน? คำตอบง่ายๆ คือ ‘เพราะมันฟรี’ ผู้ใช้งาน Gemini เวอร์ชั่นปกติอาจเข้าถึงได้แค่ Gemini Flash แต่ใน Google AI Studio คุณสามารถใช้งาน Gemini Pro ได้ฟรีไม่จำกัด นี่คือข้อได้เปรียบสำคัญที่ไม่อาจมองข้าม

    แถมเรายังสามารถปรับแต่งค่าต่างๆเพื่อให้ AI ตอบคำถามตามความต้องการของเราได้ เช่น ตั้งค่า Temperature ต่ำ หรือ สูง เพื่อกำหนดความสร้างสรรค์ของ AI กำหนด output length ว่าจะให้ตอบได้ไม่เกินครั้งละเท่าไหร่ ไปจนถึงการกำหนด Top P เพื่อควบคุมความหลากหลายของคำตอบ โดยให้ AI เลือกจากกลุ่มคำที่มีความน่าจะเป็นสูงสุด

    อีกหนึ่งสิ่งที่ น่าประทับใจมากๆของ Google AI Studio ก็คือจำนวน Context window ที่ให้มาอย่างจุใจถึง 1 ล้าน ส่งผลให้การคุยของผู้ใช้งานนั้นราบรื่นอย่างไร้ปัญหา และด้วยเหตุนี้ Google AI Studio จึงเหมาะมากที่เราจะใช้เพื่อ POC concept ของเราสำหรับประยุกต์ใช้ในอนาคตนั้นเอง

    สำหรับจุดประสงค์ในการทำครั้งนี้ก็คือการที่เราสามารถอ่านไฟล์สแกน PDF ได้เป็นจำนวนมากเพื่อที่จะดึงข้อมูลจากกระดาษออกมาและ insert เข้าฐานข้อมูล

    ด้วยเหตุนี้ เพื่อให้ได้มาซึ่ง tools ที่ตอบโจทย์เรามากที่สุด จึงมีความจำเป็นว่าเราต้องทดลองใช้ Model ที่กูเกิ้ลมีนั้นมาทดสอบเสียก่อน โดยในเคสนี้เราจะทดสอบด้วยโมเดล 2.5 Flash และ 2.5 Pro

    ความแตกต่างระหว่าง Pro Model & Flash Model 

    Pro Model – เป็น โมเดลที่ถูกสร้างมาสำหรับการคิดอย่างเป็นตรรกะ (reasoning), การโค้ดดิ้ง (Coding) และความเข้าใจในหลากหลายมิติ ( Multimodal understanding ) เพื่อใช้ในการคิดวิเคราะห์ปัญหาที่ซับซ้อน ส่งผลให้มีค่า Latency ที่สูงเพราะ Model จำเป็นต้องคิดไปทีละ Step ก่อนที่จะได้รับคำตอบ

    Flash Model – สำหรับ Flash Model นั้นถูกออกแบบมาให้ใช้กับงานประมวลผลเป็นจำนวนมากเช่น ไฟล์ PDF หลายๆไฟล์ งานที่ต้องการ latency ตำ่และรับ input ได้เยอะ, หรือ agentic use cases

    จากข้อมูลข้างต้นเราจึงควรเลือกใช้ Flash Model เพราะด้วยความที่ Latency ต่ำ + กับเหมาะทำงานซ้ำๆ ถือว่าตอบโจทย์ของเราเป็นอย่างมาก

    หลังจากที่เลือก Model เรียบร้อยแล้วเรามาดูขั้นตอนในการทำงานก่อนดีกว่า

    Flowchart

    จาก Flowchart ข้างต้นในขั้นตอนแรกนั้นเราต้องสแกนเอกสารก่อนและข้างล่างนี้ก็คือเอกสารที่เราต้องการอ่านนั้นเอง

    ใน ช่วง Proof cf Concept นั้น เราไม่จำเป็นที่จะต้องใช้ API_KEY โดยเราสามารถ Log in Gmail และใช้งานบน แพลทฟอร์ม Google AI Studio ได้เลย

    ⁠จากนั้นทำการอัพโหลดรูปภาพเข้าไป และพิมพ์ System Instruction ดังนี้

    จากนั้นทำการกด Run และรอผลัพธ์

    จะเห็นได้ว่าเราได้ข้อมูลในรูปแบบ CSV พร้อมนำไปใช้ในขั้นตอนต่อไปได้ทันที! แต่หากเราไม่ได้กำหนด System Instruction หรือ Prompt ที่ชัดเจนและรัดกุมแล้วละก็ เราอาจได้ข้อมูลในรูปแบบที่ไม่ใช่ CSV หรือได้ Format ที่ไม่น่าพึงพอใจ เพราะฉะนั้น ‘หลักการ Prompt Engineering’ จึงสำคัญอย่างยิ่งมากในการดึงศักยภาพของ AI ออกมาใช้ได้อย่างเต็มที่.

    สิ่งที่ใช้ในการคำนวณและวิเคราะห์เอกสาร PDF เหล่านี้ แท้จริงแล้วก็คือ โมเดลภาษาขนาดใหญ่ (LLM) อย่าง Gemini ที่ทำหน้าที่หลักในการแปลงข้อมูลจากไฟล์ให้มาเป็น CSV ตาม Prompt ที่เรากำหนด

    อย่างไรก็ตาม กว่าที่จะมาเป็น CSV อย่างที่เราเห็นนั้น ตัว Google AI Studio ก็ต้องแปลงไฟล์แนบในรูป PDF ให้ AI ‘เข้าใจ’ ได้ก่อน

    โดยในขั้นแรก ระบบจะทำการตรวจสอบว่า Input ที่ส่งมาเป็นไฟล์แนบ (เช่น PDF หรือรูปภาพ) หรือไม่ ถ้าใช่ ระบบจะส่งไฟล์นั้นไปประมวลผลด้วย OCR (Optical Character Recognition) ก่อน กระบวนการ OCR นี้จะแปลงภาพของตัวอักษรใน PDF ให้กลายเป็น ข้อความดิจิทัล (Text) ที่คอมพิวเตอร์อ่านได้

    จากนั้น ข้อความดิจิทัลที่ได้จะถูกนำเข้าสู่กระบวนการ Tokenization ซึ่งเป็นการแบ่งข้อความยาวๆ ออกเป็นหน่วยย่อยๆ ที่เรียกว่า ‘โทเคน (Tokens)’ (เช่น คำ, ส่วนของคำ, หรือเครื่องหมายวรรคตอน)

    ต่อมา โทเคนเหล่านี้จะถูกแปลงไปสู่รูปแบบที่ AI สามารถประมวลผลได้ ซึ่งก็คือ ‘Embedding’ ครับ พูดง่ายๆ คือ Embedding คือการแปลงโทเคนแต่ละหน่วยให้เป็นตัวเลขเวกเตอร์ ซึ่งเป็นชุดตัวเลขที่แสดงถึงความหมายและความสัมพันธ์ของโทเคนนั้นๆ ในเชิงคณิตศาสตร์ ทำให้ AI สามารถเข้าใจ และ วัดความใกล้เคียง ของความหมายระหว่างคำต่างๆ ได้

    หลังจากนั้น ข้อมูลที่เป็นตัวเลขเวกเตอร์ (Embeddings) เหล่านี้จะถูกส่งเข้าสู่ โมเดล AI ซึ่งโดยทั่วไป จะประกอบด้วยส่วนหลักๆ คือ Encoder และ Decoder

    • Encoder ทำหน้าที่ประมวลผล Input (ในที่นี้คือ Embeddings ของข้อความจาก PDF) เพื่อสร้างความเข้าใจบริบทและความหมายทั้งหมดของข้อความนั้นๆ
    • Decoder จะใช้ความเข้าใจที่ได้จาก Encoder และคำสั่งจาก Prompt ของเรา (เช่น แปลงเป็น CSV) เพื่อสร้าง Output ออกมาเป็นลำดับของโทเคน ซึ่งต่อมาจะถูกแปลงกลับเป็นข้อความในรูปแบบที่เราต้องการหรือข้อความที่มนุษย์อ่านได้ (เช่น CSV) นั่นเอง

    ทุกสิ่งทุกอย่างข้างต้นนั้นล้วนเกิดจากข้อมูลมหาศาลที่โมเดลได้รับการเรียนรู้มาแล้วล่วงหน้า ผสมผสานกับวิธีทางคณิตศาสตร์ที่ซับซ้อน ส่งผลให้ AI สามารถวิเคราะห์และคาดเดาได้อย่างแม่นยำว่าข้อมูลที่อยู่บน PDF เหล่านั้นคืออะไร และควรจะจัดเรียงออกมาในรูปแบบใด

    พูดกันมาขนาดนี้แล้ว ละ OCR คืออะไร? OCR (Optical Character Recognition) คือหนึ่งในกระบวนการสำคัญของ Machine Learning ที่ทำหน้าที่แปลง รูปภาพของตัวอักษร (เช่น ตัวอักษรบนไฟล์ PDF ที่มาจากการสแกน) ให้กลายเป็น ข้อความดิจิทัล ที่คอมพิวเตอร์สามารถอ่านและประมวลผลได้ โดยมันจะวิเคราะห์แพทเทิร์นของพิกเซลในรูปภาพเพื่อระบุว่าเป็นตัวอักษรใด แล้วจึงแปลงให้เป็นข้อความที่แก้ไขหรือค้นหาได้

    หลังจากนั้นก็รับค่านั้นและทำการแปลงกับออกมาเป็นรูปด้วย library ต่างๆ เช่น opencv เป็นต้น 

    หลักจากที่ได้อธิบายว่า AI นั้นมันน่าอัศจรรย์มากขนาดไหน เพราะหลังจากนี้เราไม่จำเป็นต้องหามรุ่งหามค่ำนั่งกรอกข้อมูลกันเป็นพันลวันแล้ว เรายังสามารถต่อยอดทำให้มัันอัตโนมัติได้ยิ่งขึ้นไปอีกด้วยนะAIM

    หากเราเข้าไปที่ตรงหน้า chat ที่เราคุยกับ gemini ใน google ai studio

    เราสามารถเปลี่ยน chat ที่เราคุยกับ gemini ให้เป็น code ที่ใช้รันผ่าน Terminal, หน้าเว็บไซต์ หรือจะเป็นยิงผ่าน Postman ก็ได้เหมือนกัน

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

    หลังจากนั้นให้ทำการกด + Create API Key google ai studio จะทำการค้นหา Google Cloud Project

    หากเพื่อนๆ พึ่งเคยสร้างโปรแกรมนี้ครั้งแรก เราแนะนำว่าให้เข้าไปสมัครและเปิดใช้งานในหน้า Google Cloud Project ก่อน  https://cloud.google.com/?hl=en  และเลือกไปที่ Console หากเราได้ทำการล้อคอิน Gmail เรียบร้อยแล้ว

    เลือกเมนู New Project

    ทำการตั้งชื่อ Project name และเลือก Location (สามารถใช้ no organization เป็น default ได้)

    รอจนหน้า Google Cloud Project แจ้งว่าเสร็จแล้วให้เราค้นหาในช่องค้นหาด้านบนว่า Gemini API และทำการ Enable ซะ

    หลังจากที่ Enable แล้วให้กลับไปที่หน้า Google AI Studio และทำตามขั้นตอนการ Get API Key

    ทีนี้เราก็จะเห็น Project ที่พึ่งสร้างแล้วกดคลิกเพื่อเลือกและกด Create API key in existing project

    และเราก็จะได้ API Key เพื่อใช้เรียก service แล้ว แต่ระวังไว้ด้วยนะว่าอย่าเผลอแชร์ให้คนอื่นละไม่งั้นคุณจะโดนบิลอ่วมเลยละ

    หลังจากนั้นก็กลับมาที่โค้ด copy และ วางในเครื่อง local เพื่อทดสอบเสียก่อน โดยเราสามารถเอา API_KEY ที่ได้วางแทนตรงนี้ได้เลยย

    แต่การทำอย่างงั้นเรียกได้ว่าไม่ปลอดภัยเอาซะเลย เพราะฉะนั้นเราควรที่จะใช้ library อย่าง dotenv เพื่อมาอุดปัญหานี้โดย dotenv จะทำอ่านค่าบางอย่างจากไฟล์ที่ชื่อว่า .env ซึ่งเป็นไฟล์ที่เก็บตัวแปรต่างๆที่เราไม่อยากให้คนนอกรู้นั้นเอง

    ทีนี้เราก็สามารถทดสอบโค้ดที่ google ai studio ให้มาได้แล้ว เย้

    # To run this code you need to install the following dependencies:
    # pip install google-genai
    
    import base64
    import os
    from google import genai
    from google.genai import types
    
    
    def generate():
        client = genai.Client(
            api_key=os.environ.get("GEMINI_API_KEY"),
        )
    
        model = "gemini-2.5-pro-preview-05-06"
        contents = [
            types.Content(
                role="user",
                parts=[
                    types.Part.from_text(text="""INSERT_INPUT_HERE"""),
                ],
            ),
        ]
        generate_content_config = types.GenerateContentConfig(
            response_mime_type="text/plain",
            system_instruction=[
                types.Part.from_text(text="""Extract data into CSV format where | separates columns. Use this exact column order:  
    {'|'.join(CSV_COLUMNS)}.  
    
    Rules:  
    1. Keep f1 always 0.  
    1.1 Always keep {NUM_COLUMNS} columns 
    2. Extract doc_no as the second column and datedoc as the fourth. Do not swap these.  
    3. Blank fields must contain 0.  
    4.  If which row  you can't understand skip it.
    Example:
    {'|'.join(CSV_COLUMNS)}
    1|01038/2559|0|1/7/2559|รายงานการสำรองข้อมูลประจำเดือน มิย.59|งานสารบรรณ (สบ.)|กองบริหารการคลัง (กค.)|เดินเรื่องเอง เดินเรื่องเอง|0
    2|กบ0026/1222|0|30/6/2559|ขอส่งเงินค่าไฟฟ้าที่พักอาศัยเจ้าหน้าที่ ประจำเดือน พค.59|งานสารบรรณ (สบ.)|กองบริหารการคลัง (กค.)|0|0
    Always make it {NUM_COLUMNS} columns
    Ensure the output always aligns exactly to this structure."""),
            ],
        )
    
        for chunk in client.models.generate_content_stream(
            model=model,
            contents=contents,
            config=generate_content_config,
        ):
            print(chunk.text, end="")
    
    if __name__ == "__main__":
        generate()
    

    อย่างไรก็ดีโค้ดที่ gemini ให้มานั้นยังทำงานได้แค่กับการ pass ค่า text เข้าไปได้เท่านั้นแถมยังเป็นการรันแบบ ถามตอบแค่ครั้งเดียว ซึ่ง โค้ดดังกล่าวนั้นยังไม่ตอบโจทย์ที่เราต้องนำมาแก้ไขเลย ผมก็เลยมีการแก้โค้ด โดยทำการเพิ่ม method ที่สามารถรับไฟล์เข้าไปเพื่อให้ Gemini Flash Model อ่านผ่าน OCR ได้นั้นเอง

    และเมื่อทำการอ่านค่าได้แล้วเราก็ต้องเพิ่มโค้ดอีกนิดนึงว่าแทนที่จะอ่านทีละไฟล์เราก็สร้าง loop ให้โค้ดของเราอ่านผ่านโฟลเดอร์ที่เก็บไฟล์เอาไว้จนกว่าจะหมดแปลงค่าออกมาเป็น csv format ให้เรานั้นเอง

    เมื่อ ถอดค่าจากไฟล์สแกนได้แล้ว เราก็บันทึกลง .csv ที่เตรียมไว้เพื่อที่เราจะเอาข้อมูลดังกล่าวไปใช้ในขั้นตอน INSERT ข้อมูลลงฐานข้อมูลได้ต่อไปนั้นเอง

    Sourcecode 

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

    ผมได้ทดลองให้คุณดูแล้วว่า AI นั้นมีประโยชน์มากแค่ไหนหากเราเข้าใจและใช้มันได้อย่างมีประสิทธิภาพ ผมว่ามันถึงตาคุณแล้วละที่จะต้องลองนำไปประยุกต์ใช้ เพื่อให้งานราชการของเรามีประสิทธิภาพมากขึ้นครับ คุณอาจจะลองเริ่มจากลองใช้ Google AI Studio ก่อนก็ได้ครับมันฟรี ลดกำแพงการศึกษาไปอี๊ก อยากให้คุณได้ลองสัมผัสนะครับ ละจะติดใจแน่นอน

    References

    For Gemini API – https://ai.google.dev/gemini-api/docs

    For Encoder/Decoder – https://www.youtube.com/watch?v=Q7mS1VHm3Yw&t=7834s

  • 20 Blocks Essential for WordPress Content Creation

    20 Blocks Essential for WordPress Content Creation

    ผมได้มีโอกาสเรียนคอร์ส Web For Impact Batch 05 กับ Ad’Toy จาก Datarockie ซึ่งผมชอบแนวคิดที่ว่า เราควรที่จะมีเว็บไซต์เป็นของตัวเองไว้สำหรับเป็นบ้านที่แท้จริงเพื่อการแชร์ไอเดีย หรือเก็บบางสิ่งบางอย่างที่เรามองว่าเป็นทรัพย์สินของเรา ที่ไม่ใช่การไปอาศัยอยู่ใต้ชายคาของใคร อาทิ เช่น Facebook Page, Medium Blog, และ อื่นๆ อีกมากมาย

    คุณลองนึกดูสิ หากวันใดวันหนึ่งเพจของคุณถูกปิดตัวลง เนื้อหาที่คุณลงไว้ทั้งหมดก็จะหายไปพร้อมกับเพจของคุณ ซึ่งไม่สามารถที่จะกู้กลับมาได้ และการไปเริ่มต้นใหม่ก็ต้อง เริ่มหา members หายอด reachs ใหม่ทั้งหมดหมดเลยซึ่งนั้นก็ดูจะไม่แฟร์เอาซะเลยใช่ไหมครับ

    แนวคิดนี้ทำให้ผมตั้งสินใจสมัคร Paid Plan ของ WordPress เพื่อมาแชร์ไอเดียหรือสิ่งที่ผมได้เรียนรู้ในชีวิตประจำวัน (หยุดเขียนไปตั้ง 1 เดือนแหนะ แซดเวอร์ งานยุ่งไม่ไหว​)

    จากการลองผิดลองถูกมา 3-4 โพสต์ ผมเลยอยากที่จะมาแชร์ 20 blocks ที่ผมคิดว่าจำเป็นสำหรับการรังสรรค์งานออกมาจากที่แอดทอยแนะนำในคอร์สและที่ผมลองหาดูเอง

    ก่อนที่จะเข้าถึงเนื้อหาผมอยากจะบอกก่อนว่าในเนื้อหาที่จะพูดถึงแค่ประเภท block นะครับจะไม่ได้เจาะลึกว่าแต่ละ block มี toolbar ให้ใช้อะไรบ้างในส่วนนี้ท่านสามารถอ่านต่อได้ผ่านการคลิกตรงนี้ได้เลยครับ

    Table of Content


    • Basic Content Blocks
    • Interactive & Layout Blocks
    • Advanced & Specialty Blocks
    • Workflow ในการวางโครงสร้าง Content

    Basic Content Blocks

    1. Paragraph Blocks

    block ที่ใช้งานแทบจะเรียกว่าทุกๆ จุดของหน้าpost เพื่อการเขียนเนื้อหาบทความของทุกคน

    สามารถเริ่มพิมพ์อะไรก็ได้เลย

    2. Heading Blocks

    block สำหรับการขึ้นหัวข้อใหม่ หรือว่า เนื้อหาพาร์ทใหม่ การใช้ heading block นั้นมีส่วนช่วยให้ผู้ใช้งานอ่านหรือเลือกหัวข้อได้ง่ายขึ้น และยังเป็นผลดีต่อเว็บไซต์ อีกด้วย ( search engines )

    Shortcut: Heading block สามารถเพิ่มได้หลายวิธี เช่น พิมพ์​ # หรือ /h{1-6}

    heading

    3. List Blocks

    List blocks ใช้สำหรับการแสดงรายการ เพื่อจัดการ content ให้เป็นระเบียบยิ่งขึ้น โดยสามารถใช้สร้าง bulleted point หรือ numbered list ก็ได้

    List block

    Shortcut: เราสามารถพิม์ /list หรือ พิมพ์ – เพื่อสร้าง bulleted point กับ พิมพ์ตัวเลขเพื่อสร้าง numbered list

    4. Quote Blocks

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

    Shortcut: /quote หรือ > เพื่อเรียกใช้งาน

    Quote Block

    5. Image Blocks

    blocks สำหรับเพิ่มรูปภาพลงบนหน้าเว็บไซต์ของเราเพื่อใช้เป็นภาพประกอบบทความ แต่ก็มีข้อควรระวังนั้นคือเราควรเลือกใช้ภาพนามสกุล .webp เพื่อให้มีขนาดเล็กและโหลดเร็วซึ่งเป็นผลดีต่อเว็บไซต์

    Shortcut: สามารถพิมพ์ /image

    Image Block

    6. Gallery Blocks

    block สำหรับใช้โชว์รูปภาพเหมือน Image Block แต่จุดแตกต่างกับ Image block ก็คือ gallery block สามารถโชว์ภาพได้มากกว่า 1 ภาพ เหมาะสำหรับใช้กับเว็บไซต์ อาหาร หรือ เว็บไซต์ช่างภาพที่ต้องการโชว์รูปซึ่งเป็นผลงานของเว็บไซต์ เราสามารถปรับจำนวน แถว และ หลัก ของ Gallery Block ได้ว่าอยากได้เท่าไหร่เพื่อให้เหมาะกับการแสดงผลบนหน้าเว็บไซต์

    Shortcut: /gallery เพื่อใช้งานได้

    ข้อควรระวัง

    การใช้งาน Gallery Block มีประโยชน์อย่างมากในการจัดชุดภาพขนาดใหญ่ แต่ก็ควรระวังหากไฟล์ภาพมีขนาดใหญ่เกินไปซึ่งสามารถส่งผลต่อการโหลดภาพ แนะนำว่าภาพควรจะเป็นนามสกุล .webp เพื่อให้ได้ขนาดที่เล็กและโหลดเร็วเป็นผลดีต่อเว็บไซต์

    Gallery Block

    7. Video Blocks

    ใช้สำหรับการอัพโหลด วีดิโอ ใน หน้า page หรือ post เพื่อใช้สำหรับประกอบบทความหรือโฆษณา แต่ในการใช้ Video Block นั้นผู้ใช้งานต้องอัพเกรดเป็นเวอร์ชั่น Premium จึงจะสามารถใช้งาน block ดังกล่าวได้

    Shortcut: /video

    ใช้งาน เราจะมีตัวเลือก 3 options ได้แก่ Upload, Media Library, Insert from URL

    • Upload: ใช้สำหรับการเลือก Video จากบนเครื่องของเรา
    • Media Library: ใช้สำหรับการเลือก Video ที่อยู่บน WordPress
    • Insert from URL: ใช้สำหรับการเลือกวีดิโอโดยการแปะ url ที่อยู่บนอินเทอร์เน็ตเพื่อใช้งาน
    • การใช้ Video block นั้นเหมาะกับการที่เราจะดึงดูดความสนใจของผู้ใช้งาน แต่เราก็ต้องคำนึกถึงขนาดไฟล์และความเร็วอินเทอร์เน็ตของผู้ใช้งานไว้ด้วยเช่นกัน ไม่อย่างนั้นจะเป็นการไปลดประสบการณ์การใช้งานของผู้ใช้งานแทน
    Video Block

    8. Table Blocks

    Table block ใช้สำหรับสร้างตารางเพื่อแสดงค่าบางอย่างประกอบบทความ โดยเราสามารถเลือกได้ว่าจะเอากี่แถว กี่คอลัมน์

    Shortcut: /table

    • หากต้องการสร้างตารางที่มีความซับซ้อนขึ้นเช่น สามารถ เรียงลำดับข้อมูล หรือแบ่งหน้าได้อาจจะมองเป็นการใช้ plugin เสริมอย่าง TablePress
    Table Block

    9. File Block

    block สำหรับวาง file ที่สามารถดาวน์โหลดได้ โดยไฟล์ดังกล่าวจะต้องเป็นนามสกุลที่ wordpress ซัพพอร์ทจึงจะสามารถอัพโหลดได้

    Shortcut: /file

    File Block

    10. Audio Block

    ใช้สำหรับการวางไฟล์ประเภทเสียงต่าง เช่น เพลง, พอดแคสท์, และอื่นๆ เพื่อประกอบบทความ

    Shortcut: /audio

    Interactive & Layout Blocks

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

    11. Button Blocks

    block สำหรับการสร้างปุ่ม เพื่อใช้ interact กับหน้าเว็บไซต์ โดยเราสามารถเพิ่มข้อความ ลิ้งค์ หรือตกแต่งได้ ตัวอย่างเช่น เราอยากให้เขาคลิ้กเพื่อออกไปหน้าเว็บไซต์อื่นๆ เป็นต้น

    Shortcut: /button

    Button Block

    12. Column Blocks

    block สำหรับการจัดหน้า โดย block นี้สามารถเพิ่ม ข้อความ สื่อต่างๆ, และสามารถใส่ได้สูงสุด 6 columns ใน 1 แถว โดยเราสามารถเลือกได้ว่าจะเอากี่ column จากค่าเริ่มต้น

    Shortcut: /columns

    Column Block

    13. Cover Blocks

    block สำหรับเพิ่มรูปภาพหรือวีดิโอที่สามารถเขียนข้อความบนภาพได้

    Shortcut: /cover

    Cover Block

    14. Spacer Blocks

    block สำหรับ เพิ่มช่องว่างระหว่าง block อื่นๆ ยกตัวอย่างเช่น หากคุณอยากได้ช่องว่างในแนวตั้ง ก็ใช้ vertical spacer และ ถ้าอยากได้ช่องว่างในแนวนอน ก็เรียก horizontal spacer

    Shortcut: /spacer

    Spacer Block

    15. Group Blocks

    block สำหรับจับกลุ่ม block อื่นๆ เพื่อให้ง่ายต่อการจัดระเบียบ content บนหน้าโพสต์หรือเพจ โดยเราสามารถ ตั้งค่าพื้นหลัง, สี, ช่องว่าง ให้หลากหลายขึ้น

    Shortcut: /group

    Group Block

    16. Media & Text Blocks

    block ที่เราสามารถวางไฟล์ภาพ หรือวีดิโอ พร้อมๆกับ ข้อความ ประกอบได้ ช่วยเพิ่มความชัดเจนให้กับวิดีโอที่จะสื่อได้ดียิ่งขึ้น เราสามารถตั้งค่าสีพื้นหลังและปรับตำแหน่งของข้อความ

    Shortcut: /media

    Media & Text

    17. Embed Blocks

    block ที่ให้คุณสามารถเพิ่มเนื้อหาจากแหล่งอื่นๆบนเว็บไซต์ของคุณได้ด้วยการวางลิ้งค์ใน block อย่างไรก็ตามเราต้องศึกษาเพิ่มเติมว่าเว็บไซต์ไหนบ้างที่อนุญาตให้เราสามารถเอาลิ้งค์จากเว็บเข้ามาแปะบนหน้าเว็บเราได้ เช่น YouTube, Spotify และ Twitter

    Shortcut: /embed

    Embed Block

    Advanced & Specialty Blocks

    18. Shortcode Blocks

    block สำหรับใส่โค้ดไม่ว่าจะเป็น embed file หรือการสร้าง object บ้างอย่างที่ซับซ้อนได้

    Shortcut: /shortcode

    Shortcode Block

    19. HTML Blocks

    block ที่อนุญาตให้เราเขียน htmlได้โดยสามารถเขียน styling เองได้ตามใจ

    Shortcut: /html

    html block

    20. Query Loop Blocks

    block ที่ใช้ในการลูป content บางอย่างที่มีลักษณะเป็นกลุ่ม เช่น โพสต์ของเราเป็นต้น โดยสามารถสร้าง template มาลองรับเพื่อให้ทุกอันแสดงผลเหมือนกัน

    Shortcut: /query

    Query Block

    Workflow ในการวางโครงสร้าง Content

    สำหรับผมเวลาเอา content ที่เตรียมใน notion หรือ จาก notes ต่างๆ มาขึ้นใน wordpress ก็จะแบ่งตามนี้

    • ใช้ heading block เพื่อแบ่งพาร์ท
    • ใช้ paragraph block เพื่อเขียนเนื้อหา
    • ใช้ image block หรือ video block เพื่อให้เห็นภาพมากขึ้นหรือเข้าใจง่ายขึ้น
    • ใช้ spacer block เพื่อจัดหน้าให้ดูอ่านง่าย
    • ใช้ code block ในการวาง code แทนการแคปหน้าจอ

    จบกันไปแล้วนะครับสำหรับ 20 blocks ที่คุณควรรู้จักบน WordPress แล้วคุณละ ใช้ block ไหนบ่อยที่สุด หรือถ้าหากมีไอเดียหรือ block อะไรที่น่าสนใจสามารถแชร์กันได้ใต้ comments ได้เลยครับ

    Reference

    WordPress Documentation

  • 💡 สิ่งเจ๋งๆ ที่ทำได้ด้วย Intersection Observer API

    💡 สิ่งเจ๋งๆ ที่ทำได้ด้วย Intersection Observer API

    เพื่อนๆ เคยต้องทำ UI ที่จะแสดง animation เมื่อเราเลื่อนถึงวัตถุนั้นหรือไม่ หรือว่าต้องทำ lazy loading กับ รูปภาพ ที่จะโหลดก็ต่อเมื่อผู้เข้าเว็บไซต์เลื่อนถึง และ การทำ Infinite Scrolling ที่จะทำการ ค้นหาข้อมูลใหม่เมื่อเลื่อนหน้าจอจนสุดไหมครับ

    ปัญหาเหล่านี้เป็นความยากลำบากของเหล่า Frontend Developer มาช้านานและวิธีการแก้ปัญหานั้น อาจจะส่งผลต่อการทำงานของหน้าเว็บไซต์และทำให้การใช้งานของ user นั้นติดขัด ส่งผลให้ในปัจจุบัน การมาถึงของ intersection observer api จึงเข้ามาแก้ปัญหาในจุดนี้

    Table of Content


    • Compatibility

    What is Intersection Observer API

    Intersection Observer API เป็น API ที่ถูกเพิ่มเข้าบน เบราวเซอร์ต่าง ๆ เพื่อทำหน้าที่ในการสังเกต ว่าวัตถุดังกล่าวที่ได้รับการตั้งค่าไว้ เลื่อนเข้ามาในบริเวณ browser viewport แล้วหรือยัง หรือ หากจับสังเกตได้ว่าเกิดเหตุการณ์ข้างต้นขึ้น หรือ เกิดการเรียกใช้งาน ตัว API จะทำการ เรียกใช้งานฟังก์ชัน เพื่อดำเนินคำสั่งตามที่เราเขียนไว้นั้นเอง

    ภาพจาก คุณ Khaled Ben Yahya

    โดย API สามารถปรับแต่งตาม options ต่าง ๆ ซึ่งเราจะอธิบายในพาร์ทต่อไป

    Why Do We Need Intersection Observer API & What Problems Does It Solve?

    สาเหตุที่มีการสร้าง Intersection Observer API คือการที่เราพยายาม ทำ lazy loading, infinite scrolling, การคิดกำไรจากการเลื่อนผ่าน ads บนเว็บไซต์ , และ การทำ animation on scroll

    ในสมัยก่อนนั้นเวลาเราจะทำสิ่งเหล่านี้ เราต้องไปใช้ onScroll event และ listen มันตลอดว่า หาก ผู้ใช้งานเลื่อนจนถึงจุดที่เราตั้งค่า ไว้เนี้ย ค่อย trigger เหตุการณ์ต่าง ๆ เช่น โชว์ รูปภาพ, เล่น animation, query ข้อมูลใหม่ หรือ คำนวณเงินจากการมองเห็นโฆษณา

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

    แม้กระทั่ง พวก library ที่จะ โชว์ ad ก็เช่นกัน เวลาขึ้นมาพร้อมกันหลายๆ อัน ด้านใน ก็จะเรียก ฟังก์ชั่นที่คอย ตรวจเช็คค่า ว่ามีการเลื่อนถึงแล้วหรือยัง คุณลองนึกภาพว่า มีซัก 10 อันดูสิ แค่นี้ก็ทำให้ผู้ใช้งานรู้สึกเหนื่อยหนายและท้อแท้ต่อการใช้เว็บไซต์ของคุณแล้ว

    การมาของ Intersection Observer API ช่วยให้ เว็บไซต์ของคุณไม่จำเป็นจะต้อง แบ่ง ความสามารถ เพื่อค่อยติดตามพฤติกรรมข้างต้นอีกแล้ว และสามารถนำ ไปใช้ทำอย่างอื่นให้มีประสิทธิภาพมากขึ้น

    อย่างไรก็ตาม Intersection Observer API ไม่สามารถระบุได้ในระดับ pixel หรือ บอกว่าให้ดู ณ จุดใด จุดหนึ่ง ตัวมันสามารถเช็คได้เพียงแค่ว่า มีการเลื่อนมาพบวัตถุนี้แล้วประมาณ กี่ % เท่านั้น

    🔧 Deep Dive: Intersection Observer API Options & Mechanics

    หลังจาก ที่เราได้รู้ที่มาที่ไป ว่ากว่าจะมาเป็น Intersection Observer API เราต้องผ่านอะไรมาบ้าง เรามาเรียนรู้และทำความเข้าใจหลักการทำงานของ API กันดีกว่าครับ

    /**
     * สร้าง Observer สำหรับติดตามการปรากฏของ Elements บนหน้าจอ
     * 
     * @param {function} callback - ฟังก์ชันที่จะทำงานเมื่อ element ปรากฏ/หายไป
     * @param {object} options - ตั้งค่าการทำงาน
     *        root: พื้นที่ที่จะติดตาม (null = viewport)
     *        threshold: % ความเห็น [0-1]
     *        rootMargin: ระยะขอบเพิ่มเติม
     * 
     * 
     */
    const observer = new IntersectionObserver(callback, options); 
    

    โดยการเรียกใช้งาน Intersection Observer API เราต้องเรียกผ่าน constructor และ pass arguments สองตัวได้แก่ callback และ options

    const options = {
      root: document.querySelector("#scrollArea"),
      rootMargin: "0px", // จำเป็นต้องเป็น หน่วย pixel หรือ %
      threshold: 1.0,
    };
    
    

    เรามาเริ่มทำความเข้าใจเกี่ยวกับ options กันก่อน ภายใน options นั้นจะประกอบไปด้วย 3 attributes ได้แก่

    root , rootMargin และ threshold

    root คือ ที่ๆเราจะกำหนดว่า จะใช้ element อะไรเป็น viewport สำหรับเช็คการมองเห็นของ element ที่เราเลือกสังเกต โดยจำเป็นเป็นต้องเป็น ancestor element โดยปกติแล้วจมีค่า default เป็น browser viewport

    rootMargin คือ string ที่เราสามารถกำหนด เหมือนเวลาตั้งค่า margin ตั้งค่าเป็น px หรือ % ก็ได้ ใน CSS โดยเราจะใส่ค่าก็ต่อเมื่อเราต้องการเพิ่มหรือลดพื้นที่ในการ เช็คการสังเกตการ โดยปกติ จะมีค่าเป็น 0

    threshold คือค่าที่มีไว้เพื่อระบุว่าเมื่อไหร่จะ trigger callback function โดย เราสามารถใส่เป็นค่าตัวเลขเดียว หรือใส่เป็น Array of number ก็ได้เช่นกัน ซึ่งมันจะคิดว่าต้องถึง element

    หลังจากที่เราทำความเข้าใจกับ options แล้ว เรามาดู ที่ตัว callback function กันบ้างดีกว่า

    ภายใน callback function นั้น เราจะได้ array of object IntersectionObserverEntry และภายในก็จะประกอบไปด้วย method และ properties ต่าง ๆ เช่น

    boundingClientRect method ที่จะคืนค่า กรอบสี่เหลี่ยม ที่เป็น immutable ตามลักษณะของ DOMRectReadOnly โดยคำนวณจากหน้าจอ Viewport และ คิด ค่า border-width และ ค่า padding หากเป็น standard box โดย ค่าที่ return จะประกอบไปด้วย lefttoprightbottomxywidth, and height ใช้ในการอธิบายตำแหน่งและขนาดของกล่องสี่เหลี่ยม

    intersectionRatio – ค่าที่สามารถใช้บอกว่า element ที่เราทำการ observer อยู่นั้น ปรากฎอยู่บน กรอบของ root element เท่าไหร่ ก็คือ ค่าของ intersectionRect ต่อ ค่า ของ boundingClientRect

    intersectionRect – ให้ค่า เหมือนกัน กับ boundingClientRect แต่จะเป็นค่าของ ตัว element ที่เราไป observer อยู่

    isIntersecting – ค่า boolean ที่จะบอกว่าเราว่า ขณะนี้ element ที่เรา observer อยู่ ปรากฎบน root rect หรือยัง

    rootBounds – ค่า DomRectReadOnly ของ root โดยหากมีการ กำหนด rootMargin ก็จะนำมาคำวณด้วยเช่นกัน

    target – ค่า Element ที่เพิ่งมีการ observe ไป เช่น สมมติว่า เราเลื่อนผ่าน object หนึ่ง ค่า root ของเรามันเปลี่ยนไป และไป observe โดน element ดังกล่าว หากเราเลื่อนเข้าไปเจอ ค่าtarget ก็จะเป็นค่านั้น หรือเราเลื่อนออก จาก element นั้นก็จะได้ค่านั้นเช่นกัน

    time – เวลาที่ จะบอก ว่า intersect กับ intersection root โดย สัมพันธ์กับ ค่า เวลาใน Intersection Observer

    Use Cases

    หลังจากที่เราทำความเข้าใจในหลักการแล้วว่า อะไร คือ Intersection Observer API เรามาดู use case scenario ในการทำงานจริงกันดีกว่า โดยเบื้องต้นผมจะโชว์ตัวอย่างเป็น

    • infinite scroll
    • animate on scroll
    • lazy loading

    โดยตัวอย่าง ที่จะโชว์นั้นจะเป็น ตัวอย่าง จาก side-project ที่ผมทำครับ

    Infinite Scrolling

    ตัวอย่างแรกจะเป็นการ ดึงข้อมูลจาก OPENSOURCE API อย่าง The Rick and Morty API ครับ

    เรามาเริ่มที่ไฟล์แรกกันดีกว่า ครับนั้นคือ ไฟล์ index.tsx ที่เราใช้ render หน้านี้

    import { fetcher } from "../utils/fetcher";
    import { Character, PaginatedResponse } from "../types/types";
    
    import useSWR from "swr"; 
    import { useCallback, useEffect, useRef, useState } from "react";
    import { useIntersection } from "../hook/useIntersection.ts";
    import { CharactersPage } from "../components/CharactersPage.tsx";
    
    export default function Index() {
      const loadMoreSection = useRef<HTMLDivElement>(null);
      const [allCharacters, setAllCharacters] = useState<Character[]>([]);
      const [characterPage, setCharacterPage] = useState<number>(1);
    
      const { data, isLoading } = useSWR<PaginatedResponse<Character>>(
        characterPage
          ? `https://rickandmortyapi.com/api/character?page=${characterPage}`
          : null,
        {
          fetcher: fetcher,
          revalidateOnFocus: false, // Prevent revalidation on window focus
          revalidateIfStale: false, // Prevent automatic revalidation
        }
      );
      const hanldeLoadMore = useCallback(() => {
        if (data?.info.next && !isLoading) {
          setCharacterPage((prev) => prev + 1);
        }
      }, [data?.info.next, isLoading]);
      const { observer } = useIntersection(hanldeLoadMore);
    
      useEffect(() => {
        if (data?.results) {
          setAllCharacters((prev) => [...prev, ...data.results]);
        }
      }, [data]);
    
      useEffect(() => {
        const currentRef = loadMoreSection.current;
        if (currentRef && observer) {
          observer.observe(currentRef);
          return () => {
            observer.unobserve(currentRef);
          };
        }
      }, [observer, loadMoreSection]);
      if (!data && !isLoading) return "Loading...";
    
      return (
        <main className="container mx-auto relative ">
          <h1 className="text-red-400 text-3xl text-center py-4">
            Welcome to the Rick C-137 Directory app
          </h1>
    
          <CharactersPage data={allCharacters} />
    
          <div ref={loadMoreSection}>Load More...</div>
        </main>
      );
    }
    
    
    

    จาก โค้ดข้างต้นนะครับ ผมได้ทำการ fetch ข้อมูลใหม่โดยใช้ library SWR มา จากนั้นก็ทำการ เช็คเงื่อนไขตัวแปรที่ได้คืนมาจาก useSWR อย่าง loading และ data ว่ามีข้อมูลหรือป่าว ถ้าไม่มี ให้แสดงหน้า Loading แต่ถ้ามีก็ให้แสดง การ์ด ข้อมูล Rick & Morty

    ถึงเวลาพระเอกของเรานั้นคือ custom hook อย่าง useIntersection ที่จะรับฟังก์ชั่น handleLoadMore เข้าไป เพื่อเปลี่ยนค่าตัวแปร page เมื่อเราเลื่อนจนถึงด้านล่างสุดครับ

    โดยผมจะทำการเรียก useEffect เพื่อเรียกใช้ ตัวแปรที่ชื่อว่า observer เพื่อให้ตัวแปรนี้ทำการ observe Element ที่ชื่อว่า loadMoreSection หรือ เลิก observe เมื่อเราออกจากหน้านี้

    import { useCallback, useEffect, useRef, useState } from "react";
    
    interface IntersectionOptions {
        threshold?: number;
        rootMargin?: string;
        root?: Element | null;
    }
    
    export const useIntersection = (
        handleLoadMore: () => void,
        options: IntersectionOptions = {},
    ) => {
        const [visible, setVisible] = useState(false);
        const observerRef = useRef<IntersectionObserver | null>(null);
        const handleIntersection = useCallback( // ด้วยการใช้ useCallback จะทำให้สร้างฟังก์ชั่นนี้ขึ้นมาใหม่ เมื่อตัวแปรที่เรากำหนดมีการเปลี่ยนเท่านั้น 
            (entries: IntersectionObserverEntry[]) => {
                for (const entry of entries) {
                    if (entry.isIntersecting) {
                        setVisible(true);
    
                        handleLoadMore();
                    }
                }
            },
            [handleLoadMore],
        );
    
        useEffect(
            () => {
                const {
                    threshold = 1.0,
                    rootMargin = "30px",
                    root = null,
                } = options;
                observerRef.current = new IntersectionObserver(handleIntersection, {
                    threshold,
                    rootMargin,
                    root,
                });
                return () => {
                    if (observerRef.current) {
                        observerRef.current.disconnect();
                    }
                };
            },
            [handleIntersection, options],
        );
    
        const observer = observerRef.current;
        return {
            observer,
            visible,
        };
    };
    
    
    

    ในส่วนของ useIntersection.ts custom hook นี้จะรับตัวแปร เข้ามา ได้แก่ ฟังก์ชั่น handleLoadMore และ IntersectionObserverOption

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

    ในส่วนของ การเช็คว่า intersect แล้วหรือยังนั้น จากที่เราได้อธิบายไปข้างต้นแล้วว่า ในตัวแปรประเภท IntersectionObserverEntry จะมี isIntersecting อยู่ หรือ หาก มันถึงจุดที่ intersect กันแล้วเราก็จะให้เรียก ฟังก์ชั่น handleLoadMore() เพื่อให้ เรียก ข้อมูลชุดใหม่จาก API

    ในส่วนของการ สร้าง Instance ของ Intersection Observer นั้น เรานำไปไว้ใน useEffect เพราะว่า เราต้องการให้ มัน recreate เฉพาะเมื่อมีมีการเปลี่ยนแปลงของ handleIntersection หรือก็คือ ให้ init เฉพาะตอนที่ มีการเลื่อนจนถึงล่างสุดและต้องโหลดหน้าใหม่ ให้เปลี่ยนตัว observe ตัวใหม่นั้นเอง

    ส่วนเหตุผลที่บางครั้งการทำ Infinite Scrolling นั้นดีกว่าการทำ Pagination หรือปุ่ม Loadmore นั้นเพราะว่ามันช่วยเพิ่ม User Experience ในการใช้งาน ลองจินตนาการถึงเวลาเล่น Facebook แล้วเรา ไถฟีด จนล่างสุดดูสิ ถ้าเราต้องมาคอยกดปุ่มให้เปลี่ยนหน้า หรือ กดปุ่มเพื่อให้โหลดมันก็จะเสียเวลาในการดูคอนเทนท์ของผู้ใช้งานนั้นเอง

    ในกรณีข้างต้นนี้เราจะอธิบายเพิ่มเติบเกี่ยวกับ IntersectionObserver options อย่าง threshold , rootMargin, และ Root

    เริ่มจาก RootMargin ถ้าหากเรา ตั้งค่า ให้เป็น 30px หรือมากกว่านั้น จะทำให้แม้ว่าเราจะยังไม่ทันเลื่อนจนถึง จุดที่จะ intersect กับ element ที่ observe ก็ตามมันจะทำการ trigger handleLoadMore ทันที ส่งผลให้เริ่มการ fetch ข้อมูลใหม่แม้ว่าผู้ใช้จะยังไม่ทันเลื่อนจนล่างสุดก็ตาม

    จะเห็นว่าเรายังไม่ได้เลื่อนจนชนขอบล่าง ตัว Intersection Observer ก็ทำการโหลด ข้อมูลใหม่รอไว้เรียบร้อยแล้ว

    ถัดไปสำหรับ threshold เป็นการ บอกว่าต้องผ่านไปแล้ว คิดเป็นเท่าไหร่ ของ Element นั้นจาก 0-1 จึงจะให้ทำการบอกว่านี้คือการ intersect แล้ว

     <div ref={loadMoreSection} className="h-40 w-full bg-red-400">
            Load More...
     </div>
    
    

    ผมได้ทำการปรับ ขนาด ของ loadMoreSection เพิ่มเป็น height : 160px และ ให้มีสีแดง จาก โค้ด option ที่เรากำหนดไว้เราบอกว่า Element จะต้องถูก observe ทั้งหมด จึงจะมองว่า intersect

    จากภาพเราจะมองเห็นว่าผมยังเลื่อนไม่ถึงสุดขอบจอ หรือ ก็คือ เรายังไม่ได้ เห็น element ครบ 100% มันจึงยังไม่ trigger ให้ fetch ข้อมูลใหม่

    ในส่วนของ root นั้น ในตัวอย่างนี้จะ ไม่สามารถโชว์ความแตกต่างได้แต่ถ้าให้ผมอธิบาย ยกตัวอย่างเช่น กล่อง แชท เวลามีคน live ใน ยูทูปก็ได้ครับ เวลาที่เรา เลื่อนภายใน กล่องแชท มันจะ trigger fetch แชทใหม่เรื่อยๆ ในขณะเดียวกัน แม้ว่าเราเลื่อน ดู comment ของ วีดิโอ มันก็จะไม่ trigger อะไรในกล่องนั้นเลย

    อย่างไรก็ดี จากโค้ดข้างต้นยังมีข้อบกพร่องคือบางครั้งการที่เรา fetch ข้อมูลใหม่ โดย อาศัยมองแค่ว่า ข้อมูลเปลี่ยนกับ เช็คว่ากำลังโหลด ข้อมูลอยู่ไหม อาจจะทำให้ เกิดบัคได้ เพราะ โดยปกติแล้วเวลาโหลดข้อมูลใหม่ โดยใช้ SWR นั้น จะทำการเปลี่ยนค่า data เป็น null ซึ่งอาจทำให้ การ render นั้นผิดพลาดและส่งผลให้ ไปแสดงค่าเริ่มต้นที่บนสุดแทน

    เพราะฉะนั้นเราจึงจำเป็นต้องใช้ ตัวแปรที่จะไม่เปลี่ยนค่า เมื่อมีการ render ใหม่อย่าง useRef เพื่อให้จดจำค่าล่าสุดว่า อยู่ตรงไหนของ scrollbar นั้นเอง

      const scrollPositionRef = useRef(0);
      const saveScrollPosition = useCallback(() => {
        scrollPositionRef.current = window.scrollY;
      }, []);
    
      const restoreScrollPosition = useCallback(() => {
        if (scrollPositionRef.current) {
          window.scrollTo(0, scrollPositionRef.current);
          requestAnimationFrame(() => {
            if (window.scrollY === scrollPositionRef.current) {
              scrollPositionRef.current = 0;
            }
          });
        }
      }, []);
        const isLoadingRef = useRef(false);
      const handleLoadMore = useCallback(() => {
        if (data?.info.next && !isLoading && !isLoadingRef.current) {
          isLoadingRef.current = true;
    
          saveScrollPosition();
          setCharacterPage((prev) => prev + 1);
        }
      }, [data?.info.next, isLoading, saveScrollPosition]);
    
      const { observer } = useIntersection(handleLoadMore);
    
      useEffect(() => {
        if (data?.results) {
          setAllCharacters((prev) => {
            const newCharacters = data.results.filter(
              (newChar) =>
                !prev.some((existingChar) => existingChar.id === newChar.id)
            );
            return [...prev, ...newCharacters];
          });
    
          isLoadingRef.current = false;
          restoreScrollPosition();
        }
      }, [data, restoreScrollPosition]);
    
    

    โดย ทุกครั้งที่เราจะโหลดข้อมูลใหม่ เราจะให้ เรียกใช้งานฟังก์ชั่น saveScrollPosition เพื่อทำการจำ ตำแหน่งสุดท้ายเอาไว้ และ เมื่อมีการ เรียกข้อมูลใหม่ useEffect ก็จะ trigger และทำการเรียกใช้ restoreScrollPosition เพื่อ ให้scroll ไปที่ตำแหน่งนั้นหาก เกิดข้อผิดพลาดในการแสดงผลนั้นเอง

    Animating on Scroll

    ในตัวอย่างถัดมาจะเป็นการแสดงการแสดง animation บางอย่างเมื่อเราเลื่อน ไป intersect กับ object นั้นๆ

    <script lang="ts">
    	import { onMount } from 'svelte';
    	export let text = '';
    	export let delay = 200;
    	export let once = false;
    	let isTextRevealed: HTMLElement;
    	onMount(() => {
    		const observer = new IntersectionObserver((entries) => {
    			for (let entry of entries) {
    				console.log(entry.target);
    				if (once) {
    					if (entry.isIntersecting) {
    						entry.target.classList.add('reveal');
    					}
    				} else entry.target.classList.toggle('reveal', entry.isIntersecting);
    			}
    		});
    		observer.observe(isTextRevealed);
    		return () => {
    			observer.unobserve(isTextRevealed);
    		};
    	});
    </script>
    
    <div bind:this={isTextRevealed} class="relative inline-block">
    	<p id="text" class="opacity-0" style="animation-delay: {delay}ms">
    		{@html text}
    	</p>
    	<div class="pointer-events-none absolute inset-0 overflow-hidden">
    		<div
    			id="cover"
    			class="absolute inset-0 bg-current transform-gpu -translate-x-[101%]"
    			style="animation-delay: {delay}ms"
    		/>
    	</div>
    </div>
    
    

    ก็คือจากตัวอย่างนี้ ผมจะเรียกใช้ component ที่ชื่อว่า TextReveal.svelte ซึ่งเมื่อ Browser Viewport ของเราเลื่อนไปจนถึงจุดที่ Intersect กับ Component นี้ก็ให้มัน แสดงตัวอักษรนั้นออกมาเหมือนในวีดิโอข้างต้น

    Image Lazy Loading

    import { useCallback, useEffect, useRef, useState } from "react";
    import { Character } from "../types/types";
    import { useIntersection } from "../hook/useIntersection";
    
    interface ICharacterCard {
      character: Character;
    }
    export const CharacterCard: React.FC<ICharacterCard> = ({ character }) => {
      const baseUrl = "<https://placehold.co/300x300>";
      const imageSection = useRef<HTMLImageElement | null>(null);
      const [imageSrc, setImageSrc] = useState<string>(baseUrl); // This is a gray placeholder
    
      const handleLoadImage = useCallback(() => {
        setImageSrc(character.image ?? "");
      }, [character.image]);
      const { observer } = useIntersection(handleLoadImage);
      useEffect(() => {
        const currentRef = imageSection.current;
        if (currentRef && observer) {
          observer.observe(currentRef);
          return () => {
            observer.unobserve(currentRef);
          };
        }
      }, [observer, imageSrc]);
      return (
        <div className=" bg-slate-400 max-w-xl w-full text-white rounded-xl shadow-2xl">
          <div className="flex gap-4">
            <img
              ref={imageSection}
              src={imageSrc}
              className="rounded-l-lg"
              loading="lazy"
              alt={character.name ?? "some unnamed chracter"}
            />
    
            <div className="p-4">
              <h3 className="text-2xl font-bold mb-4">{character.name}</h3>
              <p>Status: {character.status}</p>
              <p>Origin: {character.origin?.name}</p>
              <p>Species: {character.species}</p>
    
              <p className="text-slate-900 font-xl">Last Known Area</p>
              <p>{character.location?.name}</p>
    
              <p>Gender: {character.gender}</p>
            </div>
          </div>
        </div>
      );
    };
    
    
    

    เราจะทำคล้ายกัน ตัว Infinite Scroll เลยนั้นคือเมื่อเราเลื่อนถึงตำแหน่งที่ภาพนั้นอยู่เราจะค่อยให้มันโหลดรูป ขึ้นมาจากตอนแรกที่ใช้ template รูปมาก่อนนั้นเอง

    Compatibility

    ในปัจจุบัน สามารถใช้ได้บนทุก เบราว์เซอร์แล้วแต่จะมี บาง method ที่ยังไม่สามารถเรียกใช้งานได้ ทุกท่านสามารถเข้าไปดูโดยคลิกที่ลิงค์นี้เลย Browser compatibility

    References

    https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

    https://dev.to/il3ven/easily-implement-infinite-scrolling-using-intersection-observer-in-vanilla-javascript-5695

  • สรุป key insight จาก Smoking in UK dataset การบ้าน Ad’toy

    สรุป key insight จาก Smoking in UK dataset การบ้าน Ad’toy

    การวิเคราะห์พฤติกรรมการสูบบุหรี่ในสหราชอาณาจักร

    สวัสดีครับ หลังจากห่างหายไปเป็นอาทิตย์ วันนี้เรากลับมากับ coursework จาก คอร์ส Data Analyst Bootcamp ของ Ad’ Toy โดยการบ้านนี้จะเป็นส่วนหนึ่งของวิชา Spreadsheet โฟกัสที่การทำ pivot table และ dashboard

    สำหรับการบ้านนี้ผมเลือกที่จะใช้ dataset จากบน kaggle แหล่งรวม free dataset จากทั่วทุกมุมโลก ซึ่งในคราวนี้มันจะเกี่ยวกับ การสูบบุหรี่ภายในสหราชอาณาจักร

    สาเหตุที่ผมเลือกหัวข้อนี้เพราะว่าปีหน้าผมกำลังจะไปเรียนต่อ ป.โท เลยคิดว่าอยากทำอะไรที่เกี่ยวกับประเทศที่เราไปดีกว่า

    ในสหราชอาณาจักรอังกฤษตอนนี้กำลังจะบัญญัติกฎหมายใหม่ว่าด้วยการซื้อบุหรี่นั้นมีความผิดหากผู้ซื้อมีอายุต่ำกว่า 15 ปี แม้ว่าในปัจจุบันจำนวนผู้สูบบุหรี่ในช่วงวัยรุ่น นั้นจะลดลงแต่จำนวนผู้ที่เลือกหันไปสูบ บุหรี่ไฟฟ้า ( Vape ) ก็มีเพิ่มขึ้นเช่นกัน แม้งานวิจัยหลายอย่างจะชี้ตรงกันว่า การสูบบุหรี่ไฟฟ้ามีผลข้างเคียงน้อยกว่าบุหรี่ธรรมดา แต่ในระยะยาวแล้ว ก็สามารถส่งผลต่อระบบการหายใจ สมองและหัวใจได้เช่นเดียวกัน

    จากการสำรวจข้อมูลของ ทาง Office of National Statistics ใน สหราชอาณาจักรอังกฤษ พบว่า 50% เลิกบุหรี่เพราะว่าปัญหาด้านสุขภาพในขณะที่ 25% เลิกเพราะราคาที่ปรับตัวสูงขึ้นของบุหรี่

    หลังจากที่เรารู้ข้อมูลบางส่วนเกี่ยวกับพฤติกรรมการสูบบุหรี่ไปเรียบร้อยแล้ว งั้นเรามาดู columns ของ dataset นี้กันก่อนดีกว่าครับ

    Table of Content

    About Dataset

    แบบสำรวจนี้เป็นการสำรวจข้อมูลพฤติกรรมการสูบบุหรี่ในสหราชอาณาจักร โดยข้อมูลดังกล่าวสามารถนำมาใช้วิเคราะห์ลักษณะของผู้สูบและประเภทของบุหรี่ที่สูบ โดยข้อมูลนี้จะเก็บจากกลุ่มตัวอย่าง (sample) 1,691 คน ด้วยตัวแปรทั้ง 12 ตัวดังนี้

    ColumnDescription
    genderชาย หรือ หญิง
    ageอายุ
    marital_statusสถานะการแต่งงาน แบ่งเป็น หย่าร้าง แต่ง แยกกันอยู่ โสด และ หม่าย
    highest_qualificationระดับการศึกษา
    nationalityสัญชาติ
    ethnicityเชื้อชาติ
    gross_incomeรายได้สุทธิ
    regionพื้นที่
    smokeสถานะว่าสูบหรือไม่สูบ
    amt_weekendsจำนวนบุหรี่ที่สูบ ต่อวันในช่วงวันหยุดสุดสัปดาห์
    amt_weekdaysจำนวนบุหรี่ที่สูบ ต่อวันในช่วงวันธรรมดา
    typeประเภทบุหรี่ที่สูบ

    How can I create a chart in with pivot table

    1. ขั้นตอนแรก เลือกข้อมูลทั้งหมดโดยคลิกที่บริเวณมุมซ้ายบนของตาราง
    an image show an area for drag all of content in a sheet

    2. หลังจากเลือกข้อมูลทั้งหมดแล้ว ให้เลือกเมนู Insert > Pivot Table ระบบจะถามว่าต้องการสร้างใน Sheet ใหม่หรือใช้ Sheet ที่มีอยู่แล้ว

    A dialog show asking whether you will create in a new sheet or not

    3. เมื่อกด Create จะได้ตารางว่างและ Pivot Table Editor

    A blank table
    A pivot table editor

    4. เมื่อได้หน้าตาแบบนี้แล้ว เราสามารถลากหรือกดปุ่ม Add เพื่อเพิ่มข้อมูลในส่วนต่างๆ เช่น:

    • Filters
    • Rows
    • Columns
    • Values

    5. เราสามารถเลือกให้แสดงค่าผลรวมโดยเลือกที่ Show Totals Checkbox

    6. เมื่อได้ตารางที่ต้องการแล้ว ให้เลือกตารางดังกล่าวแล้วไปที่เมนู Insert > Chart

    7. สามารถคลิกที่ปุ่มสามจุด (⋮) เพื่อ:

    • ปรับแต่งรายละเอียดอื่นๆ
    • แก้ไขประเภทของกราฟให้เหมาะสม
    • ปรับแต่งชื่อกราฟ
    • เปลี่ยนสัญลักษณ์
    Chart Editor

    Insight

    ภายในข้อมูลชุดนี้ ไม่มีการระบุข้อมูลประเภทเวลา (date/time) ทำให้เราไม่สามารถสร้างกราฟประเภท line chart หรือ time series chart ได้

    หาก กราฟนี้มีข้อมูลประเภทนี้เพิ่มขึ้นเราสามารถวัดได้ว่าในแต่ละช่วงเวลาของปี ปริมาณบุหรี่ที่สูบแปรผันหรือไม่ หรือ ถ้าข้อมูลเวลายาวนานเพียงพอก็สามารถนำมาตั้งสมมติฐานว่า ในอดีตจนถึงปัจจุบันบุหรี่แต่ละประเภทมีความนิยมต่างกันเช่นไร (multiple line graph in one chart) เป็นต้น

    1. อายุเฉลี่ยของคนที่มาตอบแบบสอบถามนี้ เฉลี่ยแล้วอยู่ที่ 49.84 ปี
    2. แบบสอบถามนี้แบ่งออกเป็น ผู้ชาย 726 คน และ ผู้หญิง 965 คน
    Gender bar chart
    Gender bar chart

    3. ภายในแบบสอบถามนี้มีผู้สูบบุหรี่ (Yes) คิดเป็น 24.90% และ ไม่สูบ ( No ) 75.10%

    A smokers vs non-smokers donut chart
    A smokers vs non-smokers donut chart
    Cigarette type Vs Gross Income Stacked bar chart
    Cigarette type Vs Gross Income Stacked bar chart

    4. จากการวิเคราะห์ข้อมูลระหว่างช่วงรายได้สุทธิและประเภทของบุหรี่ ทำให้ผมพบข้อมูลที่น่าสนใจ ดังนี้

    • แบบซอง (Packets)
      • ในทุกช่วงรายได้ ผู้คนเลือกที่จะซื้อบุหรี่แบบซองมากกว่าที่จะมวนเองหรือเลือกทั้งคู่ โดยมีสัดส่วนถึง 75% ในช่วงรายได้ 26,400 ไปจนถึง 36,400 และมากกว่านั้น โดยในช่วงมากกว่า 36,400 บาทนั้นไม่พบผู้ที่ซื้อบุหรี่มวนเองเลย
      • ในขณะที่ช่วงรายได้อื่นก็คิดเป็นถึง 70% ของผู้ตอบแบบสอบถามในแต่ละช่วง
    • แบบมวนเอง (Hand-rolled)
      • ในช่วงรายได้ 2,600 ถึง 5,200 มีผู้ซื้อบุหรี่มามวนเองคิดเป็น 25% ของกลุ่มตัวอย่างที่มีรายได้อยู่ในช่วงดังกล่าว
      • จำนวนจะลดลงตามช่วงรายได้ที่เพิ่มขึ้น
    • เลือกทั้งคู่
      • ในทุกช่วงรายได้มีคนเลือกที่จะทั้งซื้อและมวนเอง ยกเว้นในช่วง 28,600-36,400 ที่ไม่พบกลุ่มตัวอย่างใดเลย
      • แม้ว่าจะพบได้ในเกือบทุกช่วง แต่จำนวนเปอร์เซ็นต์ก็ถือว่าเป็นส่วนน้อยเมื่อเทียบกับผู้ที่เลือกอย่างใดอย่างหนึ่ง
    • ปัจจัยที่มีผลต่อการเลือกประเภทบุหรี่
      • ราคา: ราคาบุหรี่แบบซองนั้นมีราคามากกว่า ส่งผลให้ในช่วงรายได้ต่ำๆ นั้นคนจึงเลือกที่จะซื้อแบบมวนเองอย่างมีนัยสำคัญ
      • ความสะดวก: การเลือกซื้อบุหรี่แบบซองนั้นง่ายต่อการใช้งานเมื่อเทียบกับแบบมวนเองที่ต้องนำมาเตรียม และยังมีราคาเพิ่มเติมหากต้องใช้อุปกรณ์ในการมวน
    Multiple bar chart about Nationality vs Region
    Multiple bar chart about Nationality vs Region

    5. จากข้อมูล ผมได้นำกราฟสัญชาติมาเทียบกับพื้นที่ เพื่อดูว่าในแต่ละพื้นที่นั้น สัญชาติใดมีแนวโน้มในการสูบบุหรี่อย่างไรบ้าง

    • British
      • กลุ่มตัวอย่างที่นิยามตัวเองว่าเป็น British มีจำนวนการสูบบุหรี่กระจายตัวอยู่ในทุกพื้นที่
      • สามารถพบได้มากในบริเวณ Midland & East Anglia, The North และ South East มากกว่า 25 คนในแต่ละพื้นที่
    • English
      • กลุ่มที่นิยามตนว่าเป็น English นั้นพบได้ทั่วไปในทุกพื้นที่ แต่จะพบมากที่สุดที่ The North เป็นจำนวน 60 คน และประมาณ 58 คนบริเวณ Midland & East Anglia
      • พบได้น้อยในพื้นที่ Scotland และ Wales
    • Irish
      • ผู้ที่นิยามว่าตนเป็น Irish นั้นพบได้น้อยมากในชุดข้อมูลนี้ โดยพบได้ในทุกพื้นที่แต่อยู่ในสัดส่วนที่น้อย ยกเว้นใน Wales ที่ไม่พบคนกลุ่มนี้เลย
      • แต่ละพื้นที่จะพบเพียงแค่ 5 คน หรือน้อยกว่านั้น
    • Scottish
      • คนสกอตแลนด์สามารถพบได้มากที่สุดในพื้นที่ Scotland โดยมีมากกว่า 40 คนจากกลุ่มตัวอย่าง และเพียง 3-5 คนใน London และ The North
    • Welsh
      • คนเวลส์สามารถพบได้มากใน Wales และเพียง 2-3 คนในพื้นที่ The North และ South East
    • ข้อสังเกตเพิ่มเติม
      • จากกลุ่มตัวอย่างนี้ ไม่สามารถสรุปได้อย่างแน่ชัดว่าคนสัญชาติใดที่สูบบุหรี่มากที่สุด เนื่องจากพื้นที่ที่ใช้ในการสำรวจนั้นไม่ครอบคลุมทั่วทั้งสหราชอาณาจักร
      • กลุ่มตัวอย่างเพียง 1,691 คน ไม่สามารถสรุปได้ว่าสัญชาติในแต่ละพื้นที่ใดสูบบุหรี่มากกว่ากัน
      • จากการสังเกตจะพบว่ามีความสอดคล้องอย่างหนึ่งคือ หากเป็นคน Scottish ก็จะพบมากในพื้นที่ Scotland และ Welsh ก็พบมากใน Wales
    Gross Income vs Qualification
    Gross Income vs Qualification

    6. กราฟนี้จะเป็นการพูดถึงรายได้สุทธิเทียบกับระดับการศึกษา จากกราฟเราจะเห็นว่าในช่วงที่รายได้สูง กลุ่มประชากรที่จบระดับปริญญาตรีขึ้นไปก็เยอะเช่นกัน คิดเป็นเกือบ 50% ของช่วงรายได้ 28,600 ถึง 36,400 และช่วงที่มากกว่า 36,400 ในขณะเดียวกัน ในช่วงรายได้ต่ำจะสังเกตได้ว่าระดับการศึกษานั้นมีความหลากหลายเป็นอย่างมาก อย่างไรก็ตามเรายังไม่สามารถบอกได้ว่ารายได้กับจำนวนคนที่สูบบุหรี่มีความสัมพันธ์กัน

    • Degree
      • จากกราฟจะพบว่าคนที่เรียนจบระดับอุดมศึกษาและสูบบุหรี่จะพบมากที่สุด คิดเป็นเกือบ 50% ในช่วงรายได้ 28,600-36,400 หรือมากกว่านั้น และไม่พบเลยในช่วง 2,600-5,200
      • ในส่วนของช่วงรายได้อื่นจะคิดเป็นเปอร์เซ็นต์ไม่เกิน 10%
    • GCSE/O Level
      • สามารถพบได้ในทุกช่วงรายได้ และคิดเป็น 10-35% ของคนสูบบุหรี่ในแต่ละช่วง
      • ไม่พบในช่วงรายได้ 28,600 – 36,400
    • No Qualification
      • สามารถพบได้ประมาณ 30-50% ในช่วงรายได้ต่ำ ตั้งแต่ต่ำกว่า 2,600 ไปจนถึง 10,400 และค่อยๆ ลดลงไปเรื่อยๆ ตามช่วงรายได้สุทธิ
      • พบจำนวนคนสูบบุหรี่ที่ไม่มีวุฒิการศึกษาในช่วง 2,600-5,200 มากที่สุด คิดเป็น 50% ของกลุ่มตัวอย่างในช่วงนี้
    • A Level
      • คนที่เรียนจบระดับ A-Level พบได้น้อยมากในชุดข้อมูลนี้ โดยคิดเป็นเพียง 1-10% เท่านั้น
      • พบน้อยที่สุดในช่วง 5,200-10,400 ที่ 1.88% ของกลุ่มตัวอย่างในช่วงรายได้
    • GCSE/CSE
      • สวนทางกับผู้ที่เรียนจบระดับอุดมศึกษา กลุ่มนี้สามารถพบได้มากในช่วงรายได้น้อยและลดลงในช่วงรายได้มากขึ้น
      • พบได้มากที่สุดในผู้สูบบุหรี่ที่มีรายได้ต่ำกว่า 2,600 คิดเป็น 27.78% ของกลุ่มตัวอย่าง
    • ONC/BTEC
      • ไม่พบในรายได้ต่ำกว่า 5,200
      • พบมากที่สุดในช่วงรายได้มากกว่า 36,400 คิดเป็น 26.67%
    • ข้อสังเกต:
      • ในช่วงรายได้ปานกลางตั้งแต่ 5,200-28,600 เราสามารถพบคนสูบบุหรี่ได้จากทุกระดับการศึกษา
      • การศึกษาที่สูงมีโอกาสมีรายได้สูงตามด้วยเช่นกัน
      • แต่เราไม่สามารถระบุได้ว่าการมีรายได้สูงหรือการศึกษาสูงมีผลต่อจำนวนคนที่สูบบุหรี่หรือไม่
    Age Range vs Number of Cigarette in Weekday
    Age Range VS Number of Cigarette in Weekday
    Age Range VS Number of Cigarettes in Weekend
    Age Range VS Number of Cigarettes in Weekend

    7. ในส่วนของ bar chart สองอันนี้จะเป็นจำนวนบุหรี่ที่สูบในช่วงวันเสาร์-อาทิตย์ หรือช่วงวันธรรมดา ในแต่ละช่วงอายุ โดยจะสังเกตว่าในวันธรรมดา ผู้หญิงมีแนวโน้มสูบบุหรี่มากกว่าผู้ชายในช่วงอายุ 20-49 ในขณะที่ช่วงวันเสาร์-อาทิตย์ ผู้ชายสูบบุหรี่ในปริมาณที่มากกว่าผู้หญิงในทุกช่วงอายุ ยกเว้นเพียงช่วง 60-69 และ 80-89 เท่านั้น

    • ผู้ชาย
      • มีแนวโน้มสูบบุหรี่มากกว่าผู้หญิงในช่วงวันเสาร์-อาทิตย์ทุกช่วง ยกเว้นช่วงอายุ 60-69 และช่วงอายุ 80-89
      • ไม่พบทั้งผู้ชายและผู้หญิงในช่วงอายุ 90+ สูบบุหรี่เลยในช่วงวันเสาร์-อาทิตย์
      • พบผู้ชายสูบบุหรี่มากกว่าผู้หญิงในช่วง 70-79 โดยคิดเป็นความต่างอยู่ที่ประมาณ 13 คน
    • ผู้หญิง
      • มีแนวโน้มสูบบุหรี่มากกว่าผู้ชายในวันทำงาน ยกเว้นในช่วงอายุ 50-59 และ 60-69 ที่ผู้ชายมีแนวโน้มจะสูบบุหรี่มากกว่า และในช่วงอายุต่ำกว่า 20 ที่มีจำนวนผู้สูบบุหรี่เท่ากัน
      • ไม่พบผู้ชายสูบบุหรี่ในช่วงอายุ 90+
      • ผู้หญิงมีจำนวนสูบบุหรี่มากกว่าผู้ชายมากกว่า 20 คนในช่วงอายุ 40-49
    • ข้อสังเกต:
      • จากกราฟไม่สามารถสรุปได้ว่าเพศและวันหยุดมีความสัมพันธ์กันหรือไม่
      • ยิ่งอายุมากขึ้น มีแนวโน้มที่จะสูบบุหรี่น้อยลง
      • ไม่สามารถระบุความสัมพันธ์ได้
    Marital_status Vs  Average number of cigarettes
    Marital_status Vs Average number of cigarettes

    8. ในส่วนของกราฟนี้จะเป็นการดูว่าสถานะการสมรสกับจำนวนบุหรี่นั้นมีความสัมพันธ์อย่างไร โดยข้อมูลจำนวนบุหรี่นั้นเกิดจากการสร้าง Calculated Field ของค่าเฉลี่ยจำนวนบุหรี่ที่สูบในช่วงวันธรรมดาและวันหยุดเสาร์-อาทิตย์มารวมกัน

    • ผู้ที่มีสถานะโสดมีค่าเฉลี่ยของบุหรี่ที่สูบต่ำที่สุด ประมาณ 28 มวน และสูงสุดคือผู้ที่เป็นหม้าย อยู่ที่ 33 มวน
    • ในแต่ละสถานะการสมรสไม่ได้มีผลต่อจำนวนบุหรี่ที่สูบมากนัก เพราะว่าในกลุ่มตัวอย่างอื่นๆ ก็มีจำนวนที่ใกล้เคียงกัน
    • ข้อสังเกต
      • ไม่ได้พิจารณาปัจจัยอื่นๆ เช่น อายุ ระยะเวลา และเศรษฐกิจ
      • การใช้ค่าเฉลี่ยอาจเป็นค่ากลางที่ไม่ดีที่สุด หากการกระจายตัวนั้นไม่เป็นแบบ Normal Distribution

    Summary

    • อายุและเพศ
      • ผู้หญิงมีแนวโน้มสูบบุหรี่มากกว่าผู้ชายในช่วงวันธรรมดา
      • การสูบบุหรี่ลดลงตามอายุ
    • ปัจจัยทางเศรษฐกิจและการศึกษา
      • บุหรี่แบบซองเป็นที่นิยมในทุกช่วงรายได้
      • รายได้และการศึกษาแปรผันตามกัน
      • ผู้ที่มีรายได้น้อยเลือกที่จะมวนเอง
    • สถานภาพการสมรส
      • คนโสดมีแนวโน้มสูบบุหรี่น้อยที่สุด
      • สถานภาพกับจำนวนบุหรี่ไม่ได้มีนัยสำคัญที่ชัดเจน

    Restriction

    • ไม่มีข้อมูลช่วงเวลา จึงไม่สามารถทำ Time Series Chart ได้ ส่งผลให้ไม่รู้ว่าในระยะยาวนั้นสรุปข้อมูลได้อย่างไร
    • ข้อมูลยังน้อยไป ไม่สามารถหาความสัมพันธ์ที่ชัดเจนได้
    • ไม่มีข้อมูลบางด้าน เช่น อาชีพที่อาจจะสามารถสะท้อนระดับความเครียดกับจำนวนบุหรี่ได้

    How can I make it better

    • เก็บข้อมูลเพิ่มเติมโดยอ้างอิงตามช่วงเวลา เพื่อดูแนวโน้มการเปลี่ยนแปลงในระยะยาว
    • เก็บข้อมูลเพิ่มเติม เช่น อาชีพ และระดับความเครียด

    References

    https://www.kaggle.com/datasets/utkarshx27/smoking-dataset-from-uk/data

    https://www.bbc.com/news/health-68825322

  • R 101 สรุป สิ่งที่ได้เรียนรู้เพิ่มเติมจาก Hands-On Programming with R

    R 101 สรุป สิ่งที่ได้เรียนรู้เพิ่มเติมจาก Hands-On Programming with R

    หากใครก็ตามที่คิดว่าจะเริ่มวิเคราะห์ข้อมูล ทำกราฟ หรืออะไรก็ตามแต่ ในยุคที่เทคโนโลยีนั้นเปลี่ยนผ่านไปอย่างรวดเร็ว ใครๆ ก็ล้วนนึกถึง Python หนึ่งในภาษาที่มีความยืดหยุ่นอย่างมาก สามารถวิเคราะห์ข้อมูล ทำนายผล และยังสามารถแสดงกราฟออกมาได้ แต่ในสมัยที่ยังไม่มี Python เราใช้อะไรในการทำกันล่ะ?

    ภาษา R ถูกสร้างขึ้นในปี 1933 โดยมีจุดประสงค์เพื่อใช้งานในการคำนวณเชิงสถิติและการทำกราฟแบบต่างๆ โดยในปัจจุบันได้มีการพัฒนาส่วนเสริม (packages) ขึ้นมาเป็นจำนวนมากเพื่อให้รองรับการใช้งานสำหรับชาว Data Scientist

    Photo by Pixabay on Pexels.com

    บทความนี้เป็นการนำสิ่งที่ผมได้เรียนรู้จากการอ่านหนังสือ ที่ชื่อว่า Hands-On Programming Project 1 และ 2 มาแชร์ให้เพื่อนๆฟังกันครับ การบ้านจากคอร์ส Data Science Bootcamp ของ DataRockie

    Table of Contents

    1. Weighted Dice

    ภายในหนังสือนั้น จะนำเสนอตัวอย่างโค้ดและ syntax ผ่านการทดลองทำโปรเจกต์ง่ายๆ อย่างการทำลูกเต๋าสองลูกที่ถูกถ่วงน้ำหนัก ภายในหัวข้อนี้จะครอบคลุมเนื้อหาในส่วนที่เป็นพื้นฐาน ซึ่งหากคุณค่อยๆ ทำตามจะสามารถเข้าใจหลักการเขียนคำสั่งต่างๆ ดังนี้

    • การเขียนคำสั่ง R
    • การสร้าง R Object
    • การเขียนฟังก์ชัน
    • การโหลดใช้งานส่วนเสริม packages
    • การสุ่ม
    • การพล็อตกราฟอย่างง่าย

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

    Garrett Grolemund

    ในส่วนแรกของหนังสือ เราต้องโหลด RStudio Desktop และ R language ก่อนเพื่อเริ่มทำโปรเจคนี้

    Rstudio Interface

    ภาษา R เป็น ภาษาประเภท dynamic-programming ภาษาที่คุณไม่จำเป็นจำต้อง compile เพื่อเปลี่ยนมันเป็นสิ่งที่คอมพิวเตอร์เข้าใจ เช่น ภาษา C หรือ JAVA

    Basic Calculation Command

    R สามารถทำ + - * / ได้เหมือนกับภาษาทั่วๆไป

    10 + 2
    ## 12
    
    12 * 3
    
    36 - 3
    ## 33
    
    33 / 3
    ## 11
    
    

    Object

    หากเราต้องการสร้าง ลูกเต๋า เราสามารถใช้ : เพื่อสร้าง vector data type ที่เก็บค่าแค่มิติเดียว

    1:6
    
    ## 1 2 3 4 5 6
    
    

    แต่ว่าการที่เราไม่ได้ assign ค่าให้เข้ากับ ตัวแปรบางอย่างจะทำให้เราไม่สามารถใช้งานมันได้ เนื่องจากไม่ได้บันทึกค่า ลงใน computer’s memory โดยเราสามารถบันทึกค่า ลงตัวแปร object ได้ดังนี้

    a <- 1
    
    die <- 1:6 # ทำการบันทึก vector 1:6 ลงในตัวแปร die
    
    

    เครื่องหมาย < - จะบอกให้คอมพิวเตอร์ทำการ assign ค่า ลงไปในตัวแปรทางซ้ายมือของเรา

    R เป็น ภาษา case-sensitive เพราะฉะนั้น Name กับ name ถือว่าเป็นคนละตัวแปร

    R ห้ามตั้งชื่อโดยขึ้นต้นด้วยตัวเลข และ ห้ามใช้ special case ด้วย

    เราสามารถใช้คำสั่ง ls() เพื่อดูว่าเราได้ประกาศตัวแปรอะไรไปแล้วบ้าง

    ในภาษา R นั้น จะไม่ได้ยึดหลักการ คูณ Matrix เหมือนใน Linear Algebra แต่จะใช้วิธีการที่เรียกว่า element-wise execution ซึ่งเป็นการทำ operation โดยจับคู่ vector สองอันมาเทียบกัน

    หาก vector สองอันที่เอามาใช้ ยาว ไม่เท่ากัน R จะทำการวนลูปจน ตัวที่สั้นไปบนตัวที่ยาวจนครบ แล้วค่อยทำ operation เราเรียกว่า vector recycling

    https://rstudio-education.github.io/hopr/basics.html

    เราสามารถใช้ %*% สำหรับการคูณแบบเมทริกซ์ และ %o% สำหรับการคูณแบบ outer product

    Function

    ภายใน R มี built-in ฟังก์ชัน ให้ใช้งานอยู่หลายแบบ ไม่ว่าจะเป็น round, factorial, mean

    R จะรัน ฟังก์ชัน จากในสุดออกไปด้านนอก ดังตัวอย่าง

    สำหรับใน การ สุ่มเต๋านั้น เราจะใช้ sample ฟังก์ชัน ที่รับ argument 2 ค่า โดย รับเป็น vector กับ จำนวนผลลัพธ์ที่จะ return ออกมา

    sample(x = die, size = 1)
    ## 2
    
    

    ในหลายๆครั้ง เรา ไม่รู้หรอกว่า ฟังก์ชัน ดังกล่าวรับ argument ชื่ออะไรบ้าง เพราะฉะนั้นเราสามารถใช้คำสั่ง args() เพื่อเช็คได้

    หากเราสังเกตจะพบว่า หากเราไม่เรียกใช้ parameter replace ลูกเต๋าของเราก็จะไม่สุ่มหน้าซ้ำ ซึ่งมันก็จะผิดหลักการการสุ่มลูกเต๋า

    อย่างไรก็ตาม การที่เราต้องมานั่ง run commands อย่าง sample และ sum ทุกครั้งมันก็คงจะเป็นการทำที่น่าเบื่อหน่าย เพราะฉะนั้นเราจึงจะสร้าง ฟังก์ชัน ของเรามาใช้เอง

    my_function ← function() {}

    roll <-function() {
      die <- 1:6
      dice <- sample(die, size = 2, replace = TRUE)
      sum(dice)
    }
    
    

    โค้ดที่เรานำไปวางไว้ภายในวงเล็บปีกกาเรียกว่า body ของ ฟังก์ชัน นั้นๆ เพราะฉะนั้น บรรทัดสุดท้ายของโค้ด จะต้องคืนค่าบางอย่างกลับมา อย่างไรก็ตาม จากตัวอย่างข้างต้น การที่เราจะต้องมาประกาศตัวแปร die ทุกครั้งที่เรียกฟังก์ชั่น ก็กระไรอยู่ ทำไมเราไม่นำมาไว้ข้างนอก แล้วเอาส่งเข้าไปล่ะ

    bones <- 1:6
    roll <-function(bones) {
      dice <- sample(x= bones, size = 2, replace = TRUE)
      
    }
    
    roll(bones)
    
    

    อย่างไรก็ตาม ในกรณีนี้หากเรา ไม่ได้ระบุค่า bones จะทำให้ ฟังก์ชัน ของเรา error เพราะฉะนั้นเราควรกำหนดค่า default ให้ funciton

    roll2 <-function(bones = 1:6) {
      dice <- sample(bones, size = 2, replace = TRUE)
      sum(dice)
    }
    
    

    ใน R คุณสามารถสร้าง ฟังก์ชัน ด้วยตัวเองมากแค่ไหนก็ได้ แตกต่างจาก tools สำเร็จรูปอย่าง excel คุณเคยสร้างเมนูใหม่ใน Excel หรือไม่ เพราะฉะนั้นการที่คุณสามารถเขียน programming ได้เอง จะช่วยให้คุณเปิดกว้างได้มากขึ้น

    R สามารถเก็บค่าประเภท binary และ ค่า complex number ได้เช่นกัน

    2. Playing Card

    ภายในโปรเจคที่สอง นี้เราจะมาสร้างการ์ดเกมโดยคุณจะได้เรียนรู้การ เก็บ เรียกใช้ และการเปลี่ยนแปลค่าของข้อมูลใน computer’s memory

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

    • ในโปรเจคนี้เราจะได้เรียนการบันทึกประเภทข้อมูล เช่น string และ boolean
    • บันทึกข้อมลในรูปแบบ vector matrix array list และ dataframe
    • โหลดและบันทึกข้อมูลด้วย R
    • รับข้อมูลจาก data set
    • เปลี่ยนค่าข้อมูลใน data set
    • เขียน เทสสำหรับ logics
    • การใช้ NA

    Task 1: build the deck

    atomic vector – simple vector ที่เราได้เรียนไปในบทที่ 1 อย่าง die โดยเราสามารถใช้คำสั่ง is.vector เพื่อเช็คประเภทข้อมูลได้ โดยการบันทึกค่าเพียงค่าเดียวก็นับเป็น atomic vector เช่นกัน

    เราสามารถเขียน 1L ภายใน atomic vector เพื่อระบุค่าว่าเป็น number vecotr ได้

    length เป็น ฟังก์ชั่นที่ใช้ในการนับความยาวของ atomic vector

    เราสามารถใช้ typeof เพื่อระบุประเภทของข้อมูลว่าเป็นข้อมูลประเภทอะไรใน R

    คำว่า doubles และ numerics นั้นมีความหมายเหมือนกัน แต่ว่าคำว่า double เป็นคำที่นิยมใช้ในแวดวง programming เพื่อสื่อถึงจำนวน bytes ที่คอมพิวเตอร์ใช้ในการเก็บค่าข้อมูล

    โดยปกติแล้วหากเราไม่ระบุ L. ท้ายตัวเลขนั้น R จะบันทึกค่าเป็น double โดยค่ายังเหมือนกัน แตกต่างที่ type เท่านั้น อย่างไรก็ตามเราไม่สามารถบันทึกทุกค่าเป็น doubles ได้เพราะว่าคอมพิวเตอร์จะใช้ 64bits สำหรับการเก็บ ค่าdoubles เพราะฉะนั้น ค่า doublesใน R จะมีทศนิยมแค่ 16.ตำแหน่งเท่านั้น

    ซึ่งเหตุการณ์เหล่านี้ เรียกว่า floating-point error ซึ่งคุณสามารถพบได้เมื่อทำการคำนวณค่าด้วย ตัวเลขที่มีทศนิยม เราเรียกปัญหานี้ว่า floating-point arithmetics

    เราสามารถเขียน TRUE FALSE ใน R ด้วย T F ได้เช่นกัน

    เราสามารถใช้คำสั่ง attributes เพื่อเช็คว่า object ดังกล่าวมี attributes อะไรบ้าง โดย NULL คือค่าที่จะแสดงออกมในกรณีที่เป็น null set หรือ empty object

    ฟังก์ชัน names() เป็น ฟังก์ชั่นที่ใช้ในการตั้งชื่อ ให้กับ atomic vector, dim, classes

    อย่างเช่นเราสามารถตั้งชื่อให้ค่าในลูกเต๋าเราได้

    names(die) <- c("one", "two", "three", "four", "five", "six")
    ##  one   two three  four  five   six 
    ##    1     2     3     4     5     6 
    
    

    แต่ว่าหากเรา +1 ค่าใน die ชื่อก็จะไม่ได้เปลี่ยนตามอยู่ดี โดยหากต้องการนำออกให้ assign เป็น NULL

    เราสามารถเปลี่ยน ค่า atomic vector ไปเป็น dimensional array โดย assign ค่า number vector ไปที่ attribute dim

    โดย ค่าที่อยู๋ dimensional array นั้นจะเริ่มจากค่า rows ก่อนเสมอ

    เราสามารถสร้าง matrix ด้วยการเรียกใช้ ฟังก์ชัน ที่ชื่อว่า matrix โดยการป้อน argument number vector, จำนวนแถว และค่า boolean สำหรับการบอกว่าให้เติมแบบแถวต่อแถวหรือไม่

    m <- matrix(die, nrow = 2, byrow = TRUE)
    
    ##    [,1] [,2] [,3]
    ## [1,]    1    2    3
    ## [2,]    4    5    6
    
    
    

    การที่เราเปลี่ยนแปลง dimension ของ object นั้นจะไม่ได้เปลีย่น type แต่จะเปลี่ยน class ของ object นั้นแทน

    dim(die) <- c(2, 3)
    typeof(die)
    ##  "double"
     
    class(die)
    ##  "matrix"
    
    

    เราสามารถเรียกใช้ Sys.time() เพื่อรับค่าเวลาได้ แต่ว่า type ของมันจะเป็น double และ class จะเป็น POSIXct POSIXt

    POSIXct เป็น framework ที่คนนิยมใช้เพื่อการแสดงค่าวันที่และเวลา โดย เวลาใน POSIXct จะแสดงค่าด้วยจำนวนเวลาที่ได้ผ่านไปแล้วนับตั้งแต่วันที่ January 1st 1970

    หากเราใช้คำสั่ง unclass เราก็จะพบกับค่าตัวเลขที่ อยู่เบื้องหลัง วันที่และเวลา ในทางกลับกัน เราก็สามารถ assign class ให้กับตัวเลขเพื่อให้ได้วันที่ได้เช่นกัน

    now <- Sys.time()
    unclass(now)
    ## 1395057600
    
    mil <- 1000000
    mil
    ## 1e+06
    class(mil) <- c("POSIXct", "POSIXt")
    mil
    
    
    

    factors คือ ฟังก์ชัน ที่เราไว้ใช้เก็ฐค่าประเภท categorical information.

    Coercion

    เป็นหลักการที่ R จะทำการเปลียนค่าของ data types เช่น หากมีค่า character string ใน atomic vector R จะเปลี่ยนค่าทั้งหมดเป็น string

    หากเป็นค่า logical และมี numerics อยู่ก็จะเปลี่ยนเป็น 0 หรือ 1 แทน

    เราสามารถเรียกช้คำสั่ง as.character(gender) เพื่อให้ค่าปลี่ยนเป็น character string

    https://rstudio-education.github.io/hopr/images/hopr_0301.png

    อย่างไรก็ตาม การเรียนรู้ data types ด้านบนนั้นไม่สามารถนำมาใช้ในการสร้าง card deck ได้เพราะพวกมันเก็บ data type ได้แค่แบบเดียว

    list สามารถเก็บค่า ได้หลายรูปแบบ เช่น

    card <- list("ace", "hearts", 1)
    card
    ## [[1]]
    ## [1] "ace"
    ##
    ## [[2]]
    ## [1] "hearts"
    ##
    ## [[3]]
    ## [1] 1
    
    

    list สามารถเก็บค่า การ์ดทั้งสำรับให้เราได้แต่เราก็คงจะไม่ทำเช่นกัน นั้นจึงเป็นที่มาของ Data Frames two-dimensional version of a list.

    ค่า character string ด้านในที่บันทึกไว้จะเป็น factors ทั้งหมด หากคุณไม่อยากได้สามารถกำหนด parameter ที่ชื่อว่า stringsAsFactors ให้เป็น false

    df <-data.frame(face =c("ace", "two", "six"),
      suit =c("clubs", "clubs", "clubs"), value =c(1, 2, 3))
    df
    ## face  suit value
    ##  ace clubs     1
    ##  two clubs     2
    ##  six clubs     3
    
    

    ในการสร้าง data frames นั้นคุณจำเป็นต้องมั่นใจว่า ข้อมูลของคุณนั้นยาวเท่ากันในแต่ละ vector ไม่เช่นนั้นคุณต้องทำ recycling rules เพื่อให้มีคววามยาวเท่ากัน

    สำหรับ data frame แล้ว มี type เป็น list แต่ว่าหากเราเช็ค class เราก็จะได้เป็น data.frame

    ในแบบทดสอบนี้ในหนังสือได้เตรียมไฟล์ csv ไว้ให้แล้วเนื่องจากการที่เราต้องามาสร้าง deck เองเนี้ยโอกาสเกิด error ค่อนข้างเยอะจึงไม่ควรพิมพ์เยอะขนาดนั้น

    โดยเราสามารถเรียกใช้คำสั่ง read.csv ในการอ่านไฟล์ได้

    Task 2: 2 functions

    ในการเล่นเกมการ์ดนั้น เราต้องสามารถสลับกองไพ่และจั่วใบบนสุดเพื่อแจกได้ เพราะฉะนั้นเราต้องเขียน ฟังก์ชัน สองอันเพื่อมาารองรับ การทำงานสองสิ่งนี้

    • Positive integers deck[1, c(1, 2, 3)]
      • ถ้าเราเขียนซ้ำด้วยการใส่ vector ลงไป R ก็จะแสดงค่าซ้ำด้วย
    • Negative integers deck[-2(2:52), 1:3]
      • จะดึงค่าที่เราไมได้กำหนดอออกมา เช่นกรณีก็จะดึงมาแค่ king spades 13
      • ง่ายต่อการทำ subset
      • เราสามารถใส่ จำนวนเต็มบวก และ ลบ ใน vector เดียวกันเพื่อดึงค่าออกมา deck[c(-1, 1), 1]
    • Zero – แม้ว่าจะไม่ใช่ทั้งจำนวนเต็มบวกและลบ แต่่คุณก็จะได้ data frame เปล่ามาอยู่ดี deck[0, 0] ## data frame with 0 columns and 0 rows
    • Blank spaces – การเว้นว่างไว้ เป็นการบอก R ให้ว่า เอาค่าในทุกๆอันใน row หรือ column มา
    • Logical values การใส่ atomic vector ของ logic ลงไป R จะพยายาม match ค่า แล้วดึงข้อมูลออกมาแค่เฉพาะค่าที่ ป็น True เท่านั้น

    deck[ , "value"]

    deck[1, **c**("face", "suit", "value")]

    • Names – เราสามารถดึงข้อมูลด้วย ชื่อ ได้เช่นกัน

    โดยปกติแล้วเราก็จะใช้ deck[ , ] ในการนำค่าออกมา โดย หลักแรกจะแทน row และหลักสองจะแทน column เราสามารถสร้าง index ได้หลายรูปแบบดังนี้

    ใน R นั้น index จะเริ่มที่ 1 ไม่ใช่ 0

    ในกรณีท่ี่เรา เลือกค่ามาแค่ 1 column เราจะได้แค่ เป็น vector หากอยากได้ data frames เราต้องกำหนด drop = False

    ทีนี้เราจะมาสร้าง ฟังก์ชัน สลับการ์ดกัน

    deal <- ฟังก์ชัน(cards) {
    	cards[1, ]
    }
    
    

    เบื้องต้นหากเราใส่ 1 ลงไป เราก็จะได้การ์ดใบบนสุดของ deck มาแต่ ว่าในการเล่นจริงนั้น เราต้องการให้กองไพ่นั้น สลับและไม่อยู่ใน ลำดับเดิมตลอดเวลา เพราะฉะนั้นแทนที่เราจะใส่ 1:52 เราก็จะใส่เป็น random order เข้าไปแทน

    shuffle <- ฟังก์ชัน(cards) {
      random <- sample(x= 1:52, size = 52)
      cards[random,]
    }
    
    

    ทีนี้เราก็สามารถสลับการ์ดหลังจาก เราแจกไพ่ได้แล้ว

    deal(deck)
    ## face   suit value
    ## king spades    13
    
    deck2 <-shuffle(deck)
    
    deal(deck2)
    ## face  suit value
    ## jack clubs    11
    
    

    เราสามารถดึงค่ามาเป็น vector ได้ด้วย $ เช่น deck$value สิ่งนี้จะมีประโยชน์มากเพราะว่าเราสามารถใช้ค่า numeric vector ในการ ทำงานด้าน math ได้ เช่น mean หรือ median

    เราสามารถใช้ [[]] ในการ subset ค่าได้เช่นกันในกรณีที่เราไม่มีชื่อ ให้กับ list นั้น

    lst[[1]]
    ## 1 2
    
    

    หากเรา ใช้แค่ [] เราจะได้ list ของ column แต่หากใช้ [[ ]] เราจะได้ค่าของ vector นั้นแทน

    Task 3: change the point system to suit your game

    เราสามารถ assign ค่าเข้าไปใน atomic. vector ได้

    vec[1] <- 1000
    vec
    ## 1000    0    0    0    0    0
    
    

    ด้วยวิธีนี้เราก็สามารถสร้าง column ใหม่ได้

    deck2$new <- 1:52
    
    deck2$new <- NULL
    
    

    โดยปกติแล้วการ ดึงข้อมูลและแก้ข้อมูลเป็นเรื่องง่ายหากเรารู้ตำแหน่ง แต่หากเราไม่รู้ เราก็จำเป็นที่จะต้องนำ การทำ subset ด้วย logic เข้ามาช่วย

    deck3$value[deck3$face == "ace"] <- 14
    
    
    OperatorSyntaxTests
    &cond1 & cond2Are both cond1 and cond2 true?
    ```cond1
    xorxor(cond1, cond2)Is exactly one of cond1 and cond2 true?
    !!cond1Is cond1 false? (e.g., ! flips the results of a logical test)
    anyany(cond1, cond2, cond3, ...)Are any of the conditions true?
    allall(cond1, cond2, cond3, ...)Are all of the conditions true?
    https://rstudio-education.github.io/hopr/modify.html
    OperatorSyntaxTests
    >a > bIs a greater than b?
    >=a >= bIs a greater than or equal to b?
    <a < bIs a less than b?
    <=a <= bIs a less than or equal to b?
    ==a == bIs a equal to b?
    !=a != bIs a not equal to b?
    %in%a %in% c(a, b, c)Is a in the group c(a, b, c)?
    https://rstudio-education.github.io/hopr/modify.html

    บ่อยครั้งที่ค่าบางค่านั้นหายไป ใน R เราจะใช้ NA ในการบอกว่า not available NA ช่วยให้ ข้อมูลของเราสามารถนำมาทำด้านสถิติได้แต่บางครั้งเวลาหาค่าทางคณิตศาสตร์ อาจจะทำให้ค่า error ได้ เพราะฉะนั้น บาง ฟังก์ชัน จะมี parameter อย่าง na.rm ที่ให้เรา set True เพื่อไม่นำมาคำนวณ

    เราสามารถใช้ is.na() ในการหาว่าใน data type นั้นมีค่า NA หรือไม่

    Task 4: manage the state of the deckฃ

    R เก็บข้อมูลใน environment คล้ายกับเวลาเราเก็บไฟล์ใน โฟลเดอร์ต่าง เราสามารถใช้ parevns ฟังก์ชั่น ใน package pryr

    ใน environment tree เราสามารถเข้าถึงค่า globalenv() baseenv() emptyenv() ได้

    เราสามารถใช้ ls หรือ ls.str ในการดู objects ที่บันทึกใน environments โดย ls จะให้ดูแค่ชื่อ แต่ ls.str จะดู structure.ได้ด้วย

    โดยค่าทั้งหมดที่ เราได้ บันทึกนั้นจะอยู่ใน global environment และเรายังสามารถใช้ assign() เพื่อ เพิ่มค่า ลงไปใน global environment ได้เช่นกัน

    โดยทั่วไปแล้ว global environment จะเป็น active environment แต่หากเรา รัน ฟังก์ชัน บางอย่างอาจทำให้ค่า active environment เปลี่ยนไปได้

    ใน R. เวลา R หาค่านั้นจะมีกฎอยู่ 3 ข้อ

    1. R จะหาค่าใน active environment ก่อน
    2. ถ้าทำงานใน CLI global environment จะเป็น active environment
    3. หาก R หาไม่เจอใน environemnt R จะมองหา object นั้น parent environment
    https://rstudio-education.github.io/hopr/images/hopr_0603.png

    เวลาที่เรา run ฟังก์ชัน R จะสร้าง environment ขึ้นมาใหม่ที่ runtime เกิดขึ้น หลังจากนั้นจะเอา result ส่งกลับไป environment ที่เราบันทึก ฟังก์ชัน ไว้

    อย่างเช่นใน กรณีตัวอย่างนี้ เราจะพบว่า ตอนรันข้อมูลด้านใน ฟังก์ชั่น show_env active environment จะเป็นอันที่อยู่ภายใต้ GlobalEnv หรือก็คือ parent environment

    show_env <- ฟังก์ชัน(){
      list(ran.in = environment(), 
           parent = parent.env(environment()), 
           objects = ls.str(environment()))
    }
    
    show_env()
    
    

    global environment ไม่จำเป็นต้องเป็น parent ยกตัวอย่างเช่น หากเราเรียก ฟังก์ชัน parenvs() parent env ก็จะมาจาก pryr ซึ่งก็คือที่ๆ ตัวฟังก์ชั่นนี้ถูกสร้างครั้งแรกนั้นเอง

    และด้วยเหตุนี้ มันก็จะมี ฟังก์ชัน block เพราะว่า R ก็จะบันทึก object ไว้ใน runtime environment เพื่อให้มั่นใจว่าไม่มีค่าไหนใน global ถูกเขียนทับ

    ในกรณีที่มีการส่ง arguments R ก็จะ copy ค่าเข้าไปใน runtime environment ด้วยเช่นกัน

    สรุปก็คือ สมมติว่าเรากำลังรันโค้ดใน ภาษา R อยู่ตอนแรก โค้ดเราก็จะรันใน active environment ซึ่งเป็น environment ที่ใช้ call ฟังก์ชัน เมื่อเรียก ฟังก์ชัน R จะทำการ ตั้ง runtime environment แล้ว copy ค่าใน argument เข้ามาใน environment นี้ด้วย หลังจากนั้นก็จะรันโค้ดใน ฟังก์ชัน body หากมีการ assign ค่าก็ เก็บไว้ใน runtime environment หากมีการเรียกใช้ object ที่ไม่มีใน runtime environment ก็จะทำตาม scoping rule แล้วไล่หาใน parent environment หลังจากที่รัน ฟังก์ชัน เสร็จแล้ว R ก็จะเปลี่ยนกลับมาใช้ environment ที่เรียก ฟังก์ชัน หากมีการ assign ค่าที่ return จากฟังก์ชั่นเราก็จะ เอาค่านั้นมาบันทึกที่ environment

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

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

    setup <-ฟังก์ชัน(deck) {
      DECK <- deck
    
      DEAL <-ฟังก์ชัน() {
        card <- deck[1, ]
    assign("deck", deck[-1, ], envir =parent.env(environment()))
        card
      }
    
      SHUFFLE <-ฟังก์ชัน(){
        random <-sample(1:52, size = 52)
    assign("deck", DECK[random, ], envir =parent.env(environment()))
     }
    
    list(deal = DEAL, shuffle = SHUFFLE)
    }
    
    cards <-setup(deck)
    deal <- cards$deal
    shuffle <- cards$shuffle
    
    

    ทีนี้หากเราเผลอลบ deck ในชั้น globalไปเราก็ยังเล่นได้อยู่ดี

    และนั่นคือทั้งหมดของ Hands-ON Programming with R ใน chapter 1 และ 2 ในบทความถัดไปเราจะมาพูดถึง สิ่งที่ผมได้เรียนรู้เพิ่มเติมกันครับ

    References

    https://rstudio-education.github.io/hopr/environments.html

  • Workshop เจาะลึกการออกแบบแอปฯ สำหรับกลุ่มเปราะบาง

    Workshop เจาะลึกการออกแบบแอปฯ สำหรับกลุ่มเปราะบาง

    เมื่อวันพฤหัสบดีที่ผ่านมาผมได้มีโอกาสเข้าร่วมการรับฟังความคิดเห็นที่มีต่อ ร่าง คู่มือ แนวปฏิบัติเกี่ยวกับการออกแบบและพัฒนาแอปพลิเคชั่นบนสมาร์ตโฟนสำหรับกลุ่มเปราะบาง ตามแนวทาง WCAG 2.0 โดยผมมาในฐานะภาครัฐที่ได้หนังสือเชิญจากทาง NECTEC ผู้จัดงานในครั้งนี้

    โดยหลักๆแล้ว Workshop นี้จะเน้นใน ภาคการปฏิบัติมากกว่าการนั่งฟังบรรยายครับ และกิจกรรมจัดขึ้นครึ่งวันเช้า โดยแบ่งออกเป็นสอง sessions ได้แก่

    • Group Session Policy Review and Discussion by Critical Thinking Method
    • Group Session Development Guideline Review & Discussion by creating a wireframe for Inclusive Mobile Design

    โดยก่อนที่ผมจะพาไปรีวิวและสรุปสิ่งที่ได้ในวันนี้ ผมอยากให้ทุกท่านรู้จักกับคำสองคำได้แก่ กลุ่มเปราะบาง และ WCAG 2.0 เป็นแนวทางการพัฒนาและออกแบบเว็บไซต์ที่ทุกคนเข้าถึงได้

    Table of Content

    What is Vulnerable Group

    กลุ่มเปราะบาง (Vulnerable Group) คือกลุ่มคนที่รัฐบาล เล็งเห็นว่าเป็นผู้ที่มีความจำเป็นที่จะต้องพึ่งพาผู้อื่น ไม่สามารถออกความคิดเห็นอย่างอิสระได้ หรือ ผู้ที่พักรักษาตัวในโรงพยาบาล เด็กทารก ผู้ที่มีความพิการทางร่างกาย ร่วมไปถึง คนแก่ และผู้ด้อยโอกาส โดยเป็นกลุ่มคนที่ถือบัตรสวัสดิการแห่งรัฐ หรือ บัตรคนจนทั่วทั้งประเทศไทย ราว 14.98 ล้านคน

    Photo by Mikhail Nilov on Pexels.com

    ภายใน Workshop วันนี้จะเน้นไปที่กลุ่มเปราะบางประเภท ผู้สูงอายุ และ ผู้พิการทางร่างกายต่างๆ อาทิเช่น ตา หู แขน และ ขา เป็นต้น

    โดย ดร. ปวรัตน์ นนทศิลป์ ได้กล่าวว่าตอนที่ทางเราออกผลิตภัณฑ์อย่างแอพทางรัฐนั้น ท่านพบว่า ประชาชนในกลุ่มนี้ไม่ได้เข้าใช้สิทธิ์ในการรับเงินดิจิทัล 10,000 เป็นจำนวนมาก จึงทำให้ท่านเข้าไปถามหาฟีดแบค

    ท่านได้รับฟีดแบคดังกล่าวนั้นมาจาก ผู้พิการทางการได้ยินท่านหนึ่ง โดยเขา กล่าวว่า เขาไม่สามารถอ่านได้ อันเนื่องมาจากการพิการทางการได้ยิน

    ทำให้ท่านรู้สึกตระหนักและคิดว่าควรจะต้องมีการทำอะไรซักอย่างนำมาสู่ กิจกรรม Workshop ในวันนี้นั้นเองครับ

    อะไรคือ TWCAG 2.0

    Photo by Kevin Ku on Pexels.com

    TWCAG ย่อมจาก Thailand Web Content Accessibility Guideline โดยเป็น แนวทางการพัฒนาเว็บไซต์ที่ทุกคนเข้าถึง เข้าใจเนื้อหาต่างๆได้ โดยอ้างอิงมาจาก มาตรฐานสากลอย่าง WCAG 2.1 ที่ว่าด้วยข้อแนะนำการพัฒนาและเงื่อนไขที่สามารถระบุได้ว่าหน้าเว็บไซต์น้นได้ทำตามคำแนะนำหรือไม่ ซึ่งถูกพัฒนาด้วย W3C หรือ The World Wide Web Consortium

    โดย TWCAG ประกอบไปด้วย 4 หลักการ

    • Perceivable – การใช้งานสำหรับทุกคน
      • หลักการนี้จะว่าด้วยการที่สว่นประกอบต่างๆ ของหน้า ถูกนำเสนอด้วยวิธีที่ผู้ใช้งานสามารถรับรู้ได้อย่างตรงไปตรงมา ไม่ว่าจะเป็นการมองเห็น หรือ การได้ยิน เช่น การใช้ สีพื้นหลังและตัวอักษรให้ ชัดเจนเพียงพอเพื่อความ contrast หรือ การทีที่มีคำบรรยาย caption สำหรับภาพเคลื่อนไหว
    • Operable – การใช้งานง่าย
      • ส่วนประกอบต่างๆของหน้าจอ จะต้องเสนอในแบบที่ใช้งานได้ง่าย เช่น ฟังก์ชั่นของผลิตภัณธ์ง่าย และทำให้ผู้ใช้ ใช้งานได้อย่างมีประสิทธิภาพ
    • Understandable – ความเข้าใจง่าย
      • ส่วนติดต่อกับผู้ใช้งานบนหน้าจอต้องเข้าใจง่าย เช่น ข้อควมและบริการต้องอ่านและเข้าใจง่าย ไม่สร้างความสับสน หลีกเลี่ยงการเกิดข้อผิดพลาดคือเข้าใจผิด
    • Robust – ความแข็งแรงและความยืดหยุ่น
      • เนื้อหาภายในต่างๆต้องเข้าใจง่ายและเชื่อถือได้ ทันสมัย และ ททันเทคโนโลยี เช่น การเขียน หน้า เว็บไซต์ด้วย HTML CSS เป้นต้น

    ภายใน WCAG ก็จะมี เกณฑ์ความสำเร็จ แบ่งออกเป็น 3 ระดับได้ แก่

    • A หรือขั้นต่ำสุด เป็น a-must ที่ต้องทำไม่อย่างนั้น การเข้าถึงจะทำได้โดยยาก หรือ ไม่ได้เลย
    • AA ขั้นกลาง เพื่อช่วยให้ผู้ใช้งาน ใช้ได้อย่างสะดวกขึ้น
    • AAA ขั้นสูงสุด เพื่อให้ผู้ใช้งานเข้าาถึงและใช้งานเนื้อหาเว็บได้สูงสุด

    ท่าน ดร. ปวรัตน์ นนทศิลป์ มองว่าในเบื้องต้น แอพลิเคชั่นในไทย ท่านไม่ได้คาดหวังที่จะให้ถึงระดับ AAA ขอเพียงแค่ A เพื่อให้เข้าถึง กลุ่ม users ได้หลากหลายขึ้นก็ดีมากแล้ว

    รีวิว Session

    Group Session Policy Review and Discussion by Critical Thinking Method

    ใน session แรก ทางทีมงานได้จัดกลุ่ม เราออกเป็น 5 กลุ่มย่อย กลุ่มละ 5-6 คน โดยจะมี roadmap พร้อม flash card จำนวนนึง โดยโจทย์ก็คือ ให้เราวางแผนกันเป็นทีม และ คิดว่า ในระยะเวลาของแม่บท 5 ปี เราจะทำอะไรบ้างแบ่งตามระดับดังนี้

    • User level – สีแดง
    • Development Solution Level – สีฟ้า
    • Policy Level – สีเขียว

    ในการ์ดแต่ละใบจะมี คำอธิบายด้านหลัง ประกอบอยู่ เช่น คำว่า Open-Source Libraries การ์ดสีฟ้าที่ว่าด้วยการพัฒนา open-source library เพื่อให้ผู้คนได้นำไปใช้เพื่อเพิ่มประสิทธิภาพในด้านการเข้าถึงให้กับแอพลิเคชั่นด้ง่ายขึ้น จากการช่วยกันคิด ทีมผม ก็ปะติดปะต่อและได้ผลลัพธ์ออกมาแบบนี้

    งานกลุ่มผมที่ทำ ติดกันอย่างทุลักทุเล 555

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

    หลังจากนั้นใน ปี ที่ 3 ทางทีมเราจะเน้นไปที่การสร้าง และช่วยเหลือ developers ที่ต้องการนำ เรื่อง Accessibility รวมทั้งสร้าง นโยบายที่ว่าด้วยการที่ต้องนำ Accessibility เข้าไปในแอพ ไม่อย่างนั้นจะมีโทษ พร้อมทั้งวิธีตรวจสอบ และ รับผลลัพธ์ตอบกับ กลับมา

    และในปี 5 ก็จะเป็น เก็บ feedback และทำให้เป็นที่รู้จักใน ระดับ โลก รวมทั้งสร้าง ecosystem เพื่อรองรับ users และ ทาง บริษัทเอกชนอีกด้วย

    หลังจากที่ทุกกลุ่มทำเสร็จ เขาจะให้ แต่ละกลุ่มเดินมาดูของอีกกลุ่มว่า แต่ละกลุ่มวางแผนออกมาเป็นเช่นไร รวมทั้งถามถึงเหตุผลว่า ทำไมจึงออกแบบเป็นเช่นนี้

    Group Session Development Guideline Review & Discussion by creating a wireframe for Inclusive Mobile Design

    session ถัดมา อาจารย์จะให้ แต่ละทีมบออกแบบ **Low-Fidelity Prototyping** สำหรับแอพลิเคชั่นแจ้งเตือนภัยพิบัติโดยให้คำนึงถึงหลักการ WCAG ด้วย พร้อมทั้งให้ wireframe จอโทรศัพท์ โจทย์ ฟีเจอรืที่ต้องการ และ personas ของ user ที่จะเข้าใช้งาน

    ทีมของเรามองว่า แอพควรจะเป็นแอพที่ใช้งานง่าย เข้าใจง่าย เนื่องจาก persona ของเราเป็นคนแก่ และคนที่พิการทางการได้ยิน ที่บกพร่องทักษะการอ่านด้วย

    เราจึงมองว่า ตัวแอพลิเคชั่นก็ควรที่จะเน้นไปที่รูปภาพ หรือ สัญญะ ที่สื่อความหมายได้ง่าย แบ่งออกเป็น 3 หน้า UI และ อีก หนึ่งหน้า แจ้งเตือน

    โดยหน้าแรก จะเป็น การที่ ให้ user เลือกว่าพวกเขาบกพร่องทางร่างกายอย่างไรโดยใช้เป็น รูปภาพ ตา หู ร่างกาย 👂🏼 🦿👁️ กับ รูป ✅❌ เพื่อเปิดใช้งานฟีเจอร์ว่าจะให้มีแจ้งเตือนแบบไหนบนแอปพลิเคชั่น

    หน้าที่สองจะเป็น หน้า tutorial เพื่อบอกว่า แต่ละรูป ภัยพิบัติจะแสดงออกมาเป็นอย่างไร โดยในหน้าก็จะมีรูปภัยพิบัติ เช่น ดินถล่ม ไฟไหม้ น้ำท่วม สึนามิ เป็นต้น

    และในหน้าสุดท้ายที่เป็น main feature จะแสดงแผนที่ระบุตำแนห่งที่เกิดภัยพิบัติ และระบุด้วยสี ว่ารุนแรงแค่ไหน รวมทั้งมุดสีฟ้าเพื่อบอก escape route ให้กับ ผู้ใช้งาน ด้านล่างแผนที่จะระบุ

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

    ในหน้าแจ้งเตือน เราจะให้มี UI ขึ้น มีเสียงเตือนภัยออกมาจาก โทรศัพท์ และ สั่นเพื่อทำให้ ผู้ใช้งานรู้ตัวแม้ว่าขณะนั้นจะไม่ได้ดูมือถืออยู่ก็ตาม

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

    สรุปสิ่งได้เรียนรู้จาก Workshop ในวันนี้

    • เพื่อให้ได้ตาม Accessibility การปฏิบัติตามหลักการ ของ WCAG จะช่วยรองรับ ในส่วนของ user experience ให้ดีขึ้น
    • หากในกลุ่มไหนที่มีภาครัฐอยู่เยอะก็จะ ทำ session 1 ในช่วง ปี แรกว่าควรจะต้องมีการตั้งนโยบาย สีเขียว ออกมาก่อนแล้วจึงลงมือทำ ในขณะที่เป็นเอกชน จะมีในส่วนของ สีส้ม หรือ ระดับบ user level และ สีฟ้า เพื่อทดสอบว่าระบบหรือโมเดลที่จะใช้นั้นไปพร้อมกับกำหนดนโยบายเพื่อให้ป้องกันความเสียหายหากกำหนดนโยบายแล้วไม่มีใครใช้

    References

    https://www.dct.or.th/th/project/detail/394

    https://kb.hsri.or.th/dspace/bitstream/handle/11228/3717/jaruayporn.pdf?sequence=2&isAllowed=y#:~:text=