聊天視窗

數據驅動決策:從原始資料到洞察的全流程 - 第 3 章

第 3 章:資料清洗與預處理

發布於 2026-02-22 13:44

# 第 3 章:資料清洗與預處理 資料清洗(Data Cleaning)與預處理(Pre‑processing)是數據科學流程中最關鍵、但也最耗時的階段。若不先確保資料品質,後續的探索、特徵工程以及機器學習模型都會被「污點資料」所拖累。這一章將以實務導向,從缺失值、異常值處理談到資料轉換、標準化,並結合 **Pandas、SQL、Spark** 三大常用工具示範實作。 --- ## 3.1 缺失值處理 | 缺失值類型 | 典型場景 | 影響 | 常見處理方法 | | ----------- | -------- | ---- | ------------- | | 0 值(NaN) | 資料輸入錯誤、收集不完整 | 影響統計指標、模型訓練 | 1. 刪除 2. 取平均/中位數/眾數 3. 預測填補(KNN、Regression) | | 極端缺失 | 只有極少數樣本缺失 | 可能不影響模型 | 刪除樣本/特徵 | ### 3.1.1 刪除策略 python # 只保留完整資料列 clean_df = df.dropna() # 按特徵列比例刪除 threshold = 0.8 # 至少 80% 欄位非缺失 clean_df = df.dropna(thresh=int(threshold * len(df.columns))) ### 3.1.2 取代策略 python # 取平均 df['age'] = df['age'].fillna(df['age'].mean()) # 取眾數(離散特徵) df['gender'] = df['gender'].fillna(df['gender'].mode()[0]) ### 3.1.3 預測填補 > **KNN 填補**:基於相似樣本的平均值;適用於非線性關係。 python from sklearn.impute import KNNImputer imputer = KNNImputer(n_neighbors=5) imputed = imputer.fit_transform(df) df_imputed = pd.DataFrame(imputed, columns=df.columns) > **Regression 填補**:用其他變數預測缺失值;適合有因果假設時。 python from sklearn.linear_model import LinearRegression for col in ['salary', 'bonus']: not_null = df[df[col].notnull()] X = not_null.drop(columns=[col]) y = not_null[col] model = LinearRegression().fit(X, y) missing = df[df[col].isnull()] df.loc[missing.index, col] = model.predict(missing.drop(columns=[col])) ### 3.1.4 實務小結 | 方法 | 適用場景 | 優點 | 缺點 | | ---- | -------- | ---- | ---- | | 刪除 | 欠失比例低 | 簡單直接 | 可能造成資料量大幅縮減 | | 平均/眾數 | 欠失比例中等 | 易實作 | 可能引入偏差 | | KNN | 高變數相關性 | 保留資料完整 | 計算成本高 | | Regression | 明確因果關係 | 可解釋性強 | 模型假設需驗證 | ## 3.2 異常值(Outlier)檢測與處理 異常值可能是資料輸入錯誤,也可能是關鍵訊息。處理前必須先判斷其來源。 ### 3.2.1 檢測方法 | 方法 | 原理 | 優點 | 缺點 | | ---- | ---- | ---- | ---- | | 箱型圖(Box‑Plot) | 四分位距 (IQR) | 直觀易懂 | 只適用單變量 | | Z‑Score | 標準化差異 | 量化程度 | 需要正態分佈假設 | | DBSCAN | 聚類密度 | 可處理多變量 | 參數設定複雜 | | Isolation Forest | 隔離決策樹 | 高效、可擴展 | 需要參數調整 | python # Z‑Score 檢測 from scipy import stats z = np.abs(stats.zscore(df['score'])) df_clean = df[z < 3] ### 3.2.2 處理策略 | 方式 | 適用情境 | 範例 | | ---- | -------- | ---- | | 刪除 | 確定為錯誤 | `df = df[~(df['price'] > 1e6)]` | | 替換 | 影響較大但資料量重要 | `df.loc[mask, 'price'] = df['price'].median()` | | 轉換 | 異常值是分佈尾部 | `np.log1p(df['sales'])` | ## 3.3 資料轉換(Feature Engineering) #### 3.3.1 類別編碼 | 類別 | 編碼方式 | 何時選擇 | | ---- | -------- | -------- | | 有序 | OrdinalEncoder | 有自然順序 | | 無序 | OneHotEncoder / CountVectorizer | 無順序、維度可控 | python from sklearn.preprocessing import OneHotEncoder enc = OneHotEncoder(sparse=False) encoded = enc.fit_transform(df[['country']]) #### 3.3.2 文字向量化 | 方法 | 典型工具 | 特色 | | ---- | -------- | ---- | | TF‑IDF | sklearn TfidfVectorizer | 文字重要性加權 | | Word2Vec | gensim | 句子語義向量 | | BERT | transformers | 上下文語義 | python from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer(max_features=5000) X_tfidf = vectorizer.fit_transform(df['review']) ## 3.4 標準化與正規化(Scaling) | 目的 | 方式 | 何時使用 | | ---- | ---- | -------- | | 使不同單位特徵同尺度 | StandardScaler | 線性模型、SVM | | 將值縮放至 [0,1] | MinMaxScaler | 需要保留原始分佈 | | 使分佈更接近正態 | QuantileTransformer | 需要高斯假設 | python from sklearn.preprocessing import StandardScaler, MinMaxScaler scaler = StandardScaler() X_std = scaler.fit_transform(df_numeric) ## 3.4 工具實作示範 ### 3.4.1 Pandas python # 讀取 CSV import pandas as pd raw = pd.read_csv('sales_raw.csv') # 缺失值填補 raw['quantity'] = raw['quantity'].fillna(0) # 類別編碼 raw = pd.get_dummies(raw, columns=['region'], drop_first=True) # 儲存為 clean 檔 raw.to_parquet('sales_clean.parquet') ### 3.4.2 SQL(以 PostgreSQL 為例) sql -- 缺失值處理(取平均) UPDATE sales SET price = COALESCE(price, (SELECT AVG(price) FROM sales)) WHERE price IS NULL; -- 異常值刪除 DELETE FROM sales WHERE price > 1000000; -- 標準化(Z‑Score) ALTER TABLE sales ADD COLUMN price_z REAL; UPDATE sales SET price_z = (price - (SELECT AVG(price) FROM sales)) / (SELECT STDDEV(price) FROM sales); ### 3.4.3 Spark(PySpark) > Spark 適用於 TB 以上級別的分散式資料。 python from pyspark.sql import SparkSession from pyspark.sql.functions import col, when, isnan spark = SparkSession.builder.appName("DataPrep").getOrCreate() raw = spark.read.csv('hdfs://sales_raw.csv', header=True, inferSchema=True) # 缺失值填補 clean = raw.fillna({'age': raw.agg({'age': 'mean'}).collect()[0][0]}) # 取類別 OneHot from pyspark.ml.feature import OneHotEncoder, StringIndexer idx = StringIndexer(inputCol='category', outputCol='categoryIndex').fit(clean) encoded = OneHotEncoder(inputCol='categoryIndex', outputCol='categoryVec').transform(idx.transform(clean)) ## 3.5 最佳實踐與常見陷阱 | 項目 | 建議 | 注意事項 | | ---- | ---- | -------- | | **流程化** | 將清洗步驟寫成腳本、Notebook、Airflow DAG | 確保重現性 | | **版本管理** | 使用 Delta Lake / Parquet + 元資料(schema) | 避免「資料漂移」 | | **檢查點** | 每次清洗後立即做基礎統計、可視化 | 及時發現異常 | | **資料完整性** | 在 ETL 入口即使用 Schema Validation | 及早偵測結構錯誤 | | **監控** | 透過監控指標(缺失比例、異常比例)發送告警 | 防止數據劣化 | ## 3.6 案例研究:零售訂單資料 > **背景**:某電商平台擁有 10 萬筆訂單,包含 `order_id`、`customer_id`、`product_id`、`price`、`discount`、`region`、`order_date`、`status` 等欄位。 > > **問題**: > - `price` 欠失 5% 以上; > - `discount` 部分值為 -1(輸入錯誤); > - `region` 為文字,需編碼; > - `status` 需要進一步分類。 > > **解決流程**: > 1. 刪除 `order_id` 以減少維度; > 2. `price` 以中位數填補; > 3. `discount` 使用 Isolation Forest 分離,再替換為 0; > 4. `region` 進行 One‑Hot; > 5. `status` 轉成二元編碼 (`shipped` → 1, 其餘 → 0)。 > > **結果**:清洗後資料量保持 92%;模型訓練準確率提升 3.8%。 ## 3.7 小結 1. **缺失值處理**:先衡量欠失比例,選擇刪除、填補或預測填補。 2. **異常值**:必須先判斷是否真實訊息,再決定刪除、替換或轉換。 3. **資料轉換**:類別編碼、文字向量化等常見手段,視資料型態選擇最合適工具。 4. **工具多樣化**:Pandas 適合中小資料、SQL 適合關係型資料,Spark 適合分散式大型資料。 5. **重現性**:將清洗流程腳本化、版本化,配合元資料管理,可大幅降低「資料漂移」風險。 --- > **下章預告**:第 4 章將深入 **特徵工程(Feature Engineering)**,包括非線性映射、降維(PCA、t‑SNE)以及自訂特徵合成。