简介:Zephyr数据库收录的中国跨国并购数据,覆盖1997年至2024年3月,时间跨度近三十年,适用于国际商务、金融经济学方向的研究者、研究生及企业战略分析人员,可支撑并购趋势、区域分布、行业特征等实证研究,也可用于课程设计或行业报告的数据底稿。压缩包内共2个文件,核心为Excel格式的数据表,数据按交易事件逐条记录,方便导入SPSS、Stata等计量软件,直接进行排序、筛选、透视或作图;另一HTML文档为数据来源说明,用于核对样本收录范围与数据口径,保障论文或报告引用时的准确性。整包仅9.42MB,体量轻便,下载后即可使用。目前已有81人学习/下载,对于需要快速获取长周期结构化并购数据的研究场景,这份资料能显著减少资料检索和整理的工作量,让用户专注于分析本身。
1. zephyr-中国跨国并购数据(1997-2024.3.8).zip:一份时间跨度二十多年的并购数据集,卡住你的往往是解压这第一关
研究中国跨国并购的人,迟早会拿到类似 zephyr-中国跨国并购数据(1997-2024.3.8).zip 这样的压缩包:一个以 zephyr 命名的、按中国跨国并购主题导出的数据文件,时间范围从 1997 年一直到 2024 年 3 月 8 日。它的数据源通常是 BvD 旗下的 Zephyr 全球并购交易数据库,里面记录的是中国买方、标的或卖方涉及的跨境并购交易。这类 zip 能解决的问题很具体:把一家研究报告里「2016 年中国企业海外并购金额激增」这种结论,变成你自己可以验证、筛选和统计的明细表。适合正在写论文、做行业复盘、建并购知识库的从业者和研究者。但这类数据集压缩包的落地使用,坑从来不在下载,而在解压、编码、日期和金额口径这四道关。这篇笔记从拿到 zip 开始,一步步讲到我跑出第一张可用的年度趋势表为止。
2. 认识 Zephyr 跨国并购数据:这份 zip 到底装的是什么
2.1 为什么研究中国跨国并购常用 Zephyr 而不是其他数据库
全球并购交易数据库里,Zephyr 与 S&P Capital IQ、Refinitiv SDC 是三个最常被学术论文引用的来源。Zephyr 的特点是覆盖欧洲和新兴市场交易的粒度较细,交易记录里既包含并购(M&A),也包含少量 IPO、风投和私有化交易。对于中国跨国并购研究,它有几个不可替代的选型理由。
第一,Zephyr 对交易结构的拆解比较完整。一笔跨境并购会拆出收购方、标的公司、出让方、交易顾问等多层信息,你可以根据买方国家或标的国家筛出「中国公司收购海外资产」和「外资收购中国资产」两个方向。第二,它与 Orbis 全球企业数据库同源,交易里的公司实体可以关联到工商注册、财务数据,做回归分析时不需要手工补字段。第三,导出格式可控,通过机构订阅账号可以直接拉出 csv 或 xls 的 zip 包,字段结构相对稳定,适合写可复现的清洗流程。
要注意的是,Zephyr 是商业数据库,普通个人没有直接下载入口,多数人是通过学校图书馆或研究机构的订阅账号导出,导出的压缩包往往就是类似标题这样的命名规则:数据库名加数据主题加时间范围。这也意味着你拿到的 zip 内部是标准数据库导出,不是某个分析师手工整理的表,字段名大概率是英文,而内容值大量是中文,这为后面的编码问题埋下了伏笔。
2.2 解压前先验货:用 zipinfo 和 unzip -t 检查完整性与文件清单
拿到 zip 的第一步不是双击解压,而是先看包里有什么。尤其当这个 zip 是从数据库导出工具里生成时,包内经常包含多个子文件:交易明细表、公司表、字段说明文档、甚至一个 readme.txt。先验货能避免你解压到一半发现缺文件。
在 Linux 或 macOS 终端下,用这两条命令足够:
ls -lh "zephyr-中国跨国并购数据(1997-2024.3.8).zip" zipinfo -v "zephyr-中国跨国并购数据(1997-2024.3.8).zip" | head -80zipinfo -v的详细模式会列出每个条目的压缩方式、压缩前后大小、CRC-32 校验值以及文件名。看到条目列表后,你就知道该按什么顺序处理了。对于几十 MB 以上的数据集,我一般还会顺手跑一次完整性测试:
unzip -t "zephyr-中国跨国并购数据(1997-2024.3.8).zip"unzip -t会逐个条目做 CRC 校验并输出 OK 或错误信息。如果结尾出现 "No errors detected",说明压缩包结构完整,可以进入解压环节。如果中途报某个条目缺失或 CRC 不匹配,先不要急着重下,后面第 3 章会专门讲损坏压缩包的修复。这一步花三十秒,能省掉后面解压到一半突然报错、还得回头重来的一小时。
2.3 导出文件的常见构成:从交易编号到交易金额的核心字段
Zephyr 的中国跨国并购导出,字段结构在不同订阅权限下会略有差异,但核心字段基本围绕「这笔交易是谁、什么时候、花了多少钱、买了什么」这几个问题展开。下面是一份常见的字段清单,也是后续清洗时要重点处理的列:
| 字段名 | 含义 | 典型取值 | 清洗注意点 |
|---|---|---|---|
| Deal No | 交易唯一编号 | 数字或混合字符串 | 必须读成字符串,防止长数字被截断 |
| Target Name | 标的公司名 | 中文或英文 | 注意同一公司名在不同年份的大小写差异 |
| Acquirer Name | 收购方公司名 | 中文或英文 | 存在多收购方时拆行 |
| Announced Date | 公告日期 | YYYY-MM-DD | 格式混杂,见 4.3 |
| Completed Date | 完成日期 | YYYY-MM-DD 或空 | 缺失率通常明显高于公告日 |
| Deal Status | 交易状态 | Completed / Pending / Withdrawn | 统计口径的分水岭 |
| Deal Value (USDm) | 交易金额 | 数值 | 单位陷阱,见 4.4 |
| Currency | 币种 | USD / EUR / HKD 等 | 跨国并购里常见非美元计价 |
| Stake | 收购股权比例 | 0-100 数值 | 少数交易只披露区间 |
| Target Country | 标的国家 | 国家名 | 注意港澳台地区标注方式 |
| Acquirer Country | 收购方国家 | 国家名 | 中国跨国并购的判定依赖这个字段 |
| Sector | 标的所属行业 | NAICS 或文本行业名 | 行业口径要提前统一 |
需要强调一点:不同时间导出的 zip,字段名里关于金额单位的写法很不一样。有的写Deal Value (USDm),有的写Deal Value (USD Thousand),还有的干脆只写Deal Value,单位要看字段说明文档。拿到压缩包后,先把字段说明文档(如果有)解出来读一遍,比对着字段名猜单位可靠得多。
3. 把 zip 完整解出来:中文编码、伪加密与密码移除
3.1 Linux 下解压中文 zip 的正确姿势:unzip -O gbk 与离线兜底
这类带中文文件名的 zip 在 Linux 下解压,最常见的翻车现场是:解压后文件名全部变成乱码,比如zephyr-涓浗璺ㄥ浗骞惰喘鏁版嵁。这不是压缩包坏了,而是 zip 内文件名用了 GBK 编码,而 Linux 系统的默认 locale 是 UTF-8,unzip 按 UTF-8 解码文件名,就解出了如上这样的文字。
解决办法是让 unzip 明白「请按 GBK 解释文件名」:
unzip -O gbk "zephyr-中国跨国并购数据(1997-2024.3.8).zip" -d ./data_raw-O参数指定非 UTF-8 字符集的解码方式,gbk对应中文 Windows 系统的文件名编码;-d ./data_raw指定解压目标目录,避免把一堆文件散落在当前目录。注意,并不是所有 unzip 版本都带-O,Debian/Ubuntu 的 unzip 通常支持,CentOS 的默认 unzip 经常不支持。如果执行时报invalid option -- O,就需要换 Python 兜底:
import zipfile with zipfile.ZipFile("zephyr-中国跨国并购数据(1997-2024.3.8).zip") as z: for info in z.infolist(): # 先把原始文件名按 gbk 解码,再转换成 utf-8 字符串 raw_name = info.filename.encode("cp437").decode("gbk", errors="replace") print(raw_name)这里解释一下为什么是cp437 -> gbk:zipfile 模块读取文件名时默认按 cp437 解码,拿到的是「被误解后的字符串」,要还原真实文件名就得先把它编码回原始字节,再用 gbk 正确解码。errors="replace"是为了防止个别特殊字符解码失败导致中断。
这段 Python 代码不需要任何第三方库,标准库 zipfile 在离线环境下也能跑,正好应对「Linux 服务器不能联网、又下载不了 unzip 补丁包」的场景。解压后如果目标机器没有中文字体或 locale 不支持 UTF-8,文件名依然可能在终端显示为?,但实际存储在磁盘上的文件名是正常的,不影响后续程序读取。
3.2 zip 伪加密的识别与修复:一个标志位引发的密码错觉
解压时突然弹出要求输入密码,而提供方又没给过密码——先别急着找人要或跑字典,你很可能遇到了 zip 伪加密。伪加密的意思是:压缩包在文件头里声明「本条目已加密」,但文件内容实际是明文压缩,数据根本没有经过加密处理。解压工具看到加密标志位,就停下来等密码。
表现是:不管输什么密码都报错,或者随便输一个密码也能顺利解压,又或者用 7-Zip 能直接解、用 unzip 却要求密码。判断是否伪加密的关键是看文件头里的通用标志位(general purpose bit flag)第 0 位。这一位为 1 代表加密,伪加密包就是只把这一位置 1 而没有真正加密数据。
可以用 Python 检查每一个条目的标志位:
import zipfile with zipfile.ZipFile("zephyr-中国跨国并购数据(1997-2024.3.8).zip") as z: for info in z.infolist(): if info.flag_bits & 0x0001: print("加密标志:", info.filename, hex(info.flag_bits))如果打印出来的条目能列出很多,而你又确认数据方没加密,那基本可以断定是伪加密。修复思路也直接:把 local header 和 central directory 里的加密位同时清零。local header 的加密位在第 6 字节,central directory 的加密位在第 8 字节,按 ZIP 规范修改这两个字节即可:
import struct import zipfile def strip_fake_encryption(zip_path: str, out_path: str) -> None: zin = zipfile.ZipFile(zip_path) targets = [] for info in zin.infolist(): if info.flag_bits & 0x0001: targets.append((info.header_offset, info.filename)) zin.close() if not targets: print("未检测到加密标志位,无需处理") return data = bytearray(open(zip_path, "rb").read()) # 清理 local file header 的加密位,header_offset 指向 PK\x03\x04 开头 for offset, name in targets: flag = struct.unpack_from("<H", data, offset + 6)[0] struct.pack_into("<H", data, offset + 6, flag & ~0x0001) print("已清理 local 标志位:", name) # 清理 central directory 的加密位,扫描所有 PK\x01\x02 签名 cd_pos = 0 while True: cd_pos = data.find(b"PK\x01\x02", cd_pos) if cd_pos < 0: break flag = struct.unpack_from("<H", data, cd_pos + 8)[0] if flag & 0x0001: struct.pack_into("<H", data, cd_pos + 8, flag & ~0x0001) cd_pos += 4 with open(out_path, "wb") as f: f.write(data) print("输出:", out_path) # strip_fake_encryption("zephyr-中国跨国并购数据(1997-2024.3.8).zip", "fixed.zip")这段代码先把所有带加密标志的条目列出来,再按 local header 偏移精确定位并清零,最后用PK\x01\x02签名扫描 central directory 并同步清理。两个位置都改,是因为有的解压工具读 local 标志,有的校验 central 标志,只改一边会留下隐患。
注意一个边界:如果压缩包是真加密,清零标志位后文件虽然能解出来,但内容会是乱码或 CRC 校验失败。所以修改之前一定先备份原文件,把它当成「只有伪加密才适用」的修复手段。我的习惯是先用参数unzip -t fixed.zip做一次校验,CRC 通过就说明内容确实没加密。
3.3 合法场景下的 zip 密码移除:只有这三种情况建议动手
zip 密码移除这个需求,实际工作中主要集中在三个正当场景:一是自己加密后忘了密码;二是数据提供方交付时附过密码,但邮件找不到了;三是机构账号导出的加密包由你负责处理且有权解压。除此之外的情况不建议碰,也没有必要。
先说一个最容易被忽略的尝试:空密码。部分数据库导出的「加密包」其实就是 3.2 的伪加密,密码位是空字符串,在命令行下直接回车就能解:
unzip -P "" "zephyr-中国跨国并购数据(1997-2024.3.8).zip"-P ""显式指定空密码。如果这一步成功,说明加密位只是摆设。如果确实有密码,且你能确认密码是简单数字或短单词,可以用 John the Ripper 的 zip2john 先把密码哈希抽出来再爆破:
zip2john "zephyr-中国跨国并购数据(1997-2024.3.8).zip" > zhash.txt john zhash.txt --format=zip --wordlist=rockyou.txtzip2john从 zip 里提取可用于密码猜测的哈希,--format=zip指定格式,--wordlist指定字典。这套流程只适合低强度密码的恢复,ZIP 的加密算法对字典攻击并不友好,复杂密码在普通机器上跑几天也未必有结果。更高效的做法永远是回到源头:找数据提供方的交付记录,或者让机构管理员重新生成下载链接。
3.4 损坏压缩包与 missing zip entry:先用 zip -F 修复再做决定
解压时报missing [N] bytes in zip entry或bad CRC,通常不是文件下载不完整,而是 zip 包在传输或拼接时被截断过。遇到这种情况,先做一次修复尝试:
cp "zephyr-中国跨国并购数据(1997-2024.3.8).zip" damaged.zip zip -F damaged.zip --out repaired.zipzip -F的作用是利用 zip 包内已有的 central directory 信息尝试修复偏移,它会把能救的条目救出来,输出为 repaired.zip。如果-F不够,还可以用-FF,它会扫描整个文件重建目录,但修复结果不一定保留所有文件。无论用哪个参数,修复完都要跑unzip -t repaired.zip验证,再决定是否把 damaged.zip 删掉。
另外提醒一个解压安全常识:无论从哪种渠道拿到的 zip,都不要直接在目标目录里无脑解压。一个恶意的 zip 可以在文件名里写上../../evil.sh,解压时突破目录跑到上层。在 Linux 下先看一遍文件名再动手:
unzip -Z1 "zephyr-中国跨国并购数据(1997-2024.3.8).zip" | grep -E '\.\./?'unzip -Z1只列出文件路径,grep 如果有输出,说明包里存在路径穿越级别的文件名,应该先隔离处理而不是直接解压。这个习惯适用于所有来路不明的数据集压缩包。
4. 用 Python 把并购数据读进 DataFrame:编码、字段与口径
4.1 先分清文件格式再决定读取路线
解压完成后,ls -la ./data_raw先看清楚包里的文件后缀。常见数据库导出的 zip 内部会有三种格式:csv、xlsx、dbf。它们的读取路线完全不同。
csv 文件用pandas.read_csv,但要先确认编码和分隔符;xlsx 用pandas.read_excel且需要 openpyxl 引擎;dbf 是 FoxPro 时代的库文件,在金融和咨询机构的旧系统里很常见,用dbfread库读取,或者在 LibreOffice 里转成 csv。在 Linux 下可以用file命令快速判断真实格式:
file ./data_raw/*如果输出显示CSV/Text、Microsoft Excel 2007+或dBASE,就按对应路线走。这里有个实操判断:csv 文件如果超过 200MB,不建议用 pandas 一次性读入,优先考虑polars的惰性读取,或者先用head -5看看表头,再决定是否分块。Zephyr 导出的全量中国跨国并购交易明细包几十 MB 到几百 MB 都正常,别一上来就把内存吃满。
4.2 编码三连:gbk、utf-8-sig 与 Excel 乱码的解法
中文并购数据文件的编码是第一个真正会拦人的坑。Windows 导出的 csv 绝大多数是 GBK/GB18030,少数新版系统会导出 UTF-8 但带不带 BOM 也不一定;而你在 Linux 下用 pandas 默认按 UTF-8 读取,立刻报UnicodeDecodeError。
稳妥的读取方式是先做小样本探测:
import pandas as pd probe = open("deals.csv", "rb").read(8192) for enc in ("utf-8-sig", "gbk", "gb18030", "utf-16"): try: probe.decode(enc) except UnicodeDecodeError: continue else: print("候选编码:", enc) break这个片段读文件头部 8KB 做解码测试,优先试 utf-8-sig 因为带 BOM 的文件一定以\xef\xbb\xbf开头,能直接识别;gbk 和 gb18030 依次覆盖常见中文 Windows 导出。注意这种盲试只能命中「小样本内不存在非法字符序列」的编码,误报率存在,所以得到候选编码后还要打印前几行确认公司名和标的名能正常显示:
df = pd.read_csv( "deals.csv", encoding="gbk", # 探测结果 dtype={"Deal No": "str"}, low_memory=False, )dtype={"Deal No": "str"}强制交易编号读成字符串,避免长数字被 pandas 识别为 int64 后丢失精度;low_memory=False让 pandas 一次性推断各列类型,避免大文件按块读取时同一列前后类型不一致。如果是 UTF-8 编码的文件,把编码参数改成utf-8-sig而不是utf-8,原因是有些数据商会给文件加 BOM。
Excel 用户的乱码场景值得单独提一下:pandas 处理好之后如果直接df.to_csv("out.csv"),用 Excel 双击打开看到中文全乱。这是因为 pandas 默认写出的 UTF-8 无 BOM,Excel 按本机 ANSI 代码页打开。解决方式是在写出时显式指定 BOM:
df.to_csv("out_utf8_bom.csv", index=False, encoding="utf-8-sig")utf-8-sig在写入时会添加 BOM 头,Excel 就能正确识别 UTF-8。这个细节在跨团队交付数据时几乎每次都出现。
4.3 日期字段:公告日、完成日与 1900 空值文学
并购数据里的日期字段是最容易「假干净」的。表面上看Announced Date列全是2023-09-18这样的标准文本,但当你pd.to_datetime一把梭之后,会发现报错或者出现大量 NaT。
常见情况有三种:一是混合格式,一部分行是2023-09-18,另一部分是2023/9/18;二是 Excel 序列号,比如45188这种数字;三是空值和异常值混入,比如1900-01-00或1899-12-30。
推荐的处理方式是先宽松解析,再针对失败值补解析:
import pandas as pd df["Announced Date"] = pd.to_datetime( df["Announced Date"], errors="coerce", format="%Y-%m-%d" ) # 解析失败的日期留下来单独处理 failed = df["Announced Date"].isna() if failed.any(): print("解析失败样本:", df.loc[failed, "Announced Date"].head(20))errors="coerce"保证整列解析不中断,失败的置为 NaT;format指定主格式提速。对于2023/9/18这类斜杠格式,可以用第二次pd.to_datetime(..., format="%Y/%m/%d")再补一次。对于 Excel 序列号,基准日期是 1899-12-30,处理方式是:
from datetime import timedelta def excel_serial_to_date(x): if pd.isna(x): return pd.NaT try: return pd.Timestamp("1899-12-30") + timedelta(days=int(x)) except (ValueError, TypeError): return pd.NaT df["date_from_serial"] = df["Announced Date"].map(excel_serial_to_date)Excel 序列号的基准选 1899-12-30 而不是 1900-01-01,是因为 Excel 故意保留了 1900 年闰年的错误,把 1900-02-29 也算进去了,这是做数据处理的人都应该记住的历史包袱。实际工作中,最好在解析前先df["Announced Date"].astype(str).str[:10]看一眼前十个字符的形态,再决定主格式,别盲目套模板。
4.4 金额与币种:跨国并购数据里最贵的单位陷阱
金额字段是并购数据里最值得敬畏的一列。Zephyr 导出时,Deal Value的单位通常在字段名里写明,比如Deal Value (USDm)表示百万美元,Deal Value (USD Thousand)表示千美元。但当你把数据合并到一张大表里时,不同文件的单位很容易被忽略。统计 2016 年中国跨国并购总额时,如果这一列有的文件是百万美元、有的文件是美元原值,最后算出来的年度总量会差几个数量级。
更隐蔽的是币种问题。跨国并购中大量交易的计价币种不是美元,而是欧元、港元、日元。Zephyr 一般会同时给出原币种金额和美元折算金额,字段名区别明显,但如果你只选了原币种列而没有同步选汇率列,后面按美元口径排序就会把大额欧元交易排错位置。
我的处理方式是先单独抽一个转换函数,把所有金额统一成美元,并保留一个value_unit列记录原始单位:
def to_usd(value: pd.Series, unit: str) -> pd.Series: factor_map = { "USDm": 1e6, "USD Million": 1e6, "USDk": 1e3, "USD Thousand": 1e3, "USD": 1.0, } factor = factor_map.get(unit, 1.0) return pd.to_numeric(value, errors="coerce") * factor df["deal_value_usd"] = to_usd(df["Deal Value (USDm)"], "USDm")pd.to_numeric(..., errors="coerce")把金额列里可能出现的-、n.a.、N/A统一转成 NaN,不会因为一两个脏值让整列报错。字段名里单位如果写的是USDm,转换时乘 1e6;如果原始文件单位混乱,就把 unit 参数做成列,逐行判断。
金额处理完,下一步统计前还有一个动作:按公告日期取当年平均汇率,把非美元交易的原币种金额折算成美元。我的习惯是不用年末汇率,因为并购交易的公告日和完成日可能跨年,统一按公告日所在月份的平均汇率折算,后续做时间序列分析时口径才稳。
5. 清洗阶段的避坑排查:并购数据去重与口径的四个翻车点
5.1 交易状态混淆:Completed、Pending 与 Announced 的统计口径之争
现象:你统计的「中国跨国并购交易数量」比监管披露的完成数量高出一大截,甚至翻倍。
原因:Zephyr 里交易状态通常有 Completed、Pending、Rumored、Withdrawn 等几类。很多研究者在筛选时只按时间范围过滤,没过滤状态,于是一堆尚在谈判中的 Pending 交易和已经终止的 Withdrawn 交易全被计入。
解决:先看这一列到底有哪些取值,再决定过滤规则。
df["Deal Status"] = df["Deal Status"].str.strip().str.title() print(df["Deal Status"].value_counts()) # 数量统计用全部交易,金额统计建议只看 Completed completed = df[df["Deal Status"] == "Completed"].copy()str.strip()去掉行尾空格,str.title()把completed归一到Completed。统计交易数量时可以保留 Pending 做「宣布交易」口径,但金额规模分析一定要区分状态,否则已终止的交易会把历史总金额虚增不少。
5.2 同一个交易出现多条记录:按 Deal No 去重前先想清楚粒度
现象:deal_df.duplicated(subset="Deal No").sum()返回好几千条重复,但直接drop_duplicates之后总金额又对不上原始导出。
原因:Zephyr 的交易明细里,一个交易编号可能对应多行记录——一个收购方收购两个标的、一个交易存在多个卖方、或买方由多家公司联合组成时,数据库会按公司维度拆行。这些行属于同一笔交易,但金额不一定重复。
解决:先按 Deal No 聚合,再看金额是否一致。
amount_check = df.groupby("Deal No").agg( 行数=("Deal No", "count"), 金额和=("deal_value_usd", "sum"), ) print(amount_check[amount_check["行数"] > 1].head(10))如果同一个 Deal No 的多行金额之和等于该交易公布的总金额,说明是拆分行,继续保留并按交易聚合;如果金额和与总金额不符,说明存在数据库录入错误,要给该交易打上异常标记。去重前先想清楚分析粒度:按交易计数,用drop_duplicates(subset="Deal No");按公司维度分析收购方行为,则不需要去重。
5.3 金额缺失三成以上:直接丢弃会让你的结论带偏见
现象:做金额分析时df.dropna(subset=["deal_value_usd"])后,样本量骤然少了百分之三十,而且剩余样本里大额交易占比明显偏高。
原因:Zephyr 的金额字段并非强制披露,未上市公司的小额并购、早期谈判中的交易经常没有金额。这些缺失不是随机发生的,通常偏向规模较小的交易。直接删除缺失行,等于把中小型并购从样本里系统性剔除。
解决:区分「缺失」和「金额为零」,并单独报告缺失率。
df["has_value"] = df["deal_value_usd"].notna() print("金额缺失率:", round(1 - df["has_value"].mean(), 3)) # 金额分析的主表 value_analysis = df[df["has_value"]].copy() # 数量分析保留全部 count_analysis = df.copy()做金额回归时,可以把has_value当作控制变量放进模型,或者在论文里明确写一句「金额分析仅覆盖已披露金额的交易,占全部交易的 X%」。这个 X% 必须报告,否则审稿人拿到数据一核对就会质疑结论。
5.4 日期与币种矛盾:完成日早于公告日、币种与金额不匹配
现象:某交易的 Completed Date 比 Announced Date 还早三个月,或者一笔标明的 USD 金额在按美元排序时明显偏离等量级。
原因:数据录入阶段常见两类错误——日期字段在跨系统迁移时错位;币种字段在导出时用了目标公司所在国默认币种,没有随交易原币种切换。前者会让时间序列分析出现负数的「交易耗时」,后者会让金额排序失真。
解决:不直接删除,而是打上数据质量异常标记,保留在数据集里供后续剔除或复审。
df["completed_before_announced"] = ( df["Completed Date"] < df["Announced Date"] ).fillna(False) usd_median = df.loc[ (df["Currency"] == "USD") & df["deal_value_usd"].notna(), "deal_value_usd" ].median() df["value_outlier"] = ( df["deal_value_usd"] > usd_median * 50 ).fillna(False)完成日早于公告日的行已经违反了正常的交易时间线,属于录入级错误;金额超过中位数五十倍的行需要人工复核,可能是把原币种金额直接当美元填入,也可能是真实存在的巨型交易。给这两类各留一列标记,最终分析时用~df["completed_before_announced"]过滤,而不是把这些行从文件里删掉——保留才能追溯。这点处理习惯,能让你半年后再看这份数据时还说得清每一行是去是留。
6. 把清洗后的跨国并购数据变成可持续查询的本地资料库
6.1 从 DataFrame 灌入 SQLite:按年、行业、买方国家检索
数据清洗完成后的下一步,是让它进入可以反复查询的结构。相比反复df[df["..."]==...],我更推荐把最终表灌入 SQLite,既能随时按年份、行业、买方国家组合检索,又不会每次重新读一遍两百兆的 csv。
import sqlite3 conn = sqlite3.connect("cn_cross_border_ma.db") df.to_sql("deals", conn, if_exists="replace", index=False) conn.execute('CREATE INDEX idx_deals_anno ON deals("Announced Date")') conn.execute('CREATE INDEX idx_deals_target ON deals("Target Country")') conn.close()to_sql把清洗后的 DataFrame 整表写进 SQLite,if_exists="replace"方便反复重建;两张索引分别覆盖时间检索和标的国家检索,这是跨国并购分析最常用的两个切片维度。检索时按年度看规模:
SELECT strftime('%Y', "Announced Date") AS year, sum(deal_value_usd) / 1e9 AS total_billion_usd FROM deals WHERE "Deal Status" = 'Completed' GROUP BY year;6.2 用三条标志性并购案做数据真实性校验
数据靠谱与否,与其相信交付方文档,不如用你脑海里已经有了的常识去校验。我会从研究时段里挑三笔媒体曝光度极高、金额和年份都不会记错的中国跨国并购案:2016 年中国化工宣布收购先正达、2016 年美的集团宣布收购库卡、2017 年海信收购东芝电视。在 SQLite 里按收购方名称模糊查,看金额数量级和公告日期能不能对上。
对得上,说明这份导出的金额单位和日期解析逻辑是对的;对不上,先回头查 4.3 和 4.4 两步,而不是怀疑数据源。凡是历史数据清洗,我最后都会留这一步「用已知答案检验未知表」的工序,它能拦下绝大多数因为单位或编码导致的结构性错误。这是我的习惯,也是希望你能带走的一个收尾动作。
希望帮到你。
本文还有配套的精品资源,点击获取