Data Preparation และ Feature Engineering

เตรียมข้อมูลให้พร้อมสำหรับ AI

"ในปี 2020 บริษัท Netflix สามารถประหยัดค่าใช้จ่ายได้กว่า 1 พันล้านดอลลาร์ต่อปี เพียงเพราะปรับปรุงวิธีการเตรียมข้อมูลผู้ใช้งาน..."

คุณรู้หรือไม่ว่า Data Scientists ทั่วโลกใช้เวลากว่า 80% ของการทำงานไปกับการ "ทำความสะอาด" ข้อมูล? แล้วทำไมงานที่ดูน่าเบื่อนี้ถึงสำคัญขนาดนั้น?

มาค้นหาคำตอบไปด้วยกัน...

บทที่ 1 ทำไมต้องเตรียมข้อมูล?

1.1 ข้อมูลคือน้ำมันดิบ แต่ AI ต้องการน้ำมันที่กลั่นแล้ว

คุณเคยสงสัยไหมว่า ทำไม AI บางตัวถึงทำนายได้แม่นยำ แต่บางตัวกลับพลาดอย่างน่าตกใจ? คำตอบอาจไม่ได้อยู่ที่ความฉลาดของ AI แต่อยู่ที่ "วัตถุดิบ" ที่เราป้อนให้มันต่างหาก

ลองนึกภาพว่าคุณเป็นเชฟที่กำลังจะทำอาหาร

ความจริงที่น่าตกใจ นักวิทยาศาสตร์ข้อมูลใช้เวลา 80% ในการเตรียมข้อมูล และแค่ 20% ในการสร้างโมเดล!

1.2 ตัวอย่างจากชีวิตจริง ร้านกาแฟมหาวิทยาลัย ☕

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

มาดูกันว่าข้อมูลของพวกเขาเป็นอย่างไร

วันที่,เมนู,ราคา,จำนวน,ลูกค้า
1/1/2024,ลาเต้ร้อน,60,3,นศ.ปี1
01-01-2024,ร้อนลาเต้,60,2,อาจารย์
1 ม.ค.,Latte(Hot),60,,นักศึกษา

ปัญหาที่เจอ

  1. รูปแบบวันที่ไม่เหมือนกัน
  2. ชื่อเมนูเดียวกันแต่เขียนต่างกัน
  3. มีค่าว่าง (missing values)
  4. ประเภทลูกค้าไม่เป็นมาตรฐาน

1.3 ผลกระทบของข้อมูลที่ไม่ดี

คุณอาจคิดว่า "แค่ข้อมูลไม่เรียบร้อยหน่อย AI น่าจะฉลาดพอที่จะเข้าใจได้" แต่ความจริงอาจทำให้คุณต้องตกใจ...

นี่คือสิ่งที่จะเกิดขึ้นถ้าเราไม่แก้ไขข้อมูล

# ตัวอย่างผลกระทบ
# ถ้าไม่แก้ไขข้อมูล AI จะคิดว่า
- "ลาเต้ร้อน" กับ "ร้อนลาเต้" เป็นคนละเมนู
- ไม่สามารถคำนวณยอดขายรวมได้ถูกต้อง
- ไม่สามารถวิเคราะห์แนวโน้มตามเวลาได้

บทที่ 2 ประเภทของข้อมูลและปัญหาที่พบบ่อย

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

2.1 ประเภทข้อมูลพื้นฐาน

ก่อนที่เราจะเริ่ม "สืบสวน" ข้อมูล มาทำความรู้จักกับตัวละครหลักในเรื่องนี้กันก่อน

ประเภท ลักษณะ ตัวอย่าง
ข้อมูลตัวเลข (Numerical)
  • ต่อเนื่อง (Continuous)
  • ไม่ต่อเนื่อง (Discrete)
  • อุณหภูมิ (25.5°C), ส่วนสูง (170.5 cm)
  • จำนวนนักศึกษา (30 คน), จำนวนห้อง (5 ห้อง)
ข้อมูลประเภท (Categorical)
  • มีลำดับ (Ordinal)
  • ไม่มีลำดับ (Nominal)
  • ระดับการศึกษา (ป.ตรี < ป.โท < ป.เอก)
  • สีที่ชอบ (แดง, เขียว, น้ำเงิน)
ข้อมูลข้อความ (Text) ข้อความอิสระ ชื่อ, ที่อยู่, ความคิดเห็น
ข้อมูลวันเวลา (DateTime) วันที่และเวลา วันที่, เวลา, ช่วงเวลา

2.2 สร้างข้อมูลหลัก (Master Dataset) ที่จะใช้ตลอดบทเรียน 🎯

เราจะสร้างชุดข้อมูลนักศึกษาที่มีปัญหาหลากหลายแบบที่พบในชีวิตจริง แล้วใช้ข้อมูลชุดนี้ตลอดทั้งบทเรียน เพื่อให้เห็นการพัฒนาข้อมูลแบบต่อเนื่อง

import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import warnings
warnings.filterwarnings('ignore')

# สร้างข้อมูลนักศึกษา 20 คน ที่มีปัญหาหลากหลาย
np.random.seed(42)  # กำหนด seed เพื่อให้ได้ผลเหมือนกันทุกครั้ง

# ข้อมูลพื้นฐาน
student_data = {
    'รหัสนักศึกษา': [f'64{str(i).zfill(3)}' for i in range(1, 21)],
    'ชื่อ': ['สมชาย', 'สมหญิง', None, 'สมศักดิ์', 'สมใจ', 'กานต์', 'กิตติ', 
             'จิรา', 'ชนาธิป', 'ดารา', 'ธีระ', 'นภา', 'บุญมี', 'ปรีชา', 
             'พิมพ์', 'ภูมิ', 'มานี', 'ยุพา', 'รัตนา', 'ลัดดา'],
    'อายุ': [20, 21, None, 22, 19, 150, 21, 20, 23, 21,  # 150 = outlier
            None, 22, 20, 21, 22, 19, 20, 21, None, 22],
    'เพศ': ['ชาย', 'หญิง', 'Male', 'ช', 'ญ', 'ชาย', 'หญิง', 'F', 'M', 'หญิง',
            None, 'ชาย', 'หญิง', 'ชาย', 'Female', 'ช', 'ญ', 'หญิง', 'ชาย', None],
    'คณะ': ['วิทยาศาสตร์', 'วิทย์', 'Science', 'คณะวิทยาศาสตร์', 'วิศวะ',
            'Engineering', 'วิศวกรรม', 'ศิลปศาสตร์', 'Arts', 'ศิลป์',
            None, 'วิทย์', 'วิศวฯ', 'Liberal Arts', 'Sci', 
            'วิทยาศาสตร์', 'วิศวกรรมศาสตร์', None, 'ศิลปศาสตร์', 'Science'],
    'GPA': [3.21, 3.45, 3.67, -1, 3.89, 3.12, 4.5, 2.98, 3.34, 3.56,
            3.78, None, 3.23, 3.45, 3.67, 3.89, 3.01, 3.23, None, 3.45],
    'ส่วนสูง_cm': [170, 165, 175, 168, 160, 172, 158, 180, 163, 167,
                  None, 171, 169, 164, 162, 173, 166, 161, 174, 168],
    'น้ำหนัก_kg': [65, 58, None, 70, 52, 75, 48, 82, 55, 60,
                  68, None, 72, 54, 56, 78, 62, 50, 76, 64],
    'ชั่วโมงเรียน_ต่อสัปดาห์': [15, 20, 25, 10, 30, 12, 35, 18, 22, 28,
                              None, 16, 24, 19, 26, 14, 21, 27, None, 23]
}

