前言
書接上回,我們發布了《用多因子策略建立強大的加密資產投資組合》系列文章的第一篇- 理論基礎篇,本篇是第二篇- 資料預處理篇。
在計算因子資料前/後,以及測試單因子的有效性之前,都需要對相關資料進行處理。具體的資料預處理涉及重複值、異常值/缺失值/極端值、標準化和資料頻率的處理。
一、重複值
數據相關定義:
-
鍵(Key):表示一個獨一無二的索引。 eg. 對於一份有全部token所有日期的數據,鍵是“token_id/contract_address – 日期”
-
值(Value):被鍵索引的物件就稱之為「值」。
診斷重複值的首先需要理解資料「應當」是什麼樣子。通常數據的形式有:
-
時間序列資料(Time Series)。鍵是“時間”。 eg.單一token5年的價格數據
-
橫斷面資料(Cross Section)。鍵是“個體”。 eg.2023.11.01當日crypto市場所有token的價格數據
-
面板資料(Panel)。鍵是“個體-時間”的組合。 eg.從2019.01.01-2023.11.01 四年所有token的價格數據。
原則:確定了資料的索引(鍵),就能知道資料應該在什麼層面沒有重複值。
檢查方式:
-
pd.DataFrame.duplicated(subset=[key1, key2, …])
-
檢查重複值的數量:pd.DataFrame.duplicated(subset=[key1, key2, …]).sum()
-
抽樣看重複的樣本:df[df.duplicated(subset=[…])].sample()找到樣本後,再用df.loc選出該索引對應的全部重複樣本
-
-
pd.merge(df1, df2, on=[key1, key2, …]indicator=True, validate=’1:1′)
-
在橫向合併的函數中,加入indicator參數,會產生_merge字段,對其使用dfm[‘_merge’].value_counts()可以檢查合併後不同來源的樣本數量
-
加入validate參數,可以檢驗合併的資料集中索引是否如預期一般(1 to 1、1 to many或many to many,其中最後一種情況其實等於不需要驗證)。如果與預期不符,合併過程會報錯併中止執行。
-
二、異常值/缺失值/極端值
產生異常值的常見原因:
-
極端情況。例如token價格0.000001$或市值僅50萬美元的token,隨便變動一點,就會有數十倍的回報率。
-
數據特性。例如token價格數據從2020年1月1日開始下載,那麼自然就無法計算2020年1月1日的報酬率數據,因為沒有前一日的收盤價。
-
數據錯誤。資料提供者難免會犯錯,例如將12元每token記錄成每token1.2元。
針對異常值和缺失值處理原則:
-
刪除。對於無法合理更正或修正的異常值,可以考慮刪除。
-
替換。通常用於極端值的處理,例如縮尾(Winsorizing)或取對數(不常用)。
-
填充。對於缺失值也可以考慮以合理的方式填充,常見的方式包括平均值(或移動平均)、插值(Interpolation)、填入0 df.fillna(0)、向前df.fillna(‘ffill’)/向後填充df.fillna(‘bfill’)等,要考慮填充所依賴的假設是否合。
機器學習慎用向後填充,有Look-ahead bias 的風險
針對極端值的處理方法:
1.百分位法。
透過將順序從小到大排列,將超過最小和最大比例的資料替換為臨界的資料。對於歷史數據較豐富的數據,此方法相對粗略,較不適用,強行刪除固定比例的數據可能造成一定比例的損失。
2.3σ / 三倍標準差法
標準差σfactor 反映因子資料分佈的離散程度,即波動性。利用μ±3×σ 範圍來辨識並取代資料集中的異常值,約有99.73% 的資料落入該範圍。此方法適用前提:因子資料必須服從常態分佈,即X∼N(μ,σ2)。
其中,μ=∑ⁿᵢ₌₁⋅Xi/N, σ²=∑ⁿᵢ₌₁=(xi-μ)²/n ,因子值的合理範圍是[μ−3×σ,μ+3×σ]。
對資料範圍內的所有因子做出以下調整:
此方法不足在於,量化領域常用的數據如股票價格、token價格常呈現尖峰厚尾分佈,並不符合常態分佈的假設,在該情況下採用3σ方法將有大量數據錯誤地被識別為異常值。
3.絕對值差中位數法(Median Absolute Deviation, MAD)
此方法基於中位數和絕對偏差,使處理後的資料對極端值或異常值沒那麼敏感。比基於平均值和標準差的方法更穩健。
絕對偏差值的中位數MAD=median ( ∑ⁿᵢ₌₁(Xi – Xmedian) )
因子值的合理範圍是[ Xmedian-n×MAD,Xmedian + n×MAD]。對資料範圍內的所有因子做出以下調整:
# 處理因子資料極端值狀況class Extreme(object): def __init__(s, ini_data): s.ini_data = ini_data def three_sigma(s,n=3): mean = s.ini_data.mean() std = s.ini_data .std() low = mean – n*std high = mean + n*std return np.clip(s.ini_data,low,high) def mad(s, n=3): median = s.ini_data.median() mad_median = abs(s.ini_data – median).median() high = median + n * mad_median low = median – n * mad_median return np.clip(s.ini_data, low, high) def quantile(s,l = 0.025, h = 0.975): low = s.ini_data.quantile(l) high = s.ini_data.quantile(h) return np.clip(s.ini_data, low, high)
三、標準化
1.Z-score標準化
-
前提:XN(μ,σ)
-
由於使用了標準差,此方法對於資料中的異常值較為敏感
x’ᵢ=(x−μ)/σ=(X−mean(X))/std(X)
2.最大最小值差標準化(Min-Max Scaling)
將每個因子數據轉換為在(0,1) 區間的數據,以便比較不同規模或範圍的數據,但它不會改變數據內部的分佈,也不會使總和變為1。
-
由於考慮極大極小值,對異常值敏感
-
統一量綱,利於比較不同維度的資料。
x’ᵢ=(xᵢ−min(x))/max(x)−min(x)
3.排序百分位(Rank Scaling)
將資料特徵轉換為它們的排名,並將這些排名轉換為介於0和1之間的分數,通常是它們在資料集中的百分位數。 *
-
由於排名不受異常值影響,此方法對異常值不敏感。
-
不保持數據中各點之間的絕對距離,而是轉換為相對排名。
NormRankᵢ=(Rankₓᵢ−min(Rankₓᵢ))/max(Rankₓ)−min(Rankₓ)=Rankₓᵢ/N
其中,min(Rankₓ)=0, N 為區間內資料點的總個數。
# 標準化因子資料class Scale(object): def __init__(s, ini_data,date): s.ini_data = ini_data s.date = date def zscore(s): mean = s.ini_data.mean() std = s.ini_data .std() return s.ini_data.sub(mean).div(std) def maxmin(s): min = s.ini_data.min() max = s.ini_data.max() return s.ini_data.sub(min ).div(max – min) def normRank(s): # 對指定列進行排名,method=’min’意味著相同值會有相同的排名,而不是平均排名ranks = s.ini_data.rank(method= ‘min’) return ranks.div(ranks.max())
四、數據頻率
有時獲得的數據並非我們分析所需的頻率。例如分析的層次為月度,原始資料的頻率為日度,此時就需要用到“下採樣”,即聚合資料為月度。
下採樣
指的是將一個集合裡的數據聚合為一行數據,例如日度數據聚合為月度。此時需要考慮每個被聚合的指標的特性,通常的操作有:
-
第一個值/最後一個值
-
平均數/中位數
-
標準差
上採樣
指的是將一行數據的數據拆分為多行數據,例如年度數據用在月度分析上。這種情況一般就是簡單重複即可,有時需要將年度資料按比例歸集於各個月份。
Falcon (https://falcon.lucida.fund /)是新一代的Web3投資基礎設施,它基於多因子模型,幫助用戶「選」、「買」、「管」、「賣」加密資產。 Falcon在2022年6月由Lucida所孵化。
更多內容可至 https://linktr.ee/lucida_and_falcon