前两课定义了采集流程,这一课把不同来源的记录整理成能共同使用的数据表。关键不只是去掉空值,还要解释一行代表什么、同一篇论文怎样合并、冲突字段保留哪一份。只有这些规则明确,后面的统计和建模才有稳定的输入。
前置阅读:Pandas 数据分析与可视化(本篇的所有操作都是那篇的进阶应用)、系列二第 1-2 课(数据来源)。
先盘家底:数据质量审计
清洗之前先量化”有多脏”。对 raw 层跑一次审计,每个字段看三个数:缺失率、异常值、来源间不一致率:
import pandas as pd
raw = pd.read_json("data/raw/papers_2026-08-28.jsonl", lines=True)
audit = pd.DataFrame({
"missing_rate": raw.isna().mean(),
"unique_count": raw.nunique(),
"dtype": raw.dtypes,
})
print(audit.sort_values("missing_rate", ascending=False))
# 按来源看质量差异
print(raw.groupby("source")[["year", "authors", "venue"]]
.apply(lambda g: g.isna().mean()))
审计时应按来源分别查看缺失和格式差异。例如预印本可能尚无正式发表 venue,网页里的年份也可能写成“in press”。这些情况需要不同处理:尚未发生的发表信息不能凭空补齐,无法解析的年份应保留原值并标记。先处理影响主键、切分和标签的字段,再决定其他字段是否保留;缺失率高本身并不是删除字段的充分理由。
字段级清洗:每个字段一条规则
标题:规范化用于去重,原样用于展示
import re
import unicodedata
def normalize_title(t: str) -> str:
t = unicodedata.normalize("NFKD", t) # 统一 Unicode(全角→半角等)
t = t.lower().strip()
t = re.sub(r"[^a-z0-9一-鿿 ]", " ", t) # 标点变空格
return re.sub(r"\s+", " ", t).strip()
df["title_norm"] = df["title"].map(normalize_title)
注意保留两列:title(原始,展示用)和 title_norm(规范化,匹配用)。规范化是有损操作,原始值必须留底。
年份: coercion + 合理性边界
df["year"] = pd.to_numeric(df["year"], errors="coerce")
# 科研论文的合理范围;范围外的当缺失处理,而不是乱猜
df.loc[~df["year"].between(1950, 2026), "year"] = pd.NA
作者:从四种格式到一种
arXiv 给 ["Ashish Vaswani", ...] 列表,Crossref 给 {"given": "Ashish", "family": "Vaswani"} 对象列表,网页给分号连接的字符串。统一成”名 姓”字符串列表:
def normalize_authors(value, source):
if value is None or (isinstance(value, float) and pd.isna(value)):
return []
if source == "crossref":
return [f"{a.get('given', '')} {a.get('family', '')}".strip()
for a in value]
if source == "conf_page":
return [n.strip() for n in str(value).split(";") if n.strip()]
return list(value) # arxiv / openalex 已是列表
df["authors"] = [normalize_authors(v, s) for v, s in zip(df["authors"], df["source"])]
机构名同理,但要更狠一点:MIT、Massachusetts Institute of Technology、M.I.T. 是同一所——建一个别名映射表手工维护 Top 100 机构,剩下的保留原样。别试图全自动归一,机构名的变体数量超出想象,小映射表 + 人工维护是性价比最高的方案。
重复合并:四条记录的实体对齐
同一篇论文可能以”arXiv 预印本 + OpenAlex 记录 + Crossref 正式版 + 会议名单”四种身份出现。合并策略分两档:
强匹配(自动合):DOI 相同,或 arXiv ID 相同。零误判,直接合。
弱匹配(规则合):title_norm 完全相同 + 第一作者姓氏相同 + 年份差 ≤ 1。三个条件同时满足才合——只凭标题合并会把同名综述论文(“A Survey on X”满地都是)错误合并。
df = df.sort_values("source_priority") # crossref > openalex > arxiv > conf
merged = df.groupby("merge_key", as_index=False).agg({
"title": "first", # 高优先级来源的标题
"year": "max", # 正式发表年份优先
"venue": lambda s: s.dropna().iloc[0] if s.notna().any() else pd.NA,
"citations": "max", # 引用数取最大(各来源统计口径不同,取最全的)
"source": lambda s: "|".join(sorted(set(s))), # 保留全部来源痕迹
})
source 合并成 "crossref|openalex" 这种形态,来源痕迹永远不能丢——数据出问题时它是唯一的排查线索。
质量门:清洗结果要过验收
清洗不是跑完脚本就结束,要有一组硬指标验收:
def quality_gate(df):
checks = {
"总行数": len(df),
"paper_id 重复率": df["paper_id"].duplicated().mean(),
"title 缺失率": df["title"].isna().mean(),
"year 缺失率": df["year"].isna().mean(),
"作者为空的占比": (df["authors"].str.len() == 0).mean(),
}
assert checks["paper_id 重复率"] == 0, "主键仍有重复"
assert checks["title 缺失率"] < 0.001
return checks
断言失败的字段打回重洗。这套门在每次数据更新时重跑——清洗规则对新数据失效是常态,门拦住的就是”规则悄悄失效”这件事。
落地:写进 cleaned 层
merged.to_parquet("data/cleaned/papers_2026-08-28.parquet",
compression="snappy", index=False)
带日期的文件名 + Parquet 格式,遵循大数据管理一篇的分层纪律。raw → cleaned 的转换脚本进 Git,任何人可以重放。
踩坑排查
| 症状 | 原因 | 处理 |
|---|---|---|
| 合并后论文数异常少 | 弱匹配条件太严/太松 | 抽样人工检查 50 对,调条件 |
| 标题规范化后中文没了 | 正则只保留 a-z | 字符类里加 一-鿿 |
| year 大量缺失 | 某来源日期字段路径变了 | 回查 raw 列,契约测试报警 |
| 同一作者被拆成两个人 | 姓名格式未归一(“Vaswani, A.” vs “Ashish Vaswani”) | 姓氏+名字首字母做宽松匹配键 |
| cleaned 表行数每天波动大 | 采集层不稳定 | 查上游采集日志,清洗层不背锅 |
| merge 后 citations 倒退 | 取 max 掩盖了来源口径差异 | 保留各来源原值,另算 merged 值 |
作品集证据
本课产出:一份数据质量审计报告、一套字段级清洗规则、强/弱两档实体对齐逻辑、可重复执行的质量门。“我设计过四来源异构数据的清洗与实体对齐方案,误判率通过抽样验证”——这句是数据分析岗面试的硬通货。
练习
- 对 raw 数据跑一次完整质量审计,按缺失率排序输出报告。
- 实现弱匹配合并规则,抽样 50 对人工核对,计算精确率和召回率。
- 为 Top 20 机构建别名映射表,量化映射前后机构分布的变化。
- 把质量门加进清洗脚本,故意构造一份带重复 paper_id 的输入验证断言触发。
面试常问
Q:多来源数据合并的优先级怎么定? 按来源可信度定字段级优先级:Crossref 的出版信息最权威,OpenAlex 的引用关系最全,arXiv 的预印本时间最早,网页数据兜底。合并时保留全部来源痕迹,冲突字段按优先级取信,且优先级规则写进文档。
Q:清洗规则怎么验证有效性? 三件套:抽样人工核对(量化误判率)、质量门硬指标(重复率、缺失率断言)、规则单测(边界输入的输出固定)。只靠”跑完看结果差不多”的清洗不可信。
Q:原始数据为什么要原样保留? 清洗规则会迭代。留 raw 层意味着规则改进后可以不重新采集就重放清洗;同时 raw 是数据出问题时唯一的对账依据。存储成本远低于重采成本。
Q:实体对齐的精确率和召回率怎么权衡? 科研数据场景宁缺毋滥:错误合并(两篇变一篇)比漏合并(一篇变两篇)危害大,前者污染所有下游统计。所以弱匹配设多重条件保精确率,召回率的损失用”来源痕迹保留”来兜底排查。
💬 评论
这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。