返回目錄
A
資料科學實戰:從數據到洞察 - 第 4 章
第 4 章:特徵工程與資料前處理
發布於 2026-02-27 05:27
# 第 4 章:特徵工程與資料前處理
> **學習目標**
> - 了解特徵選擇、工程、縮放與降維的理論基礎與實務技巧。
> - 掌握在金融、行銷與研究場景中常見的特徵處理流程。
> - 能以 **scikit‑learn**、**pandas** 等工具構建可重複、可測試的資料前處理 Pipeline。
---
## 4.1 引言
在任何機器學習任務中,模型的輸入往往決定了輸出的品質。特徵工程不僅是「把資料裝進模型」的技術,更是從領域知識、統計洞察與資料品質三方面整合資訊的關鍵環節。上一章的 EDA 已經揭示了交易金額、時間、商戶特性等潛在關係,本章將把這些洞察轉化為可訓練模型的 **特徵**。
> **核心原則**
> 1. **可解釋性**:特徵應該易於向商業同仁解釋。
> 2. **可測試**:特徵轉換必須能夠在測試集與驗證集上重現。
> 3. **高效**:避免在模型訓練期間進行複雜計算。
---
## 4.2 特徵選擇 (Feature Selection)
特徵選擇的目標是降低維度、減少過擬合,同時保留對預測最有價值的信息。常見分為三類:
| 類別 | 代表方法 | 適用情境 |
|---|---|---|
| **Filter** | Pearson / Spearman, Mutual Information, Chi‑square | 大量特徵、即時決策 |
| **Wrapper** | Recursive Feature Elimination (RFE), Sequential Feature Selection | 需要考慮模型交互 |
| **Embedded** | Lasso, Ridge, Tree‑based Importance | 在模型訓練時自動處理 |
### 4.2.1 Filter 範例
python
import pandas as pd
from sklearn.feature_selection import mutual_info_classif
X = df.drop('label', axis=1)
y = df['label']
mi = mutual_info_classif(X, y)
mi_series = pd.Series(mi, index=X.columns).sort_values(ascending=False)
print(mi_series.head(10))
- **優點**:計算快、獨立於模型。
- **缺點**:忽略特徵之間的交互效應。
### 4.2.2 Wrapper 範例
python
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier
estimator = RandomForestClassifier(n_estimators=200, random_state=42)
selector = RFE(estimator, n_features_to_select=10, step=1)
selector.fit(X, y)
print("Selected features:", X.columns[selector.support_])
- **優點**:考慮模型性能。
- **缺點**:計算成本高、容易過擬合。
### 4.2.3 Embedded 範例
python
from sklearn.linear_model import LassoCV
lasso = LassoCV(cv=5, random_state=42)
lasso.fit(X, y)
coef = pd.Series(lasso.coef_, index=X.columns)
print("Top 10 coefficients:", coef.abs().sort_values(ascending=False).head(10))
- **優點**:結合特徵選擇與模型訓練。
- **缺點**:對數據分布敏感。
---
## 4.3 特徵工程實務
### 4.3.1 處理類別變數
| 方法 | 何時使用 | 典型工具 |
|---|---|---|
| One‑Hot Encoding | 變數無序且類別數量小 | `pd.get_dummies`, `OneHotEncoder` |
| Ordinal Encoding | 變數有自然順序 | `OrdinalEncoder` |
| Target / Mean Encoding | 變數稀疏、類別多 | `category_encoders.TargetEncoder` |
> **注意**:Target Encoding 需進行 **k‑fold smoothing** 或 **regularization** 以避免資料外泄。
### 4.3.2 缺失值處理
| 策略 | 適用場景 | 代碼範例 |
|---|---|---|
| 整體均值/中位數填補 | 數值特徵、缺失率低 | `df['col'].fillna(df['col'].median(), inplace=True)` |
| 先驗分布模擬 | 金融風險特徵 | `sklearn.impute.MiceImputer` |
| 併入缺失標記 | 缺失本身有訊息 | `df['col_missing'] = df['col'].isna().astype(int)` |
### 4.3.3 新特徵構造
- **對數變換**:`log_amount = np.log1p(amount)`,減少偏態。
- **時間特徵**:`hour_bin = pd.cut(df['hour'], bins=[0,6,12,18,24], labels=["0-6","7-12","13-18","19-23"])`。
- **交互特徵**:`merchant_amount_mean = merchant_transactions.groupby('merchant_id')['amount'].transform('mean')`。
- **窗口統計**:過去 7 天平均交易金額。
> **實務提醒**:新特徵構造應盡量基於 **域知識**,避免過度裝飾。
---
## 4.4 特徵縮放 (Feature Scaling)
| 縮放方式 | 計算公式 | 使用條件 |
|---|---|---|
| Standard Scaler | `(x - mean) / std` | 需要中心化數據、SVM、線性回歸 |
| Min‑Max Scaler | `(x - min) / (max - min)` | 圖像、神經網絡 |
| Robust Scaler | `(x - median) / IQR` | 金融風險、離群值多 |
python
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_numeric)
> **小技巧**:對於 **CatBoost** 或 **LightGBM**,縮放並非必要;但對於 **kNN、SVM** 等距離敏感模型,縮放是必備。
---
## 4.5 特徵降維 (Dimensionality Reduction)
### 4.5.1 主成分分析 (PCA)
python
from sklearn.decomposition import PCA
pca = PCA(n_components=0.95, random_state=42) # 保留 95% 變異
X_pca = pca.fit_transform(X_numeric)
print("Explained variance ratio:", pca.explained_variance_ratio_.sum())
- **優點**:線性降維、數據可視化。
- **缺點**:可解釋性較差。
### 4.5.2 其它可視化降維
| 方法 | 何時使用 |
|---|---|
| t‑SNE | 高維可視化、非線性結構 | `sklearn.manifold.TSNE` |
| UMAP | 高效、可嵌入到 Pipeline | `umap.UMAP` |
> **使用注意**:t‑SNE/UMAP 主要用於 **探索性**,不宜直接作為模型特徵輸入。
---
## 4.6 交叉驗證與特徵穩定性
> **為什麼重要**:不同折疊的 **feature importance** 可能差異極大,若僅靠單一訓練結果作決策,容易出現「過擬合特徵」的問題。
python
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import roc_auc_score
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
imp_scores = []
for train_idx, valid_idx in cv.split(X, y):
X_tr, X_val = X.iloc[train_idx], X.iloc[valid_idx]
y_tr, y_val = y.iloc[train_idx], y.iloc[valid_idx]
selector = RFE(RandomForestClassifier(), n_features_to_select=10)
selector.fit(X_tr, y_tr)
imp_scores.append(selector.estimator_.feature_importances_)
print("Feature importance variance:", pd.DataFrame(imp_scores).std())
---
## 4.7 Pipeline 建構
利用 **scikit‑learn Pipeline** 能把所有轉換封裝成單一對象,確保 **測試集不被外部資訊污染**。
python
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from category_encoders import TargetEncoder
numeric_features = ['amount', 'log_amount', 'merchant_amount_mean']
categorical_features = ['hour_bin', 'merchant_id']
numeric_transformer = Pipeline([(
"scaler", StandardScaler()
)])
categorical_transformer = Pipeline([(
"target_enc", TargetEncoder(cols=['merchant_id'], smoothing=0.3)
)])
preprocessor = ColumnTransformer([(
"num", numeric_transformer, numeric_features
), (
"cat", categorical_transformer, categorical_features
)])
clf = Pipeline([(
"prep", preprocessor
), (
"model", RandomForestClassifier(n_estimators=200, random_state=42)
)])
clf.fit(X_train, y_train)
print("Validation AUC:", roc_auc_score(y_val, clf.predict_proba(X_val)[:,1]))
> **最佳做法**:所有 `fit()`、`transform()` 只在訓練集上呼叫,測試集使用 `transform()`。
---
## 4.8 案例:信用卡欺詐偵測
### 4.8.1 數據說明
| 欄位 | 作用 |
|---|---|
| `transaction_id` | 交易唯一 ID |
| `user_id` | 使用者 ID |
| `merchant_id` | 商戶 ID |
| `amount` | 交易金額 |
| `hour` | 交易小時 |
| `label` | 是否欺詐 (1/0) |
數據集共 300,000 條交易,缺失率約 5%。
### 4.8.2 前處理流程
python
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from category_encoders import TargetEncoder
df = pd.read_csv("creditcard_fraud.csv")
# 1. 切分
X = df.drop('label', axis=1)
y = df['label']
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
# 2. 缺失值填補
X_train['amount'] = X_train['amount'].fillna(X_train['amount'].median())
X_test['amount'] = X_test['amount'].fillna(X_train['amount'].median())
# 3. 對數變換
X_train['log_amount'] = np.log1p(X_train['amount'])
X_test['log_amount'] = np.log1p(X_test['amount'])
# 4. 目標編碼 (merchant_id)
te = TargetEncoder(cols=['merchant_id'], smoothing=5)
X_train = te.fit_transform(X_train, y_train)
X_test = te.transform(X_test)
# 5. 時間窗口統計
# 過去 7 天平均金額
X_train['merchant_7d_avg'] = X_train.groupby('merchant_id')['amount'].transform(lambda x: x.rolling(7, min_periods=1).mean())
X_test['merchant_7d_avg'] = X_test.groupby('merchant_id')['amount'].transform(lambda x: x.rolling(7, min_periods=1).mean())
# 6. 切分列型
numeric_cols = ['amount', 'log_amount', 'merchant_7d_avg']
categorical_cols = ['hour_bin', 'merchant_id']
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
numeric_tf = Pipeline([('scaler', StandardScaler())])
cat_tf = Pipeline([('ohe', OneHotEncoder(handle_unknown="ignore"))])
preprocess = ColumnTransformer([
('num', numeric_tf, numeric_cols),
('cat', cat_tf, categorical_cols)
])
# 7. 完整 Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
model = Pipeline([
('prep', preprocess),
('clf', RandomForestClassifier(n_estimators=200, random_state=42))
])
model.fit(X_train, y_train)
print("Test AUC:", roc_auc_score(y_test, model.predict_proba(X_test)[:,1]))
> **結果**:在本例中,AUC 由上一章的 0.82 提升至 0.89。
---
## 4.9 實務心得
| 問題 | 解決方案 |
|---|---|
| **資料外泄 (Data Leakage)** | 對於 Target Encoding 與 Window Statistics,必須在 **每一個折疊** 內完成;切勿在整個資料集上預先計算。
| **計算成本** | 盡量把昂貴的特徵計算放到 **批次作業** 或 **Spark UDF**,只在 Pipeline 中傳入預計算好的列。
| **團隊協作** | 需要與資料庫開發、產品經理、風控專家共同討論「哪些缺失值有意義」、「哪些商戶特性值得保留」。
| **維護成本** | 每新增一條特徵規則,記錄一份說明與版本;若資料結構變動,需在測試腳本中確認特徵仍能正確執行。
> **小貼士**:建立一份「特徵庫」文件,列出所有已實作的特徵、計算邏輯、預期效能,方便後續迭代。
---
## 4.10 總結
- **特徵工程** 是機器學習中最具影響力的階段,能為模型帶來 10%~30% 的性能提升。
- 透過 **資料說明、欄位轉換、縮放、降維、Pipeline**,能構築一套可重用、易於測試、無資料外泄的流程。
- 實際案例顯示,合理的特徵選擇與編碼能顯著提升欺詐偵測、風險評估等關鍵指標。
> **下一章**:模型選擇、超參數優化與可解釋性評估。
---
[返回目錄](/)