4、行业虚拟变量:什么是虚拟变量?创建行业虚拟变量矩阵
好,咱们接着聊行业中性化。前面几章铺垫了不少,今天要啃的这块骨头,叫「虚拟变量」。名字听着挺唬人,其实没那么玄乎。
4.1 虚拟变量到底是个啥?
说白了,虚拟变量就是把「类别」变成「数字」的一种手段。
你想想看,咱们的因子模型里,输入的都是数字——市盈率、市净率、动量因子,全是数值。但行业呢?「银行」、「医药」、「消费」——这些都是文字标签,计算机不认识。怎么办?
嗯,这里就要用到虚拟变量了。
虚拟变量(Dummy Variable),也叫哑变量。它的核心思想很简单:把一个有 K 个类别的变量,转换成 K-1 个 0/1 变量。
举个例子。假设只有三个行业:银行、医药、消费。我们不需要三个变量,只需要两个:
- 变量1:是否为银行?是=1,否=0
- 变量2:是否为医药?是=1,否=0
- 消费呢?当变量1和变量2都是0时,自然就是消费
这就是「K-1」的由来。为什么要少一个?因为如果三个行业都用三个变量,会出现「完全多重共线性」——三个变量加起来永远等于1,模型就没法算了。我在项目中遇到过有人踩这个坑,跑回归时发现系数全是NaN,查了半天才发现是虚拟变量陷阱。
4.2 行业虚拟变量矩阵的构建
回到多因子模型。我们通常有几十个行业,比如申万一级行业就有31个。我们需要为每只股票生成一个行业虚拟变量向量。
假设我们有 N 只股票,K 个行业。那么行业虚拟变量矩阵就是一个 N × K 的矩阵。每只股票,在它所属的行业那一列是1,其他列都是0。
我习惯用 pandas 的 get_dummies 函数来做这件事。一行代码搞定。
import pandas as pd
import numpy as np
# 假设我们有一个DataFrame,包含股票代码和所属行业
df = pd.DataFrame({
'stock': ['000001', '000002', '000003', '000004', '000005'],
'industry': ['银行', '房地产', '医药', '银行', '消费']
})
# 创建行业虚拟变量矩阵
dummy_matrix = pd.get_dummies(df['industry'], prefix='行业')
print(dummy_matrix)
输出结果是这样的:
行业_房地产 行业_消费 行业_医药 行业_银行
0 0 0 0 1
1 1 0 0 0
2 0 0 1 0
3 0 0 0 1
4 0 1 0 0
你看,000001和000004都是银行股,它们在「行业_银行」这一列都是1。其他股票在对应行业列是1,其余是0。完美。
4.3 为什么要用虚拟变量做中性化?
这个问题很关键。为什么我们不直接用行业名称,非要转成0和1?
原因在于:回归模型需要数值输入。当我们做行业中性化时,本质上是在做一步回归:
因子值 = α + β₁×行业1 + β₂×行业2 + ... + βₖ×行业K + 残差
这个残差,就是剔除行业影响后的「纯净因子」。而行业变量必须是数值,虚拟变量就是最自然的编码方式。
我曾经见过有人直接用行业编号(1,2,3,...)做回归,结果一塌糊涂。为什么?因为编号隐含了顺序关系——行业3比行业2大,但实际没有这种大小关系。虚拟变量完美避免了这个问题。
核心要点:虚拟变量只表示「属于」或「不属于」,不表示任何顺序或大小关系。这正是我们做行业中性化所需要的。
4.4 实战中的注意事项
嗯,这里有几个坑,我踩过,你们就别踩了。
警告:虚拟变量陷阱
如果你把 K 个行业全部生成 K 个虚拟变量,然后直接丢进回归模型,会出现完全多重共线性。因为每只股票在 K 个变量上的值加起来恒等于1。解决办法:要么去掉一个行业作为基准,要么在回归时去掉截距项。
我的习惯:用 get_dummies 时设置 drop_first=True,自动去掉第一个类别。这样生成的矩阵就是 K-1 列,回归时加上截距项,完美。
# 推荐做法:去掉第一个类别
dummy_matrix = pd.get_dummies(df['industry'], prefix='行业', drop_first=True)
print(dummy_matrix.columns)
# 输出:['行业_消费', '行业_医药', '行业_银行']
# 少了「行业_房地产」,它成了基准行业
还有一个细节:训练集和测试集的行业类别要一致。如果训练集有31个行业,测试集突然冒出一个新行业,虚拟变量矩阵的列数就对不上了。我在做回测时遇到过这种情况,后来用了一个技巧:先获取所有可能的行业列表,然后统一生成虚拟变量,确保列对齐。
4.5 行业虚拟变量的可视化
为了让你更直观地理解,我画了一张图。这张图展示了行业虚拟变量矩阵的结构。
你看这张图,每只股票在它所属的行业那一列是1(彩色格子),其他列都是0(灰色格子)。每行有且仅有一个1。这就是行业虚拟变量矩阵的典型结构。
4.6 代码实战:完整流程
最后,我给你一个完整的代码示例。假设我们有一个因子值,需要做行业中性化:
import pandas as pd
import statsmodels.api as sm
# 模拟数据
np.random.seed(42)
n = 1000
df = pd.DataFrame({
'stock': [f'stock_{i}' for i in range(n)],
'industry': np.random.choice(['银行', '医药', '消费', '房地产', '科技'], n),
'factor': np.random.randn(n) # 原始因子值
})
# 创建行业虚拟变量(去掉第一个类别)
dummy = pd.get_dummies(df['industry'], prefix='行业', drop_first=True)
# 添加截距项
X = sm.add_constant(dummy)
# 回归:因子值 ~ 行业虚拟变量
model = sm.OLS(df['factor'], X).fit()
# 残差就是行业中性化后的因子
df['factor_neutral'] = model.resid
print(f"原始因子方差: {df['factor'].var():.4f}")
print(f"中性化后因子方差: {df['factor_neutral'].var():.4f}")
print(f"方差减少比例: {(1 - df['factor_neutral'].var()/df['factor'].var())*100:.2f}%")
运行这段代码,你会发现中性化后的因子方差变小了。为什么?因为行业因素解释了一部分因子波动,我们把这部分剔除了。剩下的,就是纯粹的个股选择能力。
嗯,这就是虚拟变量的全部秘密。说白了,就是把行业标签翻译成计算机能理解的语言,然后做回归,剔除行业影响。下一章我们会深入聊回归的具体细节,以及如何验证中性化效果。
公众号:蓝海数据掘金营,微信deep3321