聊天視窗

數據洞見:從原始數據到商業決策的機器學習實戰 - 第 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)」將示範如何利用統計圖表、分布檢定與相關矩陣,深入了解資料結構,並為後續特徵拆解與縮減提供決策依據。