# สร้าง DataFrame หลัก
df_master = pd.DataFrame(student_data)

# เพิ่มวันที่ลงทะเบียน (สำหรับ feature engineering)
base_date = datetime(2024, 1, 15)
df_master['วันที่ลงทะเบียน'] = [base_date + timedelta(days=np.random.randint(-30, 30)) 
                                for _ in range(20)]

print("="*50)
print("🎯 ข้อมูลหลัก (Master Dataset) ที่จะใช้ตลอดบทเรียน")
print("="*50)
print(df_master)
print("\n📊 สรุปปัญหาที่พบในข้อมูล:")
print(f"- ค่าหายทั้งหมด: {df_master.isnull().sum().sum()} ค่า")
print(f"- อายุผิดปกติ: {df_master[df_master['อายุ'] > 100]['อายุ'].values if any(df_master['อายุ'] > 100) else 'ไม่มี'}")
print(f"- GPA ผิดปกติ: {df_master[(df_master['GPA'] < 0) | (df_master['GPA'] > 4)]['GPA'].values if any((df_master['GPA'] < 0) | (df_master['GPA'] > 4)) else 'ไม่มี'}")
print(f"- รูปแบบการเขียนคณะ: {df_master['คณะ'].nunique()} แบบ")
print(f"- รูปแบบการเขียนเพศ: {df_master['เพศ'].nunique()} แบบ")

# เก็บสำเนาข้อมูลต้นฉบับ
df_original = df_master.copy()  # สำคัญ! ใช้ .copy() เพื่อไม่ให้กระทบข้อมูลต้นฉบับ
คำอธิบาย Code
  • import pandas as pd - นำเข้าไลบรารี pandas ซึ่งเป็นเครื่องมือหลักในการจัดการข้อมูลใน Python และตั้งชื่อย่อว่า pd
  • data = {...} - สร้าง dictionary เพื่อเก็บข้อมูลโดยมี key เป็นชื่อคอลัมน์ และ value เป็น list ของข้อมูลในคอลัมน์นั้น
  • None/NaN - เป็นค่าพิเศษที่แทนค่าว่าง หรือไม่มีข้อมูล
  • pd.DataFrame(data) - แปลง dictionary ให้กลายเป็น DataFrame ซึ่งเป็นโครงสร้างข้อมูลแบบตาราง
  • print(df) - แสดงผลข้อมูลในรูปแบบตาราง จะเห็นว่ามี NaN ปรากฏในตำแหน่งที่มีค่า None
ปัญหาที่พบในข้อมูลของเรา:
  • ข้อมูลหาย (Missing Values) - ชื่อ, อายุ, เพศ, คณะ, GPA, ส่วนสูง, น้ำหนัก มีค่า None/NaN
  • ข้อมูลไม่สอดคล้อง (Inconsistent Data) - คณะเขียนหลายแบบ (วิทย์, Science, วิทยาศาสตร์), เพศเขียนหลายแบบ (ช, ชาย, Male)
  • ค่าผิดปกติ (Outliers) - อายุ 150 ปี, GPA -1 และ 4.5

2.3 วิเคราะห์ข้อมูลเบื้องต้น (EDA)

# วิเคราะห์ข้อมูล df_master ที่สร้างไว้
print("="*50)
print("📋 ข้อมูลทั่วไปของ Dataset")
print("="*50)
df_master.info()

print("\n" + "="*50)
print("📊 สถิติเชิงพรรณนา")
print("="*50)
print(df_master.describe())

# ตรวจสอบค่าหายในแต่ละคอลัมน์
missing_counts = df_master.isnull().sum()
print("\n❌ ค่าหายในแต่ละคอลัมน์:")
for col, count in missing_counts[missing_counts > 0].items():
    percentage = (count / len(df_master)) * 100
    print(f"   - {col}: {count} ค่า ({percentage:.1f}%)")

บทที่ 3 เทคนิคการจัดการข้อมูลพื้นฐาน

หลังจากที่เรารู้จักปัญหาแล้ว คราวนี้มาเรียนรู้วิธีแก้ไขกัน เหมือนกับการมีกล่องเครื่องมือช่างที่พร้อมจะซ่อมทุกอย่างที่พัง แต่ต้องรู้ว่าจะใช้เครื่องมือไหน เมื่อไหร่ อย่างไร

3.1 การจัดการข้อมูลหาย

เมื่อเจอข้อมูลหาย เรามีทางเลือกเหมือนตอนที่เราทำการบ้านไม่เสร็จ - แกล้งป่วย (ลบทิ้ง), ลอกเพื่อน (ใช้ค่าเฉลี่ย), หรือเดาเอา (ใช้ค่าที่เจอบ่อย) มาดูกันว่าแต่ละวิธีใช้ตอนไหน

วิธีที่ 1 ลบแถวที่มีค่าหาย

เหมือนการตัดชิ้นส่วนที่เน่าออกจากผลไม้ - ใช้ได้ถ้าส่วนที่เน่ามีน้อย แต่ถ้าเน่าเกือบหมดแล้วจะตัดยังไง?

# ตัวอย่างข้อมูลที่มีค่าหายเพียงเล็กน้อย
import pandas as pd

# สร้างข้อมูลตัวอย่าง 10 คน โดยมี 1 คนที่ไม่กรอกข้อมูล
data = {
    'ชื่อ': ['ก้อง', 'กล้า', 'กิ่ง', 'กานต์', 'กุล', 'กบ', 'กิตติ', 'กมล', 'กร', 'กนก'],
    'อายุ': [21, 22, 20, 21, None, 22, 21, 20, 21, 22],  # เฉพาะกุลไม่กรอกอายุ
    'GPA': [3.5, 3.2, 3.8, 3.0, 3.7, 3.3, 3.6, 3.4, 3.1, 3.9]
}
df = pd.DataFrame(data)

# แสดงข้อมูลก่อนลบ
print("ก่อนลบ")
print(df)

# เหมาะเมื่อมีข้อมูลหายน้อย - ลบแถวที่มีค่าหาย
df_clean = df.dropna()

print(f"\nผลการลบ - ก่อนลบ {len(df)} แถว, หลังลบ {len(df_clean)} แถว")
print("หลังลบ (เหลือ 9 คน):")
print(df_clean)
คำอธิบาย Code
  • data = {...} - สร้างข้อมูลตัวอย่าง 10 คน โดยคนที่ 5 (กุล) ไม่ได้กรอกอายุ (None)
  • df.dropna() - ลบแถวทั้งหมดที่มีค่า NaN (ค่าหาย) ออกจาก DataFrame
    • จะลบทั้งแถวถ้าคอลัมน์ใดคอลัมน์หนึ่งมี NaN
    • ในตัวอย่างนี้ - ลบแถวของกุลที่ไม่กรอกอายุ
  • df_clean - เก็บ DataFrame ใหม่ที่ลบค่าหายออกแล้ว (ไม่แก้ไข df เดิม)
  • len(df) - นับจำนวนแถวทั้งหมด = 10 แถว
  • len(df_clean) - นับจำนวนแถวหลังลบ = 9 แถว
  • ข้อควรระวัง - ถ้าข้อมูลหายเยอะ วิธีนี้จะทำให้ข้อมูลเหลือน้อยมาก

