跳到主要内容Python算法
Python 复现 FactSet Revere 供应链断裂与重构变量测度方法
基于丁浩员等(2024)《经济研究》论文,利用 Python 处理 FactSet Revere 全球供应链数据库,实现跨国供应链断裂与重构变量测度。流程包含原始数据清洗、沪深 A 股供应商筛选、关系标记调整、月度数据展开及断裂恢复转移指标计算。代码提供 Pandas 处理逻辑,解决时间序列衔接问题,支持不同恢复标记策略。
GopherDev11 浏览 1 对 FactSet 全球供应链数据库的简单解读
数据库可通过 WRDS 官网获取,包含 company.dta、relations.dta 两个数据文件及变量说明文档。
重要变量解读
company.dta
- start_和 end_: 公司信息的时间段数据。end_若为 01jan4000 默认视为至今。
- id: 企业在该套数据库中的唯一标识符,用于与 relations.dta 匹配。
- ticker: 上市企业代码。中国沪深 A 股为六位代码,需注意补齐深交所代码前的 0,结合年份可与国泰安数据库匹配。
- cusip 和 isin: 国际通用唯一标识符,北美地区为 cusip,其他为 isin。
- country: 公司所处地区(注意区分国家与地区,如 TW、HK、MO)。

relations.dta
- 时间段数据:表示公司在 start_至 end_期间的特征属性。
- rel_type: 供应链关系类型,包括供应商 (supplier)、客户 (customer) 等。该变量表示 target 企业是 source 企业的 rel_type。
- source/target id: 各自在数据库中的唯一标识符,可与 company.dta 匹配。
- source/target isin/cusip: 同上。

2 丁浩员等测度方式解读
2.1 断裂和恢复指标
数据为月度频次,以'供应链关系 - 月份'为单元。例如,若供应链关系在 2019 年 1 月至 6 月存在,则生成六条数据。
断裂 (Break)
若同一供应链关系对在 2019 年 1 月至 6 月存在,7 月至 9 月不存在,10 月至 12 月存在:
- 1 月至 5 月、10 月至 11 月,Break 为 0。
- 6 月和 12 月,Break 为 1,代表发生断裂行为。
- 7 月至 9 月期间无数据。

恢复 (Recover)
若供应链在 2019 年 6 月断裂后于 10 月恢复,论文注释中显示在 2019 年 6 月的 Recover 标'1'。

