用Python DSL 写轻量数据质检工具

平时做经营分析或者跑日报,第一步往往不是画图,而是先面对一份"不太干净"的导出的明细表:重复的单号、位数对不上的手机号、莫名其妙的负数金额、格式乱掉的日期......

在跑任何分析之前,得先确认数据靠谱。

下面介绍一种简单的做法:用几十行 Python 写一个轻量的数据质量 DSL,把校验规则写成能看懂的话,还能顺手接进你的数据管道。

1. 为什么要用DSL

提到"数据质量校验",很多人第一反应是写个函数,读进 DataFrame 之后用一串 if 去判断重复、空值、范围。

短期内没问题,但规则一多起来就露馅了:报错信息和业务逻辑搅在同一层,函数越写越长,改一条规则都要担心牵动别的检查。

说白了,你是在用通用编程语言去硬凑一套"校验规范",而这套规范本身并没有自己的形状。

DSL(Domain-Specific Language,领域特定语言)的思路正好反过来:它不要求你另学一门语言,而是在 Python 里搭一套只管"数据质量"的小词汇。

我们把每条检查抽象成 Rule("订单号唯一", 条件, "单号不能重复") 这样的写法。

读起来像业务需求本身,而不是一段控制流。

它的好处很实在:

  • 规则可读,新人看一眼就知道定了什么规矩;
  • 规则可测,每条独立运行、互不影响;
  • 规则可复用,换个数据集照样套得上。

接下来就沿着这个思路,一边搭这套词汇,一边用它去查一份真实的门店销售明细。

2. 先造一份"带病"的样例数据

为了方便你直接尝试文中的代码,不用再去下载什么数据集,文中所有示例都基于一份模拟的门店销售明细。

字段就五个:订单号、手机号、金额、销售日期、备注。

这里,我们故意往里混了重复单号、位数不足的手机号、负金额、畸形日期,还有高金额却没备注的订单。

这样后面每加一条规则,都能立刻看到它抓出什么问题。

3. 核心就一个 Rule 类

整个 DSL 的核心抽象只有一个:Rule。

它接收三样东西:

  1. 规则名字
  2. 一个返回"有效行"布尔序列的条件函数
  3. 以及一句报错提示

判断违规时,对条件取反 ~ 就得到违规行,check 方法统一吐出一份报告(规则名、原因、违规行数、样例行号)。

python 复制代码
class Rule:
    def __init__(self, name, condition, error_msg):
        self.name, self.condition, self.error_msg = name, condition, error_msg
    def check(self, df):
        violations = df[~self.condition(df)]
        ...  # 有违规就返回报告字典,否则返回 None

关键约定是:条件函数返回 True 表示"这行没问题"。

后面所有辅助函数都遵守这个约定,于是 Rule 本身永远不用改。

4. 让条件说人话:几个辅助函数

如果每条规则都现写 lambda,代码照样难读。

所以我们先准备一批返回条件函数的"小词",像搭积木一样拼规则:

python 复制代码
def not_null(col):         return lambda df: df[col].notna()
def unique_values(col):    return lambda df: ~df.duplicated(subset=[col], keep=False)
def between(col, lo, hi):  return lambda df: df[col].between(lo, hi)
def matches_pattern(col, pat): return lambda df: df[col].astype(str).str.match(pat, na=False)

unique_values 用 keep=False 把所有重复行都标出来;

matches_pattern 配合正则校验手机号格式,na=False 保证空值直接判失败。

这套"词汇表"就是 DSL 可读的来源。

5. 装进 DataValidator 跑起来

Rule 本身不负责"跑多条规则",这件事交给 DataValidator。

它维护一个规则列表,add_rule 返回 self 所以能链式调用,validate 逐条检查、把有问题的报告收集起来------某条规则挂了也不会中断其他检查。

python 复制代码
v = DataValidator()
v.add_rule(Rule("订单号唯一", unique_values("order_id"), "订单号不能重复"))
v.add_rule(Rule("金额非负", between("amount", 0, 100000), "金额须 ≥ 0"))
issues = v.validate(df)
for i in issues:
    print(i["rule"], i["sample_rows"])  # 打印规则名和违规行号

跑完你会拿到一份清单:哪条规则没过、影响了哪几行。定位脏数据从此不用靠肉眼翻表。

6. 真实场景:跨列校验与日期校验

真实业务里很多规则是"跨列"的。

比如我们定:金额超过 500 的订单必须有备注。

它同时看 amount 和 note 两列,写成一个普通函数即可,照样包进 Rule:

python 复制代码
def high_amount_needs_note(df, threshold=500):
    high = df["amount"] > threshold
    has_note = df["note"].notna() & (df["note"].astype(str).str.strip() != "")
    return ~high | has_note

日期更麻烦,畸形字符串(如 2024-13-45)解析会失败。

做法是先用 pd.to_datetime(..., errors="coerce") 把解析不了的变成 NaT,再判断原值非空且解析也非空才算有效。

7. 接进管道:用装饰器兜底

校验写好了,关键是"卡住"坏数据,别让它流到下游分析。

最省事的方式是包一层装饰器:在真正处理函数之前先跑一遍 validate,有问题直接抛异常,把具体规则细节带出来。

python 复制代码
def validate_dataframe(validator):
    def decorator(func):
        def wrapper(df, *a, **k):
            issues = validator.validate(df)
            if issues: raise ValueError("数据校验未通过:" + str(issues))
            return func(df, *a, **k)
        return wrapper
    return decorator

把它盖在 analyze、to_excel 这类入口函数上,数据一不对劲就被拦在门口,下游再也不会被脏数据坑。

8. 最终效果

完整的代码共享在:data_quality_dsl.py url11.ctfile.com/f/45455611-...

代码用 pandas 写了一个轻量数据质量校验工具,核心是两个类:

  • Rule 把"一条规则"封装成「名字 + 条件函数 + 报错提示」
  • DataValidator 负责收集多条规则并逐条检查、汇总违规报告。

代码里还提供了一批可读的条件辅助函数(非空、唯一、范围、正则、日期格式)和跨列校验(高金额订单必须有备注),规则读起来就像业务需求本身。

最后用一份故意混入脏数据的「门店销售明细」做样例,并通过一个装饰器把校验卡在数据处理入口,直接运行就能看到哪几条规则没过、影响了哪些行。

基于这个代码,可以扩展一些适合自己业务的质量校验规则,然后就可以自动检测数据的质量了。

相关推荐
郝学胜-神的一滴1 小时前
AI 编程智能体 17:四步搞定专属AI智能体
大数据·人工智能·python·神经网络·算法·langchain·强化学习
岁寒良木1 小时前
麒麟 V10 (x86_64) 离线/联网部署 Docker 26.1.4 与 MaxKB v2.10 LTS 实战指南
python·docker·kylin
程序员梅雨1 小时前
【从零开始学习数据仓库 | 第一篇】:维度建模理论
大数据·数据仓库·学习·阿里云·数据分析
Norna1 小时前
receive 取消了,TCP 为什么还在读:AnyIO 的一次背压修复
python
Ven%2 小时前
在魔搭 Notebook 容器里跑 Qwen Code:从零配置到省魔粒的完整实践
人工智能·python·ai·ai编程·modelscope
2401_885885042 小时前
巴林语音通知接口如何对接?系统告警语音API接入教程
android·前端·网络·python·语音识别
执假以为真2 小时前
Python内存分析器memray的简单介绍
python
收纳过往2 小时前
数据库加索引的时机:慢查询日志说话
android·开发语言·数据库·python·低代码·rxjava
小鹿学程序3 小时前
市赛python学习
python