วิธีที่ 2 เติมค่าด้วยค่าเฉลี่ย

นึกถึงตอนเราไม่รู้ว่าเพื่อนๆ ในห้องสูงเท่าไหร่ เราก็จะเดาจากค่าเฉลี่ยของคนที่เรารู้ วิธีนี้ใช้ได้ดีกับข้อมูลตัวเลข

# ตัวอย่างข้อมูลนักศึกษาที่มีอายุหาย
import pandas as pd

# สร้างข้อมูลตัวอย่าง
data = {
    'ชื่อ': ['สมชาย', 'สมหญิง', 'สมศักดิ์', 'สมใจ', 'สมปอง'],
    'อายุ': [20, None, 19, 22, 21]  # สมหญิงไม่ได้กรอกอายุ
}
df = pd.DataFrame(data)

# แสดงข้อมูลก่อนเติมค่า
print("ก่อนเติมค่า")
print(df)

# เหมาะกับข้อมูลตัวเลข - ใช้ค่าเฉลี่ย
mean_age = df['อายุ'].mean()
df['อายุ'].fillna(mean_age, inplace=True)

print(f"\nค่าเฉลี่ยอายุ - {mean_age:.1f} ปี")
print("หลังเติมค่า")
print(df)
คำอธิบาย Code
  • data = {...} - สร้างข้อมูลตัวอย่าง 5 คน โดยคนที่ 2 (สมหญิง) ไม่ได้กรอกอายุ (None)
  • df['อายุ'].mean() - คำนวณค่าเฉลี่ยของคอลัมน์ 'อายุ' โดยไม่นับรวมค่า NaN
    • ในตัวอย่างนี้ - (20 + 19 + 22 + 21) / 4 = 20.5 ปี
    • pandas ข้ามค่า NaN อัตโนมัติ
  • fillna(mean_age, inplace=True) - เติมค่า 20.5 ลงในช่องที่เป็น NaN (ของสมหญิง)
    • inplace=True หมายถึงแก้ไขข้อมูลใน DataFrame เดิมโดยตรง ไม่สร้าง DataFrame ใหม่
  • f"{mean_age:.1f}" - แสดงผลค่าเฉลี่ยโดยมีทศนิยม 1 ตำแหน่ง (.1f)
  • วิธีนี้เหมาะกับข้อมูลตัวเลขที่มีการกระจายตัวปกติ
🤖 เหมาะกับ ML Models ไหน?
  • Linear Regression, Logistic Regression - Sensitive ต่อค่าหาย ควรเติมค่าด้วย mean/median
  • Decision Trees, Random Forest - รับมือค่าหายได้ดี แต่เติมค่าจะช่วยเพิ่ม performance
  • Neural Networks, Deep Learning - ต้องเติมค่าหายเสมอ ไม่งั้น train ไม่ได้
  • SVM - ต้องการข้อมูลครบถ้วน ควรเติมค่าหายด้วย mean สำหรับ numerical data

วิธีที่ 3 เติมค่าด้วยค่าที่พบบ่อยที่สุด

สำหรับข้อมูลประเภท (เช่น คณะ, สี, ขนาด) เราจะใช้ค่าที่คนส่วนใหญ่เลือก เหมือนตอนสั่งอาหารกลุ่มแล้วไม่รู้จะสั่งอะไร ก็สั่งตามเพื่อนส่วนใหญ่:

# ตัวอย่างข้อมูลนักศึกษาที่มีคณะหาย
import pandas as pd

# สร้างข้อมูลตัวอย่าง
data = {
    'ชื่อ': ['สมชาย', 'สมหญิง', 'สมศักดิ์', 'สมใจ', 'สมปอง'],
    'คณะ': ['วิทยาศาสตร์', 'วิทยาศาสตร์', None, 'ศิลปศาสตร์', 'วิทย์']
}
df = pd.DataFrame(data)

# แสดงข้อมูลก่อนเติมค่า
print("ก่อนเติมค่า")
print(df)

# เหมาะกับข้อมูลประเภท - ใช้ค่าที่พบบ่อยที่สุด (mode)
df['คณะ'].fillna(df['คณะ'].mode()[0], inplace=True)

print("\nหลังเติมค่า")
print(df)
คำอธิบาย Code
  • data = {...} - สร้างข้อมูลตัวอย่าง 5 คน โดยคนที่ 3 (สมศักดิ์) ไม่ได้กรอกคณะ (None)
  • df['คณะ'].mode() - หาค่าที่พบบ่อยที่สุด (Mode) ในคอลัมน์ 'คณะ'
    • ในตัวอย่างนี้ - 'วิทยาศาสตร์' พบ 3 ครั้ง, 'ศิลปศาสตร์' พบ 1 ครั้ง
    • ดังนั้น mode = 'วิทยาศาสตร์'
  • [0] - เลือกค่า mode ตัวแรก (ในกรณีที่มีหลายค่าที่พบบ่อยเท่ากัน)
  • fillna(..., inplace=True) - เติม 'วิทยาศาสตร์' ลงในช่องที่เป็น NaN (ของสมศักดิ์)
  • print() - แสดงผลก่อนและหลังเติมค่าเพื่อเปรียบเทียบ
  • วิธีนี้เหมาะกับข้อมูลประเภท (categorical) เช่น คณะ สี ขนาด เพราะไม่สามารถหาค่าเฉลี่ยได้

3.2 การทำให้ข้อมูลเป็นมาตรฐาน

คุณเคยพยายามหาเพลงในแอพฟังเพลงแล้วหาไม่เจอไหม? เพราะศิลปินชื่อ "เบิร์ด ธงไชย" แต่คุณพิมพ์ "Bird Thongchai" นี่แหละคือปัญหาที่เราต้องแก้:

# ตัวอย่าง - ทำให้ชื่อคณะเป็นมาตรฐานเดียวกัน
def standardize_faculty(name):
    if name in ['วิทยาศาสตร์', 'วิทย์', 'Science', 'คณะวิทยาศาสตร์']:
        return 'วิทยาศาสตร์'
    elif name in ['ศิลปศาสตร์', 'ศิลป์', 'Arts']:
        return 'ศิลปศาสตร์'
    else:
        return name

# ใช้งาน
df['คณะ_มาตรฐาน'] = df['คณะ'].apply(standardize_faculty)
คำอธิบาย Code
  • def standardize_faculty(name): - สร้างฟังก์ชันที่รับชื่อคณะเข้ามาแล้วแปลงเป็นชื่อมาตรฐาน
  • if name in [...] - ตรวจสอบว่าชื่อที่รับเข้ามาอยู่ใน list ของชื่อที่เป็นไปได้หรือไม่
  • return 'วิทยาศาสตร์' - คืนค่าชื่อคณะที่เป็นมาตรฐาน
  • else: return name - ถ้าไม่ตรงกับเงื่อนไขใดๆ ให้คืนชื่อเดิม
  • df['คณะ'].apply(standardize_faculty) - ใช้ฟังก์ชันกับทุกค่าในคอลัมน์ 'คณะ'
    • apply() จะเรียกฟังก์ชันทีละแถวและส่งค่าในแถวนั้นเข้าไปในฟังก์ชัน
  • df['คณะ_มาตรฐาน'] - สร้างคอลัมน์ใหม่ที่เก็บชื่อคณะที่เป็นมาตรฐานแล้ว

