一段客户关系到底在哪一个月开始断掉,往往是后续所有研究最容易被忽略、却又最关键的一步。没有这一层断裂识别,后面的存续力和恢复力分析都没有真正可靠的起点。本案例要做的,就是把这种“断裂时点”从原始关系数据里完整拆出来,并最终落成一张可直接使用的抵抗力结果面板。
案例背景
本案例围绕跨国供应链关系的断裂识别构建结果面板。研究对象是中国供应商与海外客户之间的关系链条,核心问题是:在逐月关系的时间维度上,一段跨国供应链关系究竟是在哪一个月发生断裂。项目最终形成的是抵抗力结果面板,记录的是关系在每一个月的断裂状态,而不是静态的关系存量。
案例价值
第一,方法可迁移。这条链路不仅适用于当前抵抗力结果面板,也适用于其他跨国供应链结果数据的构建任务。第二,全流程工程化。项目从原始关系数据出发,经过样本筛选、月度展开、断裂识别、结果抽取,再到公司匹配,形成完整结果交付闭环。第三,教学与复用友好。每一步都有明确输入输出,中间结果文件保存完整,很适合课堂演示、论文复刻和后续再开发。
案例代码获取
可直接在此下载:ppmandata.net/codeBase/list,或者扫码成为超级课程会员免费获取。

指标定义与构建方法
本案例最终保留一个核心结果字段:Break。它的含义是:当前月关系存在、下一月关系断开时,该月记为 1;如果下一月关系仍然持续,则记为 0。从当前结果看,抵抗力结果面板共有 444,852 条月度观察记录,其中 Break=1 的记录共有 20,175 条,Break=0 的记录共有 424,677 条,Break 均值为 0.0454。这说明在完整关系链路中,真正的断裂月份占比不高,但一旦识别出来,就能成为后续存续力与恢复力分析的关键起点。
Python 实现案例
第一步
任务目标是从 FactSet 原始关系数据中筛出“中国供应商 - 海外客户”样本。关键函数是 step1_sample_filter.py 中围绕 CUSTOMER 和 SUPPLIER 两个方向构造的筛选逻辑。技术难点在于原始关系方向并不统一,必须先把关系折算成一致口径,否则后面的月度面板和断裂识别都会偏掉。
mask_b = ( (chunk["rel_type"] == "CUSTOMER") & (chunk["source_isin"].str[:2] == config.SOURCE_COUNTRY) & (chunk["target_isin"].str[:2] != config.SOURCE_COUNTRY))
第二步
任务目标是把区间型关系展开成逐月面板。关键函数是 expand_row_to_months。技术难点在于结束月份边界处理,脚本里通过 end_dt - 1 day 做区间校正,避免把终止月多算进去。
defexpand_row_to_months(row): start_p = row["start_dt"].to_period("M") end_p_adj = (row["end_dt"] - pd.Timedelta(days=1)).to_period("M") months = pd.period_range(start=start_p, end=end_p_adj, freq="M")return months.tolist()
第三步
任务目标是识别断裂月份。关键函数是 step3_break_sustainability.py 中基于 shift(-1) 的逐月断裂检测逻辑。技术难点在于必须先把关系转换成逐月链条,才能准确识别“当前月有、下一月无”的断裂时点。
panel["next_month"] = panel.groupby(group_cols)["month"].shift(-1)panel["Break"] = np.where(cond_gap | cond_last, 1, 0)
第四步
任务目标是抽取抵抗力结果面板并并回公司匹配信息。关键函数是 step4_recovery_indicators.py 中输出 Break 子表的逻辑,以及 step5_match_resset.py 中把匹配字段并回结果面板的逻辑。技术难点在于这一步已经不是单纯算值,而是要把断裂结果整理成一张可直接交付的最终 CSV。
df_break = panel[id_cols + ["Break"]].copy()df_break.to_csv(config.BREAK_CSV, index=False, encoding="utf-8-sig")