脚本专家 发表于 2026-9-30 12:00:00

Pandas结合Pydantic构建数据清洗与验证管道

数据清洗是数据分析和脚本开发中绕不开的环节。原始表里常见重复记录、缺失值、格式错误,甚至明显违背业务常识的条目。临时写 drop_duplicates() 或 fillna() 能救急,但换一份数据又要重写。更稳妥的方式是用 Pandas 处理表格,用 Pydantic 定义有效规则,再把清洗、验证、报告串成一条可复用的数据管道。

依赖安装很简单:

pip install pandas pydantic


一、先定义什么算有效数据

Pydantic 的 BaseModel 适合描述业务规则。以社区团购订单为例,字段包括姓名、年龄、手机号和订单金额。年龄限制在 0 到 100 之间,手机号必须是 11 位数字且以 1 开头,金额不能为负数。注意 @field_validator 需要和 @classmethod 搭配使用。


from pydantic import BaseModel, field_validator, ValidationError
from typing import Optional
import pandas as pd
import numpy as np

class OrderValidator(BaseModel):
    name: str
    age: Optional = None
    phone: Optional = None
    amount: Optional = None

    @field_validator('age')
    @classmethod
    def validate_age(cls, v):
      if v is not None and (v < 0 or v > 100):
            raise ValueError('年龄必须在0到100之间')
      return v

    @field_validator('phone')
    @classmethod
    def validate_phone(cls, v):
      if v and (not v.isdigit() or len(v) != 11 or not v.startswith('1')):
            raise ValueError('手机号必须是11位数字且以1开头')
      return v

    @field_validator('amount')
    @classmethod
    def validate_amount(cls, v):
      if v is not None and v < 0:
            raise ValueError('金额不能为负数')
      return v


二、管道骨架与统计记账

定义好验证模式后,创建 DataPipeline。构造函数中初始化 cleaning_stats,记录 duplicates_removed、nulls_handled 和 validation_errors。这样每次运行后都能知道去掉了多少重复、补了多少空、拦下了多少错误。


class DataPipeline:
    def __init__(self):
      self.cleaning_stats = {
            'duplicates_removed': 0,
            'nulls_handled': 0,
            'validation_errors': 0
      }


三、清洗:先去重,再补缺

clean_data 接收 DataFrame,返回处理后的版本。第一步是 drop_duplicates(),而且必须在填充缺失值之前执行。原因是重复记录会影响中位数计算,如果先填充再取中位数,统计基准会被重复行拉偏。去重之后,数值列用中位数填充,因为中位数比均值更抗离群值;文本列统一填成 Unknown,方便后续人工排查。每处理一处空值,nulls_handled 就累加对应数量。


def clean_data(self, df: pd.DataFrame) -> pd.DataFrame:
      initial_rows = len(df)
      df = df.drop_duplicates()
      self.cleaning_stats['duplicates_removed'] = initial_rows - len(df)

      numeric_columns = df.select_dtypes(include=).columns
      for col in numeric_columns:
            null_count = df.isnull().sum()
            if null_count > 0:
                df = df.fillna(df.median())
                self.cleaning_stats['nulls_handled'] += null_count

      string_columns = df.select_dtypes(include=['object']).columns
      for col in string_columns:
            null_count = df.isnull().sum()
            if null_count > 0:
                df = df.fillna('Unknown')
                self.cleaning_stats['nulls_handled'] += null_count

      return df


四、验证:逐行检查,坏数据不中断管道

清洗后的数据看起来完整,但不一定正确。validate_data 会逐行迭代,把每一行转成字典后传给 OrderValidator。有效行收集到 valid_rows,无效行捕获 ValidationError,并记录行号和错误详情。这样单条坏数据不会让整个管道崩溃,能处理的尽量处理,问题则记录在案供人工审查。


def validate_data(self, df: pd.DataFrame):
      valid_rows = []
      errors = []
      for idx, row in df.iterrows():
            try:
                validated = OrderValidator(**row.to_dict())
                valid_rows.append(validated.model_dump())
            except ValidationError as e:
                errors.append({'row': idx, 'errors': str(e)})
      self.cleaning_stats['validation_errors'] = len(errors)
      return pd.DataFrame(valid_rows), errors


五、编排:把清洗和验证串起来

process 方法作为统一入口,先用 df.copy() 调用 clean_data,再把清洗结果交给 validate_data,最后返回 cleaned_data、validation_errors 和 stats 的综合报告。调用方只需要一个入口就能拿到全部结果。


def process(self, df: pd.DataFrame):
      cleaned_df = self.clean_data(df.copy())
      validated_df, validation_errors = self.validate_data(cleaned_df)
      return {
            'cleaned_data': validated_df,
            'validation_errors': validation_errors,
            'stats': self.cleaning_stats
      }


六、社区团购订单场景实测

社区团购订单常来自微信群接龙、小程序下单或团长手动录入,容易出现同一用户重复下单、手机号少填、年龄乱填、金额为负或空白等情况。下面构造一份示例数据:


if __name__ == '__main__':
    sample_data = pd.DataFrame(
      {
            'name': ['张伟', '李娜', '王芳', None, '刘洋', '李娜'],
            'age': ,
            'phone': [
                '13812345678',
                '12345',
                '13987654321',
                '13712345678',
                '13612345678',
                '12345',
            ],
            'amount': ,
      }
    )

    print('=' * 60)
    print('原始数据:')
    print(sample_data)
    print('=' * 60)

    pipeline = DataPipeline()
    result = pipeline.process(sample_data)

    print()
    print('清洗后的有效数据:')
    print(result['cleaned_data'])
    print()
    print('验证错误清单:')
    if result['validation_errors']:
      for err in result['validation_errors']:
            print(' 行号 {}:{}'.format(err['row'], err['errors']))
    else:
      print(' 无')
    print()
    print('统计信息:')
    for key, value in result['stats'].items():
      print(' {}: {}'.format(key, value))
    print()
    print('=' * 60)
    print(
      '处理完成。有效数据 {} 行,错误 {} 行。'.format(
            len(result['cleaned_data']), len(result['validation_errors'])
      )
    )


运行后,管道会先去掉一条完全重复的“李娜”记录,把缺失的姓名填成 Unknown,用中位数填补缺失的金额。接着逐行验证:李娜的年龄 -5 和手机号 12345 会被拦下,刘洋的年龄 150 也会被标记为错误。最终有效数据中,张伟、王芳以及姓名被补为 Unknown 的用户通过;错误清单列出问题行号和原因。团长拿到报告后,可以直接定位哪些订单需要核对手机号,哪些年龄明显是误填。

七、可扩展方向

这条管道核心代码不到 100 行,但已经具备可维护系统的雏形。可以增加自定义清洗规则,例如标准化地址、脱敏手机号;也可以让 Pydantic 模式可配置,使同一管道适配不同来源的数据;数据量很大时,还能把逐行验证换成向量化操作或并行处理。重点不是一次写得多完美,而是拥有可复用骨架,把重复清洗和验证交给管道,把精力放在从干净数据中提取洞察上。

通过 Pandas 负责表格操作、Pydantic 负责规则校验、DataPipeline 负责编排和统计,一条可复用的数据清洗与验证管道就完成了。

热心网友3 发表于 2026-9-30 19:00:00

Re: Pandas结合Pydantic构建数据清洗与验证管道

感谢分享,这个把清洗、验证、统计串成管道的思路很清晰,复用性确实比临时 drop_duplicates 和 fillna 强很多。不过我照着代码看了一遍,发现 clean_data 里数值列和文本列填充那段可能有点小问题:`null_count = df.isnull().sum()` 返回的是整表的 Series,不是当前列的数量;而且 `df.fillna(df.median())` 会一次性对所有列填充,文本列也可能被中位数填进去,容易出问题。建议改成 `null_count = df.isnull().sum()`,然后按列填充,比如 `df = df.fillna(df.median())`,文本列同理用 `df.fillna('Unknown')`。 另外验证环节,文本列缺失已经被填成了 `'Unknown'`,手机号验证会把它当成非数字直接拦截,导致所有原本缺失的手机号都变成无效记录。可能需要在验证时把 `'Unknown'` 当作跳过项,或者在清洗阶段用更明确的缺失标记,再在验证模型里区分处理。还有逐行 iterrows 在数据量大时确实偏慢,可以考虑用 `apply` 或批量转换后交给 Pydantic 验证,性能会好一些。 整体框架很实用,统计记账这点也很赞,期待后续把验证部分补完。

热心网友6 发表于 2026-9-30 19:10:00

Re: Pandas结合Pydantic构建数据清洗与验证管道

这个组合挺清晰,Pandas 做表格清洗、Pydantic 做规则约束,再用统计字段把去重、补空、验证错误数量记下来,复用性和可观测性都照顾到了。先去重再填中位数、文本缺失填 Unknown 这些选择也合理,尤其避免重复行影响中位数这点很实在。 看代码时有两个地方可能需要再顺一下:数值列循环里 `null_count = df.isnull().sum()` 拿到的是整表每列空值数,不是当前列的空值数,后面判断和累加容易出问题,建议直接用当前列来取。填充时 `df.fillna(df.median())` 也会把各列的中位数混着填,最好按列填充,文本列也一样。另外字段类型可以写明确一些,比如 Optional、Optional、Optional,验证器里对手机号做 isdigit 前也要确认拿到的是字符串。整体骨架很好,等后面把验证结果报告和错误明细补全就更完整了。

热心网友5 发表于 2026-9-30 19:20:00

Re: Pandas结合Pydantic构建数据清洗与验证管道

这个管道思路很清晰,Pydantic 管规则、Pandas 管表格,再配上清洗统计,后续复盘会方便很多。先 drop_duplicates 再 fillna 这个顺序确实关键,不然中位数容易被重复行带偏。 有两个小地方可以留意:一是 null_count = df.isnull().sum() 拿到的是每列空值数的 Series,后面直接 if null_count > 0 在部分 pandas 版本里容易报“真值不明确”,最好按列统计;二是手机号如果被读成整数,验证时 isdigit 可能先报错,清洗阶段或模型里统一转成字符串会更稳。 逐行验证、收集错误、不让坏数据中断管道这点很实用。后面 validate_data 没贴完,期待补全错误明细和报告输出部分。
页: [1]
查看完整版本: Pandas结合Pydantic构建数据清洗与验证管道