【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)...
相关推荐
乔江seven2 小时前
LlamaIndex 实现ReAct Agent
前端·python·react.js
后厂村2 小时前
多模态 LLM Wiki Skill
人工智能
挂科边缘2 小时前
【PASCAL VOC 数据集介绍篇】目标检测与分割常用的数据集:PASCAL VOC 数据集全版本详解与避坑指南
人工智能·目标检测·计算机视觉
GrowAdmin2 小时前
你真的了解Agent Skills吗?一文讲清它的“发现-激活-执行”
人工智能
风吹花动叶随雪落2 小时前
怎么下载venv,安装python环境
人工智能
侠客工坊2 小时前
大模型落地移动端:解析侠客工坊端侧 Agent 的零拷贝(Zero-Copy)屏幕感知与空间映射
android·人工智能
lifallen2 小时前
一篇文章讲透 Flink State
大数据·数据库·python·flink
yuan199972 小时前
OpenCV ViBe 运动检测算法实现
人工智能·opencv·算法
mooyuan天天2 小时前
AI大模型辅助Web渗透测试-TRAE智能体自动化解CTF题(命令执行 powershell)
人工智能·web安全·渗透测试·ctf·ai辅助渗透测试