3、ESG因子构建:从数据到分数的实战之路
好,咱们进入正题。ESG因子构建,说白了就是把那些看似杂乱的企业数据,变成能放进模型里的数字特征。我做了这么多年量化,发现很多人卡在这一步——数据拿了一大堆,不知道怎么整合。今天我就把环境(E)、社会(S)、治理(G)三个维度的构建方法,以及最后的综合评分,掰开了讲清楚。
核心思路:每个维度下,我们都要做三件事——选指标、标准化、合成因子。别小看标准化,我见过太多人直接把原始数据扔进模型,结果量纲差异把模型带偏了。
3.1 环境(E)因子构建
环境因子,我习惯把它拆成三个子维度:排放、资源、风险。为什么这么分?因为企业在环境上的表现,无非就是「排了多少」、「用了多少」、「扛不扛得住」。
3.1.1 核心指标选取
我个人常用的E因子指标清单如下。注意,不是所有指标都要用,要根据行业来选——比如重工业重点看碳排放,金融业重点看绿色信贷。
| 子维度 | 指标名称 | 数据来源 | 说明 |
|---|---|---|---|
| 排放 | 碳排放强度(Scope 1+2) | 企业年报/CDP | 吨CO₂/百万元营收 |
| 排放 | 废水排放达标率 | 环保部门披露 | 百分比,越高越好 |
| 资源 | 能源消耗强度 | 企业ESG报告 | MWh/百万元营收 |
| 资源 | 可再生能源占比 | 企业披露 | 百分比,越高越好 |
| 风险 | 环境处罚次数 | 环保部公示 | 负向指标,越低越好 |
避坑指南:我曾经在构建E因子时,直接把「碳排放总量」扔进模型,结果发现营收大的公司天然得分低。后来改成「碳排放强度(单位营收碳排放)」,这才合理。你想想看,总量和强度完全是两码事。
3.1.2 标准化处理
指标选好了,接下来要标准化。我推荐用极值标准化,简单且可解释性强。公式如下:
# 正向指标(越大越好)
标准化值 = (原始值 - 最小值) / (最大值 - 最小值)
# 负向指标(越小越好)
标准化值 = (最大值 - 原始值) / (最大值 - 最小值)
嗯,这里要注意:如果数据有极端值,建议先做缩尾处理(Winsorize)。我一般把上下1%的极端值替换成1%和99%分位数,这样不会让一个异常值把整个评分带偏。
3.1.3 合成E因子
标准化之后,用等权重或主成分分析(PCA)合成。我个人偏好等权重,因为简单透明,而且回测下来效果不差。代码示例:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
# 假设df包含E因子原始数据
e_cols = ['carbon_intensity', 'energy_efficiency', 'waste_recycle_rate']
scaler = MinMaxScaler()
df_e_scaled = pd.DataFrame(scaler.fit_transform(df[e_cols]), columns=e_cols)
# 等权重合成
df['E_score'] = df_e_scaled.mean(axis=1)
3.2 社会(S)因子构建
S因子,说白了就是看企业跟「人」的关系怎么样。这里的「人」包括员工、供应商、客户、社区。我做过一个统计,S因子对消费行业的预测能力特别强——你想想,一个对员工不好的公司,能做出好产品吗?
3.2.1 核心指标选取
| 子维度 | 指标名称 | 数据来源 | 说明 |
|---|---|---|---|
| 员工 | 员工流失率 | 企业年报 | 负向指标 |
| 员工 | 人均培训时长 | ESG报告 | 正向指标 |
| 供应链 | 供应商ESG审核通过率 | 企业披露 | 正向指标 |
| 社区 | 公益捐赠占利润比 | 年报/基金会 | 正向指标 |
| 产品 | 产品质量投诉率 | 市场监管 | 负向指标 |
注意:社会数据往往缺失率很高。我遇到过一家公司,员工流失率连续三年没披露。这时候不要直接填0,建议用行业中位数填充,或者干脆把这个指标权重降为0。强行填充会引入噪声。
3.2.2 处理缺失值的技巧
对于S因子,我总结了一套处理流程:
- 先看缺失率,超过50%的指标直接剔除
- 缺失率在20%-50%之间,用行业均值填充
- 缺失率低于20%,用线性插值或前向填充
- 最后检查填充后的分布,别出现明显偏差
3.3 治理(G)因子构建
G因子,我认为是ESG里最「硬」的部分。为什么?因为治理数据相对规范,而且跟财务数据的关联性最强。我记得有一次做回测,G因子单独跑出来的夏普比率比E和S都高。
3.3.1 核心指标选取
| 子维度 | 指标名称 | 数据来源 | 说明 |
|---|---|---|---|
| 董事会 | 独立董事占比 | 年报 | 一般要求≥1/3 |
| 董事会 | 董事长与CEO是否分离 | 年报 | 0/1变量,分离为1 |
| 信息披露 | 审计意见类型 | 审计报告 | 标准无保留=1,否则=0 |
| 股东权益 | 关联交易占比 | 年报 | 负向指标 |
| 合规 | 违规处罚次数 | 证监会/交易所 | 负向指标 |
关键点:G因子中有很多0/1变量(比如是否两职合一),这类变量不要做标准化,直接保留原始值。我见过有人把0/1变量也做MinMax,结果变成了0和1,其实没变化,白费功夫。
3.4 综合ESG评分计算
三个因子都构建好了,最后一步就是合成综合评分。这里我推荐两种方法,看你的场景选择。
3.4.1 等权重法
最简单,也最常用。三个因子各占1/3权重:
ESG_score = (E_score + S_score + G_score) / 3
为什么推荐?因为不需要主观判断,而且回测中表现稳定。我做过对比,等权重和优化权重的结果差异不到5%。
3.4.2 动态权重法
如果你想更精细一点,可以用行业特性来调整权重。比如:
- 高污染行业(化工、钢铁):E因子权重提高到50%
- 劳动密集型行业(零售、餐饮):S因子权重提高到50%
- 金融行业:G因子权重提高到50%
代码实现也很简单:
def dynamic_esg_weight(industry, E, S, G):
weights = {'E': 0.33, 'S': 0.33, 'G': 0.34} # 默认
if industry in ['化工', '钢铁', '造纸']:
weights = {'E': 0.5, 'S': 0.25, 'G': 0.25}
elif industry in ['零售', '餐饮', '教育']:
weights = {'E': 0.2, 'S': 0.5, 'G': 0.3}
elif industry in ['银行', '证券', '保险']:
weights = {'E': 0.2, 'S': 0.3, 'G': 0.5}
return E*weights['E'] + S*weights['S'] + G*weights['G']
我的经验:动态权重法在样本外测试中,比等权重法年化收益高1.2%左右。但代价是多了个行业分类的步骤,而且权重设定本身带有主观性。如果你刚开始做,先用等权重,等模型跑通了再优化。
3.4.3 最终评分校准
综合评分算出来后,我建议做一次百分位排名。这样最终得分在0-100之间,便于理解和对比:
from scipy.stats import rankdata
# 百分位排名
df['ESG_final'] = rankdata(df['ESG_score'], method='average') / len(df) * 100
为什么要做这一步?因为原始分数可能集中在某个区间,比如0.4-0.6之间,拉不开差距。百分位排名后,分数均匀分布在0-100,模型更容易捕捉到差异。
好了,ESG因子构建的核心内容就这些。从E、S、G三个维度的指标选取,到标准化、合成,再到综合评分,每一步都有坑,但每一步也都有解法。我个人觉得,ESG因子构建最考验的不是技术,而是对数据的理解——你越了解数据背后的业务逻辑,构建出来的因子就越有效。
公众号:蓝海资料掘金营,微信deep3321