2. 因子IC的计算方法:Spearman秩相关系数、Pearson相关系数、IC计算实战

聊到因子IC的计算,很多新手第一反应就是「直接算相关系数呗」。嗯,这话没错,但具体用哪种相关系数,什么时候用,坑在哪里,这里面的门道可不少。我个人习惯把IC计算分成两派:一派是Pearson,一派是Spearman。今天咱们就把这两兄弟掰开揉碎了讲清楚。

2.1 Pearson相关系数:线性关系的「尺子」

Pearson相关系数,说白了就是衡量两个变量之间线性关系的强度和方向。公式长这样:

ρ = Cov(X, Y) / (σ_X * σ_Y)

其中Cov是协方差,σ是标准差。取值范围在[-1, 1]之间。1代表完全正相关,-1代表完全负相关,0代表没有线性关系。

但这里有个大坑——Pearson对异常值极其敏感。我在项目中遇到过一回,某只小盘股因为财报数据异常,因子值突然飙到天上,结果整个IC序列都被带偏了。后来我花了整整一个下午排查,才发现是这一个点搞的鬼。

⚠️ 避坑指南:Pearson要求数据近似正态分布,且不能有极端异常值。如果你的因子值分布很「胖」或者有离群点,Pearson的结果可能不靠谱。

另外,Pearson只能捕捉线性关系。你想想看,如果因子和收益率之间是U型关系(比如低因子值和高因子值都表现好,中间表现差),Pearson算出来可能接近0,但这不代表因子没用。嗯,这种情况我建议你画个散点图先看看。

2.2 Spearman秩相关系数:稳健的「排名派」

Spearman秩相关系数,说白了就是把原始数据换成排名再算Pearson。公式其实一样,只不过输入的是秩次(rank):

ρ_s = 1 - (6 * Σd_i²) / (n * (n² - 1))

其中d_i是第i个样本在两个变量上的排名差,n是样本数。

为什么我更喜欢Spearman? 三个字:稳健性。它不关心具体的数值大小,只关心谁排在前面谁排在后面。这就意味着,哪怕某只股票的因子值因为数据错误变成了100倍,只要它的排名没变,Spearman的结果就不受影响。

我记得有一次做多因子模型回测,用Pearson算出来的IC序列波动特别大,换成Spearman之后,序列变得平滑多了,而且选股效果反而更好。后来复盘发现,原来是数据源里有些股票的价格调整没做干净,导致收益率计算有偏差。Spearman因为只看排名,天然免疫了这类问题。

💡 实战建议:在量化选股中,我通常默认使用Spearman秩相关系数。原因很简单——我们做选股,本质上就是在做排名。因子值差0.01还是差0.02,其实没那么重要,重要的是谁在前20%,谁在后20%。

2.3 两种方法的对比

对比维度 Pearson相关系数 Spearman秩相关系数
数据类型 连续数值 排名/顺序
异常值敏感度
关系类型 线性关系 单调关系(线性或非线性)
数据分布要求 近似正态 无要求
选股场景适用性 一般 推荐

你可能会问:「那我是不是永远都用Spearman就行了?」也不尽然。如果你在做因子择时,或者需要精确衡量因子值与收益率之间的线性弹性,Pearson还是有它的用武之地的。但如果是做截面选股,我个人建议无脑上Spearman。

2.4 IC计算实战:手把手写代码

光说不练假把式。咱们直接上代码,看看在Python里怎么算IC。

import pandas as pd
import numpy as np
from scipy.stats import spearmanr, pearsonr

def calc_ic(factor_series, return_series, method='spearman'):
    """
    计算因子IC值
    
    Parameters:
    -----------
    factor_series : pd.Series
        因子值,index为股票代码
    return_series : pd.Series
        下期收益率,index为股票代码
    method : str
        'spearman' 或 'pearson'
    
    Returns:
    --------
    ic_value : float
        IC值
    p_value : float
        显著性p值
    """
    # 对齐数据,去掉缺失值
    common_idx = factor_series.dropna().index.intersection(
        return_series.dropna().index
    )
    f = factor_series[common_idx]
    r = return_series[common_idx]
    
    if method == 'spearman':
        ic, p = spearmanr(f, r)
    elif method == 'pearson':
        ic, p = pearsonr(f, r)
    else:
        raise ValueError("method must be 'spearman' or 'pearson'")
    
    return ic, p

# 示例:计算某一天的截面IC
# 假设我们有2024-01-05的因子值和2024-01-06的收益率
factor_data = pd.Series({
    '000001': 2.5, '000002': 1.8, '000003': 3.2,
    '000004': 0.9, '000005': 2.1
}, name='factor')

