【Python实战】10万行数据自动清洗:pandas+AI智能识别+异常检测完整方案

一、项目背景

1.1 痛点分析

数据清洗占数据分析工作的60%-80%,但传统方式效率极低:

环节 手工方式 时间
空值处理 逐行检查 1小时
重复数据 排序比对 1小时
格式统一 查找替换 2小时
异常值检测 肉眼判断 2小时
数据验证 抽样核对 2小时
总计 - 8小时

10万行数据清洗一天,100万行就是一周。

1.2 技术需求

核心需求:

  • 自动扫描数据质量问题
  • 智能处理空值(删除/填充/预测)
  • 自动去重(精确匹配+模糊匹配)
  • 格式自动统一(日期/手机号/金额/地址)
  • 异常值智能检测与处理
  • 生成数据质量报告

二、技术架构

原始数据 → 质量扫描 → 空值处理 → 去重 → 格式统一 → 异常检测 → 验证输出

↑ ↑ ↑ ↑ ↑ ↑ ↑

pandas pandas pandas pandas regex/AI scipy pandas

sklearn DashScope

复制代码
技术栈:
- **pandas**:数据读取和处理
- **numpy**:数值计算
- **scipy**:统计分析和异常检测
- **sklearn**:机器学习填充缺失值
- **DashScope/Qwen3**:AI智能识别和格式纠正
- **re**:正则表达式格式匹配

---

## 三、环境准备

### 3.1 安装依赖

