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 月至 2019 年 6 月存在,那么这个供应链关系在生成的数据中就应该有六条数据。
关于断裂的测量,文章只有短短的两行话,并没有说得很详细。
以我的理解,如果同一个供应链关系对在 2019 年 1 月至 6 月存在,7 月至 9 月不存在,10 月至 12 月存在,那么 2019 年 1 月至 5 月、10 月至 11 月,Break 变量为 0,代表未发生断裂行为。2019 年 6 月和 2019 年 12 月,Break 为 1,代表发生断裂行为。而 2019 年 7 月至 9 月,该供应链对无数据(这是我无法确定的,这段时间标 0 和标 1 感觉都不合适,我就当做该期间数据不存在)。
恢复指标的测量比较好理解,还是上边的例子,同一个供应链关系,在 2019 年 6 月断裂后又在 2019 年 10 月恢复,从论文注释中可以看出,他们是在 2019 年 6 月的 Recover 标'1'。
而我其实是希望更好的捕捉到供应链恢复这个行为,想将 2019 年 10 月的 Recover 标记为'1'。因此,我把两种标注的代码都在下文展示,注意看代码中的注释提示。
2.2 转移指标的解读
关于重构中转移的变量感觉可能争议比较大,在此我只表明我对文章的理解和猜测,同样并不代表作者本来的做法。
如果一家中国供应商,在整个数据库中有多个不同的外国客户,以与客户彻底断裂时间,注意是彻底的断裂,即不再出现恢复情况的最早时间 T 为基准,若与其他客户构建供应链关系的时间晚于 T,则将其构建新供应链的时间的 Transfer 标记为 1。例如:
- 某中国供应商在数据库中有且只有 5 个外国客户,分别为 A、B、C、D、E
- 与外国客户 A 在 2019 年 1 月至 2 月和 2019 年 10 月至 12 月存在供应链关系
- 与外国客户 B 在 2019 年 3 月至 5 月存在供应链关系
- 与外国客户 C 在 2019 年 4 月至 7 月存在供应链关系
- 与外国客户 D 在 2019 年 6 月至 8 月存在供应链关系
- 与外国客户 E 在 2019 年 9 月至 10 月存在供应链关系
那么最早彻底断链时间为 2019 年 5 月,则在 D 的 6 月份 Transfer 标 1,E 的 9 月份 Transfer 标 1,其他为 0。当然这种做法也存在 bug,会把后续构建的新供应链,都认为是第一个彻底断裂的客户转移过来的,但是事实上情况可能更加复杂。
当然也有一种可能,就是跟断裂和恢复一样,标在原供应链的结束时间(即 B 的 5 月份,C 的 7 月份,D 的 8 月份都标 1),同样也有 bug,比如 E 供应链的构建到底是 BCD 谁转移过来的?事实上会有三条链断只为了构建一个新的 E 吗?所以这个转移变量的测量有点儿弄不清。
再次声明,以上是我的理解,并不一定是这篇文章中真正的标注方式,我也不知道该怎么去处理这块数据,大家自行根据需求把控。所以如果在论文中用到这个变量,建议最好明确把自己的思路说一遍,防止出问题。
最后把 Recover 和 Transfer 的 1 合并,即为 Refill。
2.3 遗留的问题
可能是作者和审稿人的不注意,留下了个难以解释的问题,在数据说明部分提到了'本文从该数据库中筛选出所有中国上市公司的跨国供应链关系数据。其中'跨国'说明研究数据应该不包括客户为中国企业的供应链关系对,但是转移指标的说明时,又说可以转移到中国,有点儿相互矛盾。
3 供应链断裂与重构指标的测度
依据丁浩员等的做法,该部分的目标是进行以中国沪深 A 股上市企业作为供应商,外国企业作为客户的供应链断裂与重构指标的测度。本节不考虑中国企业作为客户的情况。
3.1 原始数据库处理
原始数据确实有点大,建议根据自己所需要的研究区间进行精简操作。例如我的研究区间是 18-24,那就只需要保留 start_是 2018 年及之后的数据,对 company.dta、relations.dta 都进行如下操作,并另存为新数据文件:
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(上证),当然如果想保留北证,就加上 400000-499999、800000-899999。其他的诸如新三板(9 开头)、美股(英文)、港股上市(五位数字代码)的都给删除。
最后根据企业 id、股票代码进行 excel 的删除重复值操作,保证一个 id 就只对应一个股票代码。将这两列保存为 CNcompany.xlsx,主要用于后续的数据匹配工作。
在 relations.dta 中仅保留关系为 customer 和 supplier 的数据,去除竞争者合作者之类的干扰数据。
keep if rel_type=="CUSTOMER" | rel_type=="SUPPLIER"
3.2.2 供应链数据标记
运行下方 Python 代码(注意看我的注释),读取 relations.dta,将结果输出至 result.xlsx,即为与中国沪深 A 股上市公司相关的所有供应链关系对。
import pandas as pd
unique=pd.read_excel('CNcompany.xlsx',header=[0])
IDlist=unique['id'].tolist()#读取唯一 id,在这个列表里的 id 代表的都是中国沪深 A 股上市公司
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)):
print(i)#仅用来查看运行进度
#如果 source 和 target 都是中国沪深 A 股上市公司,录信息,标记为 S&T,表示位于 source 和 target 端
if int(df['source_company_id'][i]) in IDlist and int(df['target_company_id'][i]) in IDlist:
result['start_'][index]=df['start_'][i]
result['end_'][index]=df['end_'][i]
result['id'][index]=df['id'][i]
result['rel_type'][index]=df['rel_type'][i]
result['source_company_id'][index]=df['source_company_id'][i]
result['source_ticker'][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=
index+=
(df[][i]) IDlist:
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=
index+=
(df[][i]) IDlist:
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=df[][i]
result[][index]=
index+=
:
result.to_excel()
3.2.3 根据 rel_type 关系和 flag 标记进行调整
至此时,reslut.xlsx 上有着供应链关系仅有 customer 和 supplier,至少有一端是中国沪深 A 股上市公司,以及标记中国企业具体在哪一端的 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,完成单一端为中国沪深 A 股上市公司的数据调整。 注意:以上操作不一定要按我的方法来,只要准确理解了 rel_type 和 flag 标记的含义,自行处理即可。
对于 flag 标记为 S&T,即两端均为中国沪深 A 股上市公司,如果做到是跨国供应链,直接删除即可(我是这么做的)。如果做的是全球供应链,建议与自己的导师讨论,这里不再讨论。
3.3 供应链关系的时间顺序调整与断裂、恢复变量测度
3.3.1 供应链关系的时间顺序调整
在数据中,大部分(95% 左右)供应链关系对是按照时间顺序下来的,即上一条时间线的 end_是早于或等于下一条的 start_
但是有少部分数据像下图这样,第 3 行和第 5 行中间出现了接不上的情况,这时候就得删除第四行,或者把第四行换到第三行的位置,才能让供应链关系的时间顺序被后边的代码识别。
需要做这部分工作也的确是迫于无奈,因为我一开始没有想到合适的办法去解决这一现象,耗费了大量时间人工来做。 后来我求助了外援,他是用 stata 进行该部分的处理,似乎也求助过 deepseek、chatGPT 等 AI 工具,但是具体做法没向我透露,需要大家自行想办法。
3.3.2 月度数据的生成
运行下方 Python 之前,在 excel 里插入'supplier_customer'空列,并把 source_company_id 和 target_company_id 做一个字符串拼接,中间用'-'分割,作为每段供应链关系的唯一标识符。
注意,由于代码运行逻辑中需要靠着从供应链关系开始的第一行往下读,如果 excel 里最后一行是这段供应链关系唯一的一行数据,往下读的时候就会报错。所以请事先作出调整,把非单行供应链关系调到最后边。
运行后会输出该批次有问题需要调整的拼接值,如果上一步弄好了那就不会输出,如果有问题的话需要手工调整。
建议分批运行代码(调整代码中的 start 和 end 值),完成后进行 excel 拼接。
最终输出最终包含 break 和 recover 的数据文件。
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',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
DATA=pd.read_excel(,sheet_name=,header=[])
SCchain=(DATA[])
SCchains=(SCchain)
df=pd.DataFrame(columns=[,,,,,,,,],index=(,))
adjustSC=[]
start=
end=
SC SCchains[start:end]:
l=SC.split()
supplier_id=l[]
customer_id=l[]
index=DATA.loc[DATA[]==SC].index[]
count=
DATA[][index+count]==SC:
count+=
count==:
startmonth=DATA[][index]
endmonth=DATA[][index]
j (monthList.index(endmonth)-monthList.index(startmonth)+):
df[][dfindex]=supplier_id
df[][dfindex]=customer_id
df[][dfindex]=SC
df[][dfindex]=monthList[monthList.index(startmonth)+j]
df[][dfindex]=
dfindex+=
df[][dfindex-]=
count>=:
flag=
breaki=
monthList.index(DATA[][index+i-])==monthList.index(DATA[][index+i]) i!=count:
monthList.index(DATA[][index+i-])<monthList.index(DATA[][index+i]) i==count:
flag==:
startmonth=DATA[][index]
:
startmonth=DATA[][index+breaki]
df[][index+i-]=
endmonth=DATA[][index+i-]
month monthList[ monthList.index(startmonth): monthList.index(endmonth)+]:
df[][dfindex]=supplier_id
df[][dfindex]=customer_id
df[][dfindex]=SC
df[][dfindex]=month
df[][dfindex]=
dfindex+=
df[][dfindex-]=
flag=
breaki=i
monthList.index(DATA[][index+i-])>monthList.index(DATA[][index+i]):
(SC+)
adjustSC.append(SC)
adjustSC=(adjustSC)
adjustSC=(adjustSC)
(adjustSC)
((adjustSC))
chain1=(df[])
chains1=(chain1)
((chains1))
((SCchains))
df.to_excel()
3.4 供应链转移的测量
读取 3.2.1 中调整好的以中国沪深 A 股上市公司为供应商 excel 表(即代码中的 SUPPLIER_DATA.xlsx),以及上一步测算好供应链断裂、恢复变量的 excel 表(即代码中的 result.xlsx)。 由于转移变量的测量逻辑比较复杂,所以需要单独新建一个 py 文件测量,代码如下:
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',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
DATA=pd.read_excel(,sheet_name=,header=[])
df=pd.read_excel(,sheet_name=,header=[])
SCchain=(DATA[])
SCchains=(SCchain)
SCchains.remove()
df[]=df[].astype()
company=(df[])
companies=(company)
((companies))
company companies:
company_chain=[]
SC SCchains:
SC.startswith(company):
company_chain.append(SC)
(company_chain)==:
(company_chain)>:
startDate={}
endDate={}
chain company_chain:
index=DATA.loc[DATA[]==chain].index[]
count=
DATA[][index+count]==chain:
count+=
start=DATA[][index]
end=DATA[][index+count-]
startDate[chain]=start
endDate[chain]=end
mindate=monthList.index()
end (endDate.values()):
monthList.index(end)<mindate:
mindate=monthList.index(end)
chain,start startDate.items():
monthList.index(start)>mindate:
(chain)
df.loc[index,]=
df.to_excel()


