
在 ETL 和 ML Pipeline 中,"垃圾进,垃圾出"(GIGO) 是最昂贵的故障模式。将数据质量校验从"事后排查"左移到"管道内嵌",是构建可靠数据系统的核心能力。
以下是基于 Great Expectations (GX) 与 Pandera 的实战指南,帮助你在数据管道中嵌入断言,防止脏数据污染下游。
核心选型:Pandera vs Great Expectations
在动手之前,需根据场景选择正确的工具。两者并非互斥,而是互补:
| 维度 | Pandera | Great Expectations (GX) |
|---|---|---|
| 定位 | DataFrame 原生校验库 | 企业级数据质量平台/框架 |
| 代码风格 | Pythonic、类型注解、轻量 | 配置驱动(YAML/JSON)、Expectation Suite |
| 集成方式 | 直接嵌入 pandas/polars/spark 代码流 |
通过 Checkpoint 对接 Airflow/Dagster/dbt |
| 输出物 | 抛出异常或返回布尔值 | 生成 HTML 数据文档、验证结果存储到后端 |
| 适用场景 | 单元级校验、ML特征工程、ETL中间态转换 | 跨系统边界验收、合规审计、数据契约(Data Contract) |
| 学习曲线 | ⭐⭐ (低) | ⭐⭐⭐⭐ (高) |
💡 选型建议
- ML Pipeline / 特征工程 :首选 Pandera。它与 Pydantic 类似,可作为函数装饰器或类型提示,确保进入模型的 Tensor/DataFrame 结构绝对正确。
- ETL 数仓加载 / 数据湖入湖 :首选 Great Expectations。它能在数据落地前进行"门禁检查",并自动生成给业务方看的数据质量报告。
- 混合架构:用 Pandera 做进程内的快速防御性编程,用 GX 做跨任务节点的里程碑验收。
Pandera 实战:ML Pipeline 中的防御性校验
Pandera 的核心优势是零侵入。你可以像写类型注解一样定义数据契约。
1. 定义 Schema 作为"活文档"
import numpy as np
import pandera as pa
from pandera.typing import DataFrame, Series
class UserFeatureSchema(pa.DataFrameModel):
user_id: Series[str] = pa.Field(str_length={"min_value": 6, "max_value": 32})
age: Series[int] = pa.Field(ge=0, le=150, nullable=False)
income: Series[float] = pa.Field(ge=0, coerce=True) # 自动尝试类型转换
signup_date: Series[str] = pa.Field(regex=r"^\d{4}-\d{2}-\d{2}$")
# 跨列校验:未成年人收入必须为0
@pa.dataframe_check
def minor_income_check(cls, df: DataFrame) -> Series[bool]:
return ~((df["age"] < 18) & (df["income"] > 0))
2. 在 ETL/ML 函数中嵌入断言
# 方式A:装饰器模式(推荐用于ML预处理函数)
@pa.check_types
def preprocess_features(raw_df: DataFrame[UserFeatureSchema]) -> DataFrame[UserFeatureSchema]:
"""如果输入不符合Schema,函数入口处直接报错;
如果输出不符合Schema,函数出口处报错。"""
processed = raw_df.assign(income_log=lambda x: np.log1p(x.income))
return processed
# 方式B:显式校验(推荐用于ETL读取后)
try:
validated_df = UserFeatureSchema.validate(df, lazy=True)
# lazy=True 收集所有错误而非遇到第一个就停
except pa.errors.SchemaErrors as err:
print(err.failure_cases) # 精确打印哪些行、哪列、什么值违规
raise ValueError("Raw data validation failed") from err
Great Expectations 实战:ETL 管道的质量门禁
GX 适合在 Airflow/Dagster 等编排器中作为独立 Task 运行。
1. 创建 Expectation Suite(期望套件)
不要手写 JSON,使用 GX 的交互式 Notebook 或 CLI 生成:
great_expectations suite new --name user_table.gold_layer
核心期望示例(对应 YAML 配置):
expect_column_values_to_not_be_null: 主键非空expect_column_values_to_be_between: 数值范围合理expect_compound_columns_to_be_unique: 联合唯一性expect_table_row_count_to_be_between: 行数波动监控(防全量误删)
2. 在编排器中集成 Checkpoint
以 Airflow 为例,将 GX 校验作为 DAG 中的传感器或操作符:
from great_expectations_provider.operators.great_expectations import GreatExpectationsOperator
validate_gold_users = GreatExpectationsOperator(
task_id="validate_gold_users",
expectation_suite_name="user_table.gold_layer",
checkpoint_name="gold_users_checkpoint",
fail_task_on_validation_failure=True, # 🔴 关键:校验失败则阻断下游
slack_webhook=None, # 可接入告警
)
# DAG 依赖:只有校验通过,才触发下游BI刷新或模型训练
extract_task >> validate_gold_users >> [refresh_bi_task, train_model_task]
3. 处理"部分失败"策略
在生产环境中,全量阻断可能过于激进。GX 支持配置 Warning/Error 阈值:
- Error: 违反即阻断管道(如主键重复、字段缺失)。
- Warning: 记录但不阻断(如某列空值率从 1% 升至 3%),触发告警人工介入。
防止脏数据污染的 4 条黄金法则
- 边界校验原则 :只在数据进入 和离开你的系统边界时做重型校验(GX)。内部转换过程用轻量校验(Pandera)或跳过,避免性能瓶颈。
- Fail-Fast + 可观测性 :校验失败必须产生结构化错误信息 (哪个文件、哪一行、什么规则被破坏)。仅抛出
AssertionError是不够的,必须对接 Slack/PagerDuty/邮件。 - Schema Evolution 管理:数据契约不是静态的。将 Schema 定义纳入 Git 版本控制。当上游变更导致校验失败时,应视为"契约破坏",需走变更审批流程,而非默默修改校验规则。
- 采样与全量分离:对于 TB 级数据,开发阶段用采样调试规则,生产环境利用 Spark/Flink 连接器做全量或分区级校验。GX 和 Pandera 均支持分布式引擎后端。
生态集成速查
| 编排/计算引擎 | Pandera 集成方式 | Great Expectations 集成方式 |
|---|---|---|
| Apache Airflow | PythonOperator 内调用 | GreatExpectationsOperator |
| Dagster | Asset 类型注解 / IOManager | gx-dagster 官方集成 |
| dbt | dbt-pandera 插件 | dbt-gx 包 / Generic Tests |
| Spark / Databricks | pandera.pyspark |
GX Spark Datasource |
| Polars | 原生支持 (v0.20+) | 社区插件 / 转 Pandas 校验 |
通过将校验逻辑代码化、版本化、自动化,你可以将数据质量从"运维负担"转变为"工程资产",从根本上杜绝脏数据对下游 BI 报表和 ML 模型的隐性侵蚀。