```bash
pip install pandas numpy scipy scikit-learn dashscope openpyxl

3.2 配置

复制代码
# config.py
DASHSCOPE_API_KEY = "your-api-key-here"

# 清洗规则配置
CLEAN_CONFIG = {
'date_formats': ['%Y-%m-%d', '%Y/%m/%d', '%Y年%m月%d日', '%Y%m%d'],
    'phone_pattern': r'^1[3-9]\d{9}$',
'email_pattern': r'^[\w.-]+@[\w.-]+\.\w+$',
'outlier_method': 'iqr',  # iqr / zscore / isolation_forest
'outlier_threshold': 1.5
}

四、核心模块实现

4.1 数据质量扫描模块

自动扫描数据质量问题,生成健康报告:

复制代码
import pandas as pd
import numpy as np

class DataProfiler:
def __init__(self, df):
        self.df = df
        self.report = {}
  
def scan(self):
"""全面扫描数据质量"""
        self.report = {
'基本信息': self._basic_info(),
'空值统计': self._null_analysis(),
'重复统计': self._duplicate_analysis(),
'类型检测': self._type_detection(),
'格式问题': self._format_issues(),
'异常值预检': self._outlier_preview(),
'质量评分': 0
        }
      
# 计算质量评分(0-100)
        self.report['质量评分'] = self._calculate_score()
      
        return self.report
  
def _basic_info(self):
        return {
'总行数': len(self.df),
'总列数': len(self.df.columns),
            '数值列': list(self.df.select_dtypes(include=[np.number]).columns),
            '文本列': list(self.df.select_dtypes(include=['object']).columns),
            '内存占用': f"{self.df.memory_usage(deep=True).sum() / 1024 / 1024:.2f}MB"
        }
  
def _null_analysis(self):
        null_stats = {}
        for col in self.df.columns:
            null_count = self.df[col].isnull().sum()
            null_pct = round(null_count / len(self.df) * 100, 2)
            null_stats[col] = {
'空值数': int(null_count),
                '空值率': f"{null_pct}%",
'建议': self._null_suggestion(null_pct, self.df[col].dtype)
            }
        return null_stats
  
def _null_suggestion(self, null_pct, dtype):
        if null_pct == 0:
            return '无需处理'
        elif null_pct < 5:
            return '删除空值行' if dtype == 'object' else '中位数填充'
        elif null_pct < 30:
            return 'KNN填充' if dtype != 'object' else '众数填充'
else:
            return '考虑删除该列'
  
def _duplicate_analysis(self):
        dup_count = self.df.duplicated().sum()
        return {
'完全重复行': int(dup_count),
            '重复率': f"{round(dup_count / len(self.df) * 100, 2)}%"
        }
  
def _type_detection(self):
"""检测数据类型是否合理"""
        issues = {}
        for col in self.df.columns:
            if self.df[col].dtype == 'object':
# 检测是否应该是数值
                numeric_count = self.df[col].apply(
lambda x: str(x).replace('.', '').replace('-', '').isdigit()
                    if pd.notna(x) else False
).sum()
              
                if numeric_count / len(self.df) > 0.8:
                    issues[col] = '疑似数值列被识别为文本'
      
        return issues
  
def _format_issues(self):
"""检测格式不统一问题"""
        issues = {}
        for col in self.df.columns:
            if self.df[col].dtype == 'object':
                unique_patterns = self.df[col].dropna().apply(
lambda x: self._get_pattern(str(x))
).nunique()
              
                if unique_patterns > 3:
                    issues[col] = f"发现{unique_patterns}种格式"
      
        return issues
  
def _get_pattern(self, text):
        import re
        pattern = re.sub(r'\d', 'D', text)
        pattern = re.sub(r'[a-zA-Z]', 'A', pattern)
        pattern = re.sub(r'[\u4e00-\u9fff]', 'C', pattern)
        return pattern
  
def _outlier_preview(self):
"""异常值预检"""
        outliers = {}
        for col in self.df.select_dtypes(include=[np.number]).columns:
            Q1 = self.df[col].quantile(0.25)
            Q3 = self.df[col].quantile(0.75)
            IQR = Q3 - Q1
            count = ((self.df[col] < Q1 - 1.5 * IQR) | (self.df[col] > Q3 + 1.5 * IQR)).sum()
            if count > 0:
                outliers[col] = int(count)
        return outliers
  
def _calculate_score(self):
"""计算质量评分"""
        score = 100
      
# 空值扣分
        total_null = self.df.isnull().sum().sum()
        null_rate = total_null / (len(self.df) * len(self.df.columns))
        score -= null_rate * 100 * 2
      
# 重复扣分
        dup_rate = self.df.duplicated().sum() / len(self.df)
        score -= dup_rate * 100
      
# 异常值扣分
        outlier_count = sum(self.report['异常值预检'].values()) if self.report['异常值预检'] else 0
        outlier_rate = outlier_count / len(self.df)
        score -= outlier_rate * 50
      
        return max(0, round(score, 1))

4.2 智能空值处理模块

根据数据特征自动选择最佳填充策略:

复制代码
from sklearn.impute import KNNImputer

class NullHandler:
def __init__(self):
        self.strategies = {}
  
    def handle(self, df, strategy='auto'):
"""智能处理空值"""
        if strategy == 'auto':
            return self._auto_handle(df)
        elif strategy == 'drop':
            return df.dropna()
        elif strategy == 'fill_median':
            return df.fillna(df.median(numeric_only=True))
  
def _auto_handle(self, df):
"""自动选择最佳策略"""
        for col in df.columns:
            null_pct = df[col].isnull().sum() / len(df) * 100
          
            if null_pct == 0:
continue
          
            if null_pct > 50:
# 空值超过50%,考虑删除列
                self.strategies[col] = '删除列(空值过多)'
                df = df.drop(columns=[col])
continue
          
            if df[col].dtype in ['int64', 'float64']:
                if null_pct < 5:
# 少量空值用中位数
                    df[col] = df[col].fillna(df[col].median())
                    self.strategies[col] = '中位数填充'
else:
# 较多空值用KNN
                    df = self._knn_fill(df, col)
                    self.strategies[col] = 'KNN填充'
else:
# 文本列用众数
                mode_val = df[col].mode()
                if len(mode_val) > 0:
                    df[col] = df[col].fillna(mode_val[0])
                    self.strategies[col] = '众数填充'
else:
                    df[col] = df[col].fillna('未知')
                    self.strategies[col] = '默认值填充'
      
        return df
  
def _knn_fill(self, df, target_col):
"""KNN填充缺失值"""
        numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
      
        if target_col not in numeric_cols:
            return df
      
        imputer = KNNImputer(n_neighbors=5)
        df[numeric_cols] = imputer.fit_transform(df[numeric_cols])
      
        return df
  
def get_strategies(self):
        return self.strategies

4.3 智能去重模块

支持精确匹配和模糊匹配:

复制代码
class DeduplicatorEngine:
def __init__(self):
        self.removed = 0
  
    def deduplicate(self, df, mode='exact', key_columns=None):
"""智能去重"""
        before = len(df)
      
        if mode == 'exact':
            df = self._exact_dedup(df, key_columns)
        elif mode == 'fuzzy':
            df = self._fuzzy_dedup(df, key_columns)
      
        self.removed = before - len(df)
        return df
  
    def _exact_dedup(self, df, key_columns=None):
"""精确去重"""
        if key_columns:
            return df.drop_duplicates(subset=key_columns, keep='first')
        return df.drop_duplicates(keep='first')
  
def _fuzzy_dedup(self, df, key_columns):
"""模糊去重(处理相似但不完全相同的记录)"""
        if not key_columns:
            return df
      
# 标准化后去重
        df_normalized = df.copy()
        for col in key_columns:
            if df_normalized[col].dtype == 'object':
                df_normalized[col] = df_normalized[col].str.lower().str.strip()
                df_normalized[col] = df_normalized[col].str.replace(r'\s+', '', regex=True)
      
# 标记重复
        mask = df_normalized.duplicated(subset=key_columns, keep='first')
      
        return df[~mask]

4.4 格式统一模块

自动识别并统一各种格式:

复制代码
import re

class FormatStandardizer:
def __init__(self):
        self.changes = {}
  
def standardize(self, df):
"""自动标准化格式"""
        for col in df.columns:
            col_lower = col.lower()
          
            if any(kw in col_lower for kw in ['日期', 'date', '时间', 'time']):
                df[col] = self._standardize_date(df[col])
                self.changes[col] = '日期格式统一'
          
            elif any(kw in col_lower for kw in ['手机', '电话', 'phone', 'mobile']):
                df[col] = self._standardize_phone(df[col])
                self.changes[col] = '手机号格式统一'
          
            elif any(kw in col_lower for kw in ['邮箱', 'email']):
                df[col] = self._standardize_email(df[col])
                self.changes[col] = '邮箱格式统一'
          
            elif any(kw in col_lower for kw in ['金额', '价格', 'amount', 'price']):
                df[col] = self._standardize_amount(df[col])
                self.changes[col] = '金额格式统一'
      
        return df
  
def _standardize_date(self, series):
"""统一日期格式为YYYY-MM-DD"""
def convert(val):
            if pd.isna(val):
                return val
            val = str(val).strip()
          
            formats = ['%Y-%m-%d', '%Y/%m/%d', '%Y年%m月%d日',
                       '%Y%m%d', '%d/%m/%Y', '%m/%d/%Y',
                       '%Y.%m.%d', '%d-%m-%Y']
          
            for fmt in formats:
                try:
                    return pd.to_datetime(val, format=fmt).strftime('%Y-%m-%d')
except (ValueError, TypeError):
continue
          
            try:
                return pd.to_datetime(val).strftime('%Y-%m-%d')
except:
                return val
      
        return series.apply(convert)
  
def _standardize_phone(self, series):
"""统一手机号格式"""
def convert(val):
            if pd.isna(val):
                return val
            val = str(val).strip()
            val = re.sub(r'[^\d]', '', val)  # 只保留数字
          
            if val.startswith('86') and len(val) == 13:
                val = val[2:]
            elif val.startswith('0086') and len(val) == 15:
                val = val[4:]
          
            if len(val) == 11 and val.startswith('1'):
                return val
            return val
      
        return series.apply(convert)
  
def _standardize_email(self, series):
"""统一邮箱格式"""
def convert(val):
            if pd.isna(val):
                return val
            return str(val).strip().lower()
      
        return series.apply(convert)
  
def _standardize_amount(self, series):
"""统一金额格式(纯数字)"""
def convert(val):
            if 
...(truncated)...
相关推荐
科研前沿13 分钟前
镜像视界 CameraGraph™+多智能体:构建自感知自决策的全域空间认知网络技术方案
大数据·运维·人工智能·数码相机·计算机视觉
爱学习的张大15 分钟前
具身智能论文问答(2):Diffusion Policy
人工智能
AI科技星16 分钟前
全域数学·72分册·射影原本 无穷维射影几何卷细化子目录【乖乖数学】
人工智能·线性代数·算法·机器学习·数学建模·数据挖掘·量子计算
Chef_Chen18 分钟前
论文解读:MemOS首次把记忆变成大模型的一等公民资源,Scaling Law迎来第三条曲线
人工智能·agent·memory
风落无尘24 分钟前
《智能重生:从垃圾堆到AI工程师》——第四章 变化的艺术
人工智能·线性代数·算法
七颗糖很甜25 分钟前
电离层对地基雷达测量精度的影响分析与校正方法
python
发哥来了37 分钟前
AI视频生成模型选型指南:五大核心维度对比评测
大数据·人工智能·机器学习·ai·aigc
发哥来了42 分钟前
AI驱动生产线的实际落地:一个东莞厂商的技术选型实录
大数据·人工智能·机器学习·ai·aigc
AC赳赳老秦42 分钟前
知识产权辅助:用 OpenClaw 批量生成专利交底书 / 软著申请材料,自动校验格式与内容合规性
java·人工智能·python·算法·elasticsearch·deepseek·openclaw
AI科技1 小时前
原因大揭秘:为什么别人的编曲伴奏做得又快又好,2026年度甄选5款AI编曲软件汇总
人工智能