返回目錄
A
數據洞見:從原始數據到商業決策的機器學習實戰 - 第 3 章
3. 資料清理與前處理
發布於 2026-02-28 07:36
# 3. 資料清理與前處理
在任何機器學習專案中,**資料品質**直接決定了模型表現與可信度。資料清理與前處理(Data Cleaning & Pre‑processing)是把「原始」資料轉換成「可用」資料的關鍵步驟,涵蓋缺失值處理、異常偵測、型別轉換、特徵縮放、編碼等技術。以下以實務案例為例,逐步說明每個子領域的理論與程式實作。
---
## 3.1 缺失值處理
### 3.1.1 觀察缺失值
> **目的**:快速了解缺失率,判斷是否需要填補。
python
import pandas as pd
df = pd.read_csv('data/churn.csv')
print(df.isna().mean() * 100)
| Feature | 缺失率(%) | 觀察說明 |
|-------------------|--------------|----------|
| age | 0.2 | 可忽略 |
| tenure | 0.0 | 無缺失 |
| total_charges | 5.4 | 需要處理 |
> 若缺失率 < 1%,可直接刪除;若 > 30%,須考慮填補或重新取得資料。
### 3.1.2 刪除 vs 填補
| 方法 | 優點 | 缺點 |
|------|------|------|
| 刪除 | 實作簡單 | 失去樣本、資訊損失 |
| 填補 | 保留樣本 | 可能引入偏差 |
常見填補策略:
| 策略 | 適用場景 |
|------|----------|
| 均值/中位數 | 連續特徵 |
| 最頻值 | 類別特徵 |
| 前向/後向填補 | 時序資料 |
| KNN / MICE | 多變量關聯性 |
| 回歸填補 | 可用其他特徵預測 |
python
from sklearn.impute import SimpleImputer
imp_mean = SimpleImputer(strategy='mean')
df['total_charges'] = imp_mean.fit_transform(df[['total_charges']])
### 3.1.3 進階填補
#### KNN Imputer
python
from sklearn.impute import KNNImputer
knn_imp = KNNImputer(n_neighbors=5)
df[['age', 'total_charges']] = knn_imp.fit_transform(df[['age', 'total_charges']])
#### MICE(Multiple Imputation by Chained Equations)
python
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
mice = IterativeImputer(max_iter=10, random_state=0)
X_imputed = mice.fit_transform(df)
> **實務建議**:對於重要特徵(如 `total_charges`)先用簡單填補,若模型表現不佳再嘗試 KNN/MICE。<br>**記錄填補策略**,以便回溯與重現。
---
## 3.2 異常偵測與處理
### 3.2.1 異常定義
> 異常值(Outlier)是與大多數資料點偏離甚遠的觀測值,可能由測量錯誤、資料錄入失誤或真實極端事件造成。若不處理,易影響統計量、模型收斂與預測精度。
### 3.2.2 常用方法
| 方法 | 優點 | 缺點 |
|------|------|------|
| IQR(四分位距) | 計算簡單 | 對分布不敏感 |
| Z‑score | 兼容正態分布 | 受極端值影響 |
| Isolation Forest | 能處理高維、非正態分布 | 需調參 |
| LOF(局部離群因子) | 捕捉局部異常 | 計算量大 |
python
# IQR 方法示範
Q1 = df['age'].quantile(0.25)
Q3 = df['age'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
outliers = df[(df['age'] < lower) | (df['age'] > upper)]
print(f"異常數量: {len(outliers)}")
python
# Isolation Forest
from sklearn.ensemble import IsolationForest
iso = IsolationForest(contamination=0.05, random_state=42)
iso.fit(df[['age', 'tenure']])
df['outlier'] = iso.predict(df[['age', 'tenure']]) # -1 為異常
### 3.2.3 處理方法
| 方法 | 何時使用 |
|------|----------|
| 刪除 | 異常比例 < 5% 且無重大資訊 |
| 修正 | 觀測值可根據邏輯修正(例如:年齡 200 → 200 轉 20) |
| 取代 | 用中位數或眾數填補 |
| 保留 | 異常代表重要商業洞察(如高額交易) |
> **關鍵**:在決定刪除前,先用視覺化(箱形圖、散點圖)檢驗異常是否為真實信號。
---
## 3.3 資料型別轉換
### 3.3.1 日期時間解析
python
df['signup_date'] = pd.to_datetime(df['signup_date'], format='%Y-%m-%d')
# 提取年月日、星期、季節
df['signup_month'] = df['signup_date'].dt.month
### 3.3.2 數值 / 類別轉換
python
# 類別轉為整數
df['gender'] = df['gender'].map({'Male':0, 'Female':1})
# 日期到連續特徵
df['days_since_signup'] = (pd.Timestamp('today') - df['signup_date']).dt.days
> **注意**:類別型別應保持原始字符串,僅在需要數值化時才轉換。
---
## 3.4 特徵縮放(Scaling)
### 3.4.1 標準化 (Z‑score)
> 讓特徵均值 0,方差 1,適用於大多數演算法(SVM、KNN、線性模型)。
python
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(df[['age', 'tenure', 'total_charges']])
### 3.4.2 正規化 (Min‑Max)
> 將特徵縮放到 [0, 1],適用於需要限制範圍的模型(神經網路、決策樹)。
python
from sklearn.preprocessing import MinMaxScaler
mm = MinMaxScaler()
X_norm = mm.fit_transform(df[['age', 'tenure']])
### 3.4.3 Box‑Cox & Yeo‑Johnson
> 用於處理偏態分布,將資料轉為更接近正態分布。
python
from scipy.stats import boxcox, yeojohnson
df['total_charges_bc'], _ = boxcox(df['total_charges'] + 1) # +1 以避免 0
---
## 3.5 編碼類別特徵
| 編碼方式 | 適用模型 | 需求 |
|----------|----------|------|
| One‑Hot | 大多數模型 | 無序類別 |
| Label | 樹模型、XGBoost | 有序類別 |
| Target Encoding | 需要高維減少 | 需避免過擬合 |
| Embedding | 深度學習 | 高維稀疏類別 |
python
# One‑Hot
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(sparse=False)
X_ohe = enc.fit_transform(df[['contract_type']])
---
## 3.6 文字特徵處理
python
# 文本簡化流程示範
import nltk
nltk.download('stopwords')
from nltk.corpus import stopwords
from sklearn.feature_extraction.text import TfidfVectorizer
stop = set(stopwords.words('chinese'))
vectorizer = TfidfVectorizer(stop_words=stop, max_features=500)
X_text = vectorizer.fit_transform(df['feedback_text'])
> **建議**:文字特徵應先做分詞、去除停用詞,再決定是否使用 Word2Vec、BERT 等預訓練模型。
---
## 3.7 Pipeline 建構
使用 `scikit-learn` 的 `Pipeline` 可以確保 **資料處理順序可重現**,並方便於交叉驗證與部署。
python
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
numeric_features = ['age', 'tenure', 'total_charges']
categorical_features = ['gender', 'contract_type']
numeric_pipe = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
categorical_pipe = Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('onehot', OneHotEncoder(handle_unknown='ignore'))
])
preprocess = ColumnTransformer([
('num', numeric_pipe, numeric_features),
('cat', categorical_pipe, categorical_features)
])
model = Pipeline([
('preprocess', preprocess),
('clf', RandomForestClassifier(random_state=42))
])
model.fit(X_train, y_train)
> **注意**:`ColumnTransformer` 讓你對不同類型特徵使用不同的預處理流程,避免因特徵不一致而導致模型失效。
---
## 3.8 資料清理最佳實踐
1. **自動化**:將清理腳本納入 Airflow DAG,確保每日自動執行。<br>2. **品質指標**:建立缺失率、異常率、型別錯誤率等指標,並透過 Datadog 監控。<br>3. **血緣追蹤**:利用 Collibra 等元資料管理工具紀錄「哪個原始欄位 → 哪個處理步驟 → 最終欄位」。<br>4. **版本控制**:將清理程式碼、參數、轉換規則儲存在 Git,並與 Snowflake 資料表版本相對應。<br>5. **重現性**:使用 `pickle` 或 `joblib` 存檔 `Pipeline`,確保同一套流程可在不同環境重現。<br>6. **人機協作**:在資料品質異常時,觸發 DataSteward 介入,並記錄手動修正紀錄。<br>7. **文件化**:在 README 或 Confluence 中說明每個欄位的處理邏輯,方便後續維護。<br>
---
## 3.9 小結
> **關鍵洞察**:資料清理與前處理不是一次性的工作,而是 **資料管道** 的核心。高品質的前處理能降低模型失敗風險,縮短迭代週期,並為商業洞察提供堅實基礎。<br>
> 在實務上,建議先以簡單、可重現的流程起步,隨著資料量與複雜度提升,逐步加入自動化、監控與治理層面。<br>
> 接下來,我們將進一步探討如何在清理後的資料上進行 **探索性資料分析(EDA)**,為特徵工程與模型建構奠定理論與實務基礎。
---
> **下一章預告**:第4章「探索性資料分析 (EDA)」將示範如何利用統計圖表、分布檢定與相關矩陣,深入了解資料結構,並為後續特徵拆解與縮減提供決策依據。