返回目錄
A
數據驅動決策:從原始資料到洞察的全流程 - 第 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)以及自訂特徵合成。