เตรียมข้อมูลให้พร้อมสำหรับ AI
"ในปี 2020 บริษัท Netflix สามารถประหยัดค่าใช้จ่ายได้กว่า 1 พันล้านดอลลาร์ต่อปี เพียงเพราะปรับปรุงวิธีการเตรียมข้อมูลผู้ใช้งาน..."
คุณรู้หรือไม่ว่า Data Scientists ทั่วโลกใช้เวลากว่า 80% ของการทำงานไปกับการ "ทำความสะอาด" ข้อมูล? แล้วทำไมงานที่ดูน่าเบื่อนี้ถึงสำคัญขนาดนั้น?
มาค้นหาคำตอบไปด้วยกัน...
คุณเคยสงสัยไหมว่า ทำไม AI บางตัวถึงทำนายได้แม่นยำ แต่บางตัวกลับพลาดอย่างน่าตกใจ? คำตอบอาจไม่ได้อยู่ที่ความฉลาดของ AI แต่อยู่ที่ "วัตถุดิบ" ที่เราป้อนให้มันต่างหาก
ลองนึกภาพว่าคุณเป็นเชฟที่กำลังจะทำอาหาร
มาดูเรื่องจริงที่เกิดขึ้นกับร้านกาแฟแห่งหนึ่งในมหาวิทยาลัยดัง พวกเขาลงทุนซื้อระบบ AI มาทำนายยอดขายแพงมาก แต่ผลที่ได้กลับน่าผิดหวัง... จนกระทั่งพบว่าปัญหาไม่ได้อยู่ที่ AI แต่อยู่ที่ข้อมูลที่ป้อนเข้าไป
มาดูกันว่าข้อมูลของพวกเขาเป็นอย่างไร
วันที่,เมนู,ราคา,จำนวน,ลูกค้า
1/1/2024,ลาเต้ร้อน,60,3,นศ.ปี1
01-01-2024,ร้อนลาเต้,60,2,อาจารย์
1 ม.ค.,Latte(Hot),60,,นักศึกษา
คุณอาจคิดว่า "แค่ข้อมูลไม่เรียบร้อยหน่อย AI น่าจะฉลาดพอที่จะเข้าใจได้" แต่ความจริงอาจทำให้คุณต้องตกใจ...
นี่คือสิ่งที่จะเกิดขึ้นถ้าเราไม่แก้ไขข้อมูล
# ตัวอย่างผลกระทบ
# ถ้าไม่แก้ไขข้อมูล AI จะคิดว่า
- "ลาเต้ร้อน" กับ "ร้อนลาเต้" เป็นคนละเมนู
- ไม่สามารถคำนวณยอดขายรวมได้ถูกต้อง
- ไม่สามารถวิเคราะห์แนวโน้มตามเวลาได้
คุณเคยเห็นภาพยนตร์ที่นักสืบต้องแยกแยะหลักฐานต่างๆ เพื่อไขคดีไหม? การทำงานกับข้อมูลก็เหมือนกัน เราต้องรู้จักประเภทของ "หลักฐาน" ที่เรามีก่อน ถึงจะรู้ว่าต้องจัดการอย่างไร
ก่อนที่เราจะเริ่ม "สืบสวน" ข้อมูล มาทำความรู้จักกับตัวละครหลักในเรื่องนี้กันก่อน
ประเภท | ลักษณะ | ตัวอย่าง |
---|---|---|
ข้อมูลตัวเลข (Numerical) |
|
|
ข้อมูลประเภท (Categorical) |
|
|
ข้อมูลข้อความ (Text) | ข้อความอิสระ | ชื่อ, ที่อยู่, ความคิดเห็น |
ข้อมูลวันเวลา (DateTime) | วันที่และเวลา | วันที่, เวลา, ช่วงเวลา |
เราจะสร้างชุดข้อมูลนักศึกษาที่มีปัญหาหลากหลายแบบที่พบในชีวิตจริง แล้วใช้ข้อมูลชุดนี้ตลอดทั้งบทเรียน เพื่อให้เห็นการพัฒนาข้อมูลแบบต่อเนื่อง
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() เพื่อไม่ให้กระทบข้อมูลต้นฉบับ
import pandas as pd
- นำเข้าไลบรารี pandas ซึ่งเป็นเครื่องมือหลักในการจัดการข้อมูลใน Python และตั้งชื่อย่อว่า pddata = {...}
- สร้าง dictionary เพื่อเก็บข้อมูลโดยมี key เป็นชื่อคอลัมน์ และ value เป็น list ของข้อมูลในคอลัมน์นั้นNone/NaN
- เป็นค่าพิเศษที่แทนค่าว่าง หรือไม่มีข้อมูลpd.DataFrame(data)
- แปลง dictionary ให้กลายเป็น DataFrame ซึ่งเป็นโครงสร้างข้อมูลแบบตารางprint(df)
- แสดงผลข้อมูลในรูปแบบตาราง จะเห็นว่ามี NaN ปรากฏในตำแหน่งที่มีค่า None# วิเคราะห์ข้อมูล 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}%)")
หลังจากที่เรารู้จักปัญหาแล้ว คราวนี้มาเรียนรู้วิธีแก้ไขกัน เหมือนกับการมีกล่องเครื่องมือช่างที่พร้อมจะซ่อมทุกอย่างที่พัง แต่ต้องรู้ว่าจะใช้เครื่องมือไหน เมื่อไหร่ อย่างไร
เมื่อเจอข้อมูลหาย เรามีทางเลือกเหมือนตอนที่เราทำการบ้านไม่เสร็จ - แกล้งป่วย (ลบทิ้ง), ลอกเพื่อน (ใช้ค่าเฉลี่ย), หรือเดาเอา (ใช้ค่าที่เจอบ่อย) มาดูกันว่าแต่ละวิธีใช้ตอนไหน
เหมือนการตัดชิ้นส่วนที่เน่าออกจากผลไม้ - ใช้ได้ถ้าส่วนที่เน่ามีน้อย แต่ถ้าเน่าเกือบหมดแล้วจะตัดยังไง?
# ตัวอย่างข้อมูลที่มีค่าหายเพียงเล็กน้อย
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)
data = {...}
- สร้างข้อมูลตัวอย่าง 10 คน โดยคนที่ 5 (กุล) ไม่ได้กรอกอายุ (None)df.dropna()
- ลบแถวทั้งหมดที่มีค่า NaN (ค่าหาย) ออกจาก DataFrame
df_clean
- เก็บ DataFrame ใหม่ที่ลบค่าหายออกแล้ว (ไม่แก้ไข df เดิม)len(df)
- นับจำนวนแถวทั้งหมด = 10 แถวlen(df_clean)
- นับจำนวนแถวหลังลบ = 9 แถวนึกถึงตอนเราไม่รู้ว่าเพื่อนๆ ในห้องสูงเท่าไหร่ เราก็จะเดาจากค่าเฉลี่ยของคนที่เรารู้ วิธีนี้ใช้ได้ดีกับข้อมูลตัวเลข
# ตัวอย่างข้อมูลนักศึกษาที่มีอายุหาย
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)
data = {...}
- สร้างข้อมูลตัวอย่าง 5 คน โดยคนที่ 2 (สมหญิง) ไม่ได้กรอกอายุ (None)df['อายุ'].mean()
- คำนวณค่าเฉลี่ยของคอลัมน์ 'อายุ' โดยไม่นับรวมค่า NaN
fillna(mean_age, inplace=True)
- เติมค่า 20.5 ลงในช่องที่เป็น NaN (ของสมหญิง)
inplace=True
หมายถึงแก้ไขข้อมูลใน DataFrame เดิมโดยตรง ไม่สร้าง DataFrame ใหม่f"{mean_age:.1f}"
- แสดงผลค่าเฉลี่ยโดยมีทศนิยม 1 ตำแหน่ง (.1f)สำหรับข้อมูลประเภท (เช่น คณะ, สี, ขนาด) เราจะใช้ค่าที่คนส่วนใหญ่เลือก เหมือนตอนสั่งอาหารกลุ่มแล้วไม่รู้จะสั่งอะไร ก็สั่งตามเพื่อนส่วนใหญ่:
# ตัวอย่างข้อมูลนักศึกษาที่มีคณะหาย
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)
data = {...}
- สร้างข้อมูลตัวอย่าง 5 คน โดยคนที่ 3 (สมศักดิ์) ไม่ได้กรอกคณะ (None)df['คณะ'].mode()
- หาค่าที่พบบ่อยที่สุด (Mode) ในคอลัมน์ 'คณะ'
[0]
- เลือกค่า mode ตัวแรก (ในกรณีที่มีหลายค่าที่พบบ่อยเท่ากัน)fillna(..., inplace=True)
- เติม 'วิทยาศาสตร์' ลงในช่องที่เป็น NaN (ของสมศักดิ์)print()
- แสดงผลก่อนและหลังเติมค่าเพื่อเปรียบเทียบคุณเคยพยายามหาเพลงในแอพฟังเพลงแล้วหาไม่เจอไหม? เพราะศิลปินชื่อ "เบิร์ด ธงไชย" แต่คุณพิมพ์ "Bird Thongchai" นี่แหละคือปัญหาที่เราต้องแก้:
# ตัวอย่าง - ทำให้ชื่อคณะเป็นมาตรฐานเดียวกัน
def standardize_faculty(name):
if name in ['วิทยาศาสตร์', 'วิทย์', 'Science', 'คณะวิทยาศาสตร์']:
return 'วิทยาศาสตร์'
elif name in ['ศิลปศาสตร์', 'ศิลป์', 'Arts']:
return 'ศิลปศาสตร์'
else:
return name
# ใช้งาน
df['คณะ_มาตรฐาน'] = df['คณะ'].apply(standardize_faculty)
def standardize_faculty(name):
- สร้างฟังก์ชันที่รับชื่อคณะเข้ามาแล้วแปลงเป็นชื่อมาตรฐานif name in [...]
- ตรวจสอบว่าชื่อที่รับเข้ามาอยู่ใน list ของชื่อที่เป็นไปได้หรือไม่return 'วิทยาศาสตร์'
- คืนค่าชื่อคณะที่เป็นมาตรฐานelse: return name
- ถ้าไม่ตรงกับเงื่อนไขใดๆ ให้คืนชื่อเดิมdf['คณะ'].apply(standardize_faculty)
- ใช้ฟังก์ชันกับทุกค่าในคอลัมน์ 'คณะ'
apply()
จะเรียกฟังก์ชันทีละแถวและส่งค่าในแถวนั้นเข้าไปในฟังก์ชันdf['คณะ_มาตรฐาน']
- สร้างคอลัมน์ใหม่ที่เก็บชื่อคณะที่เป็นมาตรฐานแล้วค่าผิดปกติเหมือนคนที่อ้างว่าวิ่ง 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}")
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
)หลังจากทำความสะอาดข้อมูลเรียบร้อยแล้ว ถึงเวลาของการ "แต่งเติม" ให้ข้อมูลของเรามีคุณค่ามากขึ้น เหมือนการเปลี่ยนแร่ธรรมดาให้กลายเป็นเครื่องประดับที่สวยงาม
คุณรู้ไหมว่า AI ส่วนใหญ่ "อ่านภาษาไทยไม่ออก"? พวกมันเข้าใจแต่ตัวเลข ดังนั้นเราต้องแปลงคำว่า "วิทยาศาสตร์" ให้กลายเป็นตัวเลขที่ AI เข้าใจได้
ลองนึกภาพว่าคุณพยายามสอนหุ่นยนต์ให้แยกสีได้ แต่หุ่นยนต์มองไม่เห็น มันรู้แค่ตัวเลข เราจึงต้องบอกว่า "แดง = 1, เขียว = 2, น้ำเงิน = 3" แต่เดี๋ยวก่อน! วิธีนี้มีปัญหา...
# ก่อนแปลง
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
pd.DataFrame({...})
- สร้าง DataFrame ตัวอย่างที่มีข้อมูลประเภท (คณะ) ที่ต้องการแปลงpd.get_dummies(data, columns=['คณะ'])
- แปลงคอลัมน์ 'คณะ' เป็นรูปแบบ One-Hot Encoding
get_dummies
จะสร้างคอลัมน์ใหม่สำหรับแต่ละค่าที่เป็นไปได้ในคอลัมน์ 'คณะ'เวลาเป็นข้อมูลที่ทรงพลังแต่ซ่อนอยู่ในรูปแบบที่ 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[['วันเวลา', 'ช่วงเวลา', 'ยอดขาย']])
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=['เช้า', 'กลางวัน', 'บ่าย', 'เย็น']
- ชื่อที่จะใช้เรียกแต่ละช่วงบางครั้งข้อมูล 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', 'สุขภาพ']])
student_health['ส่วนสูง_cm']/100
- แปลงส่วนสูงจากเซนติเมตรเป็นเมตร (เพราะสูตร BMI ใช้หน่วยเมตร)**2
- ยกกำลังสอง (เทียบเท่ากับ x²)pd.cut()
- แบ่งค่า BMI เป็นช่วงตามเกณฑ์มาตรฐาน
ถึงเวลาลงมือทำจริงแล้ว! คราวนี้คุณจะได้เป็น "นักสืบข้อมูล" ที่ต้องไขปริศนาข้อมูลที่ยุ่งเหยิง เตรียมตัวให้พร้อม เพราะสิ่งที่คุณจะเจอ... อาจทำให้คุณต้องอึ้ง!
นี่คือข้อมูลที่ "หัวหน้าฝ่ายกิจกการนักศึกษา" ส่งมาให้คุณ พร้อมข้อความว่า "ช่วยทำให้มันดูดีหน่อย ผอ.จะมาดูพรุ่งนี้!"
# สร้าง 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()}")
import pandas as pd
และ import numpy as np
- นำเข้าไลบรารีที่จำเป็น pandas สำหรับจัดการข้อมูล, numpy สำหรับการคำนวณraw_data = {...}
- สร้าง dictionary ที่เก็บข้อมูลดิบ โดยมีปัญหาตามจริงที่พบในงาน
None
แทนค่าหาย (จะแสดงเป็น NaN ใน DataFrame)pd.DataFrame(raw_data)
- แปลง dictionary เป็น DataFrameisnull().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)
fillna('ไม่ระบุ')
- เติมข้อความ 'ไม่ระบุ' ในช่องชื่อที่เป็น NaN.mode()[0]
- หาค่าที่พบบ่อยที่สุด (Mode) และเลือกค่าแรก ([0]) ในกรณีที่มีหลายค่าfillna()
กับ inplace=True
- แก้ไขข้อมูลใน DataFrame เดิมโดยตรง ไม่สร้างใหม่df_student.loc[condition, column]
- เลือกแถวตามเงื่อนไขและระบุคอลัมน์ที่จะแก้ไขdf_student['GPA'] < 0
- หาแถวที่ GPA ติดลบ แล้วเปลี่ยนเป็น NaNdf_student['GPA'] > 4
- หาแถวที่ GPA เกิน 4.0 แล้วเปลี่ยนเป็น 4.0faculty_mapping
- สร้าง dictionary สำหรับแปลงชื่อคณะ.map()
- แปลงค่าในคอลัมน์ตาม mapping ที่กำหนดpd.isna()
- ตรวจสอบว่าเป็น NaN หรือไม่str(time_str)
- แปลงเป็น string เพื่อจัดการข้อความ.replace('.', ':')
- เปลี่ยนจุดเป็นโคลอน (9.30 → 9:30).split(':')[0]
- แยกชั่วโมงออกมาเพื่อตรวจสอบความยาว.apply()
- ใช้ฟังก์ชันกับทุกค่าในคอลัมน์ตอนนี้ข้อมูลดูดีขึ้นแล้ว แต่เรายังทำให้มัน "ฉลาดขึ้น" ได้อีก โดยการสร้าง 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())
pd.to_datetime(..., format='%H:%M')
- แปลง string เวลาเป็น datetime object
format='%H:%M'
บอกรูปแบบว่าเป็น ชั่วโมง-นาที.dt.hour
- ดึงเฉพาะชั่วโมงออกมา (08:00 → 8, 09:30 → 9)pd.cut()
- แบ่งช่วงเวลา
lambda x:
- สร้างฟังก์ชันแบบย่อx.split(',')
- แยกกิจกรรมด้วยเครื่องหมายคอมมา
len()
- นับจำนวนกิจกรรมหลังแยกif x != 'ไม่ได้เข้าร่วม' else 0
- ถ้าไม่ได้เข้าร่วมให้นับเป็น 0bins=[0, 2.0, 3.0, 3.5, 4.0]
- กำหนดช่วง GPA:
pd.get_dummies()
- แปลงข้อมูลประเภทเป็นคอลัมน์แยกcolumns=['คณะ']
- ระบุคอลัมน์ที่จะแปลงprefix='คณะ'
- ใส่คำนำหน้าชื่อคอลัมน์ใหม่.columns.tolist()
- แสดงรายชื่อคอลัมน์ทั้งหมดเป็น listหลังจากผ่านการต่อสู้กับข้อมูลมาหลายรอบ เราได้เรียนรู้บทเรียนสำคัญมากมาย มาดูกันว่า "นักรบข้อมูล" ที่เก่งๆ เขาทำอะไร และหลีกเลี่ยงอะไร
คิดว่าตัวเองเป็นนักสำรวจที่กำลังจะเข้าป่า - การเตรียมตัวที่ดีคือหัวใจของความสำเร็จ
เหมือนการดูแผนที่ก่อนเดินทาง - ถ้าไม่รู้ว่าเรามีอะไร จะแก้ไขได้อย่างไร?
# ดูข้อมูลเบื้องต้น
print(df.info())
print(df.describe())
print(df.head())
df.info()
- แสดงข้อมูลทั่วไปของ DataFrame:
df.describe()
- แสดงสถิติพื้นฐานของคอลัมน์ตัวเลข
df.head()
- แสดง 5 แถวแรกของข้อมูล (default) เพื่อดูตัวอย่างข้อมูลเหมือนการเขียนไดอารี่ของการผจญภัย - วันหนึ่งคุณอาจต้องย้อนกลับมาดูว่าทำอะไรไปบ้าง
# สร้าง log การแก้ไข
changes_log = []
changes_log.append("แทนค่า GPA -1 ด้วย NaN")
changes_log.append(f"เติม GPA ที่หายด้วยค่าเฉลี่ย {mean_gpa:.2f}")
changes_log = []
- สร้าง list เปล่าสำหรับเก็บบันทึกการเปลี่ยนแปลง.append()
- เพิ่มข้อความเข้าไปใน listf"...{mean_gpa:.2f}"
- f-string สำหรับแทรกค่าตัวแปรในข้อความ
{mean_gpa:.2f}
แสดงค่าเฉลี่ย GPA ด้วยทศนิยม 2 ตำแหน่งเชื่อแต่ไม่เชื่อใจ - ตรวจสอบทุกอย่างราวกับว่าคุณเป็นนักสืบที่ระแวดระวัง
# ตรวจสอบว่าไม่มีค่าหายแล้ว
assert df.isnull().sum().sum() == 0, "ยังมีค่าหายอยู่!"
assert
- คำสั่งสำหรับตรวจสอบเงื่อนไข ถ้าเงื่อนไขเป็น False จะแสดง errordf.isnull().sum().sum() == 0
- ตรวจสอบว่าไม่มีค่า null เหลืออยู่
isnull()
- ตรวจสอบค่า null ได้ DataFrame ของ True/False.sum()
ตัวแรก - นับ True ในแต่ละคอลัมน์.sum()
ตัวที่สอง - รวมทุกคอลัมน์== 0
- ต้องเท่ากับ 0 (ไม่มีค่า null)"ยังมีค่าหายอยู่!"
- ข้อความที่จะแสดงถ้าเงื่อนไขเป็น Falseบทเรียนเหล่านี้ได้มาจากความผิดพลาดของคนนับไม่ถ้วน (รวมถึงผู้เขียนเอง!) อย่าให้ประวัติศาสตร์ซ้ำรอย
เหมือนการตัดผมที่สั้นเกินไป - ตัดง่าย แต่รอให้งอกใหม่นาน ถ้าลบแถวที่มีค่าหายทั้งหมด อาจเหลือข้อมูลน้อยจนใช้งานไม่ได้
Rule #1 ของนักข้อมูล - อย่าทำลายต้นฉบับ! เหมือนการแก้ไขภาพต้นฉบับโดยไม่มีสำเนา - ถ้าพลาดจะกลับไปแก้ไม่ได้
df_clean = df.copy() # ทำแบบนี้เสมอ
df.copy()
- สร้างสำเนาของ DataFrame.copy()
การแก้ไข df_clean จะกระทบ df ด้วยdf_clean = df # ผิด! df_clean กับ df คือตัวเดียวกัน
df_clean['col'] = 0 # df['col'] จะเปลี่ยนเป็น 0 ด้วย!
.copy()
ทำให้มั่นใจว่าข้อมูลต้นฉบับปลอดภัยเหมือนการใส่เครื่องปรุงมากเกินไป - แทนที่จะอร่อยขึ้น กลับทำให้รสชาติยุ่งเหยิง Features มากเกินไปอาจทำให้ AI สับสน (Overfitting)
ยินดีด้วย! คุณได้ผ่านการเดินทางอันยาวนานในโลกของ Data Preparation มาถึงจุดนี้แล้ว แต่การเดินทางที่แท้จริงเพิ่งจะเริ่มต้น...
เหมือนสูตรลับ 5 ขั้นตอนที่ Data Scientists ทั่วโลกใช้
ถึงเวลาทดสอบฝีมือของคุณแล้ว! แบบฝึกหัดเหล่านี้จะช่วยให้คุณมั่นใจว่าเข้าใจเนื้อหาที่เรียนมา
มีลูกค้าส่งข้อมูลมาให้คุณพร้อมข้อความว่า "ช่วยทำให้ข้อมูลนี้ใช้งานได้หน่อย พรุ่งนี้ต้องนำเสนอบอร์ด!"
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']
}
สำหรับผู้ที่อยากเดินทางต่อในเส้นทางนี้ นี่คือแหล่งความรู้ที่จะพาคุณไปได้ไกลกว่านี้
"ข้อมูลที่ดีกับอัลกอริทึมธรรมดา ดีกว่า ข้อมูลแย่ๆ กับอัลกอริทึมเทพ"
— คำพูดนี้ไม่ได้มาจากใครที่ไหน แต่เป็นความจริงที่ Data Scientists ทุกคนต้องเรียนรู้ด้วยตัวเอง
คุณรู้ไหมว่า Google ใช้อัลกอริทึมที่ไม่ได้ซับซ้อนมากในการค้นหา แต่สิ่งที่ทำให้มันเก่งคือข้อมูลมหาศาลที่ถูกจัดระเบียบอย่างดี?
การเตรียมข้อมูลอาจดูน่าเบื่อ อาจดูเหมือนงานที่ไม่มีความหมาย แต่จำไว้ว่า... พีระมิดที่ยิ่งใหญ่ก็ต้องเริ่มจากการวางหินก้อนแรกให้ตรง
เริ่มจากง่ายๆ ค่อยๆ ซับซ้อนขึ้น และอย่าลืมว่า error เป็นเรื่องปกติ - มันคือครูที่ดีที่สุดของเรา!
Happy Data Cleaning! 🧹✨
"จงภูมิใจในทุก Missing Value ที่คุณแก้ไข เพราะนั่นคือก้าวเล็กๆ สู่ AI ที่ยิ่งใหญ่"