返回目錄
A
數據之鏡:從資料洞察到決策智慧 - 第 3 章
3. 資料清理與特徵工程
發布於 2026-02-25 18:19
# 3. 資料清理與特徵工程
在前一章,我們已經完成了資料的採集與 ETL 流程,確保資料能夠以結構化方式存放於資料倉儲中。接下來的關鍵步驟是 **資料清理**(Data Cleaning)與 **特徵工程**(Feature Engineering)。這兩個環節不僅能提升模型的精準度,還能降低維護成本與解讀難度。
## 3.1 缺失值處理
### 3.1.1 缺失值的來源
- **資料收集時遺漏**:設備停止、網路斷線、使用者未填寫。
- **ETL 過程中的錯誤**:API 回傳 404、爬蟲解析失敗。
- **資料轉換時的規則**:某些字段只在特定條件下才會出現。
### 3.1.2 處理策略
| 策略 | 適用情境 | 代碼範例 |
|------|----------|----------|
| 刪除 | 缺失比例 < 5% 且不影響樣本分佈 | `df.dropna(axis=0, thresh=int(0.95*len(df)))` |
| 觀測值填補 | 連續型數值 | `df['age'].fillna(df['age'].median(), inplace=True)` |
| 分類值填補 | 類別型 | `df['gender'].fillna(df['gender'].mode()[0], inplace=True)` |
| 先進填補 | 需要保持結構 | `SimpleImputer(strategy='mean')` |
| 標記缺失 | 以便模型學習缺失模式 | `df['age_is_missing'] = df['age'].isna().astype(int)` |
> **實務提醒**:在填補前請先使用 **缺失值可視化** 了解缺失分佈,避免「填補後假象」造成模型偏差。
### 3.1.3 案例:線上零售交易資料
```python
import pandas as pd
from sklearn.impute import SimpleImputer
# 讀取交易資料
trades = pd.read_csv('sales_transactions.csv')
# 查看缺失比例
print(trades.isnull().mean())
# 針對數值型填補中位數,類別型填補最常見值
numeric_imputer = SimpleImputer(strategy='median')
categorical_imputer = SimpleImputer(strategy='most_frequent')
trades[numeric_cols] = numeric_imputer.fit_transform(trades[numeric_cols])
trades[categorical_cols] = categorical_imputer.fit_transform(trades[categorical_cols])
```
## 3.2 離群值檢測
### 3.2.1 離群值定義
離群值(Outlier)指在資料分布中明顯偏離大多數觀測值的數據點,可能是測量誤差、異常事件或真實極端情況。
### 3.2.2 檢測方法
| 方法 | 描述 | 代碼範例 |
|------|------|----------|
| 箱型圖(Boxplot) | 使用四分位數識別 | `sns.boxplot(x=df['price'])` |
| 標準差法 | | `z = (x - x.mean())/x.std(); df[z>3]` |
| IQR 法 | | `Q1, Q3 = df.quantile([0.25, 0.75]); IQR = Q3-Q1; df[(df< Q1-1.5*IQR)|(df>Q3+1.5*IQR)]` |
| Z-Score + Mahalanobis | 多變量 | `mahal = np.linalg.norm((X - X.mean(axis=0)) @ np.linalg.inv(cov), axis=1)` |
| 機器學習 | Isolation Forest、One-Class SVM | `IsolationForest().fit(X)` |
### 3.2.3 處理策略
- **刪除**:當離群值佔比低且不影響模型表現。
- **轉換**:對數、Box-Cox 轉換減少極端值影響。
- **標記**:保留但以二元欄位標示,讓模型學習離群模式。
- **更正**:若是資料輸入錯誤,進行手動更正或使用校正值。
> **案例**:金融風險模型中的「交易金額」離群值往往代表大額異常交易,處理時需保留並加上風險因子。
## 3.3 編碼技巧
### 3.3.1 類別編碼
| 編碼類型 | 適用場景 | 優點 | 缺點 |
|----------|----------|------|------|
| One-Hot | 低基數 | 無序假設 | 高維度、稀疏 |
| Ordinal | 自然序 | 低維度 | 需要實際序值 |
| Target / Mean | 文字分類 | 捕捉目標相關性 | 可能導致資料洩漏 |
| Frequency | 欄位頻率 | 代表普遍度 | 可能忽略特定關係 |
| Target Encoding | 大基數 | 降低維度 | 必須分層訓練避免洩漏 |
### 3.3.2 連續型轉換
- **標準化**(Z-Score)
```python
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler(); X_scaled = scaler.fit_transform(X)
```
- **最小-最大縮放**
```python
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler(); X_norm = scaler.fit_transform(X)
```
- **Log / Box-Cox**:解決偏態分布。
### 3.3.3 數值化技巧
| 技巧 | 目的 | 代碼 |
|------|------|------|
| 文本分詞 + TF-IDF | 提取關鍵詞 | `TfidfVectorizer()` |
| 詞嵌入(Word2Vec) | 捕捉語義 | `gensim.models.Word2Vec()` |
| 位置編碼 | 地理資訊 | `latitude/longitude` 轉成經緯度圓餅或球面坐標 |
## 3.4 特徵選擇
### 3.4.1 為何需要特徵選擇
- **減少維度**:降低計算成本與過擬合風險。
- **提升可解釋性**:讓業務人員更易理解模型。
- **提升模型表現**:去除噪聲與不相關特徵。
### 3.4.2 方法
| 方法 | 步驟 | 代碼 |
|------|------|------|
| 方差過濾(Variance Threshold) | 低方差特徵剔除 | `VarianceThreshold(threshold=0.01)` |
| 卡方檢驗 | 分類特徵相關性 | `SelectKBest(chi2, k=10)` |
| ANOVA F-test | 連續特徵相關性 | `SelectKBest(f_classif, k=10)` |
| 互信息(Mutual Information) | 非線性關係 | `mutual_info_classif` |
| L1 正則化 | 內建特徵選擇 | `LogisticRegression(penalty='l1')` |
| Tree-based 重要性 | 連續/類別 | `RandomForestClassifier().feature_importances_` |
| Recursive Feature Elimination (RFE) | 系統性剔除 | `RFE(estimator=RandomForestClassifier(), n_features_to_select=10)` |
### 3.4.3 實務流程示例
```python
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import SelectKBest, chi2
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
# 1. 數據分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 2. 特徵標準化
scaler = StandardScaler(); X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# 3. 特徵重要性評估
rf = RandomForestClassifier(random_state=42)
rf.fit(X_train, y_train)
importances = pd.Series(rf.feature_importances_, index=X.columns).sort_values(ascending=False)
print(importances.head(15))
# 4. 選擇 top 15
X_train_selected = X_train[:, importances.nlargest(15).index]
X_test_selected = X_test[:, importances.nlargest(15).index]
```
## 3.5 典型工作流圖
> **圖 3.1**:資料清理與特徵工程的端到端流程
```
+-----------------+ +-----------------+ +------------------+
| 來源資料庫 | --> | 缺失值處理 | --> | 離群值檢測/處理 |
+-----------------+ +-----------------+ +------------------+
| |
v v
+-----------------+ +------------------+
| 類別編碼/標準化 | --> | 特徵選擇 |
+-----------------+ +------------------+
| |
v v
+-----------------+ +------------------+
| 轉換後資料輸入 | --> | 模型訓練/評估 |
+-----------------+ +------------------+
```
## 3.6 小結
1. **先確定缺失與離群值分佈**,再決定是否刪除或填補。
2. **選擇合適的編碼策略**,尤其對高基數類別特徵可考慮 **target encoding** 或 **hashing trick**。
3. **標準化/縮放** 為機器學習模型的預處理基礎。
4. **特徵選擇** 是提升模型可解釋性與效能的關鍵。可以採用 **樹模型重要性** 或 **L1 正則化** 的雙重檢驗。
> 透過上述步驟,我們不僅能將資料轉化為「乾淨」且「有意義」的數值向量,更能為後續的機器學習模型奠定堅實基礎。