3.3 การจัดการค่าผิดปกติ

ค่าผิดปกติเหมือนคนที่อ้างว่าวิ่ง 100 เมตรได้ใน 2 วินาที - เป็นไปได้ในทางทฤษฎี แต่น่าจะมีอะไรผิดพลาด มาดูวิธีจับ "คนโกหก" เหล่านี้:

# ตัวอย่างข้อมูลอายุนักศึกษาที่มีค่าผิดปกติ
import pandas as pd

# สร้างข้อมูลตัวอย่าง
data = {
    'ชื่อ': ['นักศึกษา1', 'นักศึกษา2', 'นักศึกษา3', 'นักศึกษา4', 'นักศึกษา5', 
             'นักศึกษา6', 'นักศึกษา7', 'นักศึกษา8', 'นักศึกษา9', 'นักศึกษา10'],
    'อายุ': [20, 21, 22, 19, 20, 150, 21, 22, 23, 18]  # 150 คือค่าผิดปกติ
}
df = pd.DataFrame(data)

print("ข้อมูลต้นฉบับ")
print(df)
print(f"\nค่าเฉลี่ยอายุ (รวมค่าผิดปกติ) - {df['อายุ'].mean():.2f}")

# วิธีง่ายๆ - ใช้ IQR (Interquartile Range)
Q1 = df['อายุ'].quantile(0.25)
Q3 = df['อายุ'].quantile(0.75)
IQR = Q3 - Q1

# กำหนดขอบเขต
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR

# กรองค่าผิดปกติ
df_no_outliers = df[(df['อายุ'] >= lower_bound) & (df['อายุ'] <= upper_bound)]

print(f"\nขอบเขตล่าง - {lower_bound}")
print(f"ขอบเขตบน - {upper_bound}")
print(f"\nข้อมูลหลังกรองค่าผิดปกติ")
print(df_no_outliers)
print(f"\nค่าเฉลี่ยอายุ (หลังกรองค่าผิดปกติ) - {df_no_outliers['อายุ'].mean():.2f}")
คำอธิบาย Code
  • quantile(0.25) - หาค่า Quartile ที่ 1 (Q1) คือค่าที่มี 25% ของข้อมูลน้อยกว่าหรือเท่ากับค่านี้
  • quantile(0.75) - หาค่า Quartile ที่ 3 (Q3) คือค่าที่มี 75% ของข้อมูลน้อยกว่าหรือเท่ากับค่านี้
  • IQR = Q3 - Q1 - Interquartile Range คือช่วงระหว่าง Q1 และ Q3 บอกถึงการกระจายตัวของข้อมูลส่วนกลาง 50%
  • 1.5 * IQR - ตัวเลข 1.5 เป็นค่ามาตรฐานที่นิยมใช้ในการหาค่าผิดปกติ
  • lower_bound และ upper_bound - ขอบเขตล่างและบนสำหรับตัดสินว่าค่าใดเป็นค่าผิดปกติ
  • df[(df['อายุ'] >= lower_bound) & (df['อายุ'] <= upper_bound)] - กรองเฉพาะแถวที่มีอายุอยู่ในช่วงที่กำหนด
    • & คือตัวดำเนินการ AND ใน pandas (ไม่ใช่ and)
  • วิธีนี้จะช่วยตัดค่าที่ออกนอกกลุ่มข้อมูลส่วนใหญ่มากเกินไป

บทที่ 4 Feature Engineering - การสร้างคุณลักษณะใหม่

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

4.1 One-Hot Encoding แปลงข้อมูลประเภทเป็นตัวเลข

คุณรู้ไหมว่า AI ส่วนใหญ่ "อ่านภาษาไทยไม่ออก"? พวกมันเข้าใจแต่ตัวเลข ดังนั้นเราต้องแปลงคำว่า "วิทยาศาสตร์" ให้กลายเป็นตัวเลขที่ AI เข้าใจได้

ทำไมต้องแปลง?

ลองนึกภาพว่าคุณพยายามสอนหุ่นยนต์ให้แยกสีได้ แต่หุ่นยนต์มองไม่เห็น มันรู้แค่ตัวเลข เราจึงต้องบอกว่า "แดง = 1, เขียว = 2, น้ำเงิน = 3" แต่เดี๋ยวก่อน! วิธีนี้มีปัญหา...

หุ่นยนต์อาจคิดว่า น้ำเงิน (3) "มากกว่า" แดง (1) ถึง 3 เท่า! ซึ่งไม่ใช่ความจริง One-Hot Encoding จึงเป็นวิธีแก้ปัญหานี้
# ก่อนแปลง
data = pd.DataFrame({
    'ชื่อ': ['A', 'B', 'C', 'D'],
    'คณะ': ['วิทย์', 'ศิลป์', 'วิทย์', 'วิศวะ']
})

# ทำ One-Hot Encoding
data_encoded = pd.get_dummies(data, columns=['คณะ'])
print(data_encoded)

# ผลลัพธ์ - แต่ละคณะกลายเป็นคอลัมน์แยก
# ชื่อ  คณะ_วิทย์  คณะ_วิศวะ  คณะ_ศิลป์
# A     1         0         0
# B     0         0         1
# C     1         0         0
# D     0         1         0
คำอธิบาย Code
  • pd.DataFrame({...}) - สร้าง DataFrame ตัวอย่างที่มีข้อมูลประเภท (คณะ) ที่ต้องการแปลง
  • pd.get_dummies(data, columns=['คณะ']) - แปลงคอลัมน์ 'คณะ' เป็นรูปแบบ One-Hot Encoding
    • get_dummies จะสร้างคอลัมน์ใหม่สำหรับแต่ละค่าที่เป็นไปได้ในคอลัมน์ 'คณะ'
    • ชื่อคอลัมน์ใหม่จะเป็น 'คณะ_ค่า' เช่น 'คณะ_วิทย์', 'คณะ_ศิลป์', 'คณะ_วิศวะ'
  • แต่ละแถวจะมีค่า 1 ในคอลัมน์ที่ตรงกับคณะของตน และ 0 ในคอลัมน์อื่นๆ
  • วิธีนี้ทำให้ AI เข้าใจข้อมูลประเภทโดยไม่สร้างลำดับความสำคัญที่ไม่จริง
  • ตัวอย่าง - นักศึกษา A อยู่คณะวิทย์ จะมีค่า 1 ในคอลัมน์ 'คณะ_วิทย์' และ 0 ในคอลัมน์คณะอื่นๆ
🤖 One-Hot Encoding จำเป็นกับ ML Models ไหน?
  • ✅ จำเป็น: Linear Regression, Logistic Regression, Neural Networks, SVM - ไม่เข้าใจ categorical data โดยตรง
  • ❌ ไม่จำเป็น: Decision Trees, Random Forest, XGBoost, LightGBM - สามารถ split ตาม categorical values ได้โดยตรง
  • ⚠️ ข้อควรระวัง: ถ้ามี categories เยอะ (>100) จะเกิด curse of dimensionality ควรใช้ Target Encoding แทน
ทำไม Tree-based models ไม่ต้องใช้? เพราะสามารถ split โดยตรงตาม categorical values ได้ เช่น if คณะ == "วิทย์" then ...

4.2 การสร้าง Feature จากวันเวลา

