影刀RPA 数据清洗入门:缺失值与异常值处理

0 阅读5分钟

影刀RPA 数据清洗入门:缺失值与异常值处理

作者:林焱

在这里插入图片描述

什么情况用什么

从网页采集或从各系统导出的原始数据,总有各种问题——空值、乱码、格式不统一、明显错误的数据(年龄200岁、金额负数)。直接用这些数据做报表会闹笑话。在影刀RPA里用pandas做数据清洗是自动化流程中必不可少的一步。

适用场景:采集数据后清洗、多源数据合并前标准化、报表生成前数据校验、异常数据筛查。

在这里插入图片描述

怎么做

[video(video-xWuYWUy8-1784265331878)(type-csdn)(url-live.csdn.net/v/embed/526…)]

缺失值处理

在这里插入图片描述 在这里插入图片描述

import pandas as pd
import numpy as np

df = pd.read_excel(r"C:\Data\raw_data.xlsx")

# 1. 检查缺失值
print("缺失值统计:")
print(df.isnull().sum())
print(f"\n总行数: {len(df)}")
print(f"完全空行: {df.isnull().all(axis=1).sum()}")

# 2. 删除缺失值
# 删除任何列有缺失的行
df_drop_any = df.dropna()

# 只删除关键字段缺失的行
df_drop_key = df.dropna(subset=['订单号', '金额'])

# 删除全为空的行
df_drop_empty = df.dropna(how='all')

# 3. 填充缺失值
# 用固定值填充
df['备注'] = df['备注'].fillna('无')

# 用均值填充
df['年龄'] = df['年龄'].fillna(df['年龄'].mean())

# 用中位数填充(更抗异常值)
df['工资'] = df['工资'].fillna(df['工资'].median())

# 用前一行/后一行的值填充
df['温度'] = df['温度'].fillna(method='ffill')  # 前向填充
df['温度'] = df['温度'].fillna(method='bfill')  # 后向填充

# 用分组均值填充(按部门填平均工资)
df['工资'] = df.groupby('部门')['工资'].apply(lambda x: x.fillna(x.mean()))

异常值检测与处理

def detect_outliers(df, column, method='iqr'):
    """检测异常值"""
    if method == 'iqr':
        # IQR方法:超出[Q1-1.5*IQR, Q3+1.5*IQR]的为异常
        Q1 = df[column].quantile(0.25)
        Q3 = df[column].quantile(0.75)
        IQR = Q3 - Q1
        lower = Q1 - 1.5 * IQR
        upper = Q3 + 1.5 * IQR
        outliers = df[(df[column] < lower) | (df[column] > upper)]
        
    elif method == 'zscore':
        # Z-Score方法:|Z|>3的为异常
        from scipy import stats
        z_scores = np.abs(stats.zscore(df[column].dropna()))
        outlier_indices = df[column].dropna().index[z_scores > 3]
        outliers = df.loc[outlier_indices]
        
    elif method == 'range':
        # 固定范围方法
        # 需要传入合理范围
        outliers = df[(df[column] < 0) | (df[column] > 1000000)]
    
    return outliers

# 检测金额异常
amount_outliers = detect_outliers(df, '金额', method='iqr')
print(f"金额异常值: {len(amount_outliers)}条")

# 处理异常值
def handle_outliers(df, column, method='clip'):
    """处理异常值"""
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    IQR = Q3 - Q1
    lower = Q1 - 1.5 * IQR
    upper = Q3 + 1.5 * IQR
    
    if method == 'clip':
        # 截断到合理范围
        df[column] = df[column].clip(lower, upper)
    elif method == 'remove':
        # 删除异常行
        df = df[(df[column] >= lower) & (df[column] <= upper)]
    elif method == 'median':
        # 用中位数替换
        df.loc[(df[column] < lower) | (df[column] > upper), column] = df[column].median()
    
    return df

df = handle_outliers(df, '金额', method='clip')

格式清洗

def clean_data(df):
    """数据清洗主函数"""
    # 1. 去除首尾空格
    text_cols = df.select_dtypes(include=['object']).columns
    for col in text_cols:
        df[col] = df[col].astype(str).str.strip()
        # 替换"nan"字符串为真正的空值
        df[col] = df[col].replace({'nan': None, 'None': None, '': None})
    
    # 2. 数字清洗
    if '金额' in df.columns:
        df['金额'] = df['金额'].astype(str).str.replace(',', '')
        df['金额'] = df['金额'].str.replace('¥', '').str.replace('¥', '')
        df['金额'] = pd.to_numeric(df['金额'], errors='coerce')
    
    # 3. 日期清洗
    if '日期' in df.columns:
        df['日期'] = pd.to_datetime(df['日期'], errors='coerce')
    
    # 4. 手机号清洗
    if '手机号' in df.columns:
        df['手机号'] = df['手机号'].astype(str).str.replace(r'[^0-9]', '', regex=True)
        df['手机号'] = df['手机号'].str.extract(r'(1[3-9]\d{9})')
    
    # 5. 去除完全重复的行
    df = df.drop_duplicates()
    
    # 6. 重置索引
    df = df.reset_index(drop=True)
    
    return df

# 使用
df = pd.read_excel(r"C:\Data\raw_data.xlsx")
df_clean = clean_data(df)
df_clean.to_excel(r"C:\Data\cleaned_data.xlsx", index=False)

