使用 ydata-profiling 处理敏感数据:脱敏、样本控制与 PII 防护实战指南
使用 ydata-profiling 处理敏感数据:脱敏、样本控制与 PII 防护实战指南
对于医疗记录、金融交易、个人联系方式等敏感数据集,生成分析报告时最核心的诉求并非统计能力本身,而是**"报告绝不泄露任何个体记录"**。ydata-profiling(本仓库 src/data_profiling 模块)为此提供了一整套内置的敏感数据处理机制:既可以通过一行配置自动隐藏类别型与文本变量的取值,也可以完全关闭样本与重复行展示,还可以用模拟数据替换真实样本。本文以 docs/features/sensitive_data.md 为骨架,结合源码与默认配置,完整讲解敏感数据场景下的报告生成、脱敏原理、mock 样本注入与类型推断陷阱,读完即可在私有数据环境落地一套不泄露原始记录的 profiling 流程。
为什么需要敏感模式:聚合信息与个体记录的区别
在隐私受限的语境下(例如私有健康记录),一份包含样本行的报告本身就可能构成数据泄露。ydata-profiling 的设计哲学是:报告中只保留聚合统计信息(计数、均值、分位数、频数分布等),而不暴露任何个体记录。默认配置中,报告会包含数据集的前若干行(head)、末尾若干行(tail)以及重复行明细,这些内容在敏感场景下必须被移除或替换。
同时需要明确的是,本项目的 profiling 分析完全在本地完成,不会将数据发送到外部服务,因此天然适合在私有网络环境中处理敏感数据。
一键启用:sensitive=True 配置组
profile_report 提供了 sensitive 布尔开关,一行代码即可切换到隐私安全配置:
report = df.profile_report(sensitive=True)
该开关并非魔法,而是映射到 Config.arg_groups 中预定义的 sensitive 配置组,其内容为:
"sensitive": {
"samples": None, # 关闭所有样本展示
"duplicates": None, # 关闭重复行展示
"vars": {"cat": {"redact": True}, "text": {"redact": True}}, # 脱敏类别/文本列
}
从配置处理逻辑看(profile_report.py),sensitive 与 explorative 一样属于"配置组"(config groups),它会在默认配置之上执行更新:report_config = report_config.update(cfg.dict(exclude_defaults=True))。配置的合并顺序为:配置文件预设(config_file/minimal)→ 配置组(sensitive/explorative)→ 自定义 config 对象 → **kwargs,因此后传入的参数优先级更高,可以在敏感模式基础上做更细粒度的覆盖。
敏感模式实际做了什么
对照默认配置 config_default.yaml,可以清晰看到 sensitive=True 覆盖了哪些默认值:
| 配置项 | 默认值(非敏感) | sensitive=True 生效值 | 效果 |
|---|---|---|---|
samples.head / samples.tail / samples.random |
10 / 10 / 0 |
全部关闭 | 不再展示数据集真实行样本 |
duplicates.head |
10 |
关闭 | 不再展示重复行明细 |
vars.cat.redact |
false |
true |
类别型列取值被脱敏 |
vars.text.redact |
false |
true |
文本型列取值被脱敏 |
注意:
sensitive配置组并未关闭所有统计项,数值列的 min/max/分位数等聚合指标仍会计算——这符合"只给聚合、不给个体"的设计目标。但聚合本身也有泄露风险(例如唯一值过少时的极值),这正对应后文关于类型推断的警告。
脱敏的底层实现:redact_summary
sensitive 模式下类别/文本列的脱敏并非在渲染层简单打码,而是在汇总数据(summary)生成阶段就完成。核心实现在 src/data_profiling/model/summarizer.py:
redact_summary(summary, config)遍历每个变量的汇总结果,当config.vars.cat.redact and col["type"] == "Categorical"或config.vars.text.redact and col["type"] == "Text"时调用_redact_column;_redact_column对两类字段分别处理:- 键脱敏(
keys_to_redact):对value_counts_without_nan、value_counts_index_sorted、word_counts、character_counts、category_alias_values等字段,将原始取值替换为REDACTED_{i}占位符(保留计数数值); - 值脱敏(
values_to_redact):对first_rows字段,将原始值替换为REDACTED_{i}。
- 键脱敏(
这意味着导出的 JSON 报告(report.json 或 ProfileReport(...).json)在数据层面就是脱敏的,而不是仅在 HTML 展示时隐藏,杜绝了通过序列化结果间接泄露的可能。
关闭样本与重复行展示
如果不需要完整的脱敏统计(例如只需关闭样本区),可以显式传参关闭:
report = df.profile_report(duplicates=None, samples=None)
duplicates 与 samples 都属于配置中的 shorthand(简写形式)。在 config.py 中可以看到:
"samples": {"head": 0, "tail": 0, "random": 0},
"duplicates": {"head": 0},
即传 samples=None 等价于将三个样本开关全部置 0、传 duplicates=None 等价于将重复行数量置 0。这些参数通过 Config.shorthands 展开后再合入配置(profile_report.py),与 sensitive=True 的效果一致但作用域更小。
对应地,config_default.yaml 展示了默认的样本配置结构(head: 10, tail: 10, random: 0),读者可以据此在自定义 YAML 配置文件中精确控制每一类样本的数量,而不必依赖布尔开关。
用模拟数据替换真实样本:mock sample
有些场景下,报告需要保留"样本区"这一展示结构(例如给业务方演示报告形态),但又绝不能出现真实数据。profile_report 的 sample 参数支持注入自定义样本数据:
# Replace with the sample you'd like to present in the report (can be from a mock or synthetic data generator)
sample_custom_data = pd.DataFrame()
sample_description = "Disclaimer: the following sample consists of synthetic data following the format of the underlying dataset."
report = df.profile_report(
sample={
"name": "Mock data sample",
"data": sample_custom_data,
"caption": sample_description,
}
)
其中 name 与 caption 为可选键:name 控制样本区块的标题,caption 用于附加免责声明(例如"以下样本为按原数据格式生成的合成数据");data 为必填键,接受一个 pandas DataFrame,可以来自 mock 生成器或合成数据工具。
从源码调用链看,sample 参数被保存在 self._sample(profile_report.py),随后传入 describe_df(...)(profile_report.py)参与描述生成,最终通过 get_sample() 暴露(profile_report.py)。这意味着注入的 mock 样本会走完整的报告管线,在 HTML、JSON 与 notebook widget 三种输出中一致生效。
类型推断陷阱:电话号码被转成数值的隐私泄露
ydata-profiling 的统计依赖 pandas 的类型推断,而 pandas 在读取 CSV 时的类型猜测会带来一个隐蔽的隐私风险:pandas.read_csv 默认会把 0612345678 这类手机号推断为数值类型。一旦列被识别为数值,报告中的聚合指标(min、max、分位数)就会间接泄露原始取值——例如最小值恰好就是某人的手机号,这在敏感数据场景下是不可接受的。
解决方案是在读取时显式指定字符串类型:
pd.read_csv("filename.csv", dtype={"phone": str})
这个问题的本质是类型检测本身很难做到万无一失。正因如此,项目使用了 visions 类型系统来辅助开发者处理这类边界情况(详见仓库 src/data_profiling/model/typeset.py 与 typeset_relations.py 中对 ProfilingTypeSet 的定义)。在敏感场景下,建议遵循以下实践:
- 读入数据时就为敏感列显式声明
dtype,避免依赖类型猜测; - 对身份证、手机号、卡号等"看起来像数字"的标识符列,一律按字符串处理;
- 必要时使用
type_schema参数显式指定列类型,绕过自动推断。
更细粒度的脱敏控制:自定义配置
除了 sensitive=True 这一整体开关,你还可以通过自定义配置实现更精细的脱敏策略。例如在 YAML 配置中仅对类别列脱敏而保留文本列的字符统计:
# my_sensitive_config.yml
vars:
cat:
redact: true
text:
redact: false
samples:
head: 0
tail: 0
random: 0
duplicates:
head: 0
report = df.profile_report(config_file="my_sensitive_config.yml")
注意 config_file 与 minimal 互斥(profile_report.py),且配置组(如 sensitive)的优先级高于配置文件预设,若同时使用需注意覆盖关系。
进阶:PII 自动识别与治理
对于大规模、持续性的敏感数据治理,单靠报告层的脱敏还不够,需要自动识别哪些列属于个人可识别信息(PII)。项目文档在 docs/features/pii_identification_management.md 中介绍了 PII 识别与管理方案:基于命名实体识别(NER)模型结合规则模式匹配,自动检测姓名、地址、电话号码、社保号、邮箱等 PII 字段。需要说明的是,该文档明确标注其为 YData 的企业级功能(YData Fabric 平台能力),并非本开源仓库内的独立模块,开源版本中可直接使用的是本文前述的 sensitive=True、redact 与 mock sample 等内置机制。
小结:敏感数据 profiling 的四条基线
| 需求 | 推荐做法 |
|---|---|
| 一键隐私安全 | df.profile_report(sensitive=True) |
| 仅关闭样本/重复行 | df.profile_report(duplicates=None, samples=None) |
| 保留样本结构但不露真数据 | sample={"name": ..., "data": mock_df, "caption": ...} |
| 防止类型推断泄露 | pd.read_csv(..., dtype={"phone": str}) 或显式 type_schema |
配合 src/data_profiling/model/summarizer.py 中的 redact_summary 在数据层的脱敏实现,以及 src/data_profiling/config.py 的配置组机制,你可以在不修改任何业务代码的前提下,为私有数据环境构建一条"报告可分享、个体不泄露"的 profiling 流水线。