第三章:数据探索——用Pandas搞定数据清洗与异常筛查

数据探索这一步,说白了就是给原始数据“洗澡”。

我见过太多同学,模型调得飞起,结果一查数据全是脏的。嗯,那感觉就像你精心做了一桌菜,结果发现米是馊的。所以今天咱们就聊聊,怎么用Pandas把数据洗干净、补全、筛掉明显有问题的样本。

3.1 数据加载与初步预览

拿到交易数据,我习惯先看一眼全貌。不是直接建模,而是先摸摸底。

import pandas as pd
import numpy as np

# 加载交易数据
df = pd.read_csv('transaction_data.csv', parse_dates=['trans_time'])
print(df.shape)        # 看看有多少行多少列
print(df.info())       # 数据类型、非空计数一目了然
print(df.describe())   # 数值列的统计摘要

你想想看,info() 能告诉你哪些列有缺失,describe() 能让你一眼看出金额有没有离谱的极值。我个人习惯先跑这两步,心里就有数了。

小技巧: 如果数据量很大(比如几百万行),我会先 df.sample(10000) 抽个样,快速预览结构,再决定要不要全量处理。

3.2 数据清洗——把脏数据揪出来

数据清洗,核心就三件事:去重、格式统一、不合理值处理。

3.2.1 重复交易检测

交易数据里重复很常见。可能是系统重试,也可能是恶意刷单。

# 检查完全重复的行
dup_rows = df.duplicated().sum()
print(f'完全重复行数: {dup_rows}')

# 更精细的:按关键字段判断重复
dup_key = df.duplicated(subset=['user_id', 'amount', 'trans_time'], keep=False)
df[dup_key].head()

我在项目中遇到过,有个支付渠道因为回调机制,同一笔交易录入了三次。如果不处理,模型会以为这个用户交易频率很高,误判成异常。所以去重这一步,千万别省。

3.2.2 数据类型修正

有时候金额列读进来是字符串,时间列是object。不改类型,后面没法算。

# 金额转浮点,错误值变NaN
df['amount'] = pd.to_numeric(df['amount'], errors='coerce')

# 时间列统一格式
df['trans_time'] = pd.to_datetime(df['trans_time'], errors='coerce')

嗯,这里要注意:errors='coerce' 会把无法转换的值变成NaN。后面我们再处理这些缺失值。

3.3 缺失值处理——补还是不补?

缺失值处理没有银弹。我的原则是:先看缺失比例,再决定策略。

缺失比例 推荐策略 适用场景
< 5% 直接删除或填充均值/中位数 缺失量小,影响不大
5% - 30% 用中位数/众数填充,或用模型预测 特征重要,不能直接删
> 30% 考虑删除该特征,或单独编码“缺失” 缺失太多,填充反而引入噪声
# 查看每列缺失比例
missing_ratio = df.isnull().mean().sort_values(ascending=False)
print(missing_ratio)

# 金额列缺失较少,用中位数填充
df['amount'].fillna(df['amount'].median(), inplace=True)

# 商户类别缺失较多,单独标记
df['merchant_type'].fillna('UNKNOWN', inplace=True)
避坑指南: 我曾经用均值填充金额列的缺失值,结果模型训练出来一塌糊涂。后来发现交易金额分布是严重右偏的,均值被大额交易拉高了。换成中位数后,效果明显改善。所以,先看分布,再选填充策略。

3.4 异常值初步筛查——把“显眼包”找出来

异常值筛查,我把它分成两步:单变量筛查和多变量筛查。今天先讲单变量,简单粗暴但有效。

3.4.1 基于统计分布的筛查

最常用的就是IQR(四分位距)法。说白了,就是找出那些“离群太远”的点。

def detect_outliers_iqr(df, column):
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    outliers = df[(df[column] < lower_bound) | (df[column] > upper_bound)]
    return outliers

# 检测交易金额的异常值
amount_outliers = detect_outliers_iqr(df, 'amount')
print(f'金额异常交易数: {len(amount_outliers)}')

你想想看,正常消费金额大多集中在几十到几千块。突然冒出一笔100万的交易,要么是土豪,要么就是异常。IQR法能快速帮你圈出这些候选。

3.4.2 基于业务规则的筛查

光靠统计不够,还得结合业务。比如凌晨3点的交易、短时间内多次交易,这些在统计上可能不“异常”,但在风控场景里很可疑。

# 凌晨交易(0点-5点)
df['hour'] = df['trans_time'].dt.hour
night_trans = df[df['hour'].between(0, 5)]
print(f'凌晨交易数: {len(night_trans)}')

# 短时间内多次交易(同一用户,5分钟内超过3笔)
df_sorted = df.sort_values(['user_id', 'trans_time'])
df_sorted['time_diff'] = df_sorted.groupby('user_id')['trans_time'].diff().dt.total_seconds()
rapid_trans = df_sorted[df_sorted['time_diff'] < 300]  # 5分钟内
print(f'快速连续交易数: {len(rapid_trans)}')
核心思路: 异常检测不是要把所有异常都找出来,而是先筛出“明显有问题”的样本,作为后续模型训练的负样本候选。剩下的,交给模型去学。

3.5 本章知识体系总览

下面这张图,是我自己梳理的数据探索流程。你可以把它当作操作清单,每一步做完打个勾。

数据探索核心流程 1. 数据加载与预览 2. 数据清洗 去重处理 类型修正 格式统一 3. 缺失值处理 删除缺失行 统计量填充 标记“缺失” 4. 异常值初步筛查

这张图把今天讲的内容串起来了。从加载数据开始,到清洗、补缺失、筛异常,每一步都有对应的Pandas代码。你照着这个流程走一遍,数据基本就干净了。

我的习惯: 每次做完数据探索,我会把清洗前后的数据分布对比一下,比如画个直方图看看金额分布的变化。这样能直观看到处理效果,也方便跟业务同事沟通。

好了,数据探索这块就聊到这儿。记住一句话:脏数据进,脏模型出。花时间把数据洗干净,后面建模会省心很多。


蓝海数据掘金营,专注资料整理