返回目錄
A
金融數據分析實務:從資料到洞見 - 第 3 章
第三章:數據清理與預處理——保證質量的關鍵步驟
發布於 2026-03-02 10:15
# 第三章:數據清理與預處理——保證質量的關鍵步驟
> 在金融資料分析中,**資料品質直接決定模型效能**。即使使用最先進的機器學習演算法,若基礎資料錯亂、缺失、異常或時間同步不一致,最終洞見也會大打折扣。本章將從實務角度出發,說明如何以 Python pandas 為核心,對金融資料進行系統化清理與預處理,並提供可復用的範例與最佳實踐。
## 3.1 為何資料清理是前置關鍵
| 問題類型 | 典型表現 | 可能造成的風險 |
|--------|----------|-----------------|
| 缺失值 | `NaN`, `NULL` | 估計偏差、過擬合、模型失敗 |
| 異常值 | 超過 5× IQR 或 3σ | 噪聲放大、波動誤判 |
| 時間戳不統一 | UTC / 本地 / 分鐘級 | 事件排序錯亂、滑動窗口錯誤 |
| 重複資料 | 同一時間點多筆 | 數據加權失衡 |
| 資料型別錯誤 | 數值被當成字串 | 計算失敗、索引錯誤 |
> **案例**:某投資顧問公司在進行多元回歸時,發現因為每日報價中缺失了 10% 的交易量資料,導致模型顯著偏差。經過缺失值插補與重新計算後,模型 R² 提升 15%。
## 3.2 缺失值處理
### 3.2.1 缺失值檢測
python
import pandas as pd
# 讀取範例資料
df = pd.read_csv('daily_stock.csv', parse_dates=['date'])
# 檢查缺失值
print(df.isna().sum())
### 3.2.2 補值策略
| 策略 | 適用情境 | 例子 |
|------|----------|------|
| 前向填補 (`ffill`) | 時間序列連續性維持 | `df['close'].ffill()` |
| 離散值填補(中位數/平均數) | 交易量、波動率等 | `df['volume'].fillna(df['volume'].median())` |
| 插值(線性/立方) | 高頻數據缺口 | `df['price'].interpolate(method='cubic')` |
| 預測模型填補 | 大量缺失且缺失模式複雜 | `LinearRegression().fit(X, y)` |
> **實務提示**:
> - **先視覺化**:使用 `missingno` 或 `seaborn` 的 heatmap 確認缺失模式。
> - **保留缺失標記**:創建二元欄位 `is_missing`,幫助後續模型辨識缺失機制。
## 3.3 異常值檢測與處理
### 3.3.1 盒鬚圖法
python
import seaborn as sns
sns.boxplot(x=df['returns'])
### 3.3.2 IQR 及 z-score 方法
python
# IQR 方法
Q1 = df['returns'].quantile(0.25)
Q3 = df['returns'].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
mask = (df['returns'] < lower) | (df['returns'] > upper)
print(df[mask])
# z-score 方法
from scipy import stats
df['zscore'] = stats.zscore(df['returns'].dropna())
mask_z = df['zscore'].abs() > 3
print(df[mask_z])
### 3.3.3 處理策略
| 方法 | 優缺點 |
|------|--------|
| 去除 | 簡單、可保證純淨 | 可能失去重要信號 |
| 替換(中位數/平均數) | 保留結構 | 可能引入偏差 |
| 上下限裁切 | 保留範圍 | 失真風險 |
| 變數轉換(log、BoxCox) | 改善分佈 | 需要解釋力 |
> **案例**:在高頻交易資料中,某些極端價格走勢因硬體錯誤被記錄。經過 IQR 去除後,平均日波動率下降 3%,模型風險預測更穩健。
## 3.4 時間同步與時間戳統一
### 3.4.1 時區轉換
python
# 假設資料時間戳為 EST
df['date'] = df['date'].dt.tz_localize('US/Eastern').dt.tz_convert('UTC')
### 3.4.2 時間頻率對齊
python
# 以分鐘頻率重取樣,空缺補零
df = df.set_index('date').resample('1T').ffill().reset_index()
> **實務提示**:
> - **UTC 為標準**:所有交易所資料均轉為 UTC,避免夏令時混亂。
> - **稀疏頻率**:若高頻資料缺口大,先做線性插值再對齊。
> - **事件時間**:針對公告、分紅等事件,使用「事件時間」而非「交易時間」對齊。
## 3.5 資料型別與結構化
| 常見錯誤 | 修正方法 |
|----------|----------|
| 交易量被讀成字串 | `df['volume'] = df['volume'].astype(float)` |
| 日期被當成文字 | `df['date'] = pd.to_datetime(df['date'])` |
| 觀測值被拆成多欄 | `pd.concat([...], axis=1)` |
> **案例**:某基金資料庫將基金代號存成 `varchar`,在 join 時產生 `NULL`。修正後,資料一致性提高 99%。
## 3.6 特徵工程與衍生欄位
### 3.6.1 技術指標衍生
python
# 移動平均
window = 20
df['ma20'] = df['close'].rolling(window).mean()
# 相對強弱指數 (RSI)
# 這裡略示,實際可用 TA-Lib
### 3.6.2 時間特徵
python
df['day_of_week'] = df['date'].dt.dayofweek
df['month'] = df['date'].dt.month
### 3.6.3 文字特徵(情緒分數)
python
import pandas as pd
# 假設有新聞標題欄位 'headline'
from textblob import TextBlob
def sentiment(text):
return TextBlob(text).sentiment.polarity
df['sentiment'] = df['headline'].apply(sentiment)
> **實務提示**:衍生特徵前務必做 **列印**,確保資料對應正確。對於大規模特徵,考慮使用 **hashing trick** 或 **稀疏矩陣** 儲存。
## 3.7 資料分割與樣本平衡
### 3.7.1 時序分割
python
train_end = '2020-12-31'
train = df[df['date'] <= train_end]
test = df[df['date'] > train_end]
### 3.7.2 隨機抽樣
python
train, val = train_test_split(train, test_size=0.2, random_state=42)
> **注意**:時間序列模型應避免 **前向泄露**。所有未來資訊都必須在訓練集中排除。
## 3.8 實戰流程範例
以下為一完整的清理流程範例,針對日成交量與收盤價資料進行處理,最後輸出可直接進行回歸或機器學習的 DataFrame。
python
import pandas as pd
from sklearn.preprocessing import StandardScaler
# 1. 讀取原始資料
raw = pd.read_csv('daily_stock_raw.csv')
# 2. 時間同步
raw['date'] = pd.to_datetime(raw['date']).dt.tz_localize('UTC')
# 3. 缺失值處理
raw['volume'] = raw['volume'].fillna(raw['volume'].median())
raw['close'] = raw['close'].interpolate(method='linear')
# 4. 異常值處理
Q1, Q3 = raw['close'].quantile([0.25, 0.75])
IQR = Q3 - Q1
lower, upper = Q1 - 1.5*IQR, Q3 + 1.5*IQR
raw = raw[(raw['close'] >= lower) & (raw['close'] <= upper)]
# 5. 特徵衍生
raw['ma20'] = raw['close'].rolling(20).mean()
raw['return'] = raw['close'].pct_change()
raw.dropna(inplace=True)
# 6. 標準化
scaler = StandardScaler()
raw[['volume_std', 'ma20_std']] = scaler.fit_transform(raw[['volume', 'ma20']])
# 7. 資料分割
train, test = train_test_split(raw, test_size=0.2, shuffle=False)
# 8. 輸出
train.to_parquet('train.parquet', index=False)
test.to_parquet('test.parquet', index=False)
## 3.9 最佳實踐總結
| 步驟 | 建議實踐 |
|------|----------|
| 版本控制 | 資料處理腳本使用 Git,所有資料集備份於 S3 或 GCS |
| Metadata | 在每一次資料轉換後,生成 JSON 版元:`{"source":"API" , "timestamp":"2024‑07‑01T00:00:00Z", "version":1}` |
| 監控 | Airflow/Prefect DAG,失敗即重試,Slack 通知 |
| 日誌 | 以 `logging` 輸出關鍵統計(缺失率、異常數量) |
| 再現性 | 固定隨機種子,使用 `seed=42`,同時記錄依賴包版本(`pip freeze > requirements.txt`) |
> **小結**:
> - **資料清理** 是任何金融分析的基石,缺失、異常、時間錯位皆能嚴重影響模型結果。
> - 通過**自動化腳本**與**標準化流程**,可大幅提升資料品質與工作效率。
> - 建立**版本化、可追蹤**的資料管線,才能在實務中持續交付可靠洞見。