4. การทำความสะอาดข้อมูล (Data Cleaning)

ในโลกความเป็นจริง ข้อมูลมักจะสกปรก (Dirty Data) ขั้นตอนนี้จึงสำคัญที่สุดและใช้เวลาเยอะที่สุด การทำความสะอาดข้อมูล (Data Cleaning) คือกระบวนการปรับปรุงคุณภาพของข้อมูลให้พร้อมสำหรับการวิเคราะห์ โดยการแก้ไขหรือลบข้อมูลที่ไม่ถูกต้อง ไม่สมบูรณ์ ซ้ำซ้อน หรือมีรูปแบบที่ไม่เหมาะสม

graph TB
    A["ข้อมูลดิบ
(Raw Data)"] A -->|"ตรวจสอบ"| B["ข้อมูลหายไป
(Missing Values)"] A -->|"ตรวจสอบ"| C["ข้อมูลซ้ำ
(Duplicates)"] A -->|"ตรวจสอบ"| D["ประเภทข้อมูลผิด
(Wrong Data Types)"] A -->|"ตรวจสอบ"| E["ชื่อคอลัมน์ไม่สม่ำเสมอ
(Inconsistent Column Names)"] A -->|"ตรวจสอบ"| F["ค่าผิดปกติ
(Outliers)"] B -->|"จัดการ"| G["ข้อมูลสะอาด
(Clean Data)"] C -->|"จัดการ"| G D -->|"จัดการ"| G E -->|"จัดการ"| G F -->|"จัดการ"| G style A fill:#fb4934,stroke:#cc241d,stroke-width:2px,color:#fbf1c7 style B fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style C fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style D fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style E fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style F fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style G fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828

สถิติที่น่าสนใจ: นักวิทยาศาสตร์ข้อมูล (Data Scientists) ใช้เวลาถึง 60-80% ของโปรเจกต์ในการทำความสะอาดข้อมูล ก่อนจะเริ่มวิเคราะห์หรือสร้างโมเดลจริงๆ


4.1 การจัดการข้อมูลที่หายไป (Missing Values)

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

4.1.1 การตรวจหาข้อมูลที่หายไป (Detecting Missing Values)

Pandas ใช้ NaN (Not a Number) และ None เพื่อแทนค่าที่หายไป โดยมีฟังก์ชันหลักในการตรวจหา:

import pandas as pd
import numpy as np

# สร้างข้อมูลตัวอย่างที่มีค่าหายไป
data = {
    'ชื่อ': ['สมชาย', 'สมหญิง', 'สมศักดิ์', None, 'สมใจ'],
    'อายุ': [25, np.nan, 32, 28, np.nan],
    'เงินเดือน': [30000, 45000, np.nan, 38000, 42000],
    'แผนก': ['IT', 'HR', 'IT', np.nan, 'Sales']
}

df = pd.DataFrame(data)

# ตรวจหาข้อมูลที่หายไปด้วย isna() หรือ isnull()
print("ตรวจหาค่าที่หายไป (True = หายไป):")
print(df.isna())

# นับจำนวนค่าที่หายไปในแต่ละคอลัมน์
print("\nจำนวนค่าที่หายไปในแต่ละคอลัมน์:")
print(df.isna().sum())

# หาเปอร์เซ็นต์ของข้อมูลที่หายไป
print("\nเปอร์เซ็นต์ข้อมูลที่หายไป:")
missing_percent = (df.isna().sum() / len(df)) * 100
print(missing_percent)

ผลลัพธ์:

ตรวจหาค่าที่หายไป (True = หายไป):
      ชื่อ    อายุ  เงินเดือน   แผนก
0  False  False  False  False
1  False   True  False  False
2  False  False   True  False
3   True  False  False   True
4  False   True  False  False

จำนวนค่าที่หายไปในแต่ละคอลัมน์:
ชื่อ        1
อายุ        2
เงินเดือน     1
แผนก        1
dtype: int64

เปอร์เซ็นต์ข้อมูลที่หายไป:
ชื่อ       20.0
อายุ       40.0
เงินเดือน    20.0
แผนก       20.0
dtype: float64
graph LR
    A["DataFrame"]
    A --> B["isna() / isnull()"]
    A --> C["notna() / notnull()"]
    
    B --> D["Boolean DataFrame
(True = Missing)"] C --> E["Boolean DataFrame
(True = Not Missing)"] D --> F["sum() - นับจำนวน"] D --> G["any() - มีหรือไม่"] style A fill:#458588,stroke:#076678,stroke-width:2px,color:#fbf1c7 style B fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828 style C fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828 style D fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style E fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style F fill:#fe8019,stroke:#d65d0e,stroke-width:2px,color:#282828 style G fill:#fe8019,stroke:#d65d0e,stroke-width:2px,color:#282828

4.1.2 การแทนที่ค่าที่หายไป (Filling Missing Values)

การแทนที่ค่าที่หายไป (Imputation) เป็นวิธีการเติมข้อมูลที่หายไปด้วยค่าที่เหมาะสม มีหลายวิธีให้เลือกใช้:

วิธีที่ 1: แทนที่ด้วยค่าคงที่ (Constant Value)

"""
แทนที่ค่าที่หายไปด้วยค่าคงที่ที่กำหนด
เหมาะสำหรับข้อมูลที่มีความหมายเฉพาะ
"""

# แทนที่ด้วยค่าเดียวทั้ง DataFrame
df_filled = df.fillna(0)

# แทนที่แบบระบุเฉพาะคอลัมน์
df['แผนก'] = df['แผนก'].fillna('ไม่ระบุ')
df['อายุ'] = df['อายุ'].fillna(0)

print("ข้อมูลหลังแทนที่ด้วยค่าคงที่:")
print(df)

วิธีที่ 2: แทนที่ด้วยค่าเฉลี่ย/มัธยฐาน/ฐานนิยม (Statistical Measures)

"""
แทนที่ด้วยค่าทางสถิติ
- mean: เหมาะกับข้อมูลตัวเลขที่กระจายปกติ
- median: เหมาะเมื่อมี outliers
- mode: เหมาะกับข้อมูลประเภท categorical
"""

# แทนที่ด้วยค่าเฉลี่ย (Mean)
df['อายุ'] = df['อายุ'].fillna(df['อายุ'].mean())

# แทนที่ด้วยค่ามัธยฐาน (Median)
df['เงินเดือน'] = df['เงินเดือน'].fillna(df['เงินเดือน'].median())

# แทนที่ด้วยฐานนิยม (Mode) - ค่าที่เจอบ่อยที่สุด
df['แผนก'] = df['แผนก'].fillna(df['แผนก'].mode()[0])

print("ข้อมูลหลังแทนที่ด้วยค่าทางสถิติ:")
print(df)

สูตรคำนวณค่าเฉลี่ย (Mean):

x ¯ = i = 1 n x i n

โดยที่:

วิธีที่ 3: Forward Fill และ Backward Fill

"""
การเติมข้อมูลแบบไปข้างหน้า (Forward Fill) หรือย้อนกลับ (Backward Fill)
เหมาะสำหรับข้อมูลอนุกรมเวลา (Time Series)
"""

# Forward Fill (ffill) - เติมด้วยค่าก่อนหน้า
df_ffill = df.fillna(method='ffill')

# Backward Fill (bfill) - เติมด้วยค่าถัดไป
df_bfill = df.fillna(method='bfill')

# ผลลัพธ์จะต่างกัน:
# ffill: ค่าว่างจะถูกแทนด้วยค่าบรรทัดก่อนหน้า
# bfill: ค่าว่างจะถูกแทนด้วยค่าบรรทัดถัดไป

วิธีที่ 4: Interpolation (การประมาณค่า)

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

# Linear Interpolation
df['อายุ'] = df['อายุ'].interpolate(method='linear')

# Polynomial Interpolation
df['เงินเดือน'] = df['เงินเดือน'].interpolate(method='polynomial', order=2)

print("ข้อมูลหลังการประมาณค่า:")
print(df)

ตารางเปรียบเทียบวิธีการแทนที่ค่า:

วิธีการ ข้อดี ข้อเสีย เหมาะกับ
ค่าคงที่ เข้าใจง่าย ไม่เปลี่ยนการกระจายข้อมูล อาจไม่สมจริง ข้อมูลที่มี default value
Mean รักษาค่าเฉลี่ยไว้ ไวต่อ outliers ข้อมูลกระจายปกติ
Median ทนทานต่อ outliers ไม่รักษาค่าเฉลี่ย ข้อมูลมี outliers
Mode เหมาะกับ categorical อาจมีหลายค่า ข้อมูลประเภท category
Forward/Backward Fill รักษาแนวโน้ม ต้องมีข้อมูลก่อน/หลัง Time series
Interpolation สร้างความต่อเนื่อง ซับซ้อน ข้อมูลต่อเนื่อง

4.1.3 การลบข้อมูลที่หายไป (Dropping Missing Values)

บางครั้งการลบข้อมูลที่หายไป อาจเป็นวิธีที่ดีกว่าการแทนที่ โดยเฉพาะเมื่อ:

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

# ลบแถวที่มีค่า NaN ใดๆ
df_dropped_rows = df.dropna()

# ลบแถวที่ทุกคอลัมน์เป็น NaN
df_dropped_all = df.dropna(how='all')

# ลบแถวที่มี NaN ในคอลัมน์ที่ระบุ
df_dropped_subset = df.dropna(subset=['อายุ', 'เงินเดือน'])

# ลบคอลัมน์ที่มี NaN
df_dropped_cols = df.dropna(axis=1)

# ลบแถวที่มี NaN มากกว่า 2 คอลัมน์
df_dropped_thresh = df.dropna(thresh=3)  # ต้องมีข้อมูลไม่น้อยกว่า 3 คอลัมน์

# แสดงผลลัพธ์
print(f"ข้อมูลเดิม: {df.shape}")
print(f"หลังลบแถว: {df_dropped_rows.shape}")
print(f"หลังลบคอลัมน์: {df_dropped_cols.shape}")
flowchart TD
    A["ตรวจสอบ Missing Values"]
    A --> B{เปอร์เซ็นต์ที่หายไป}
    
    B -->|"< 5%"| C["ลบข้อมูล
dropna()"] B -->|"5-30%"| D["แทนที่ด้วยค่าทางสถิติ
fillna(mean/median)"] B -->|"> 30%"| E{ความสำคัญของคอลัมน์} E -->|"สำคัญมาก"| F["Advanced Imputation
(KNN, ML Models)"] E -->|"ไม่สำคัญ"| G["ลบคอลัมน์
drop(axis=1)"] C --> H["ข้อมูลสะอาด"] D --> H F --> H G --> H style A fill:#458588,stroke:#076678,stroke-width:2px,color:#fbf1c7 style B fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style C fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828 style D fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828 style E fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style F fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828 style G fill:#fb4934,stroke:#cc241d,stroke-width:2px,color:#fbf1c7 style H fill:#d3869b,stroke:#b16286,stroke-width:2px,color:#282828

4.2 การจัดการข้อมูลซ้ำ (Duplicates)

ข้อมูลซ้ำ (Duplicate Data) เกิดขึ้นเมื่อมีแถวที่มีค่าเหมือนกันทั้งหมดหรือบางคอลัมน์ ซึ่งอาจเกิดจากการบันทึกข้อมูลซ้ำ การรวมไฟล์หลายไฟล์ หรือข้อผิดพลาดในระบบ

4.2.1 การตรวจหาข้อมูลซ้ำ (Detecting Duplicates)

"""
การตรวจหาและนับข้อมูลซ้ำใน DataFrame
"""

# สร้างข้อมูลตัวอย่างที่มีข้อมูลซ้ำ
data = {
    'รหัสพนักงาน': ['E001', 'E002', 'E003', 'E002', 'E004', 'E003'],
    'ชื่อ': ['สมชาย', 'สมหญิง', 'สมศักดิ์', 'สมหญิง', 'สมใจ', 'สมศักดิ์'],
    'แผนก': ['IT', 'HR', 'IT', 'HR', 'Sales', 'IT'],
    'เงินเดือน': [30000, 45000, 32000, 45000, 42000, 32000]
}

df = pd.DataFrame(data)

# ตรวจสอบว่ามีข้อมูลซ้ำหรือไม่ (Boolean Series)
print("แถวที่ซ้ำ (True = ซ้ำ):")
print(df.duplicated())

# นับจำนวนแถวที่ซ้ำ
print(f"\nจำนวนแถวที่ซ้ำ: {df.duplicated().sum()}")

# แสดงเฉพาะแถวที่ซ้ำ
print("\nข้อมูลที่ซ้ำ:")
print(df[df.duplicated()])

# ตรวจสอบข้อมูลซ้ำตามคอลัมน์เฉพาะ
print("\nข้อมูลซ้ำตามรหัสพนักงาน:")
print(df[df.duplicated(subset=['รหัสพนักงาน'])])

ผลลัพธ์:

แถวที่ซ้ำ (True = ซ้ำ):
0    False
1    False
2    False
3     True  # แถวที่ 3 ซ้ำกับแถวที่ 1
4    False
5     True  # แถวที่ 5 ซ้ำกับแถวที่ 2
dtype: bool

จำนวนแถวที่ซ้ำ: 2

ข้อมูลที่ซ้ำ:
  รหัสพนักงาน      ชื่อ แผนก  เงินเดือน
3     E002  สมหญิง   HR  45000
5     E003 สมศักดิ์   IT  32000

4.2.2 การลบข้อมูลซ้ำ (Removing Duplicates)

"""
การลบข้อมูลซ้ำออกจาก DataFrame
มีตัวเลือกในการเก็บข้อมูลตัวแรกหรือตัวสุดท้าย
"""

# ลบข้อมูลซ้ำ เก็บตัวแรก (default)
df_no_dup = df.drop_duplicates()

# ลบข้อมูลซ้ำ เก็บตัวสุดท้าย
df_keep_last = df.drop_duplicates(keep='last')

# ลบข้อมูลซ้ำทั้งหมด (ไม่เก็บเลย)
df_drop_all = df.drop_duplicates(keep=False)

# ลบข้อมูลซ้ำตามคอลัมน์เฉพาะ
df_no_dup_id = df.drop_duplicates(subset=['รหัสพนักงาน'])

# ลบข้อมูลซ้ำและรีเซ็ต index
df_clean = df.drop_duplicates().reset_index(drop=True)

print("ข้อมูลหลังลบข้อมูลซ้ำ:")
print(df_clean)
print(f"\nจำนวนแถว: เดิม {len(df)} -> หลังลบซ้ำ {len(df_clean)}")

ตารางเปรียบเทียบพารามิเตอร์ keep:

พารามิเตอร์ การทำงาน ผลลัพธ์ ใช้เมื่อ
keep='first' (default) เก็บข้อมูลตัวแรก ลบตัวที่ซ้ำทั้งหมด เก็บ index ที่เล็กที่สุด ต้องการข้อมูลแรกสุด
keep='last' เก็บข้อมูลตัวสุดท้าย ลบตัวก่อนหน้า เก็บ index ที่ใหญ่ที่สุด ต้องการข้อมูลล่าสุด
keep=False ลบข้อมูลซ้ำทั้งหมด ไม่เหลือข้อมูลที่ซ้ำเลย ต้องการเฉพาะข้อมูลที่ไม่ซ้ำ

4.2.3 การจัดการข้อมูลซ้ำแบบซับซ้อน

"""
การจัดการข้อมูลซ้ำที่ซับซ้อนกว่า
- หาข้อมูลที่ซ้ำบางส่วน (Partial Duplicates)
- รวมข้อมูลซ้ำแทนการลบ
"""

# หาข้อมูลที่ชื่อซ้ำกัน แต่ข้อมูลอื่นต่าง
duplicated_names = df[df.duplicated(subset=['ชื่อ'], keep=False)]
print("พนักงานที่มีชื่อซ้ำกัน:")
print(duplicated_names.sort_values('ชื่อ'))

# ตัวอย่างการรวมข้อมูลซ้ำแทนการลบ
# เช่น เก็บค่าเงินเดือนที่สูงกว่า
df_merged = df.sort_values('เงินเดือน', ascending=False).drop_duplicates(
    subset=['รหัสพนักงาน'], 
    keep='first'
)

print("\nรวมข้อมูลซ้ำ (เก็บเงินเดือนสูงสุด):")
print(df_merged)
stateDiagram-v2
    [*] --> CheckDuplicates: ตรวจสอบข้อมูล
    
    CheckDuplicates --> NoDuplicates: ไม่มีข้อมูลซ้ำ
    CheckDuplicates --> HasDuplicates: มีข้อมูลซ้ำ
    
    HasDuplicates --> AnalyzePattern: วิเคราะห์รูปแบบ
    
    AnalyzePattern --> FullDuplicate: ซ้ำทุกคอลัมน์
    AnalyzePattern --> PartialDuplicate: ซ้ำบางคอลัมน์
    
    FullDuplicate --> DropDuplicates: ลบข้อมูลซ้ำ
drop_duplicates() PartialDuplicate --> DecideStrategy: เลือกกลยุทธ์ DecideStrategy --> KeepFirst: เก็บตัวแรก DecideStrategy --> KeepLast: เก็บตัวสุดท้าย DecideStrategy --> Merge: รวมข้อมูล DropDuplicates --> [*] KeepFirst --> [*] KeepLast --> [*] Merge --> [*] NoDuplicates --> [*] note right of HasDuplicates ตรวจสอบด้วย duplicated() end note note right of Merge ใช้ sort + drop_duplicates หรือ groupby end note

4.3 การเปลี่ยนประเภทข้อมูล (Data Type Conversion)

ประเภทข้อมูล (Data Types) ที่ถูกต้องมีความสำคัญต่อการวิเคราะห์ข้อมูล ข้อมูลที่มีประเภทผิดอาจทำให้ไม่สามารถคำนวณได้ หรือให้ผลลัพธ์ที่ผิดพลาด

4.3.1 การตรวจสอบประเภทข้อมูล (Checking Data Types)

"""
การตรวจสอบและแสดงประเภทข้อมูลของแต่ละคอลัมน์
"""

# สร้างข้อมูลตัวอย่าง
data = {
    'รหัสพนักงาน': ['E001', 'E002', 'E003', 'E004'],
    'อายุ': ['25', '32', '28', '35'],  # ควรเป็น int แต่เป็น string
    'เงินเดือน': ['30000.50', '45000', '32000.75', '38000'],  # ควรเป็น float
    'วันที่เริ่มงาน': ['2020-01-15', '2019-03-20', '2021-06-10', '2018-11-05'],
    'สถานะทำงาน': ['1', '1', '0', '1']  # 1=ทำงาน, 0=ลาออก (ควรเป็น boolean)
}

df = pd.DataFrame(data)

# ตรวจสอบประเภทข้อมูล
print("ประเภทข้อมูลของแต่ละคอลัมน์:")
print(df.dtypes)
print("\n")

# แสดงข้อมูลโดยละเอียด
print("ข้อมูลโดยละเอียด:")
print(df.info())

ผลลัพธ์:

ประเภทข้อมูลของแต่ละคอลัมน์:
รหัสพนักงาน      object
อายุ            object
เงินเดือน        object
วันที่เริ่มงาน    object
สถานะทำงาน      object
dtype: object

ข้อมูลโดยละเอียด:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4 entries, 0 to 3
Data columns (total 5 columns):
 #   Column       Non-Null Count  Dtype 
---  ------       --------------  ----- 
 0   รหัสพนักงาน   4 non-null      object
 1   อายุ          4 non-null      object
 2   เงินเดือน      4 non-null      object
 3   วันที่เริ่มงาน  4 non-null      object
 4   สถานะทำงาน    4 non-null      object
dtypes: object(5)
memory usage: 292.0+ bytes

ประเภทข้อมูลหลักใน Pandas:

ประเภท Pandas ประเภท Python คำอธิบาย ตัวอย่าง
int64 int จำนวนเต็ม 64 บิต 1, 100, -50
float64 float จำนวนทศนิยม 64 บิต 3.14, -0.5, 100.0
object str ข้อความหรือข้อมูลผสม 'สวัสดี', 'E001'
bool bool ค่าความจริง True, False
datetime64 datetime วันที่และเวลา 2024-01-15 14:30:00
category - ข้อมูลหมวดหมู่ 'ชาย', 'หญิง'

4.3.2 การแปลงประเภทข้อมูล (Converting Data Types)

"""
การแปลงประเภทข้อมูลด้วย astype() และฟังก์ชันเฉพาะทาง
"""

# 1. แปลง String เป็น Integer
df['อายุ'] = df['อายุ'].astype(int)

# 2. แปลง String เป็น Float
df['เงินเดือน'] = df['เงินเดือน'].astype(float)

# 3. แปลง String เป็น Datetime
df['วันที่เริ่มงาน'] = pd.to_datetime(df['วันที่เริ่มงาน'])

# 4. แปลง String เป็น Boolean
df['สถานะทำงาน'] = df['สถานะทำงาน'].astype(int).astype(bool)

# 5. แปลงเป็น Category (ประหยัดหน่วยความจำ)
df['รหัสพนักงาน'] = df['รหัสพนักงาน'].astype('category')

print("ประเภทข้อมูลหลังการแปลง:")
print(df.dtypes)
print("\n")
print("ข้อมูลหลังการแปลง:")
print(df)

ผลลัพธ์:

ประเภทข้อมูลหลังการแปลง:
รหัสพนักงาน       category
อายุ               int64
เงินเดือน          float64
วันที่เริ่มงาน    datetime64[ns]
สถานะทำงาน          bool
dtype: object

ข้อมูลหลังการแปลง:
  รหัสพนักงาน  อายุ  เงินเดือน วันที่เริ่มงาน  สถานะทำงาน
0       E001  25  30000.50  2020-01-15      True
1       E002  32  45000.00  2019-03-20      True
2       E003  28  32000.75  2021-06-10     False
3       E004  35  38000.00  2018-11-05      True

4.3.3 การจัดการข้อผิดพลาดในการแปลง (Error Handling)

"""
การจัดการกรณีที่ข้อมูลไม่สามารถแปลงได้
ใช้ errors='coerce' เพื่อแปลงค่าที่ผิดเป็น NaN
"""

# ข้อมูลที่มีค่าผิดพลาด
data = {
    'ตัวเลข': ['10', '20', 'ไม่ใช่ตัวเลข', '30', 'abc']
}
df_error = pd.DataFrame(data)

# แปลงโดยข้ามข้อผิดพลาด (แปลงค่าที่ผิดเป็น NaN)
df_error['ตัวเลข_int'] = pd.to_numeric(df_error['ตัวเลข'], errors='coerce')

print("ข้อมูลหลังการแปลงพร้อมจัดการ error:")
print(df_error)

# ตัวอย่างการแปลง Datetime พร้อม error handling
dates = pd.Series(['2024-01-15', '2024-13-40', '2024-02-20', 'ไม่ใช่วันที่'])
dates_converted = pd.to_datetime(dates, errors='coerce')

print("\nวันที่หลังแปลง:")
print(dates_converted)

พารามิเตอร์ errors ใน Pandas:

  1. errors='raise' (default): หยุดทำงานและแสดง error ทันทีเมื่อเจอข้อมูลที่แปลงไม่ได้
  2. errors='coerce': แปลงค่าที่ผิดเป็น NaN และทำงานต่อ
  3. errors='ignore': เก็บค่าเดิมไว้สำหรับข้อมูลที่แปลงไม่ได้

4.3.4 การปรับแต่งประเภทข้อมูลเพื่อประหยัดหน่วยความจำ

"""
การเลือกใช้ประเภทข้อมูลที่เหมาะสมเพื่อลดการใช้หน่วยความจำ
สำคัญสำหรับ Dataset ขนาดใหญ่
"""

# สร้างข้อมูลขนาดใหญ่
large_df = pd.DataFrame({
    'id': range(1000000),
    'value': np.random.randint(0, 100, 1000000),
    'category': np.random.choice(['A', 'B', 'C'], 1000000)
})

# ตรวจสอบหน่วยความจำก่อนปรับแต่ง
print("หน่วยความจำก่อนปรับแต่ง:")
print(large_df.memory_usage(deep=True))

# ปรับแต่งประเภทข้อมูล
large_df['id'] = large_df['id'].astype('int32')  # จาก int64 → int32
large_df['value'] = large_df['value'].astype('int8')  # จาก int64 → int8
large_df['category'] = large_df['category'].astype('category')  # จาก object → category

# ตรวจสอบหน่วยความจำหลังปรับแต่ง
print("\nหน่วยความจำหลังปรับแต่ง:")
print(large_df.memory_usage(deep=True))

# คำนวณเปอร์เซ็นต์ที่ประหยัดได้
memory_before = large_df.memory_usage(deep=True).sum() / (1024**2)  # MB

ตารางเปรียบเทียบขนาดของประเภทข้อมูล:

ประเภทข้อมูล ขนาด (bytes) ช่วงค่า เหมาะสำหรับ
int8 1 -128 ถึง 127 ตัวเลขเล็กๆ
int16 2 -32,768 ถึง 32,767 ตัวเลขขนาดกลาง
int32 4 -2.1B ถึง 2.1B ID, รหัส
int64 8 -9.2E18 ถึง 9.2E18 ตัวเลขใหญ่มาก
float32 4 ±3.4E38 ทศนิยมความแม่นยำต่ำ
float64 8 ±1.7E308 ทศนิยมความแม่นยำสูง
category variable - ข้อความที่ซ้ำๆ
graph TB
    A["ข้อมูลดิบ
(Raw Data)"] A --> B{ตรวจสอบ
ประเภทข้อมูล} B -->|object| C["ต้องแปลง?"] B -->|int64/float64| D["ต้องลดขนาด?"] B -->|datetime| E["ถูกต้องแล้ว"] C -->|ใช่| F["เลือกประเภทเป้าหมาย"] C -->|ไม่| E D -->|ใช่| G["เลือกขนาดเล็กลง
(int32, int8)"] D -->|ไม่| E F --> H["Numeric?"] F --> I["Date/Time?"] F --> J["Categorical?"] H --> K["to_numeric()
astype()"] I --> L["to_datetime()"] J --> M["astype('category')"] G --> N["astype()"] K --> O["ข้อมูลสะอาด
(Clean Data)"] L --> O M --> O N --> O E --> O style A fill:#458588,stroke:#076678,stroke-width:2px,color:#fbf1c7 style B fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style C fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style D fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style E fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828 style F fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828 style G fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828 style H fill:#fe8019,stroke:#d65d0e,stroke-width:2px,color:#282828 style I fill:#fe8019,stroke:#d65d0e,stroke-width:2px,color:#282828 style J fill:#fe8019,stroke:#d65d0e,stroke-width:2px,color:#282828 style K fill:#d3869b,stroke:#b16286,stroke-width:2px,color:#282828 style L fill:#d3869b,stroke:#b16286,stroke-width:2px,color:#282828 style M fill:#d3869b,stroke:#b16286,stroke-width:2px,color:#282828 style N fill:#d3869b,stroke:#b16286,stroke-width:2px,color:#282828 style O fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828

4.4 การจัดการชื่อคอลัมน์ (Column Name Management)

ชื่อคอลัมน์ (Column Names) ที่ดีควรมีความสม่ำเสมอ อ่านง่าย และไม่มีอักขระพิเศษที่ทำให้เกิดปัญหา การจัดการชื่อคอลัมน์ที่ดีจะทำให้โค้ดอ่านง่ายและลดข้อผิดพลาด

4.4.1 ปัญหาที่พบบ่อยกับชื่อคอลัมน์

"""
ตัวอย่างปัญหาที่พบบ่อยกับชื่อคอลัมน์
"""

# ข้อมูลที่มีชื่อคอลัมน์ปัญหาต่างๆ
data = {
    'Employee ID': [1, 2, 3],  # มีช่องว่าง
    'Salary($)': [30000, 45000, 32000],  # มีอักขระพิเศษ
    'First Name': ['John', 'Jane', 'Bob'],  # ตัวพิมพ์ใหญ่และมีช่องว่าง
    'DEPARTMENT': ['IT', 'HR', 'Sales'],  # ตัวพิมพ์ใหญ่ทั้งหมด
    'start_date': ['2020-01-15', '2019-03-20', '2021-06-10'],  # snake_case
    'PhoneNumber': ['0812345678', '0898765432', '0856789012']  # PascalCase
}

df = pd.DataFrame(data)

print("ชื่อคอลัมน์เดิม:")
print(df.columns.tolist())

# ปัญหา 1: ไม่สามารถเข้าถึงด้วย dot notation
# df.Employee ID  # จะเกิด SyntaxError

# ปัญหา 2: ต้องใช้ bracket notation เสมอ
print(df['Employee ID'])  # ต้องใช้แบบนี้

4.4.2 การเปลี่ยนชื่อคอลัมน์ (Renaming Columns)

"""
วิธีการเปลี่ยนชื่อคอลัมน์ต่างๆ
"""

# วิธีที่ 1: เปลี่ยนชื่อแบบระบุเฉพาะคอลัมน์
df_renamed = df.rename(columns={
    'Employee ID': 'employee_id',
    'Salary($)': 'salary',
    'First Name': 'first_name'
})

# วิธีที่ 2: เปลี่ยนชื่อทั้งหมดพร้อมกัน
df.columns = ['emp_id', 'salary', 'first_name', 'department', 'start_date', 'phone']

# วิธีที่ 3: ใช้ฟังก์ชันกับทุกชื่อคอลัมน์
# แปลงเป็นตัวพิมพ์เล็กทั้งหมด
df.columns = df.columns.str.lower()

# แทนที่ช่องว่างด้วย underscore
df.columns = df.columns.str.replace(' ', '_')

# ลบอักขระพิเศษ
df.columns = df.columns.str.replace(r'[^\w\s]', '', regex=True)

print("ชื่อคอลัมน์หลังปรับปรุง:")
print(df.columns.tolist())

4.4.3 การทำให้ชื่อคอลัมน์เป็นมาตรฐาน (Standardizing Column Names)

"""
สร้างฟังก์ชันเพื่อทำให้ชื่อคอลัมน์เป็นมาตรฐาน
"""

def standardize_column_names(df):
    """
    ทำให้ชื่อคอลัมน์เป็นมาตรฐาน:
    - แปลงเป็นตัวพิมพ์เล็กทั้งหมด
    - แทนที่ช่องว่างด้วย underscore
    - ลบอักขระพิเศษ
    - ตัดช่องว่างหน้า-หลัง
    
    Parameters:
    -----------
    df : pandas.DataFrame
        DataFrame ที่ต้องการปรับชื่อคอลัมน์
    
    Returns:
    --------
    pandas.DataFrame
        DataFrame ที่มีชื่อคอลัมน์มาตรฐาน
    """
    # ทำสำเนาเพื่อไม่ให้กระทบ DataFrame เดิม
    df_clean = df.copy()
    
    # ขั้นตอนการทำความสะอาด
    df_clean.columns = (
        df_clean.columns
        .str.strip()  # ตัดช่องว่างหน้า-หลัง
        .str.lower()  # แปลงเป็นตัวพิมพ์เล็ก
        .str.replace(' ', '_', regex=False)  # แทนที่ช่องว่าง
        .str.replace(r'[^\w]', '', regex=True)  # ลบอักขระพิเศษ
        .str.replace(r'_+', '_', regex=True)  # รวม underscore ติดกันเป็นอันเดียว
    )
    
    return df_clean

# ทดสอบฟังก์ชัน
df_standard = standardize_column_names(df)
print("ชื่อคอลัมน์มาตรฐาน:")
print(df_standard.columns.tolist())

4.4.4 การจัดการชื่อคอลัมน์ภาษาไทย

"""
เทคนิคสำหรับการทำงานกับชื่อคอลัมน์ภาษาไทย
"""

# ข้อมูลภาษาไทย
data_th = {
    'รหัสพนักงาน': ['E001', 'E002', 'E003'],
    'ชื่อ-นามสกุล': ['สมชาย ใจดี', 'สมหญิง รักเรียน', 'สมศักดิ์ ทำงาน'],
    'เงินเดือน (บาท)': [30000, 45000, 32000],
    'แผนก/ฝ่าย': ['IT', 'HR', 'Sales']
}

df_th = pd.DataFrame(data_th)

# วิธีที่ 1: สร้าง mapping ภาษาไทย-อังกฤษ
column_mapping = {
    'รหัสพนักงาน': 'employee_id',
    'ชื่อ-นามสกุล': 'full_name',
    'เงินเดือน (บาท)': 'salary_thb',
    'แผนก/ฝ่าย': 'department'
}

df_en = df_th.rename(columns=column_mapping)

# วิธีที่ 2: เก็บชื่อภาษาไทยไว้ใน metadata
df_en.attrs['thai_columns'] = column_mapping

print("ชื่อคอลัมน์ภาษาอังกฤษ:")
print(df_en.columns.tolist())
print("\nMapping ภาษาไทย:")
print(df_en.attrs['thai_columns'])

# ฟังก์ชันช่วยในการแปลงกลับเป็นภาษาไทย
def to_thai_columns(df):
    """แปลงชื่อคอลัมน์กลับเป็นภาษาไทย"""
    if 'thai_columns' in df.attrs:
        reverse_mapping = {v: k for k, v in df.attrs['thai_columns'].items()}
        return df.rename(columns=reverse_mapping)
    return df

df_back_to_thai = to_thai_columns(df_en)
print("\nแปลงกลับเป็นภาษาไทย:")
print(df_back_to_thai.columns.tolist())

Best Practices สำหรับชื่อคอลัมน์:

  1. ใช้ตัวพิมพ์เล็กทั้งหมด (lowercase)
  2. ใช้ underscore แทนช่องว่าง (snake_case)
  3. หลีกเลี่ยงอักขระพิเศษ ($, %, @, etc.)
  4. ชื่อสั้น กระชับ แต่มีความหมาย
  5. สม่ำเสมอทั้ง DataFrame
flowchart LR
    A["ชื่อคอลัมน์ไม่มาตรฐาน"]
    
    A --> B["Employee ID"]
    A --> C["Salary($)"]
    A --> D["First Name"]
    A --> E["DEPARTMENT"]
    
    B --> F["strip()"]
    C --> F
    D --> F
    E --> F
    
    F --> G["lower()"]
    G --> H["replace(' ', '_')"]
    H --> I["remove special chars"]
    
    I --> J["employee_id"]
    I --> K["salary"]
    I --> L["first_name"]
    I --> M["department"]
    
    J --> N["ชื่อคอลัมน์มาตรฐาน"]
    K --> N
    L --> N
    M --> N
    
    style A fill:#fb4934,stroke:#cc241d,stroke-width:2px,color:#fbf1c7
    style B fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style C fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style D fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style E fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style F fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style G fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style H fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style I fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style J fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828
    style K fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828
    style L fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828
    style M fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828
    style N fill:#d3869b,stroke:#b16286,stroke-width:2px,color:#282828

4.5 การจัดการค่าผิดปกติ (Outlier Detection & Handling)

ค่าผิดปกติ (Outliers) คือข้อมูลที่มีค่าแตกต่างจากข้อมูลส่วนใหญ่อย่างมีนัยสำคัญ อาจเกิดจากข้อผิดพลาดในการบันทึก ข้อผิดพลาดของเครื่องมือวัด หรือเป็นข้อมูลที่พิเศษจริงๆ

4.5.1 การตรวจหาค่าผิดปกติ (Detecting Outliers)

วิธีที่ 1: IQR Method (Interquartile Range)

วิธี IQR เป็นวิธีที่นิยมใช้กันมากที่สุด โดยใช้ควอไทล์ (Quartiles) ในการหาค่าผิดปกติ

"""
การตรวจหาค่าผิดปกติด้วย IQR Method
วิธีนี้ทนทานต่อค่าผิดปกติมากกว่า mean และ standard deviation
"""

import numpy as np
import pandas as pd

# สร้างข้อมูลตัวอย่างที่มีค่าผิดปกติ
data = {
    'พนักงาน': ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
    'เงินเดือน': [30000, 32000, 31000, 35000, 33000, 150000, 31500, 34000, 32500, 33500]
    # พนักงาน F มีเงินเดือน 150000 ซึ่งผิดปกติ
}

df = pd.DataFrame(data)

def detect_outliers_iqr(df, column):
    """
    ตรวจหาค่าผิดปกติด้วย IQR Method
    
    Parameters:
    -----------
    df : pandas.DataFrame
        DataFrame ที่ต้องการตรวจสอบ
    column : str
        ชื่อคอลัมน์ที่ต้องการตรวจสอบ
    
    Returns:
    --------
    tuple : (lower_bound, upper_bound, outliers)
        ขอบเขตล่าง, ขอบเขตบน, และ DataFrame ของค่าผิดปกติ
    """
    # คำนวณ Q1 (25th percentile) และ Q3 (75th percentile)
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    
    # คำนวณ IQR
    IQR = Q3 - Q1
    
    # กำหนดขอบเขต (ใช้ค่า 1.5 เป็นมาตรฐาน)
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    
    # หาค่าผิดปกติ
    outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
    
    print(f"Q1 (25%): {Q1:,.2f}")
    print(f"Q3 (75%): {Q3:,.2f}")
    print(f"IQR: {IQR:,.2f}")
    print(f"ขอบเขตล่าง: {lower_bound:,.2f}")
    print(f"ขอบเขตบน: {upper_bound:,.2f}")
    
    return lower_bound, upper_bound, outliers

# ทดสอบฟังก์ชัน
lower, upper, outliers = detect_outliers_iqr(df, 'เงินเดือน')
print(f"\nพบค่าผิดปกติ {len(outliers)} รายการ:")
print(outliers)

สูตร IQR Method:

IQR = Q3 Q1 Lower Bound = Q1 1.5 × IQR Upper Bound = Q3 + 1.5 × IQR

โดยที่:

วิธีที่ 2: Z-Score Method

"""
การตรวจหาค่าผิดปกติด้วย Z-Score
เหมาะกับข้อมูลที่มีการกระจายแบบปกติ (Normal Distribution)
"""

def detect_outliers_zscore(df, column, threshold=3):
    """
    ตรวจหาค่าผิดปกติด้วย Z-Score Method
    
    Parameters:
    -----------
    df : pandas.DataFrame
        DataFrame ที่ต้องการตรวจสอบ
    column : str
        ชื่อคอลัมน์ที่ต้องการตรวจสอบ
    threshold : float, default=3
        ค่า Z-Score ที่ถือว่าเป็นค่าผิดปกติ
    
    Returns:
    --------
    pandas.DataFrame
        DataFrame ของค่าผิดปกติ
    """
    # คำนวณ Z-Score
    mean = df[column].mean()
    std = df[column].std()
    df['z_score'] = (df[column] - mean) / std
    
    # หาค่าผิดปกติ
    outliers = df[abs(df['z_score']) > threshold]
    
    print(f"ค่าเฉลี่ย: {mean:,.2f}")
    print(f"ส่วนเบี่ยงเบนมาตรฐาน: {std:,.2f}")
    print(f"Threshold: ±{threshold}")
    
    return outliers

# ทดสอบฟังก์ชัน
outliers_z = detect_outliers_zscore(df.copy(), 'เงินเดือน')
print(f"\nพบค่าผิดปกติ {len(outliers_z)} รายการ:")
print(outliers_z[['พนักงาน', 'เงินเดือน', 'z_score']])

สูตร Z-Score:

z = x μ σ

โดยที่:

4.5.2 การจัดการค่าผิดปกติ (Handling Outliers)

"""
วิธีการจัดการค่าผิดปกติต่างๆ
เลือกใช้ตามบริบทของข้อมูล
"""

# วิธีที่ 1: ลบค่าผิดปกติออก (Removal)
def remove_outliers(df, column):
    """ลบค่าผิดปกติออกจาก DataFrame"""
    lower, upper, _ = detect_outliers_iqr(df, column)
    df_clean = df[(df[column] >= lower) & (df[column] <= upper)]
    return df_clean

# วิธีที่ 2: แทนที่ด้วยขอบเขต (Capping/Winsorizing)
def cap_outliers(df, column):
    """แทนที่ค่าผิดปกติด้วยขอบเขตบนและล่าง"""
    lower, upper, _ = detect_outliers_iqr(df, column)
    df_capped = df.copy()
    df_capped[column] = df_capped[column].clip(lower=lower, upper=upper)
    return df_capped

# วิธีที่ 3: แทนที่ด้วยค่าสถิติ (Imputation)
def impute_outliers(df, column, method='median'):
    """แทนที่ค่าผิดปกติด้วยค่าสถิติ"""
    lower, upper, _ = detect_outliers_iqr(df, column)
    df_imputed = df.copy()
    
    if method == 'median':
        fill_value = df[column].median()
    elif method == 'mean':
        fill_value = df[column].mean()
    else:
        raise ValueError("method ต้องเป็น 'median' หรือ 'mean'")
    
    # แทนที่ค่าผิดปกติ
    mask = (df[column] < lower) | (df[column] > upper)
    df_imputed.loc[mask, column] = fill_value
    
    return df_imputed

# วิธีที่ 4: Log Transformation (สำหรับข้อมูลที่เบ้ขวา)
def transform_log(df, column):
    """แปลงข้อมูลด้วย log เพื่อลดผลกระทบของ outliers"""
    df_transformed = df.copy()
    df_transformed[f'{column}_log'] = np.log1p(df[column])  # log1p = log(1 + x)
    return df_transformed

# ทดสอบแต่ละวิธี
print("ข้อมูลเดิม:")
print(df)

print("\n1. ลบค่าผิดปกติ:")
df_removed = remove_outliers(df.copy(), 'เงินเดือน')
print(df_removed)

print("\n2. แทนที่ด้วยขอบเขต:")
df_capped = cap_outliers(df.copy(), 'เงินเดือน')
print(df_capped)

print("\n3. แทนที่ด้วย Median:")
df_imputed = impute_outliers(df.copy(), 'เงินเดือน', method='median')
print(df_imputed)

print("\n4. Log Transformation:")
df_log = transform_log(df.copy(), 'เงินเดือน')
print(df_log[['พนักงาน', 'เงินเดือน', 'เงินเดือน_log']])

ตารางเปรียบเทียบวิธีจัดการค่าผิดปกติ:

วิธีการ ข้อดี ข้อเสีย เหมาะกับ
Removal เรียบง่าย ไม่บิดเบือนข้อมูล สูญเสียข้อมูล อาจลดขนาดตัวอย่างมาก ข้อมูลมีจำนวนมาก outliers น้อย
Capping รักษาจำนวนข้อมูล ลดผลกระทบ เปลี่ยนค่าจริง อาจยังคงมีอคติ ต้องการข้อมูลทุกตัว
Imputation รักษาจำนวนข้อมูล ปลอดภัย อาจบิดเบือนการกระจาย outliers เป็นข้อผิดพลาด
Transformation รักษาข้อมูล ลดผลกระทบ ยากต่อการตีความ ข้อมูลเบ้ (skewed)
Keep รักษาข้อมูลจริง อาจทำให้โมเดลผิดพลาด outliers มีความหมาย
graph TD
    A["ตรวจพบ Outliers"]
    A --> B{ต้นเหตุของ Outliers?}
    
    B -->|"Data Entry Error"| C["แก้ไขข้อมูล
หรือลบออก"] B -->|"Measurement Error"| D["ลบออก
หรือ Impute"] B -->|"Natural Variation"| E["เก็บไว้
หรือ Transform"] B -->|"ไม่แน่ใจ"| F{ผลกระทบต่อการวิเคราะห์?} F -->|"มีผลมาก"| G["Capping
หรือ Transformation"] F -->|"มีผลน้อย"| H["เก็บไว้"] C --> I["ข้อมูลสะอาด"] D --> I E --> I G --> I H --> I style A fill:#fb4934,stroke:#cc241d,stroke-width:2px,color:#fbf1c7 style B fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style C fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828 style D fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828 style E fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828 style F fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828 style G fill:#fe8019,stroke:#d65d0e,stroke-width:2px,color:#282828 style H fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828 style I fill:#d3869b,stroke:#b16286,stroke-width:2px,color:#282828

4.6 การตรวจสอบความถูกต้องของข้อมูล (Data Validation)

การตรวจสอบความถูกต้องของข้อมูล (Data Validation) เป็นกระบวนการตรวจสอบว่าข้อมูลเป็นไปตามกฎเกณฑ์และข้อกำหนดที่ตั้งไว้หรือไม่

4.6.1 การตรวจสอบข้อจำกัดของข้อมูล (Data Constraints)

"""
การตรวจสอบว่าข้อมูลเป็นไปตามข้อจำกัดที่กำหนด
"""

# สร้างข้อมูลตัวอย่าง
data = {
    'รหัสพนักงาน': ['E001', 'E002', 'E999', 'E004', 'ABC'],  # ABC ไม่ถูกต้อง
    'อายุ': [25, 150, 28, -5, 35],  # 150 และ -5 ไม่สมเหตุสมผล
    'เงินเดือน': [30000, 45000, -10000, 32000, 35000],  # -10000 ไม่สมเหตุสมผล
    'อีเมล': ['john@email.com', 'invalid-email', 'jane@email.com', 'bob@email.com', 'alice@email.com']
}

df = pd.DataFrame(data)

def validate_employee_data(df):
    """
    ตรวจสอบความถูกต้องของข้อมูลพนักงาน
    
    Returns:
    --------
    dict : รายงานผลการตรวจสอบ
    """
    issues = []
    
    # 1. ตรวจสอบรูปแบบรหัสพนักงาน (ต้องขึ้นต้นด้วย E และตามด้วยตัวเลข 3 หลัก)
    pattern = r'^E\d{3}$'
    invalid_ids = df[~df['รหัสพนักงาน'].str.match(pattern, na=False)]
    if not invalid_ids.empty:
        issues.append({
            'ประเภท': 'รูปแบบรหัสไม่ถูกต้อง',
            'จำนวน': len(invalid_ids),
            'ข้อมูล': invalid_ids['รหัสพนักงาน'].tolist()
        })
    
    # 2. ตรวจสอบช่วงอายุ (18-70 ปี)
    invalid_ages = df[(df['อายุ'] < 18) | (df['อายุ'] > 70)]
    if not invalid_ages.empty:
        issues.append({
            'ประเภท': 'อายุไม่อยู่ในช่วงที่กำหนด',
            'จำนวน': len(invalid_ages),
            'ข้อมูล': invalid_ages[['รหัสพนักงาน', 'อายุ']].to_dict('records')
        })
    
    # 3. ตรวจสอบเงินเดือน (ต้องเป็นบวก)
    invalid_salaries = df[df['เงินเดือน'] <= 0]
    if not invalid_salaries.empty:
        issues.append({
            'ประเภท': 'เงินเดือนไม่ถูกต้อง',
            'จำนวน': len(invalid_salaries),
            'ข้อมูล': invalid_salaries[['รหัสพนักงาน', 'เงินเดือน']].to_dict('records')
        })
    
    # 4. ตรวจสอบรูปแบบอีเมล
    email_pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
    invalid_emails = df[~df['อีเมล'].str.match(email_pattern, na=False)]
    if not invalid_emails.empty:
        issues.append({
            'ประเภท': 'รูปแบบอีเมลไม่ถูกต้อง',
            'จำนวน': len(invalid_emails),
            'ข้อมูล': invalid_emails[['รหัสพนักงาน', 'อีเมล']].to_dict('records')
        })
    
    return issues

# ทดสอบการตรวจสอบ
validation_issues = validate_employee_data(df)

print("รายงานผลการตรวจสอบความถูกต้อง:")
print("="*60)
if not validation_issues:
    print("✓ ข้อมูลทั้งหมดถูกต้อง")
else:
    print(f"✗ พบปัญหา {len(validation_issues)} ประเภท:\n")
    for i, issue in enumerate(validation_issues, 1):
        print(f"{i}. {issue['ประเภท']}")
        print(f"   จำนวน: {issue['จำนวน']} รายการ")
        print(f"   รายละเอียด: {issue['ข้อมูล']}\n")

4.6.2 การสร้าง Validation Rules แบบ Reusable

"""
สร้างระบบ Validation ที่ใช้ซ้ำได้ง่าย
"""

class DataValidator:
    """
    คลาสสำหรับตรวจสอบความถูกต้องของข้อมูล
    สามารถเพิ่มกฎการตรวจสอบต่างๆ ได้
    """
    
    def __init__(self, df):
        """
        Parameters:
        -----------
        df : pandas.DataFrame
            DataFrame ที่ต้องการตรวจสอบ
        """
        self.df = df
        self.errors = []
    
    def check_not_null(self, columns):
        """ตรวจสอบว่าคอลัมน์ต้องไม่มีค่า null"""
        for col in columns:
            null_count = self.df[col].isnull().sum()
            if null_count > 0:
                self.errors.append(
                    f"คอลัมน์ '{col}' มีค่า null {null_count} รายการ"
                )
        return self
    
    def check_unique(self, columns):
        """ตรวจสอบว่าคอลัมน์ต้องมีค่าไม่ซ้ำ"""
        for col in columns:
            dup_count = self.df[col].duplicated().sum()
            if dup_count > 0:
                self.errors.append(
                    f"คอลัมน์ '{col}' มีค่าซ้ำ {dup_count} รายการ"
                )
        return self
    
    def check_range(self, column, min_val=None, max_val=None):
        """ตรวจสอบว่าค่าอยู่ในช่วงที่กำหนด"""
        if min_val is not None:
            below_min = (self.df[column] < min_val).sum()
            if below_min > 0:
                self.errors.append(
                    f"คอลัมน์ '{column}' มีค่าต่ำกว่า {min_val} จำนวน {below_min} รายการ"
                )
        
        if max_val is not None:
            above_max = (self.df[column] > max_val).sum()
            if above_max > 0:
                self.errors.append(
                    f"คอลัมน์ '{column}' มีค่าสูงกว่า {max_val} จำนวน {above_max} รายการ"
                )
        return self
    
    def check_pattern(self, column, pattern, description=""):
        """ตรวจสอบว่าค่าตรงกับ regex pattern"""
        invalid = ~self.df[column].str.match(pattern, na=False)
        invalid_count = invalid.sum()
        if invalid_count > 0:
            self.errors.append(
                f"คอลัมน์ '{column}' มีรูปแบบไม่ถูกต้อง {invalid_count} รายการ {description}"
            )
        return self
    
    def check_in_list(self, column, valid_values):
        """ตรวจสอบว่าค่าอยู่ในรายการที่กำหนด"""
        invalid = ~self.df[column].isin(valid_values)
        invalid_count = invalid.sum()
        if invalid_count > 0:
            self.errors.append(
                f"คอลัมน์ '{column}' มีค่าที่ไม่อยู่ในรายการที่กำหนด {invalid_count} รายการ"
            )
        return self
    
    def get_report(self):
        """แสดงรายงานผลการตรวจสอบ"""
        if not self.errors:
            return "✓ ข้อมูลทั้งหมดถูกต้อง"
        else:
            report = f"✗ พบปัญหา {len(self.errors)} รายการ:\n"
            for i, error in enumerate(self.errors, 1):
                report += f"  {i}. {error}\n"
            return report

# ตัวอย่างการใช้งาน
validator = DataValidator(df)

# ใช้ method chaining เพื่อตรวจสอบหลายเงื่อนไข
report = (validator
    .check_not_null(['รหัสพนักงาน', 'อายุ'])
    .check_unique(['รหัสพนักงาน'])
    .check_range('อายุ', min_val=18, max_val=70)
    .check_range('เงินเดือน', min_val=0)
    .check_pattern('รหัสพนักงาน', r'^E\d{3}$', '(รูปแบบ: E###)')
    .check_pattern('อีเมล', r'^[\w\.-]+@[\w\.-]+\.\w+$', '(รูปแบบ: xxx@xxx.xxx)')
    .get_report()
)

print(report)
flowchart TB
    A["เริ่มต้น Data Validation"]
    
    A --> B["ตรวจสอบ NULL Values"]
    A --> C["ตรวจสอบ Duplicates"]
    A --> D["ตรวจสอบ Data Types"]
    A --> E["ตรวจสอบ Ranges"]
    A --> F["ตรวจสอบ Patterns"]
    A --> G["ตรวจสอบ Business Rules"]
    
    B --> H{มี NULL?}
    C --> I{มีซ้ำ?}
    D --> J{Type ถูกต้อง?}
    E --> K{อยู่ในช่วง?}
    F --> L{ตรงรูปแบบ?}
    G --> M{เป็นไปตามกฎ?}
    
    H -->|ใช่| N["บันทึก Error"]
    I -->|ใช่| N
    J -->|ไม่| N
    K -->|ไม่| N
    L -->|ไม่| N
    M -->|ไม่| N
    
    H -->|ไม่| O["ผ่าน"]
    I -->|ไม่| O
    J -->|ใช่| O
    K -->|ใช่| O
    L -->|ใช่| O
    M -->|ใช่| O
    
    N --> P["สร้างรายงาน Validation"]
    O --> P
    
    P --> Q{มี Error?}
    Q -->|ใช่| R["แก้ไขข้อมูล"]
    Q -->|ไม่| S["ข้อมูลพร้อมใช้งาน"]
    
    R --> A
    
    style A fill:#458588,stroke:#076678,stroke-width:2px,color:#fbf1c7
    style B fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style C fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style D fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style E fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style F fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style G fill:#83a598,stroke:#458588,stroke-width:2px,color:#282828
    style H fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style I fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style J fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style K fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style L fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style M fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style N fill:#fb4934,stroke:#cc241d,stroke-width:2px,color:#fbf1c7
    style O fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828
    style P fill:#fe8019,stroke:#d65d0e,stroke-width:2px,color:#282828
    style Q fill:#fabd2f,stroke:#d79921,stroke-width:2px,color:#282828
    style R fill:#d3869b,stroke:#b16286,stroke-width:2px,color:#282828
    style S fill:#b8bb26,stroke:#98971a,stroke-width:2px,color:#282828

สรุป (Summary)

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

สิ่งสำคัญที่ต้องจำ:

  1. ตรวจสอบก่อนเสมอ - ใช้ .info(), .describe(), .isna().sum() เพื่อเข้าใจข้อมูล
  2. จัดการ Missing Values อย่างรอบคอบ - เลือกวิธีที่เหมาะสม (ลบ, แทนที่, หรือประมาณค่า)
  3. ตรวจสอบข้อมูลซ้ำ - ใช้ .duplicated() และ .drop_duplicates()
  4. แปลงประเภทข้อมูลให้ถูกต้อง - ใช้ .astype(), pd.to_datetime(), pd.to_numeric()
  5. ชื่อคอลัมน์ให้เป็นมาตรฐาน - lowercase, snake_case, ไม่มีอักขระพิเศษ
  6. ตรวจจับและจัดการ Outliers - ใช้ IQR หรือ Z-Score Method
  7. ทำ Data Validation - ตรวจสอบว่าข้อมูลเป็นไปตามกฎเกณฑ์

Workflow ที่แนะนำ:

# 1. โหลดและสำรวจข้อมูล
df = pd.read_csv('data.csv')
df.info()
df.describe()

# 2. จัดการข้อมูลที่หายไป
df = df.dropna(thresh=len(df)*0.5, axis=1)  # ลบคอลัมน์ที่หายมากกว่า 50%
df['column'] = df['column'].fillna(df['column'].median())

# 3. ลบข้อมูลซ้ำ
df = df.drop_duplicates()

# 4. แปลงประเภทข้อมูล
df['date'] = pd.to_datetime(df['date'])
df['amount'] = pd.to_numeric(df['amount'], errors='coerce')

# 5. ทำให้ชื่อคอลัมน์เป็นมาตรฐาน
df.columns = df.columns.str.lower().str.replace(' ', '_')

# 6. จัดการ Outliers
# ใช้ IQR method หรือ capping

# 7. Validation
# ตรวจสอบว่าข้อมูลเป็นไปตามกฎที่กำหนด

# 8. บันทึกข้อมูลสะอาด
df.to_csv('cleaned_data.csv', index=False)

เคล็ดลับสำหรับมือใหม่:


เอกสารอ้างอิง (References)

  1. Pandas Official Documentation

  2. "Python for Data Analysis" by Wes McKinney

  3. "Data Cleaning with Python"

  4. Kaggle Learn - Data Cleaning Course

  5. Stack Overflow - Pandas Tag

  6. Real Python - Pandas Tutorials


หมายเหตุ: เอกสารนี้เป็นส่วนหนึ่งของชุดเอกสาร "Pandas Complete Guide" ซึ่งครอบคลุมทุกแง่มุมของการทำงานกับ Pandas Library สำหรับการวิเคราะห์ข้อมูลด้วย Python

เวอร์ชัน: 1.0
อัปเดตล่าสุด: 2024
ใช้ได้กับ Pandas เวอร์ชัน: 1.5.x - 2.x