第二章:数据源与采集——碳排放数据来源与API使用
做碳因子挖掘,第一步不是写代码,而是搞清楚数据从哪来。
我刚开始接触碳金融时,以为碳排放数据就像股票行情一样,拉个API就能拿到。结果发现完全不是这么回事。碳排放数据的来源分散、格式各异、更新频率也不统一。说白了,这是个典型的「脏活累活」。
但没办法,数据质量直接决定因子有效性。你想想看,如果底层数据都是错的,再漂亮的模型也是白搭。
2.1 三大主流碳排放数据源
目前业界公认的三大数据源是:CDP、Trucost 和 Sustainalytics。我个人的经验是,这三家各有侧重,最好搭配使用。
| 数据源 | 覆盖范围 | 数据特点 | 适用场景 |
|---|---|---|---|
| CDP | 全球约1.5万家企业 | 企业自主披露,含Scope 1/2/3 | ESG整合、气候风险分析 |
| Trucost | 全球约1.2万家企业 | 基于投入产出模型估算 | 碳因子挖掘、组合碳足迹 |
| Sustainalytics | 全球约1.3万家企业 | 结合披露与估算,含争议事件 | ESG评级、负面筛选 |
2.1.1 CDP:最权威的披露数据
CDP(原碳披露项目)的数据是企业自己填的问卷。这意味着什么?意味着数据质量参差不齐。
我记得有一次做回测,发现某家能源公司的碳排放数据突然下降了80%。查了半天,原来是那年他们换了问卷模板,把Scope 2的核算方法从「位置法」改成了「市场法」。嗯,这里要注意:同一家公司不同年份的数据口径可能不一致。
CDP的数据结构大致是这样的:
{
"company_id": "AAPL",
"year": 2023,
"scope_1": 12345.6, // 单位:吨CO2e
"scope_2": 6789.0, // 位置法
"scope_3": 45678.9, // 仅包含部分类别
"methodology": "位置法",
"verification": true // 是否经过第三方核验
}
2.1.2 Trucost:估算数据的标杆
Trucost(现属S&P Global)用的是投入产出模型。说白了,就是根据企业的营收和行业碳排放强度,倒推出碳排放量。
这种方法的好处是覆盖全——哪怕企业不披露,也能给你一个估算值。但坏处也很明显:估算误差可能很大。
我个人的习惯是:对于披露数据完整的企业,优先用CDP;对于缺失数据的企业,用Trucost做插补。两者结合,效果最好。
2.1.3 Sustainalytics:争议事件与碳风险
Sustainalytics(现属Morningstar)的特色是结合了争议事件分析。比如某公司虽然披露的碳排放不高,但被爆出有环境诉讼,那它的碳风险评分就会下调。
这个数据源在做负面筛选时特别有用。我曾在构建碳风险因子时,把Sustainalytics的争议事件得分作为一个哑变量加入模型,结果因子的夏普比率提升了0.3。
2.2 数据采集方法与API使用
数据源选好了,接下来就是怎么把数据拉下来。这里我分享三种常用的采集方式。
2.2.1 手动下载(适合小规模研究)
CDP和Sustainalytics都提供Excel/CSV下载。如果你只是做几十只股票的分析,手动下载完全够用。
但要注意:下载前一定要看清楚数据字典。我见过有人把「Scope 2 (location-based)」和「Scope 2 (market-based)」搞混,结果因子方向都算反了。
2.2.2 API采集(适合自动化流程)
对于量化策略,手动下载显然不现实。这时候就需要API了。
以Trucost的API为例,调用方式如下:
import requests
import pandas as pd
# Trucost API配置
API_KEY = "your_api_key_here"
BASE_URL = "https://api.trucost.com/v1"
# 获取碳排放数据
def get_emissions(company_ids, year):
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
payload = {
"companies": company_ids,
"year": year,
"metrics": ["scope_1", "scope_2", "scope_3"]
}
response = requests.post(
f"{BASE_URL}/emissions",
headers=headers,
json=payload
)
if response.status_code == 200:
return pd.DataFrame(response.json())
else:
print(f"Error: {response.status_code}")
return None
# 示例:获取苹果和微软的2023年数据
df = get_emissions(["AAPL", "MSFT"], 2023)
print(df.head())
2.2.3 数据清洗与对齐
数据拉下来后,最头疼的是对齐问题。不同数据源的公司标识符不一样——CDP用LEI码,Trucost用ISIN,Sustainalytics用自有ID。
我常用的做法是:
- 先建立一个主映射表,把各数据源的ID统一映射到股票代码
- 然后按年份合并数据
- 最后做缺失值处理
举个例子:
# 建立映射表
mapping = {
"AAPL": {"cdp_id": "LEI_123", "trucost_id": "ISIN_US0378331005"},
"MSFT": {"cdp_id": "LEI_456", "trucost_id": "ISIN_US5949181045"}
}
# 合并数据
def merge_emissions(cdp_df, trucost_df, mapping):
# 先映射到股票代码
cdp_df["ticker"] = cdp_df["cdp_id"].map(
{v["cdp_id"]: k for k, v in mapping.items()}
)
trucost_df["ticker"] = trucost_df["trucost_id"].map(
{v["trucost_id"]: k for k, v in mapping.items()}
)
# 按ticker和year合并
merged = pd.merge(
cdp_df, trucost_df,
on=["ticker", "year"],
how="outer",
suffixes=("_cdp", "_trucost")
)
return merged
2.3 知识体系总览
说了这么多,我画了一张图帮你理清思路:
2.4 实战建议
最后,给几个实战中的建议:
- 优先用CDP的核验数据——有第三方核验的数据,质量通常更高
- Trucost做插补——对于缺失数据,用Trucost的估算值填补,但要在因子中加一个「是否估算」的哑变量
- Sustainalytics做风控——争议事件往往预示着未来的碳风险,可以作为一个预警信号
- 建立数据质量监控——我每周都会跑一遍数据完整性检查,看看有没有异常跳变
嗯,关于数据源和采集,就先聊到这儿。下一章我们会深入讨论碳因子的具体构造方法,到时候会用到今天讲的数据。
公众号:蓝海资料掘金营,微信deep3321