聊天視窗

金融數據分析實務:從資料到洞見 - 第 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`) | > **小結**: > - **資料清理** 是任何金融分析的基石,缺失、異常、時間錯位皆能嚴重影響模型結果。 > - 通過**自動化腳本**與**標準化流程**,可大幅提升資料品質與工作效率。 > - 建立**版本化、可追蹤**的資料管線,才能在實務中持續交付可靠洞見。