影刀RPA完整清洗流程

【读取Excel文件】→ raw_data.xlsx
【执行Python代码】→ 数据清洗
  1. 缺失值检查与处理
  2. 异常值检测与处理
  3. 格式标准化
  4. ![在这里插入图片描述](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/c5bc3b08925544b8b8f8b8aee872b6cd~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5p6X54SxX1JQQUFJ:q75.awebp?rk3s=f64ab15b&x-expires=1786209368&x-signature=l2LRig%2BT7NJXfzLHH6MGynm6MrQ%3D)

  5. 去重
【写入Excel文件】→ cleaned_data.xlsx
【发送邮件】→ 附带清洗报告

清洗报告生成

def generate_clean_report(original_df, cleaned_df, output_path):
    """生成数据清洗报告"""
    report = []
    report.append("数据清洗报告")
    report.append("=" * 50)
    
    # 1. 数据量变化
    report.append(f"\n原始数据: {len(original_df)}行 × {len(original_df.columns)}列")
    report.append(f"清洗后: {len(cleaned_df)}行 × {len(cleaned_df.columns)}列")
    report.append(f"删除行数: {len(original_df) - len(cleaned_df)}")
    
    # 2. 缺失值处理
    report.append("\n【缺失值处理】")
    for col in original_df.columns:
        null_count = original_df[col].isnull().sum()
        if null_count > 0:
            report.append(f"  {col}: {null_count}个空值 ({null_count/len(original_df)*100:.1f}%)")
    
    # 3. 重复值
    dup_count = original_df.duplicated().sum()
    report.append(f"\n【重复值】删除{dup_count}条完全重复记录")
    
    # 4. 异常值
    report.append("\n【异常值检测】")
    numeric_cols = original_df.select_dtypes(include=[np.number]).columns
    for col in numeric_cols:
        outliers = detect_outliers(original_df, col)
        if len(outliers) > 0:
            report.append(f"  {col}: 发现{len(outliers)}个异常值")
    
    with open(output_path, 'w', encoding='utf-8') as f:
        f.write('\n'.join(report))
    
    return output_path

有什么坑

[video(video-2guwOiUz-1784265338448)(type-csdn)(url-live.csdn.net/v/embed/526…)]

在这里插入图片描述

坑1:fillna(method='ffill')已弃用

# 问题:pandas 2.0+中method参数被弃用
# df['列'].fillna(method='ffill')  # 警告

# 解决:用ffill()方法
df['列'] = df['列'].ffill()  # 前向填充
df['列'] = df['列'].bfill()  # 后向填充

坑2:用均值填充改变数据分布

在这里插入图片描述

# 问题:用全局均值填充空值,导致数据分布变窄
# 解决:按分组填充

# 不好:全局均值
df['工资'] = df['工资'].fillna(df['工资'].mean())  # 所有人工资一样

# 更好:按部门分组填充
df['工资'] = df.groupby('部门')['工资'].transform(lambda x: x.fillna(x.mean()))

坑3:异常值截断导致数据失真

# 问题:直接clip截断异常值,所有大值都变成上限值
# 解决:根据业务场景选择处理方式

# 如果异常值是真实数据(如VIP客户的大额订单),不应截断
# 而是标记出来单独分析

df['金额异常'] = False
![在这里插入图片描述](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/0b9b370c079640e88ea214d33138a99a~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5p6X54SxX1JQQUFJ:q75.awebp?rk3s=f64ab15b&x-expires=1786209368&x-signature=2PdQ6CupWDh9IMXOL8OMc%2BGGEY0%3D)

Q1 = df['金额'].quantile(0.25)
Q3 = df['金额'].quantile(0.75)
IQR = Q3 - Q1
df.loc[(df['金额'] < Q1-1.5*IQR) | (df['金额'] > Q3+1.5*IQR), '金额异常'] = True

坑4:字符串"nan"不是空值

# 问题:从网页采集的数据,空值变成字符串"nan"或"null"
# isnull()检测不到

# 解决:先替换再检测
df = df.replace({'nan': None, 'null': None, 'None': None, '': None, 'N/A': None})
# 然后再做缺失值处理

坑5:日期列清洗后变成时间戳

# 问题:pd.to_datetime后写入Excel,显示为数字而非日期
# 解决:指定dtype或用openpyxl写

# 写入时确保日期格式
df['日期'] = pd.to_datetime(df['日期'])

# 用ExcelWriter写入时指定日期格式
![在这里插入图片描述](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/d9c129ed6c4f47cda85da25719ff4a1d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5p6X54SxX1JQQUFJ:q75.awebp?rk3s=f64ab15b&x-expires=1786209368&x-signature=KOloLGYXy%2BtubwqUuKZPQFrFAeY%3D)
![在这里插入图片描述](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/4fe9f1248ef4476ca3c65e030ced238d~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5p6X54SxX1JQQUFJ:q75.awebp?rk3s=f64ab15b&x-expires=1786209368&x-signature=EFxOtq3FZYFcEAlv79i%2B38YD1w8%3D)


with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
    df.to_excel(writer, index=False, sheet_name='数据')
    ws = writer.sheets['数据']
    # 设置日期列格式
    for cell in ws['A'][1:]:  # A列是日期
        cell.number_format = 'YYYY-MM-DD'