return_data = pd.Series({
    '000001': 0.03, '000002': 0.01, '000003': 0.05,
    '000004': -0.02, '000005': 0.02
}, name='return')

ic_spearman, p_spearman = calc_ic(factor_data, return_data, 'spearman')
ic_pearson, p_pearson = calc_ic(factor_data, return_data, 'pearson')

print(f"Spearman IC: {ic_spearman:.4f}, p-value: {p_spearman:.4f}")
print(f"Pearson IC: {ic_pearson:.4f}, p-value: {p_pearson:.4f}")

这段代码看起来简单,但有几个细节我得提醒你:

  1. 一定要对齐数据。因子值和收益率必须对应同一批股票,而且要去掉缺失值。我曾经因为忘记dropna,结果IC算出来全是NaN,排查了半天才发现是某只股票停牌导致收益率缺失。
  2. 注意时间窗口。因子值通常是T日收盘后的数据,收益率应该是T+1日到T+N日的收益。别搞反了,否则就是「用未来预测过去」,属于典型的过拟合。
  3. p值也很重要。IC值高不代表因子有效,还得看p值是否显著。一般p值小于0.05才算统计显著。

2.5 滚动IC序列:看因子是否「稳定」

单日的IC只能告诉你这一天因子有没有选股能力。但我们要的是因子能不能持续稳定地赚钱。所以,我们需要计算滚动IC序列——也就是每天算一个IC,然后连成一条线。

def calc_rolling_ic(factor_df, return_df, window=20, method='spearman'):
    """
    计算滚动IC序列
    
    Parameters:
    -----------
    factor_df : pd.DataFrame
        因子值矩阵,index为日期,columns为股票代码
    return_df : pd.DataFrame
        收益率矩阵,index为日期,columns为股票代码
    window : int
        滚动窗口大小(天数)
    method : str
        'spearman' 或 'pearson'
    
    Returns:
    --------
    ic_series : pd.Series
        滚动IC序列,index为日期
    """
    dates = factor_df.index
    ic_values = []
    
    for i in range(len(dates)):
        # 取当天的因子值和收益率
        f = factor_df.iloc[i]
        r = return_df.iloc[i]
        
        # 计算IC
        ic, _ = calc_ic(f, r, method)
        ic_values.append(ic)
    
    ic_series = pd.Series(ic_values, index=dates)
    
    # 计算滚动均值(可选)
    ic_ma = ic_series.rolling(window=window).mean()
    
    return ic_series, ic_ma

# 示例:假设我们有100天的数据
# factor_df 和 return_df 的形状都是 (100, 500)
# 代表100天,500只股票
ic_daily, ic_ma20 = calc_rolling_ic(factor_df, return_df, window=20)

print(f"IC均值: {ic_daily.mean():.4f}")
print(f"IC标准差: {ic_daily.std():.4f}")
print(f"IC>0占比: {(ic_daily > 0).mean():.2%}")
print(f"ICIR: {ic_daily.mean() / ic_daily.std():.4f}")

这里有个关键指标——ICIR(信息系数比率),也就是IC的均值除以标准差。ICIR越高,说明因子越稳定。我个人习惯把ICIR大于0.5的因子列为「可用」,大于1.0的列为「优质」。

🎯 核心要点:IC衡量的是因子在某一天的选股能力,ICIR衡量的是因子在时间序列上的稳定性。两者缺一不可。一个IC高但ICIR低的因子,说白了就是「三天打鱼两天晒网」,不太靠谱。

2.6 知识体系总览

说了这么多,咱们用一张图把IC计算的核心逻辑串起来:

因子IC计算知识体系 输入数据 输入数据 因子值(截面数据) 下期收益率(截面数据) 计算方法选择 Pearson相关系数 Spearman秩相关系数 两者对比选择 输出结果 单日IC值 + p值 滚动IC序列 ICIR(信息系数比率) 因子选股能力评估

这张图把IC计算的整个流程串起来了。从输入数据开始,到选择计算方法,再到输出结果,最后用于评估因子选股能力。你写代码的时候,也可以按照这个流程来组织逻辑。

💡 我的个人习惯:在实盘环境中,我会同时计算Spearman IC和Pearson IC,然后对比两者的差异。如果差异很大,说明数据里可能有异常值或者非线性关系,这时候我会进一步检查数据质量。如果两者差异很小,那说明数据质量不错,直接用Spearman IC做决策就行。

好了,IC的计算方法就讲到这里。记住一句话:IC是因子选股能力的「体检报告」,算对了,你就能知道因子到底行不行;算错了,后面所有的分析都是白搭。下一节咱们聊聊怎么用IC序列来评估因子的稳定性,到时候我会分享一个我踩过的坑——关于IC序列的「假稳定」现象。


公众号:蓝海资料掘金营,微信deep3321