ในโลกความเป็นจริง ข้อมูลมักจะสกปรก (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% ของโปรเจกต์ในการทำความสะอาดข้อมูล ก่อนจะเริ่มวิเคราะห์หรือสร้างโมเดลจริงๆ
ข้อมูลที่หายไป (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
การแทนที่ค่าที่หายไป (Imputation) เป็นวิธีการเติมข้อมูลที่หายไปด้วยค่าที่เหมาะสม มีหลายวิธีให้เลือกใช้:
"""
แทนที่ค่าที่หายไปด้วยค่าคงที่ที่กำหนด
เหมาะสำหรับข้อมูลที่มีความหมายเฉพาะ
"""
# แทนที่ด้วยค่าเดียวทั้ง DataFrame
df_filled = df.fillna(0)
# แทนที่แบบระบุเฉพาะคอลัมน์
df['แผนก'] = df['แผนก'].fillna('ไม่ระบุ')
df['อายุ'] = df['อายุ'].fillna(0)
print("ข้อมูลหลังแทนที่ด้วยค่าคงที่:")
print(df)
"""
แทนที่ด้วยค่าทางสถิติ
- 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):
โดยที่:
"""
การเติมข้อมูลแบบไปข้างหน้า (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: ค่าว่างจะถูกแทนด้วยค่าบรรทัดถัดไป
"""
การประมาณค่าระหว่างจุดข้อมูล
เหมาะสำหรับข้อมูลที่มีความต่อเนื่อง
"""
# 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 | สร้างความต่อเนื่อง | ซับซ้อน | ข้อมูลต่อเนื่อง |
บางครั้งการลบข้อมูลที่หายไป อาจเป็นวิธีที่ดีกว่าการแทนที่ โดยเฉพาะเมื่อ:
"""
การลบแถวหรือคอลัมน์ที่มีข้อมูลหายไป
ต้องพิจารณาอย่างระมัดระวัง เพราะอาจสูญเสียข้อมูลสำคัญ
"""
# ลบแถวที่มีค่า 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
ข้อมูลซ้ำ (Duplicate Data) เกิดขึ้นเมื่อมีแถวที่มีค่าเหมือนกันทั้งหมดหรือบางคอลัมน์ ซึ่งอาจเกิดจากการบันทึกข้อมูลซ้ำ การรวมไฟล์หลายไฟล์ หรือข้อผิดพลาดในระบบ
"""
การตรวจหาและนับข้อมูลซ้ำใน 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
"""
การลบข้อมูลซ้ำออกจาก 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 |
ลบข้อมูลซ้ำทั้งหมด | ไม่เหลือข้อมูลที่ซ้ำเลย | ต้องการเฉพาะข้อมูลที่ไม่ซ้ำ |
"""
การจัดการข้อมูลซ้ำที่ซับซ้อนกว่า
- หาข้อมูลที่ซ้ำบางส่วน (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
ประเภทข้อมูล (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 | - | ข้อมูลหมวดหมู่ | 'ชาย', 'หญิง' |
"""
การแปลงประเภทข้อมูลด้วย 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
"""
การจัดการกรณีที่ข้อมูลไม่สามารถแปลงได้
ใช้ 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:
errors='raise' (default): หยุดทำงานและแสดง error ทันทีเมื่อเจอข้อมูลที่แปลงไม่ได้errors='coerce': แปลงค่าที่ผิดเป็น NaN และทำงานต่อerrors='ignore': เก็บค่าเดิมไว้สำหรับข้อมูลที่แปลงไม่ได้"""
การเลือกใช้ประเภทข้อมูลที่เหมาะสมเพื่อลดการใช้หน่วยความจำ
สำคัญสำหรับ 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
ชื่อคอลัมน์ (Column Names) ที่ดีควรมีความสม่ำเสมอ อ่านง่าย และไม่มีอักขระพิเศษที่ทำให้เกิดปัญหา การจัดการชื่อคอลัมน์ที่ดีจะทำให้โค้ดอ่านง่ายและลดข้อผิดพลาด
"""
ตัวอย่างปัญหาที่พบบ่อยกับชื่อคอลัมน์
"""
# ข้อมูลที่มีชื่อคอลัมน์ปัญหาต่างๆ
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']) # ต้องใช้แบบนี้
"""
วิธีการเปลี่ยนชื่อคอลัมน์ต่างๆ
"""
# วิธีที่ 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())
"""
สร้างฟังก์ชันเพื่อทำให้ชื่อคอลัมน์เป็นมาตรฐาน
"""
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())
"""
เทคนิคสำหรับการทำงานกับชื่อคอลัมน์ภาษาไทย
"""
# ข้อมูลภาษาไทย
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 สำหรับชื่อคอลัมน์:
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
ค่าผิดปกติ (Outliers) คือข้อมูลที่มีค่าแตกต่างจากข้อมูลส่วนใหญ่อย่างมีนัยสำคัญ อาจเกิดจากข้อผิดพลาดในการบันทึก ข้อผิดพลาดของเครื่องมือวัด หรือเป็นข้อมูลที่พิเศษจริงๆ
วิธี 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:
โดยที่:
"""
การตรวจหาค่าผิดปกติด้วย 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:
โดยที่:
"""
วิธีการจัดการค่าผิดปกติต่างๆ
เลือกใช้ตามบริบทของข้อมูล
"""
# วิธีที่ 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
การตรวจสอบความถูกต้องของข้อมูล (Data Validation) เป็นกระบวนการตรวจสอบว่าข้อมูลเป็นไปตามกฎเกณฑ์และข้อกำหนดที่ตั้งไว้หรือไม่
"""
การตรวจสอบว่าข้อมูลเป็นไปตามข้อจำกัดที่กำหนด
"""
# สร้างข้อมูลตัวอย่าง
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")
"""
สร้างระบบ 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
การทำความสะอาดข้อมูล (Data Cleaning) เป็นขั้นตอนที่สำคัญที่สุดในกระบวนการวิเคราะห์ข้อมูล แม้จะใช้เวลามาก แต่ข้อมูลที่สะอาดจะทำให้การวิเคราะห์ถูกต้องและเชื่อถือได้
สิ่งสำคัญที่ต้องจำ:
.info(), .describe(), .isna().sum() เพื่อเข้าใจข้อมูล.duplicated() และ .drop_duplicates().astype(), pd.to_datetime(), pd.to_numeric()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)
เคล็ดลับสำหรับมือใหม่:
.copy() ก่อนทำความสะอาดPandas Official Documentation
"Python for Data Analysis" by Wes McKinney
"Data Cleaning with Python"
Kaggle Learn - Data Cleaning Course
Stack Overflow - Pandas Tag
Real Python - Pandas Tutorials
หมายเหตุ: เอกสารนี้เป็นส่วนหนึ่งของชุดเอกสาร "Pandas Complete Guide" ซึ่งครอบคลุมทุกแง่มุมของการทำงานกับ Pandas Library สำหรับการวิเคราะห์ข้อมูลด้วย Python
เวอร์ชัน: 1.0
อัปเดตล่าสุด: 2024
ใช้ได้กับ Pandas เวอร์ชัน: 1.5.x - 2.x