【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)...
相关推荐
sell-cloud3 小时前
Prompt Engineering要凉了?Fable 5.5能“猜“意图,但银行正在封杀AI Agent——企业AI的真正护城河是什么
人工智能·prompt
Navicat中国4 小时前
Navicat 18 内置 AI 助手让数据库开发从繁琐操作迈向智能协作
大数据·人工智能·数据库开发
4SAPI5 小时前
多模型请求路由怎么设计?成本、速度和能力如何平衡
人工智能·python·gateway·php
魔乐社区5 小时前
翼芯・智算测试与适配优化专区,让国产算力“选得准、跑得通、用得好“
人工智能·开源·大模型
XCDoris7 小时前
郴州logo设计机构
大数据·人工智能·python
Goboy7 小时前
一个人带三个Agent,我用全新TRAE从0到1搭了个资讯周刊站
人工智能·trae
俊哥V7 小时前
每日 AI 研究简报 · 2026-10-19
人工智能·ai
AI_AGENT_DEV_AI7 小时前
AI英语教培智能体的开发
人工智能
物联网软硬件开发-轨物科技7 小时前
【技术白皮书】光伏清检一体化机器人系统解决方案技术白皮书
大数据·运维·网络·人工智能·机器人
gCode Teacher 格码致知7 小时前
Pandas教学-17:Series与Dataframe的关系
开发语言·python·pandas