聊天視窗

數據洞察:從資料到決策的科學方法 - 第 3 章

3. 資料前處理:清洗、轉換與特徵工程

發布於 2026-02-21 01:22

# 3. 資料前處理:清洗、轉換與特徵工程 資料前處理是整個數據科學流程中最具挑戰且最具影響力的階段。乾淨、結構化且富含資訊的資料能顯著提升模型表現,降低偏差與方差,並確保模型在實務部署中的可持續性。本章將從缺失值處理、資料轉換、離群值檢測,到特徵工程的完整工作流程與最佳實務,提供理論基礎、實務範例與可落地的實作指引。 --- ## 3.1 缺失值處理 | 缺失值處理策略 | 何時適用 | 主要優點 | 潛在風險 | |-----------------|-----------|----------|----------| | 刪除行/列 | 缺失比例低、資料量足 | 簡單快速 | 可能失去重要資訊 | | 估算填補 | 缺失比例中等 | 保留所有樣本 | 可能帶入偏差 | | 先驗/模型預測填補 | 大量缺失,業務有先驗 | 避免資訊喪失 | 需要先驗知識 | ### 3.1.1 估算填補方法 - **統計量填補**:均值、中位數、眾數 - **kNN 填補**:根據相似鄰居估算 - **迴歸填補**:利用其他特徵預測缺失值 - **多重插補(Multiple Imputation)**:考慮不確定性 ### 3.1.2 Python 例子 python import pandas as pd from sklearn.impute import SimpleImputer, KNNImputer # 範例資料 df = pd.DataFrame({ 'age': [25, 30, None, 45, 38], 'income': [50000, None, 62000, 72000, 58000], 'gender': ['M', 'F', 'F', None, 'M'] }) # 1. 均值填補(數值) num_imputer = SimpleImputer(strategy='mean') df['age'] = num_imputer.fit_transform(df[['age']]) # 2. kNN 填補(同時處理數值與類別) knn_imputer = KNNImputer(n_neighbors=3) df[['age', 'income']] = knn_imputer.fit_transform(df[['age', 'income']]) # 3. 眾數填補(類別) cat_imputer = SimpleImputer(strategy='most_frequent') df['gender'] = cat_imputer.fit_transform(df[['gender']]) print(df) > **實務建議**:先觀察缺失模式(完全隨機、條件隨機或非隨機)。對於非隨機缺失,盡量採用模型預測或多重插補,並在報告中說明潛在偏差。 --- ## 3.2 資料轉換與標準化 | 轉換方法 | 用途 | 典型函式 | 範例 | |-----------|------|----------|------| | **標準化 (StandardScaler)** | 離均差為 0、單位方差 | `sklearn.preprocessing.StandardScaler` | 迴歸、SVM、KNN 需對距離敏感 | | **正規化 (MinMaxScaler)** | 將值映射到 [0,1] | `sklearn.preprocessing.MinMaxScaler` | 圖像、深度學習前處理 | | **對數變換** | 處理偏態分布 | `np.log1p` | 收入、銷售額 | | **Box-Cox / Yeo-Johnson** | 使資料更接近正態 | `scipy.stats.boxcox` | 對稱化、降低異方差 | | **分箱 (Binning)** | 降低離散特徵複雜度 | `pandas.cut` | 年齡分級 | ### 3.2.1 Python 例子 python from sklearn.preprocessing import StandardScaler, MinMaxScaler import numpy as np X = np.array([[1, 2], [2, 0], [0, 0], [4, 6]]) # 標準化 scaler_std = StandardScaler() X_std = scaler_std.fit_transform(X) print('Standardized:\n', X_std) # 正規化 scaler_mm = MinMaxScaler() X_mm = scaler_mm.fit_transform(X) print('MinMax:\n', X_mm) --- ## 3.3 離群值檢測 | 方法 | 何時適用 | 優點 | 缺點 | |------|-----------|------|------| | **Z-Score** | 正態分布 | 直觀 | 受極值影響 | | **IQR (四分位距)** | 非正態 | 對極值不敏感 | 需要選擇分位數 | | **Isolation Forest** | 高維、非線性 | 適用大資料集 | 需要參數調整 | | **DBSCAN** | 空間聚類 | 自動偵測密度 | 參數難調 | ### 3.3.1 Python 例子 python import pandas as pd from sklearn.ensemble import IsolationForest # 範例資料 np.random.seed(42) data = pd.DataFrame({'feature': np.random.normal(0, 1, 1000)}) # 加入離群點 data = pd.concat([data, pd.DataFrame({'feature': [10, -10, 12]})]) # Isolation Forest iso = IsolationForest(contamination=0.01, random_state=42) data['anomaly'] = iso.fit_predict(data[['feature']]) print('離群點比例:', (data['anomaly'] == -1).mean()) --- ## 3.4 特徵工程 ### 3.4.1 特徵選擇 | 類型 | 描述 | 典型方法 | |------|------|----------| | **Filter** | 先行評估每個特徵的重要性 | 相關係數、卡方檢定、互信息 | | **Wrapper** | 透過模型性能評估 | 前向選擇、後退剔除、遺傳演算法 | | **Embedded** | 在模型訓練時同時進行特徵選擇 | Lasso, Tree-based feature importance | ### 3.4.2 特徵構造 | 構造手法 | 典型範例 | |---------|-----------| | **交互特徵** | `age * income`、`num_items * avg_price` | | **多項式特徵** | `np.power(feature, 2)`、`PolynomialFeatures` | | **時間特徵** | 星期、季節、工作日與週末 | | **分割特徵** | `np.floor(log_feature / 10)` | | **聚合特徵** | 交易紀錄總量、平均交易額 | ### 3.4.3 文本特徵 | 轉換 | 函式 | |-------|------| | **Tokenization / TF‑IDF** | `sklearn.feature_extraction.text.TfidfVectorizer` | | **Word Embedding** | Word2Vec, GloVe | | **Label Encoding / One-Hot** | `pandas.get_dummies`, `sklearn.preprocessing.OneHotEncoder` | ### 3.4.4 Python 例子 python from sklearn.preprocessing import PolynomialFeatures import pandas as pd # 原始特徵 X = pd.DataFrame({'age': [25, 35, 45], 'income': [50000, 65000, 80000]}) # 交互特徵 X['age_income'] = X['age'] * X['income'] # 多項式特徵(degree=2) poly = PolynomialFeatures(degree=2, include_bias=False) X_poly = poly.fit_transform(X[['age', 'income']]) print('Polynomial Features:\n', X_poly) --- ## 3.5 Pipeline 的構建 利用 **scikit‑learn Pipeline** 將前處理流程封裝成可重複、可追蹤的單位,避免資料洩露(data leakage)與程式碼重複。 python from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.impute import SimpleImputer from sklearn.linear_model import LinearRegression # 數值與類別特徵列表 numeric_features = ['age', 'income'] categorical_features = ['gender', 'region'] # 數值處理流水線 numeric_transformer = Pipeline([ ('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()) ]) # 類別處理流水線 categorical_transformer = Pipeline([ ('imputer', SimpleImputer(strategy='most_frequent')), ('onehot', OneHotEncoder(handle_unknown='ignore')) ]) # 組合多列轉換器 preprocessor = ColumnTransformer([ ('num', numeric_transformer, numeric_features), ('cat', categorical_transformer, categorical_features) ]) # 完整 Pipeline model = Pipeline([ ('preprocess', preprocessor), ('regressor', LinearRegression()) ]) # 假設 df 為輸入資料 model.fit(df[numeric_features + categorical_features], df['target']) print('模型係數:', model.named_steps['regressor'].coef_) --- ## 3.5 資料前處理最佳實務 | 項目 | 建議做法 | |------|----------| | **版本控制** | 以 `DVC`、`MLflow` 或 `Delta Lake` 管理前處理腳本與資料版本 | 版本號化、元資料儲存 | | **自動化** | CI/CD 流程中加入資料前處理測試 | `pytest`, `ruff`, `pre-commit` | | **記錄與追蹤** | 使用 `pandas-profiling`、`sweetviz` 產生資料摘要 | 方便回溯、團隊協作 | | **文件化** | 產生 `README.md` 或 `data_processing.ipynb`,說明每一步假設與參數 | 方便新人快速上手 | | **性能監控** | 監測前處理時間、記憶體消耗 | 針對大規模資料集調整批次處理 | --- ## 3.6 案例研究:零售銷售預測 ### 3.6.1 數據概覽 | 欄位 | 類型 | 缺失率 | 主要資訊 | |------|------|--------|----------| | `store_id` | 類別 | 0% | 站點識別 | | `date` | 日期 | 0% | 交易時間 | | `items_sold` | 數值 | 5% | 銷售數量 | | `avg_price` | 數值 | 2% | 平均單價 | | `promo_flag` | 類別 | 0% | 促銷活動 | ### 3.6.2 前處理流程 1. **日期特徵擴充**:`day_of_week`, `month`, `is_weekend` 2. **缺失值填補**:`items_sold` 以中位數填補;`avg_price` 以 kNN 填補 3. **對數變換**:`items_sold`、`avg_price` 4. **標準化**:對於 `items_sold`, `avg_price` 5. **離群值檢測**:IQR 方式剔除極端值 6. **交互特徵**:`items_sold * avg_price` 7. **特徵選擇**:使用 Tree‑based importance 篩選出 5 個關鍵特徵 8. **Pipeline**:使用 `ColumnTransformer` 及 `Pipeline` 封裝整個流程 ### 3.6.3 Pipeline 代碼片段 python from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer from sklearn.ensemble import RandomForestRegressor numeric_features = ['items_sold', 'avg_price'] categorical_features = ['promo_flag', 'day_of_week', 'month'] numeric_transformer = Pipeline([ ('imputer', SimpleImputer(strategy='median')), ('scaler', StandardScaler()) ]) categorical_transformer = Pipeline([ ('imputer', SimpleImputer(strategy='most_frequent')), ('onehot', OneHotEncoder(handle_unknown='ignore')) ]) preprocessor = ColumnTransformer([ ('num', numeric_transformer, numeric_features), ('cat', categorical_transformer, categorical_features) ]) model = Pipeline([ ('preprocess', preprocessor), ('rf', RandomForestRegressor(n_estimators=200, random_state=42)) ]) # 假設 df 為前處理後資料 X = df[numeric_features + categorical_features] y = df['target_sales'] model.fit(X, y) print('訓練完成,模型重要性:', model.named_steps['rf'].feature_importances_) --- ## 3.7 小結 - **缺失值**:先觀察缺失模式,再選擇合適填補方法;若缺失非隨機,務必在報告中說明偏差。 - **資料轉換**:標準化與正規化取決於模型對距離的敏感度;偏態分布可用對數或 Box‑Cox 變換。 - **離群值**:Z‑Score 受極值影響,IQR 更穩健;高維可使用 Isolation Forest 或 DBSCAN。 - **特徵工程**:特徵選擇與構造相輔相成,建議使用 Pipeline 以避免資料洩露。 - **最佳實務**:版本控制、追蹤元資料、單元測試與文件化是確保可重現與可持續部署的關鍵。 透過上述方法與流程,您將能在不同業務場景中高效地完成資料前處理,為後續模型訓練與部署奠定堅實基礎。