影刀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. 
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

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写入时指定日期格式


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'