เวลาเป็นข้อมูลที่ทรงพลังแต่ซ่อนอยู่ในรูปแบบที่ AI มองไม่เห็น เหมือนกับการมีสมบัติฝังอยู่ใต้ดิน เราต้องขุดมันขึ้นมา!

ลองดูว่าจากวันเวลาธรรมดา เราสามารถ "ขุด" อะไรออกมาได้บ้าง

# ตัวอย่าง - ข้อมูลการซื้อกาแฟ
coffee_data = pd.DataFrame({
    'วันเวลา': pd.to_datetime(['2024-01-15 07:30', '2024-01-15 12:00', 
                               '2024-01-15 16:30', '2024-01-16 08:00']),
    'ยอดขาย': [1500, 3200, 2100, 1800]
})

# สร้าง features ใหม่
coffee_data['ชั่วโมง'] = coffee_data['วันเวลา'].dt.hour
coffee_data['วันในสัปดาห์'] = coffee_data['วันเวลา'].dt.day_name()
coffee_data['ช่วงเวลา'] = pd.cut(coffee_data['ชั่วโมง'], 
                                  bins=[0, 10, 14, 18, 24],
                                  labels=['เช้า', 'กลางวัน', 'บ่าย', 'เย็น'])

print(coffee_data[['วันเวลา', 'ช่วงเวลา', 'ยอดขาย']])
คำอธิบาย Code
  • pd.to_datetime([...]) - แปลง string ให้เป็นประเภท datetime ที่ pandas สามารถจัดการและคำนวณได้
  • .dt.hour - ดึงชั่วโมงจาก datetime (เช่น 07:30 จะได้ 7, 12:00 จะได้ 12)
  • .dt.day_name() - ดึงชื่อวันในสัปดาห์เป็นภาษาอังกฤษ (เช่น Monday, Tuesday)
  • pd.cut() - แบ่งข้อมูลตัวเลขเป็นช่วง (bins) และใส่ label ให้แต่ละช่วง
    • bins=[0, 10, 14, 18, 24] - กำหนดขอบเขตของแต่ละช่วง (0-10, 10-14, 14-18, 18-24)
    • labels=['เช้า', 'กลางวัน', 'บ่าย', 'เย็น'] - ชื่อที่จะใช้เรียกแต่ละช่วง
  • วิธีนี้ช่วยให้ AI เห็น patterns ตามเวลา เช่น กาแฟขายดีที่สุดช่วงกลางวัน
🤖 DateTime Features ช่วย ML Models อย่างไร?
  • Time Series Models (ARIMA, Prophet) - ต้องการ datetime features เพื่อจับ seasonality และ trends
  • Random Forest, XGBoost - ใช้ hour, day, month เพื่อ split trees ตาม patterns ของเวลา
  • LSTM/GRU (Deep Learning) - ใช้ time sequence ในการ predict แนวโน้มในอนาคต
  • Gradient Boosting - ใช้ cyclical features (เช่น sin/cos ของ hour) เพื่อจับ pattern วนซ้ำ
💡 Tip: แปลง hour เป็น sin/cos สำหรับ Neural Networks เพื่อให้ hour 23 ใกล้กับ hour 0

4.3 การรวม Features

บางครั้งข้อมูล 2 ชิ้นที่ดูธรรมดา เมื่อนำมารวมกันอาจกลายเป็นข้อมูลที่มีค่ามหาศาล เหมือนการผสมสูตรลับที่ทำให้ได้ยาวิเศษ!

มาดูตัวอย่างการ "ผสมสูตร" ที่ช่วยให้ AI ฉลาดขึ้น

# ตัวอย่าง - สร้าง BMI จากส่วนสูงและน้ำหนัก
student_health = pd.DataFrame({
    'ชื่อ': ['สมชาย', 'สมหญิง', 'สมศักดิ์'],
    'ส่วนสูง_cm': [170, 165, 175],
    'น้ำหนัก_kg': [65, 55, 80]
})

# สร้าง BMI
student_health['BMI'] = student_health['น้ำหนัก_kg'] / (student_health['ส่วนสูง_cm']/100)**2
student_health['สุขภาพ'] = pd.cut(student_health['BMI'], 
                                   bins=[0, 18.5, 25, 30, 100],
                                   labels=['ผอม', 'ปกติ', 'อ้วน', 'อ้วนมาก'])

print(student_health[['ชื่อ', 'BMI', 'สุขภาพ']])
คำอธิบาย Code
  • student_health['ส่วนสูง_cm']/100 - แปลงส่วนสูงจากเซนติเมตรเป็นเมตร (เพราะสูตร BMI ใช้หน่วยเมตร)
  • **2 - ยกกำลังสอง (เทียบเท่ากับ x²)
  • สูตร BMI = น้ำหนัก(กิโลกรัม) / ส่วนสูง(เมตร)²
  • pd.cut() - แบ่งค่า BMI เป็นช่วงตามเกณฑ์มาตรฐาน
    • 0-18.5 = ผอม
    • 18.5-25 = ปกติ
    • 25-30 = อ้วน
    • 30-100 = อ้วนมาก
  • การรวม features แบบนี้สร้างข้อมูลใหม่ที่มีความหมายมากกว่าข้อมูลดิบ
  • AI จะเข้าใจความสัมพันธ์ระหว่างสุขภาพกับปัจจัยอื่นๆ ได้ดีขึ้น
🤖 Feature Combination เหมาะกับ ML Models ไหน?
  • Linear Models - Feature interaction (BMI = weight/height²) ช่วยจับ non-linear relationships
  • Polynomial Regression - ใช้ polynomial features (x², x³, xy) เพิ่มความซับซ้อนของ model
  • Deep Learning - สามารถเรียนรู้ feature combinations เอง แต่การใส่ engineered features ช่วยเร่งการเรียนรู้
  • Ensemble Methods - ใช้ engineered features ร่วมกับ raw features เพื่อเพิ่ม diversity ของ models
💡 Tip: Domain knowledge สำคัญมาก! BMI มีความหมายทางการแพทย์ ดีกว่าการให้ model เรียนรู้เอง

บทที่ 5 Workshop - ลองทำกันเลย!

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

Dataset สำหรับฝึกหัด ข้อมูลนักศึกษาลงทะเบียนกิจกรรม

นี่คือข้อมูลที่ "หัวหน้าฝ่ายกิจกการนักศึกษา" ส่งมาให้คุณ พร้อมข้อความว่า "ช่วยทำให้มันดูดีหน่อย ผอ.จะมาดูพรุ่งนี้!"

# สร้าง dataset ตัวอย่าง
import pandas as pd
import numpy as np

# ข้อมูลดิบที่มีปัญหา
raw_data = {
    'รหัสนักศึกษา': ['64001', '64002', '64003', '64004', '64005'],
    'ชื่อ': ['สมชาย', 'สมหญิง', None, 'สมศักดิ์', 'สมใจ'],
    'คณะ': ['วิทย์', 'ศิลปศาสตร์', 'วิทยาศาสตร์', 'Arts', 'วิศวะ'],
    'ชั้นปี': [2, 3, 2, None, 4],
    'GPA': [3.2, 3.8, -1, 2.9, 4.5],  # -1 และ 4.5 ผิดปกติ
    'กิจกรรม': ['กีฬา', 'ดนตรี', 'กีฬา,ดนตรี', None, 'อาสา'],
    'เวลาเข้าร่วม': ['08:00', '9.30', '08:30', '08:00', None]
}

