การเปลี่ยน Data Type ของ Column ใน Pandas DataFrame เป็นงานประจำวันที่สำคัญสำหรับ Data Cleaning, Memory Optimization และการเตรียมข้อมูลสำหรับ Modeling
สร้าง DataFrame ตัวอย่าง
import pandas as pd
import numpy as np
df = pd.DataFrame({
'DayNo': [1, 2, 3, 4, 5, 6, 7],
'Name': ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
'Qty': [2.6, 5, 11.8, 2, 5.6, 0, 0.25],
'Category': ['A', 'B', 'A', 'C', 'B', 'A', 'C']
})
print("Original dtypes:")
print(df.dtypes)
print("\nMemory Usage:")
print(df.memory_usage(deep=True))
วิธีที่ 1: astype() - วิธีมาตรฐานและยืดหยุ่นที่สุด
เปลี่ยน Column เดียว
# Float -> Int (Truncates!)
df['Qty_int'] = df['Qty'].astype('int32')
print(df[['Qty', 'Qty_int']])
# Int -> Float
df['DayNo_float'] = df['DayNo'].astype('float64')
# String -> Category (Memory Efficient สำหรับ Low Cardinality)
df['Category_cat'] = df['Category'].astype('category')
print(f"Category memory: {df['Category'].memory_usage(deep=True)} bytes")
print(f"Category_cat memory: {df['Category_cat'].memory_usage(deep=True)} bytes")
เปลี่ยนหลาย Columns พร้อมกัน (Dictionary Mapping)
# แปลงหลายคอลัมน์พร้อมกัน
df_converted = df.astype({
'DayNo': 'int16',
'Qty': 'float32',
'Name': 'string' # Pandas StringDtype (Nullable)
})
print(df_converted.dtypes)
แปลงทั้ง DataFrame
# ทุก Column -> String
df_str = df.astype(str)
print(df_str.dtypes)
# ทุก Column -> Numeric (Error ถ้าไม่ได้)
try:
df_numeric = df.astype(float)
except ValueError as e:
print(f"Error: {e}") # Name column cannot convert
วิธีที่ 2: pd.to_numeric() - แปลงเป็น Numeric อย่างปลอดภัย
จัดการ Non-numeric Values ได้ด้วยพารามิเตอร์ errors
# สร้าง DataFrame ที่มีข้อมูล 'Dirty'
dirty_df = pd.DataFrame({
'values': ['1', '2', '3.5', 'abc', '4', None, '5.5', '']
})
print("Original:", dirty_df['values'].tolist())
# errors='raise' (Default) - Error ถ้าแปลงไม่ได้
# errors='coerce' - แปลงไม่ได้ -> NaN
# errors='ignore' - คืนค่าเดิม
cleaned = pd.to_numeric(dirty_df['values'], errors='coerce')
print("Coerced:", cleaned.tolist())
# [1.0, 2.0, 3.5, nan, 4.0, nan, 5.5, nan]
# Downcast: ลดขนาด Integer/Float ให้เล็กที่สุดที่รองรับค่าได้
downcasted = pd.to_numeric(dirty_df['values'], errors='coerce', downcast='integer')
print(f"Downcasted dtype: {downcasted.dtype}") # Int64 (Nullable) หรือ int32/int64
วิธีที่ 3: infer_objects() - Soft Conversion
ลองแปลง Object Columns ให้เป็น Type ที่เฉพาะเจาะจงมากขึ้น (Soft Conversion)
# DataFrame ที่สร้างด้วย dtype=object
df_obj = pd.DataFrame({
'A': [1, 2, 3],
'B': ['4', '5', '6'] # String Numbers
}, dtype='object')
print("Before infer_objects:")
print(df_obj.dtypes)
df_inferred = df_obj.infer_objects()
print("\nAfter infer_objects:")
print(df_inferred.dtypes)
# A -> int64, B -> object (ยังคงเป็น String)
วิธีที่ 4: convert_dtypes() - Modern Pandas (1.0+)
แปลงเป็น Nullable Dtypes (Int64, Float64, String, Boolean) อัตโนมัติ
df_nullable = df.convert_dtypes() print(df_nullable.dtypes) # DayNo Int64 # Name string # Qty Float64 # Category string
วิธีที่ 5: DateTime Conversion
df_dates = pd.DataFrame({
'date_str': ['2024-01-15', '2024-02-20', '2024-03-10', 'invalid']
})
# แปลงเป็น Datetime
df_dates['date_parsed'] = pd.to_datetime(df_dates['date_str'], errors='coerce')
print(df_dates)
# Format กำหนดเอง
df_dates['date_custom'] = pd.to_datetime(df_dates['date_str'], format='%Y-%m-%d', errors='coerce')
Memory Optimization: Downcasting
def optimize_dtypes(df):
"""ลด Memory Usage ของ DataFrame"""
df_opt = df.copy()
for col in df_opt.columns:
col_type = df_opt[col].dtype
if col_type == 'object':
# ลองแปลงเป็น Category ถ้า Unique Values น้อย
num_unique = df_opt[col].nunique()
num_total = len(df_opt[col])
if num_unique / num_total < 0.5: # < 50% Unique
df_opt[col] = df_opt[col].astype('category')
elif 'int' in str(col_type):
# Downcast Integer
df_opt[col] = pd.to_numeric(df_opt[col], downcast='integer')
elif 'float' in str(col_type):
# Downcast Float
df_opt[col] = pd.to_numeric(df_opt[col], downcast='float')
return df_opt
# ทดสอบ
large_df = pd.DataFrame({
'id': range(100000),
'category': np.random.choice(['A', 'B', 'C', 'D'], 100000),
'value': np.random.randn(100000),
'flag': np.random.choice([True, False], 100000)
})
print(f"Original Memory: {large_df.memory_usage(deep=True).sum() / 1e6:.2f} MB")
optimized = optimize_dtypes(large_df)
print(f"Optimized Memory: {optimized.memory_usage(deep=True).sum() / 1e6:.2f} MB")
Error Handling: astype() vs to_numeric()
| Scenario | astype() | to_numeric(errors='coerce') |
|---|---|---|
| Valid Number | ✅ Convert | ✅ Convert |
| String Number "123" | ✅ Convert | ✅ Convert |
| Non-numeric "abc" | ❌ ValueError | ✅ NaN |
| Empty String "" | ❌ ValueError | ✅ NaN |
| None/NaN | ✅ NaN (Float) | ✅ NaN |
สรุป
- astype(): วิธีหลัก เปลี่ยน Type ได้ทุกอย่าง ต้องแน่ใจว่า Data Clean
- to_numeric(errors='coerce'): ปลอดภัยสำหรับ Dirty Data, ได้ NaN แทน Error
- convert_dtypes(): Modern Way -> Nullable Types (Int64, String, Boolean)
- Category Type: ประหยัด Memory มากสำหรับ Low Cardinality Columns
- Downcast: ใช้
pd.to_numeric(..., downcast='integer/float') - DateTime: ใช้
pd.to_datetime()พร้อมformatและerrors='coerce'