3. 资金流数据预处理:数据源介绍与清洗实战

做资金流网络挖掘,第一步不是建模,也不是调参。

是跟数据死磕。

我做了这么多年数据挖掘,见过太多项目死在数据预处理阶段。说白了,资金流数据是所有数据里最脏的之一。银行流水、交易日志,这些数据格式五花八门,缺失值、异常值满天飞。你想想看,如果源头数据都是错的,后面挖出来的资金网络能准吗?

3.1 数据源介绍:银行流水 vs 交易日志

资金流数据主要来自两个地方:银行流水交易日志。这两者有什么区别?我简单说说。

维度 银行流水 交易日志
来源 银行系统导出 业务系统记录
字段 交易时间、金额、对方账户、摘要 交易ID、请求时间、响应时间、状态码
特点 权威、但字段少 字段丰富、但噪声大
时效性 T+1 或 T+0 实时

我个人习惯,拿到数据后先看字段列表。银行流水一般有这几个核心字段:trans_idfrom_accountto_accountamounttrans_timeremark。交易日志则更细,会记录请求来源IP、设备指纹、操作类型等。

关键点:银行流水是「结果数据」,交易日志是「过程数据」。做资金网络挖掘,两者缺一不可。

3.2 数据清洗:缺失值与异常值处理

数据清洗是体力活,也是技术活。我在项目中遇到过最头疼的情况——某银行导出的流水,to_account字段有30%是空的。为什么?因为跨行转账时,对方账户信息被脱敏了。

缺失值处理

缺失值不能一刀切。我一般分三种情况处理:

  • 关键字段缺失(如金额、交易双方):直接剔除。这类记录无法用于网络构建。
  • 非关键字段缺失(如摘要、备注):填充为"未知"或"UNKNOWN"。
  • 时间字段缺失:尝试从交易日志中关联补全。关联不上就剔除。
# 伪代码示例:缺失值处理
def clean_missing(df):
    # 剔除关键字段缺失的记录
    df = df.dropna(subset=['from_account', 'to_account', 'amount'])
    # 填充非关键字段
    df['remark'] = df['remark'].fillna('UNKNOWN')
    return df

异常值处理

异常值怎么找?我常用的方法就两个:统计分布法业务规则法

统计分布法:比如交易金额,正常情况下呈长尾分布。如果某笔交易金额是平均值的100倍以上,大概率是异常。我曾经处理过一个案例,某账户单笔转账1.2亿,后来发现是系统测试数据。

业务规则法:比如交易时间。凌晨3点的频繁交易,你想想看,正常吗?还有交易对手数量,一个普通账户一天内跟500个不同账户交易,这明显有问题。

注意:异常值不一定是错误数据。有些异常恰恰是资金网络中的关键节点。我建议先标记,不要直接删除。

3.3 交易对账与去重

对账,说白了就是确保「你记的账」和「我记的账」能对上。资金流数据中,同一笔交易可能在银行流水和交易日志中各出现一次。如果不做去重,网络中的边就会重复计算。

我常用的去重策略:

  1. 基于交易ID去重:如果两个数据源都有trans_id,直接按ID去重。
  2. 基于交易要素去重:没有ID时,用「交易时间 + 金额 + 交易双方」组合去重。
  3. 时间窗口去重:同一账户在1秒内向同一账户发起多笔相同金额的交易,只保留一笔。
# 伪代码示例:交易去重
def dedup_transactions(df):
    # 按交易要素去重
    df = df.drop_duplicates(subset=['trans_time', 'amount', 'from_account', 'to_account'])
    return df

3.4 时间窗口划分

资金流网络是动态的。同一个交易网络,在一天内和一个月内,结构完全不同。时间窗口怎么划?

我一般根据业务场景来定:

  • 日内窗口(1小时/4小时):适合检测高频交易、洗钱行为。
  • 周窗口(7天):适合检测周期性资金流动。
  • 月窗口(30天):适合检测长期资金网络结构。

嗯,这里要注意:窗口大小直接影响网络密度。窗口太小,网络稀疏;窗口太大,网络过于稠密,关键路径被淹没。我建议先做几个不同窗口的实验,对比网络指标后再定。

3.5 实体对齐与ID映射

这是资金流预处理中最容易踩坑的地方。

同一个实体,在不同数据源中可能有不同的ID。比如:

  • 银行流水中的账户号:622202****1234
  • 交易日志中的用户ID:USER_10086
  • 风控系统中的客户编号:CUST_9527

如果不做ID映射,你构建的资金网络就是断裂的。我曾经因为没做好映射,导致一个资金团伙被拆成了三个孤立子图,差点漏掉关键线索。

我的做法是:

  1. 建立统一ID体系:每个实体分配一个全局唯一的global_id
  2. 构建映射表:维护一个entity_mapping表,记录各数据源ID与global_id的对应关系。
  3. 关联规则:通过手机号、身份证号、设备指纹等字段进行跨源关联。
# 伪代码示例:ID映射
def build_entity_mapping(df_bank, df_log):
    mapping = {}
    # 按手机号关联
    for phone in set(df_bank['phone']) & set(df_log['phone']):
        bank_id = df_bank[df_bank['phone'] == phone]['account'].iloc[0]
        log_id = df_log[df_log['phone'] == phone]['user_id'].iloc[0]
        mapping[bank_id] = log_id
    return mapping

3.6 本章知识体系总览

下面这张图,是我梳理的本章核心逻辑。你可以对照着看,心里有个谱。

资金流数据预处理流程 数据源 银行流水 + 交易日志 数据清洗 缺失值 + 异常值处理 对账去重 交易ID + 要素去重 时间窗口划分 日内 / 周 / 月窗口 实体对齐与ID映射 统一ID + 映射表 清洗后的资金流数据

我的经验:数据预处理占整个项目60%以上的时间。别急着往下走,把这一步做扎实了,后面的网络挖掘会顺畅很多。


公众号:蓝海资料掘金营,微信deep3321