df_student = pd.DataFrame(raw_data)
print("=== ข้อมูลดิบ ===")
print(df_student)
print("\nปัญหาที่พบ")
print(f"- ข้อมูลหาย - {df_student.isnull().sum().sum()} ค่า")
print(f"- GPA ผิดปกติ - {df_student[(df_student['GPA'] < 0) | (df_student['GPA'] > 4)]['GPA'].tolist()}")
คำอธิบาย Code
  • import pandas as pd และ import numpy as np - นำเข้าไลบรารีที่จำเป็น pandas สำหรับจัดการข้อมูล, numpy สำหรับการคำนวณ
  • raw_data = {...} - สร้าง dictionary ที่เก็บข้อมูลดิบ โดยมีปัญหาตามจริงที่พบในงาน
    • None แทนค่าหาย (จะแสดงเป็น NaN ใน DataFrame)
    • ชื่อคณะไม่เป็นมาตรฐาน (วิทย์, วิทยาศาสตร์, Arts)
    • GPA ผิดปกติ (-1 และ 4.5)
    • รูปแบบเวลาไม่สม่ำเสมอ ('9.30' แทนที่จะเป็น '09:30')
  • pd.DataFrame(raw_data) - แปลง dictionary เป็น DataFrame
  • isnull().sum().sum() - นับจำนวนค่าหายทั้งหมด
    • isnull() ตรวจสอบว่าเป็น NaN หรือไม่ (True/False)
    • .sum() ตัวแรกนับจำนวน True ในแต่ละคอลัมน์
    • .sum() ตัวที่สองรวมทุกคอลัมน์
  • (df_student['GPA'] < 0) | (df_student['GPA'] > 4) - หาแถวที่ GPA ผิดปกติ
    • | คือ OR operator ใน pandas
  • .tolist() - แปลง pandas Series เป็น Python list เพื่อแสดงผล

ขั้นตอนการทำความสะอาดข้อมูล

มาเริ่มภารกิจ "ช่วยชีวิตข้อมูล" กันเลย! เราจะแปลงข้อมูลที่ดูยุ่งเหยิงให้กลายเป็นข้อมูลที่ ผอ. ดูแล้วต้องยิ้ม

# Step 1 - จัดการข้อมูลหาย
# ชื่อที่หาย - ใช้ "ไม่ระบุ"
df_student['ชื่อ'].fillna('ไม่ระบุ', inplace=True)

# ชั้นปีที่หาย - ใช้ค่าที่พบบ่อยที่สุด
mode_year = df_student['ชั้นปี'].mode()[0]
df_student['ชั้นปี'].fillna(mode_year, inplace=True)

# กิจกรรมที่หาย - ใช้ "ไม่ได้เข้าร่วม"
df_student['กิจกรรม'].fillna('ไม่ได้เข้าร่วม', inplace=True)

# Step 2 - แก้ไข GPA ผิดปกติ
df_student.loc[df_student['GPA'] < 0, 'GPA'] = np.nan
df_student.loc[df_student['GPA'] > 4, 'GPA'] = 4.0
mean_gpa = df_student['GPA'].mean()
df_student['GPA'].fillna(mean_gpa, inplace=True)

# Step 3 - ทำให้ชื่อคณะเป็นมาตรฐาน
faculty_mapping = {
    'วิทย์': 'วิทยาศาสตร์',
    'วิทยาศาสตร์': 'วิทยาศาสตร์',
    'ศิลปศาสตร์': 'ศิลปศาสตร์',
    'Arts': 'ศิลปศาสตร์',
    'วิศวะ': 'วิศวกรรมศาสตร์'
}
df_student['คณะ'] = df_student['คณะ'].map(faculty_mapping)

# Step 4 - แปลงเวลาให้เป็นรูปแบบเดียวกัน
def standardize_time(time_str):
    if pd.isna(time_str):
        return '08:00'  # ค่าเริ่มต้น
    time_str = str(time_str).replace('.', ':')
    if len(time_str.split(':')[0]) == 1:
        time_str = '0' + time_str
    return time_str

df_student['เวลาเข้าร่วม'] = df_student['เวลาเข้าร่วม'].apply(standardize_time)

print("\n=== ข้อมูลหลังทำความสะอาด ===")
print(df_student)
คำอธิบาย Code
Step 1: จัดการข้อมูลหาย
  • fillna('ไม่ระบุ') - เติมข้อความ 'ไม่ระบุ' ในช่องชื่อที่เป็น NaN
  • .mode()[0] - หาค่าที่พบบ่อยที่สุด (Mode) และเลือกค่าแรก ([0]) ในกรณีที่มีหลายค่า
  • fillna() กับ inplace=True - แก้ไขข้อมูลใน DataFrame เดิมโดยตรง ไม่สร้างใหม่
Step 2: แก้ไข GPA ผิดปกติ
  • df_student.loc[condition, column] - เลือกแถวตามเงื่อนไขและระบุคอลัมน์ที่จะแก้ไข
  • df_student['GPA'] < 0 - หาแถวที่ GPA ติดลบ แล้วเปลี่ยนเป็น NaN
  • df_student['GPA'] > 4 - หาแถวที่ GPA เกิน 4.0 แล้วเปลี่ยนเป็น 4.0
  • คำนวณค่าเฉลี่ย GPA จากค่าที่ถูกต้อง แล้วเติมในช่องที่เป็น NaN
Step 3: ทำให้ชื่อคณะเป็นมาตรฐาน
  • faculty_mapping - สร้าง dictionary สำหรับแปลงชื่อคณะ
  • .map() - แปลงค่าในคอลัมน์ตาม mapping ที่กำหนด
  • ชื่อคณะทุกรูปแบบจะถูกแปลงเป็นชื่อมาตรฐาน
Step 4: แปลงเวลาให้เป็นรูปแบบเดียวกัน
  • pd.isna() - ตรวจสอบว่าเป็น NaN หรือไม่
  • str(time_str) - แปลงเป็น string เพื่อจัดการข้อความ
  • .replace('.', ':') - เปลี่ยนจุดเป็นโคลอน (9.30 → 9:30)
  • .split(':')[0] - แยกชั่วโมงออกมาเพื่อตรวจสอบความยาว
  • ถ้าชั่วโมงมีแค่ 1 หลัก จะเติม 0 ข้างหน้า (9:00 → 09:00)
  • .apply() - ใช้ฟังก์ชันกับทุกค่าในคอลัมน์

การสร้าง Features ใหม่

ตอนนี้ข้อมูลดูดีขึ้นแล้ว แต่เรายังทำให้มัน "ฉลาดขึ้น" ได้อีก โดยการสร้าง features ใหม่ที่จะช่วยให้ AI มองเห็น patterns ที่ซ่อนอยู่

# Feature 1 - แปลงเวลาเป็นช่วงเวลา
df_student['ชั่วโมง'] = pd.to_datetime(df_student['เวลาเข้าร่วม'], format='%H:%M').dt.hour
df_student['ช่วงเวลา'] = pd.cut(df_student['ชั่วโมง'], 
                                 bins=[0, 9, 12, 24],
                                 labels=['เช้า', 'สาย', 'บ่าย'])

# Feature 2 - นับจำนวนกิจกรรม
df_student['จำนวนกิจกรรม'] = df_student['กิจกรรม'].apply(
    lambda x: len(x.split(',')) if x != 'ไม่ได้เข้าร่วม' else 0
)