另一种标注逻辑是将恢复标记在恢复行为发生的当月(如 2019 年 10 月),下文将展示两种代码实现。
2.2 转移指标
重构中转移变量的测量逻辑如下:
若一家中国供应商有多个外国客户,以客户彻底断裂时间 T(不再出现恢复的最早时间)为基准,若与其他客户构建新关系的时间晚于 T,则将构建新关系的时间 Transfer 标记为 1。
- 供应商有 5 个外国客户 A、B、C、D、E。
- 与 A 的关系:2019 年 1-2 月、10-12 月。
- 与 B 的关系:2019 年 3-5 月。
- 与 C 的关系:2019 年 4-7 月。
- 与 D 的关系:2019 年 6-8 月。
- 与 E 的关系:2019 年 9-10 月。
最早彻底断链时间为 2019 年 5 月。则在 D 的 6 月份、E 的 9 月份 Transfer 标 1,其他为 0。
最后将 Recover 和 Transfer 的 1 合并,即为 Refill。
2.3 遗留问题
数据说明中提到筛选所有中国上市公司的跨国供应链关系,但转移指标说明中提及可转移到中国,二者可能存在矛盾,需根据研究需求明确界定。
3 供应链断裂与重构指标测度
目标是以中国沪深 A 股上市企业作为供应商,外国企业作为客户的供应链断裂与重构指标测度。
3.1 原始数据库处理
建议根据研究区间精简数据。例如研究区间为 2018-2024,保留 start_为 2018 年及之后的数据。
gen year=year(start_)
drop if year<2018
3.2 中国沪深 A 股上市企业作为供应商的筛选与调整
3.2.1 原始数据筛选
在 company.dta 里筛选出中国沪深 A 股上市企业,删除没有股票代码的数据,再删除不是 CN 的公司。
drop if missing(ticker)
drop if country!="CN"
导出至 Excel 进行粗略筛选,仅保留在沪深 A 股上市的企业(代码范围:000001-099999 深证,300000-399999 创业板,600000-699999 上证)。将 id 和股票代码保存为 CNcompany.xlsx。
在 relations.dta 中仅保留 customer 和 supplier 的关系。
keep if rel_type=="CUSTOMER" | rel_type=="SUPPLIER"
3.2.2 供应链数据标记
运行 Python 代码读取 relations.dta,筛选与中国沪深 A 股上市公司相关的供应链关系对。
import pandas as pd
unique = pd.read_excel('CNcompany.xlsx', header=[0])
IDlist = unique['id'].tolist()
df = pd.read_stata('relations.dta')
result = pd.DataFrame(columns=['start_', 'end_', 'id', 'rel_type', 'source_company_id', 'source_ticker', 'source_cusip', 'target_company_id', 'target_cusip', 'flag'], index=range(1, 200000))
index = 1
for i in range(len(df)):
if int(df['source_company_id'][i]) in IDlist and int(df['target_company_id'][i]) in IDlist:
result.loc[index, 'start_'] = df['start_'][i]
result.loc[index, 'end_'] = df['end_'][i]
result.loc[index, 'id'] = df['id'][i]
result.loc[index, 'rel_type'] = df['rel_type'][i]
result.loc[index, 'source_company_id'] = df['source_company_id'][i]
result.loc[index, 'source_ticker'] = df['SOURCE_ticker'][i]
result.loc[index, 'source_cusip'] = df['SOURCE_cusip'][i]
result.loc[index, 'target_company_id'] = df['target_company_id'][i]
result.loc[index, 'target_cusip'] = df['TARGET_cusip'][i]
result.loc[index, 'flag'] = 'S&T'
index += 1
elif int(df['source_company_id'][i]) in IDlist:
result.loc[index, 'start_'] = df['start_'][i]
result.loc[index, 'end_'] = df['end_'][i]
result.loc[index, 'id'] = df['id'][i]
result.loc[index, 'rel_type'] = df['rel_type'][i]
result.loc[index, 'source_company_id'] = df['source_company_id'][i]
result.loc[index, 'source_ticker'] = df['SOURCE_ticker'][i]
result.loc[index, 'source_cusip'] = df['SOURCE_cusip'][i]
result.loc[index, 'target_company_id'] = df['target_company_id'][i]
result.loc[index, 'target_cusip'] = df['TARGET_cusip'][i]
result.loc[index, 'flag'] = 'S'
index += 1
elif int(df['target_company_id'][i]) in IDlist:
result.loc[index, 'start_'] = df['start_'][i]
result.loc[index, 'end_'] = df['end_'][i]
result.loc[index, 'id'] = df['id'][i]
result.loc[index, 'rel_type'] = df['rel_type'][i]
result.loc[index, 'source_company_id'] = df['source_company_id'][i]
result.loc[index, 'source_ticker'] = df['SOURCE_ticker'][i]
result.loc[index, 'source_cusip'] = df['SOURCE_cusip'][i]
result.loc[index, 'target_company_id'] = df['target_company_id'][i]
result.loc[index, 'target_cusip'] = df['TARGET_cusip'][i]
result.loc[index, 'flag'] = 'T'
index += 1
else:
continue
result.to_excel('result.xlsx')
3.2.3 根据 rel_type 关系和 flag 标记进行调整
创建 CN_supplier 和 CN_customer 两个工作表。
- rel_type 为 customer:target 是 source 的客户。flag 为 S 的不动放 CN_supplier,flag 为 T 的调换位置放 CN_customer。
- rel_type 为 supplier:target 是 source 的供应商。flag 为 S 的调换位置放 CN_supplier,flag 为 T 的不动放 CN_customer。
- 列标题 source 改为 supplier,target 改为 customer。
对于 flag 标记为 S&T(两端均为中国沪深 A 股上市公司),若做跨国供应链分析,直接删除即可。
3.3 供应链关系的时间顺序调整与变量测度
3.3.1 时间顺序调整
大部分数据按时间顺序排列,但少部分存在接不上的情况(如上一条 end_早于下一条 start_)。需删除中间行或调整顺序以保证时间连续性。
3.3.2 月度数据的生成
在 Excel 中插入'supplier_customer'空列,拼接 source 和 target id 作为唯一标识符。确保非单行供应链关系调到最后边,避免代码报错。
import pandas as pd
dfindex = 1
monthList = ['2018-01', '2018-02', '2018-03', '2018-04', '2018-05', '2018-06', '2018-07', '2018-08', '2018-09', '2018-10', '2018-11', '2018-12', '2019-01', '2019-02', '2019-03', '2019-04', '2019-05', '2019-06', '2019-07', '2019-08', '2019-09', '2019-10', '2019-11', '2019-12', '2020-01', '2020-02', '2020-03', '2020-04', '2020-05', '2020-06', '2020-07', '2020-08', '2020-09', '2020-10', '2020-11', '2020-12', '2021-01', '2021-02', '2021-03', '2021-04', '2021-05', '2021-06', '2021-07', '2021-08', '2021-09', '2021-10', '2021-11', '2021-12', '2022-01', '2022-02', '2022-03', '2022-04', '2022-05', '2022-06', '2022-07', '2022-08', '2022-09', '2022-10', '2022-11', '2022-12', '2023-01', '2023-02', '2023-03', '2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06', '2024-07', '2024-08', '2024-09', '2024-10', '2024-11', '2024-12', '4000-01']
DATA = pd.read_excel('SUPPLIER_DATA.xlsx', sheet_name='Sheet1', header=[0])
SCchain = set(DATA['supplier_customer'])
SCchains = list(SCchain)
df = pd.DataFrame(columns=['supplier_customer', 'supplier_id', 'supplier_ticker', 'customer_id', 'TIME', 'BREAK', 'REFILL', 'RECOVER', 'TRANSFER'], index=range(1, 100000))
adjustSC = []
start = 0
end = 1000
for SC in SCchains[start:end]:
l = SC.split('-')
supplier_id = l[0]
customer_id = l[1]
index = DATA.loc[DATA['supplier_customer'] == SC].index[0]
count = 1
while DATA['supplier_customer'][index + count] == SC:
count += 1
if count == 1:
startmonth = DATA['start'][index]
endmonth = DATA['end'][index]
for j in range(monthList.index(endmonth) - monthList.index(startmonth) + 1):
df.loc[dfindex, 'supplier_id'] = supplier_id
df.loc[dfindex, 'customer_id'] = customer_id
df.loc[dfindex, 'supplier_customer'] = SC
df.loc[dfindex, 'TIME'] = monthList[monthList.index(startmonth) + j]
df.loc[dfindex, 'BREAK'] = 0
dfindex += 1
df.loc[dfindex - 1, 'BREAK'] = 1
elif count >= 2:
flag = 0
breaki = 0
for i in range(1, count + 1):
if monthList.index(DATA['end'][index + i - 1]) == monthList.index(DATA['start'][index + i]) and i != count:
continue
if monthList.index(DATA['end'][index + i - 1]) < monthList.index(DATA['start'][index + i]) or i == count:
if flag == 0:
startmonth = DATA['start'][index]
else:
startmonth = DATA['start'][index + breaki]
df.loc[index + i - 1, 'RECOVER'] = 1
endmonth = DATA['end'][index + i - 1]
for month in monthList[monthList.index(startmonth): monthList.index(endmonth) + 1]:
df.loc[dfindex, 'supplier_id'] = supplier_id
df.loc[dfindex, 'customer_id'] = customer_id
df.loc[dfindex, 'supplier_customer'] = SC
df.loc[dfindex, 'TIME'] = month
df.loc[dfindex, 'BREAK'] = 0
dfindex += 1
df.loc[dfindex - 1, 'BREAK'] = 1
flag = 1
breaki = i
elif monthList.index(DATA['end'][index + i - 1]) > monthList.index(DATA['start'][index + i]):
print(SC + " need to adjust")
adjustSC.append(SC)
adjustSC = set(adjustSC)
print(adjustSC)
print(len(adjustSC))
chain1 = set(df['supplier_customer'])
chains1 = list(chain1)
print(len(chains1))
print(len(SCchains))
df.to_excel(f'result_{start} to {end}.xlsx')
3.4 供应链转移的测量
读取调整好的供应商 Excel 表和断裂恢复变量 Excel 表,新建 Python 文件测量。
import pandas as pd
monthList = ['2018-01', '2018-02', '2018-03', '2018-04', '2018-05', '2018-06', '2018-07', '2018-08', '2018-09', '2018-10', '2018-11', '2018-12', '2019-01', '2019-02', '2019-03', '2019-04', '2019-05', '2019-06', '2019-07', '2019-08', '2019-09', '2019-10', '2019-11', '2019-12', '2020-01', '2020-02', '2020-03', '2020-04', '2020-05', '2020-06', '2020-07', '2020-08', '2020-09', '2020-10', '2020-11', '2020-12', '2021-01', '2021-02', '2021-03', '2021-04', '2021-05', '2021-06', '2021-07', '2021-08', '2021-09', '2021-10', '2021-11', '2021-12', '2022-01', '2022-02', '2022-03', '2022-04', '2022-05', '2022-06', '2022-07', '2022-08', '2022-09', '2022-10', '2022-11', '2022-12', '2023-01', '2023-02', '2023-03', '2023-04', '2023-05', '2023-06', '2023-07', '2023-08', '2023-09', '2023-10', '2023-11', '2023-12', '2024-01', '2024-02', '2024-03', '2024-04', '2024-05', '2024-06', '2024-07', '2024-08', '2024-09', '2024-10', '2024-11', '2024-12', '4000-01']
DATA = pd.read_excel('SUPPLIER_DATA.xlsx', sheet_name='Sheet1', header=[0])
df = pd.read_excel('result.xlsx', sheet_name='Sheet1', header=[0])
SCchain = set(DATA['supplier_customer'])
SCchains = list(SCchain)
if '8727858-99946865-688223' in SCchains:
SCchains.remove('8727858-99946865-688223')
df['supplier_id'] = df['supplier_id'].astype(str)
company = set(df['supplier_id'])
companies = list(company)
print(len(companies))
for company_code in companies:
company_chain = []
for SC in SCchains:
if SC.startswith(company_code):
company_chain.append(SC)
if len(company_chain) == 1:
continue
elif len(company_chain) > 1:
startDate = {}
endDate = {}
for chain in company_chain:
index = DATA.loc[DATA['supplier_customer'] == chain].index[0]
count = 1
while DATA['supplier_customer'][index + count] == chain:
count += 1
start = DATA['start'][index]
end = DATA['end'][index + count - 1]
startDate[chain] = start
endDate[chain] = end
mindate = monthList.index('4000-01')
for end_val in list(endDate.values()):
if monthList.index(end_val) < mindate:
mindate = monthList.index(end_val)
for chain, start in startDate.items():
if monthList.index(start) > mindate:
print(chain)
index = df.loc[(df['Relation'] == list_of_key[k]) & (df['Time'] == monthList[mindate])].index[0]
df.loc[index, 'TRANSFER'] = 1
df.to_excel('A.result.xlsx')
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online