# Feature 3 - จัดกลุ่มผลการเรียน
df_student['ระดับผลการเรียน'] = pd.cut(df_student['GPA'], 
                                        bins=[0, 2.0, 3.0, 3.5, 4.0],
                                        labels=['ต่ำ', 'ปานกลาง', 'ดี', 'ดีมาก'])

# Feature 4 - One-Hot Encoding สำหรับคณะ
df_encoded = pd.get_dummies(df_student, columns=['คณะ'], prefix='คณะ')

print("\n=== ข้อมูลหลัง Feature Engineering ===")
print(df_encoded[['รหัสนักศึกษา', 'ช่วงเวลา', 'จำนวนกิจกรรม', 'ระดับผลการเรียน']])
print("\nคอลัมน์ทั้งหมด")
print(df_encoded.columns.tolist())
คำอธิบาย Code
Feature 1: แปลงเวลาเป็นช่วงเวลา
  • pd.to_datetime(..., format='%H:%M') - แปลง string เวลาเป็น datetime object
    • format='%H:%M' บอกรูปแบบว่าเป็น ชั่วโมง-นาที
  • .dt.hour - ดึงเฉพาะชั่วโมงออกมา (08:00 → 8, 09:30 → 9)
  • pd.cut() - แบ่งช่วงเวลา
    • 0-9 = 'เช้า' (กิจกรรมเริ่มเช้า)
    • 9-12 = 'สาย' (กิจกรรมเริ่มสาย)
    • 12-24 = 'บ่าย' (กิจกรรมช่วงบ่าย)
Feature 2: นับจำนวนกิจกรรม
  • lambda x: - สร้างฟังก์ชันแบบย่อ
  • x.split(',') - แยกกิจกรรมด้วยเครื่องหมายคอมมา
    • 'กีฬา,ดนตรี' → ['กีฬา', 'ดนตรี']
  • len() - นับจำนวนกิจกรรมหลังแยก
  • if x != 'ไม่ได้เข้าร่วม' else 0 - ถ้าไม่ได้เข้าร่วมให้นับเป็น 0
Feature 3: จัดกลุ่มผลการเรียน
  • bins=[0, 2.0, 3.0, 3.5, 4.0] - กำหนดช่วง GPA:
    • 0-2.0 = 'ต่ำ'
    • 2.0-3.0 = 'ปานกลาง'
    • 3.0-3.5 = 'ดี'
    • 3.5-4.0 = 'ดีมาก'
  • แปลง GPA ที่เป็นตัวเลขให้เป็นกลุ่มที่มีความหมาย
Feature 4: One-Hot Encoding
  • pd.get_dummies() - แปลงข้อมูลประเภทเป็นคอลัมน์แยก
  • columns=['คณะ'] - ระบุคอลัมน์ที่จะแปลง
  • prefix='คณะ' - ใส่คำนำหน้าชื่อคอลัมน์ใหม่
  • ผลลัพธ์ - สร้างคอลัมน์ใหม่ เช่น 'คณะ_วิทยาศาสตร์', 'คณะ_ศิลปศาสตร์', 'คณะ_วิศวกรรมศาสตร์'
  • .columns.tolist() - แสดงรายชื่อคอลัมน์ทั้งหมดเป็น list

บทที่ 6 Best Practices และข้อควรระวัง

หลังจากผ่านการต่อสู้กับข้อมูลมาหลายรอบ เราได้เรียนรู้บทเรียนสำคัญมากมาย มาดูกันว่า "นักรบข้อมูล" ที่เก่งๆ เขาทำอะไร และหลีกเลี่ยงอะไร

6.1 สิ่งที่ควรทำ ✅

คิดว่าตัวเองเป็นนักสำรวจที่กำลังจะเข้าป่า - การเตรียมตัวที่ดีคือหัวใจของความสำเร็จ

1. สำรวจข้อมูลก่อนเสมอ

เหมือนการดูแผนที่ก่อนเดินทาง - ถ้าไม่รู้ว่าเรามีอะไร จะแก้ไขได้อย่างไร?

# ดูข้อมูลเบื้องต้น
print(df.info())
print(df.describe())
print(df.head())
คำอธิบาย Code
  • df.info() - แสดงข้อมูลทั่วไปของ DataFrame:
    • จำนวนแถวและคอลัมน์
    • ชื่อคอลัมน์และประเภทข้อมูล
    • จำนวนค่าที่ไม่ใช่ null ในแต่ละคอลัมน์
    • หน่วยความจำที่ใช้
  • df.describe() - แสดงสถิติพื้นฐานของคอลัมน์ตัวเลข
    • count (จำนวนข้อมูล)
    • mean (ค่าเฉลี่ย)
    • std (ส่วนเบี่ยงเบนมาตรฐาน)
    • min, 25%, 50%, 75%, max (ค่าต่ำสุด, ควอไทล์, ค่าสูงสุด)
  • df.head() - แสดง 5 แถวแรกของข้อมูล (default) เพื่อดูตัวอย่างข้อมูล

2. บันทึกการเปลี่ยนแปลง

เหมือนการเขียนไดอารี่ของการผจญภัย - วันหนึ่งคุณอาจต้องย้อนกลับมาดูว่าทำอะไรไปบ้าง

# สร้าง log การแก้ไข
changes_log = []
changes_log.append("แทนค่า GPA -1 ด้วย NaN")
changes_log.append(f"เติม GPA ที่หายด้วยค่าเฉลี่ย {mean_gpa:.2f}")
คำอธิบาย Code
  • changes_log = [] - สร้าง list เปล่าสำหรับเก็บบันทึกการเปลี่ยนแปลง
  • .append() - เพิ่มข้อความเข้าไปใน list
  • f"...{mean_gpa:.2f}" - f-string สำหรับแทรกค่าตัวแปรในข้อความ
    • {mean_gpa:.2f} แสดงค่าเฉลี่ย GPA ด้วยทศนิยม 2 ตำแหน่ง
  • การทำ log แบบนี้ช่วยให้ติดตามว่าเราแก้ไขอะไรไปบ้าง สามารถย้อนกลับมาดูได้

3. ตรวจสอบผลลัพธ์

เชื่อแต่ไม่เชื่อใจ - ตรวจสอบทุกอย่างราวกับว่าคุณเป็นนักสืบที่ระแวดระวัง

# ตรวจสอบว่าไม่มีค่าหายแล้ว
assert df.isnull().sum().sum() == 0, "ยังมีค่าหายอยู่!"
คำอธิบาย Code
  • assert - คำสั่งสำหรับตรวจสอบเงื่อนไข ถ้าเงื่อนไขเป็น False จะแสดง error
  • df.isnull().sum().sum() == 0 - ตรวจสอบว่าไม่มีค่า null เหลืออยู่
    • isnull() - ตรวจสอบค่า null ได้ DataFrame ของ True/False
    • .sum() ตัวแรก - นับ True ในแต่ละคอลัมน์
    • .sum() ตัวที่สอง - รวมทุกคอลัมน์
    • == 0 - ต้องเท่ากับ 0 (ไม่มีค่า null)
  • "ยังมีค่าหายอยู่!" - ข้อความที่จะแสดงถ้าเงื่อนไขเป็น False
  • ใช้ assert เพื่อตรวจสอบคุณภาพข้อมูลหลังทำความสะอาด

6.2 สิ่งที่ไม่ควรทำ ❌

บทเรียนเหล่านี้ได้มาจากความผิดพลาดของคนนับไม่ถ้วน (รวมถึงผู้เขียนเอง!) อย่าให้ประวัติศาสตร์ซ้ำรอย

1. อย่าลบข้อมูลมากเกินไป

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

2. อย่าแก้ไขข้อมูลต้นฉบับ

Rule #1 ของนักข้อมูล - อย่าทำลายต้นฉบับ! เหมือนการแก้ไขภาพต้นฉบับโดยไม่มีสำเนา - ถ้าพลาดจะกลับไปแก้ไม่ได้

df_clean = df.copy()  # ทำแบบนี้เสมอ
คำอธิบาย Code
  • df.copy() - สร้างสำเนาของ DataFrame
  • สำคัญมาก! ถ้าไม่ใช้ .copy() การแก้ไข df_clean จะกระทบ df ด้วย
  • เหตุผล - pandas ใช้การอ้างอิง (reference) ไม่ใช่การคัดลอกค่า
  • ตัวอย่างปัญหาถ้าไม่ใช้ copy():
    df_clean = df  # ผิด! df_clean กับ df คือตัวเดียวกัน
    df_clean['col'] = 0  # df['col'] จะเปลี่ยนเป็น 0 ด้วย!
  • การใช้ .copy() ทำให้มั่นใจว่าข้อมูลต้นฉบับปลอดภัย

3. อย่าสร้าง Features มากเกินไป

เหมือนการใส่เครื่องปรุงมากเกินไป - แทนที่จะอร่อยขึ้น กลับทำให้รสชาติยุ่งเหยิง Features มากเกินไปอาจทำให้ AI สับสน (Overfitting)

บทที่ 7 สรุปและการนำไปใช้จริง

ยินดีด้วย! คุณได้ผ่านการเดินทางอันยาวนานในโลกของ Data Preparation มาถึงจุดนี้แล้ว แต่การเดินทางที่แท้จริงเพิ่งจะเริ่มต้น...

7.1 ขั้นตอนการทำ Data Preparation

เหมือนสูตรลับ 5 ขั้นตอนที่ Data Scientists ทั่วโลกใช้

  1. Explore - สำรวจและทำความเข้าใจข้อมูล (เหมือนการสำรวจดินแดนใหม่)
  2. Clean - ทำความสะอาดข้อมูล (เหมือนการล้างผักก่อนทำอาหาร)
  3. Transform - แปลงข้อมูลให้เหมาะสม (เหมือนการหั่นผักให้พอดีคำ)
  4. Engineer - สร้าง features ใหม่ (เหมือนการปรุงรสให้อร่อย)
  5. Validate - ตรวจสอบความถูกต้อง (เหมือนการชิมก่อนเสิร์ฟ)

7.2 Checklist สำหรับ Data Preparation

7.3 แบบฝึกหัด

ถึงเวลาทดสอบฝีมือของคุณแล้ว! แบบฝึกหัดเหล่านี้จะช่วยให้คุณมั่นใจว่าเข้าใจเนื้อหาที่เรียนมา

แบบฝึกหัดที่ 1 จากข้อมูลร้านกาแฟด้านล่าง ให้ทำความสะอาดและสร้าง features ใหม่

มีลูกค้าส่งข้อมูลมาให้คุณพร้อมข้อความว่า "ช่วยทำให้ข้อมูลนี้ใช้งานได้หน่อย พรุ่งนี้ต้องนำเสนอบอร์ด!"

coffee_shop_data = {
    'วันที่': ['1/1/24', '01-01-2024', '2024-01-01', '1 มค 24'],
    'เมนู': ['Latte', 'ลาเต้', 'Latte Hot', 'ลาเต้ร้อน'],
    'ขนาด': ['M', 'กลาง', 'Medium', 'M'],
    'ราคา': [60, 60, None, 60],
    'เวลา': ['08:30', '12.00', '14:30', '9:00']
}
คำอธิบาย Code และปัญหาที่ต้องแก้
  • วันที่: รูปแบบไม่เหมือนกัน (1/1/24, 01-01-2024, 2024-01-01, 1 มค 24)
    • ต้องแปลงเป็นรูปแบบมาตรฐานเดียวกัน
  • เมนู: ชื่อเดียวกันแต่เขียนต่างกัน (Latte, ลาเต้, Latte Hot, ลาเต้ร้อน)
    • ทั้งหมดคือลาเต้ร้อน ต้องทำให้เป็นชื่อเดียวกัน
  • ขนาด: รูปแบบไม่เหมือนกัน (M, กลาง, Medium)
    • ทั้งหมดคือขนาดกลาง ต้องใช้ชื่อเดียวกัน
  • ราคา: มีค่า None (ข้อมูลหาย)
    • ต้องเติมค่าที่เหมาะสม
  • เวลา: รูปแบบไม่สม่ำเสมอ (12.00 ควรเป็น 12:00, 9:00 ควรเป็น 09:00)
    • ต้องแปลงเป็นรูปแบบ HH:MM

7.4 ทรัพยากรเพิ่มเติม

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

1. เว็บไซต์เรียนรู้

  • Pandas Documentation - คัมภีร์ของนักจัดการข้อมูล
  • Kaggle Learn - สนามฝึกหัดของ Data Scientists

2. Dataset สำหรับฝึกหัด

  • Titanic Dataset (คลาสสิคที่ทุกคนต้องลอง)
  • Iris Dataset (ง่ายแต่ได้ใจความ)
  • Student Performance Dataset (ใกล้ตัวนักศึกษา)

3. Tools ที่ช่วยได้

  • Google Colab (ฟรี! ไม่ต้องติดตั้งอะไร)
  • Jupyter Notebook (สำหรับคนอยากลองของจริง)
  • VS Code with Python extension (สำหรับมือโปร)

ข้อคิดทิ้งท้าย

"ข้อมูลที่ดีกับอัลกอริทึมธรรมดา ดีกว่า ข้อมูลแย่ๆ กับอัลกอริทึมเทพ"

— คำพูดนี้ไม่ได้มาจากใครที่ไหน แต่เป็นความจริงที่ Data Scientists ทุกคนต้องเรียนรู้ด้วยตัวเอง

คุณรู้ไหมว่า Google ใช้อัลกอริทึมที่ไม่ได้ซับซ้อนมากในการค้นหา แต่สิ่งที่ทำให้มันเก่งคือข้อมูลมหาศาลที่ถูกจัดระเบียบอย่างดี?

การเตรียมข้อมูลอาจดูน่าเบื่อ อาจดูเหมือนงานที่ไม่มีความหมาย แต่จำไว้ว่า... พีระมิดที่ยิ่งใหญ่ก็ต้องเริ่มจากการวางหินก้อนแรกให้ตรง

เริ่มจากง่ายๆ ค่อยๆ ซับซ้อนขึ้น และอย่าลืมว่า error เป็นเรื่องปกติ - มันคือครูที่ดีที่สุดของเรา!

P.S. ถ้าคุณอ่านมาถึงตรงนี้ แสดงว่าคุณมีความอดทนพอที่จะเป็น Data Scientist ที่ดีได้แล้ว 😊

Happy Data Cleaning! 🧹✨

"จงภูมิใจในทุก Missing Value ที่คุณแก้ไข เพราะนั่นคือก้าวเล็กๆ สู่ AI ที่ยิ่งใหญ่"