基于表格数据建模这一套已经做烂了,前面的案例非常多,本次这个案例主要现在于这个数据集是比较全面新鲜的。有很多征信报告的特征,当然本文只是简单地把它们按照某一个组件进行粗暴的合并。呃,如果有探索欲的同学可以更加详细的丰富特征,比如计算最大最小的算子,或者是计算一些差值,时间切片,从而衍生出更丰富的特征工程,进行更加详细的数据挖掘。说不定能够取得更好的效果。

信贷风控是金融领域的核心环节,其核心目标是通过数据模型识别借款人的违约风险,为放贷决策提供依据。传统表格数据建模虽已广泛应用,但本案例的核心优势在于采用全面且新鲜的征信数据集,包含多维度征信报告特征。

本文将先完成基础建模流程,包括数据加载、预处理、特征工程(基础合并)、模型训练与评估,再提出特征工程的优化方向,为有探索欲的使用者提供进一步挖掘的思路,最终输出可直接运行的完整代码与分析过程。

一、案例背景与数据说明

1. 案例背景

信贷机构在放贷前需评估借款人的还款能力与意愿,征信数据是关键依据。它涵盖借款人的信贷历史、负债情况、履约记录等信息,能有效反映其信用状况。

本案例使用的征信数据集,相较于传统数据集,特征维度更丰富,包含征信报告中的多个核心组件(如信贷账户信息、逾期记录、查询记录等),可更全面地刻画借款人风险。

2. 数据字段说明

数据集包含两类核心数据:借款人基础信息表征信特征表,具体字段如下(示例):

表类型 字段名称 字段说明 数据类型
借款人基础信息表 user_id 借款人唯一标识 字符串
借款人基础信息表 target 违约标签(1 = 违约,0 = 正常) 整数
征信特征表 user_id 借款人唯一标识(关联键) 字符串
征信特征表 credit_account_num 信贷账户数量 整数
征信特征表 overdue_1m_count 1 个月内逾期次数 整数
征信特征表 max_overdue_days 最大逾期天数 整数
征信特征表 recent_3m_query_count 近 3 个月查询次数 整数
征信特征表 debt_ratio 负债比率 浮点数

二、完整建模流程

1. 环境准备

首先导入建模所需的 Python 库,包括数据处理、可视化、模型训练与评估相关工具。

# 基础数据处理库
import pandas as pd
import numpy as np

# 可视化库
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.sans-serif'] = ['SimHei']  # 解决中文显示问题
plt.rcParams['axes.unicode_minus'] = False

# 模型相关库
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression  # 基础风控模型
from sklearn.ensemble import RandomForestClassifier  # 集成模型
from sklearn.metrics import (accuracy_score, precision_score, recall_score, 
                             f1_score, roc_auc_score, confusion_matrix, roc_curve)

# 忽略警告
import warnings
warnings.filterwarnings('ignore')

2. 数据加载与预处理

2.1 数据加载

假设数据集以 CSV 格式存储,通过pandas加载两张核心表,并通过user_id进行关联,形成完整的建模数据集。

# 加载数据(需替换为实际文件路径)
base_df = pd.read_csv("借款人基础信息表.csv")
credit_df = pd.read_csv("征信特征表.csv")

# 数据关联(基于user_id一对一匹配)
df = pd.merge(base_df, credit_df, on="user_id", how="inner")

# 查看数据基本信息
print("数据集形状:", df.shape)
print("\n前5行数据:")
print(df.head())
print("\n数据类型:")
print(df.dtypes)
print("\n缺失值统计:")
print(df.isnull().sum())
2.2 数据预处理

处理缺失值、异常值与重复值,确保数据质量满足建模要求。

# 1. 处理重复值
df = df.drop_duplicates(subset="user_id", keep="first")  # 按user_id去重,保留第一条
print(f"去重后数据集形状:{df.shape}")

# 2. 处理缺失值(根据征信数据特性选择策略)
# 数值型特征:用中位数填充(抗异常值)
num_cols = df.select_dtypes(include=[np.int64, np.float64]).columns.drop("target")
for col in num_cols:
    df[col].fillna(df[col].median(), inplace=True)

# 3. 处理异常值(基于3σ原则识别并修正)
for col in num_cols:
    mean_val = df[col].mean()
    std_val = df[col].std()
    # 识别异常值(超出均值±3倍标准差)
    upper_bound = mean_val + 3 * std_val
    lower_bound = mean_val - 3 * std_val
    # 用边界值替换异常值
    df[col] = np.where(df[col] > upper_bound, upper_bound, df[col])
    df[col] = np.where(df[col] < lower_bound, lower_bound, df[col])

# 4. 确认预处理后的数据质量
print("\n预处理后缺失值统计:")
print(df.isnull().sum())

3. 特征工程(基础版:组件合并)

本案例先采用 “粗暴合并” 策略 —— 将征信特征表中的所有数值型特征直接作为建模变量,暂不做复杂衍生,后续再提出优化方向。

# 1. 划分特征与标签
X = df.drop(["user_id", "target"], axis=1)  # 特征:排除唯一标识与标签
y = df["target"]  # 标签:违约与否

# 2. 特征类型划分(用于后续预处理 pipeline)
numeric_features = X.select_dtypes(include=[np.int64, np.float64]).columns.tolist()
categorical_features = X.select_dtypes(include=[object]).columns.tolist()

print(f"数值型特征:{numeric_features}")
print(f"分类型特征:{categorical_features}")

# 3. 特征预处理 pipeline(避免数据泄露)
# 数值型特征:标准化(消除量纲影响)
# 分类型特征:独热编码(若存在,如借款人职业等)
preprocessor = ColumnTransformer(
    transformers=[
        ("num", StandardScaler(), numeric_features),
        ("cat", OneHotEncoder(drop="first", sparse_output=False), categorical_features)
    ])

# 查看处理后的特征维度
X_processed = preprocessor.fit_transform(X)
print(f"\n预处理后特征维度:{X_processed.shape}")

4. 模型训练与评估

4.1 数据划分

将数据集按 7:3 比例划分为训练集与测试集,确保模型在 unseen 数据上的泛化能力。

X_train, X_test, y_train, y_test = train_test_split(
    X_processed, y, test_size=0.3, random_state=42, stratify=y  # stratify=y:保持标签分布一致
)

print(f"训练集形状:X_train={X_train.shape}, y_train={y_train.shape}")
print(f"测试集形状:X_test={X_test.shape}, y_test={y_test.shape}")
print(f"训练集违约率:{y_train.sum() / len(y_train):.2%}")
print(f"测试集违约率:{y_test.sum() / len(y_test):.2%}")
4.2 模型训练

选择风控领域常用的逻辑回归(可解释性强)与随机森林(泛化能力强)作为基础模型,对比其性能。

# 1. 定义模型(基于Pipeline整合预处理与训练)
# 逻辑回归模型
lr_model = Pipeline(steps=[
    ("classifier", LogisticRegression(class_weight="balanced", random_state=42))  # 平衡类别权重
])

# 随机森林模型
rf_model = Pipeline(steps=[
    ("classifier", RandomForestClassifier(n_estimators=100, class_weight="balanced", random_state=42))
])

# 2. 训练模型
print("开始训练逻辑回归模型...")
lr_model.fit(X_train, y_train)

print("开始训练随机森林模型...")
rf_model.fit(X_train, y_train)
4.3 模型评估

分类性能(准确率、精确率等)与风控核心指标(AUC、召回率)两个维度评估模型,其中 AUC(ROC 曲线下面积)是风控模型的核心衡量标准(取值 0.5-1,越接近 1 性能越好)。

# 定义评估函数(输出多维度指标)
def evaluate_model(model, X_test, y_test, model_name):
    y_pred = model.predict(X_test)
    y_pred_proba = model.predict_proba(X_test)[:, 1]  # 违约概率预测值
    
    # 计算评估指标
    metrics = {
        "模型": model_name,
        "准确率": accuracy_score(y_test, y_pred).round(4),
        "精确率": precision_score(y_test, y_pred).round(4),
        "召回率": recall_score(y_test, y_pred).round(4),
        "F1分数": f1_score(y_test, y_pred).round(4),
        "AUC": roc_auc_score(y_test, y_pred_proba).round(4)
    }
    
    # 打印指标
    print(f"\n{model_name} 评估结果:")
    for key, value in metrics.items():
        print(f"{key}: {value}")
    
    # 绘制ROC曲线
    fpr, tpr, _ = roc_curve(y_test, y_pred_proba)
    plt.plot(fpr, tpr, label=f"{model_name} (AUC={metrics['AUC']})", linewidth=2)
    
    return metrics

# 评估两个模型
lr_metrics = evaluate_model(lr_model, X_test, y_test, "逻辑回归")
rf_metrics = evaluate_model(rf_model, X_test, y_test, "随机森林")

# 绘制ROC曲线(添加基准线)
plt.plot([0, 1], [0, 1], "k--", label="随机猜测 (AUC=0.5)")
plt.xlabel("假正例率(FPR)")
plt.ylabel("真正例率(TPR)")
plt.title("信贷风控模型ROC曲线")
plt.legend()
plt.savefig("风控模型ROC曲线.png", dpi=300, bbox_inches="tight")
plt.show()

# 对比模型性能(表格形式)
metrics_df = pd.DataFrame([lr_metrics, rf_metrics])
print("\n模型性能对比:")
print(metrics_df)
4.4 特征重要性分析(随机森林)

随机森林可输出特征重要性,帮助识别对违约风险影响最大的征信特征,为业务决策提供依据。

# 获取预处理后的特征名称(数值型+独热编码后的分类型)
# 1. 数值型特征名称
num_feature_names = numeric_features

# 2. 分类型特征名称(独热编码后)
cat_encoder = preprocessor.named_transformers_["cat"]
cat_feature_names = []
for i, col in enumerate(categorical_features):
    categories = cat_encoder.categories_[i][1:]  # drop="first",排除第一个类别
    cat_feature_names.extend([f"{col}_{cat}" for cat in categories])

# 3. 完整特征名称列表
feature_names = num_feature_names + cat_feature_names

# 提取随机森林特征重要性
rf_importance = rf_model.named_steps["classifier"].feature_importances_

# 排序并可视化前10个重要特征
importance_df = pd.DataFrame({
    "特征名称": feature_names,
    "重要性": rf_importance
}).sort_values("重要性", ascending=False).head(10)

# 绘制特征重要性柱状图
plt.figure(figsize=(12, 6))
sns.barplot(x="重要性", y="特征名称", data=importance_df)
plt.title("随机森林模型 - 前10个重要征信特征")
plt.xlabel("特征重要性")
plt.ylabel("特征名称")
plt.savefig("特征重要性.png", dpi=300, bbox_inches="tight")
plt.show()

print("\n前10个重要特征:")
print(importance_df)

三、特征工程优化方向(进阶挖掘)

基础版模型仅用了 “特征合并”,若要进一步提升模型性能,可从以下方向衍生更丰富的征信特征,挖掘数据深层价值:

1. 统计算子衍生

对同一维度的征信特征计算最大、最小、均值、标准差等统计量,反映借款人的历史行为规律。

  • 示例 1:计算过去 12 个月内 “每月逾期次数” 的最大值(max_overdue_12m)、均值(avg_overdue_12m)。
  • 示例 2:计算所有信贷账户 “授信额度” 的标准差(credit_limit_std),反映账户额度的波动情况。

2. 差值与比率特征

通过差值(变化趋势) 与比率(相对关系) 刻画借款人风险变化。

  • 示例 1:计算 “当前负债金额” 与 “6 个月前负债金额” 的差值(debt_change_6m),判断负债是否快速增加。
  • 示例 2:计算 “逾期次数” 与 “信贷账户数量” 的比率(overdue_account_ratio),反映逾期账户的占比。

3. 时间切片特征

按时间维度拆分征信数据,捕捉近期行为(对当前风险更具预测力)。

  • 示例 1:将查询记录拆分为 “近 1 个月查询次数”(query_1m)、“近 3 个月查询次数”(query_3m)、“近 6 个月查询次数”(query_6m)。
  • 示例 2:将逾期记录拆分为 “近 3 个月逾期天数”(overdue_days_3m)、“3-12 个月逾期天数”(overdue_days_3_12m),对比近期与远期履约情况。

4. 行为序列特征

若征信数据包含时间序列(如每月还款记录),可衍生序列特征:

  • 示例 1:计算 “连续正常还款月数”(continuous_normal_months),反映当前履约稳定性。
  • 示例 2:识别 “逾期后还款” 的频率(overdue_repay_ratio),判断借款人的还款意愿。

四、完整代码总结

将上述流程整合为可直接运行的完整代码,使用者仅需替换数据文件路径即可执行。

# 完整代码:基于征信数据的信贷风控模型
# ======================================

# 1. 环境准备
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import (accuracy_score, precision_score, recall_score, 
                             f1_score, roc_auc_score, confusion_matrix, roc_curve)
import warnings
warnings.filterwarnings('ignore')
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 2. 数据加载与预处理
def load_and_preprocess_data(base_path, credit_path):
    # 加载数据
    base_df = pd.read_csv(base_path)
    credit_df = pd.read_csv(credit_path)
    df = pd.merge(base_df, credit_df, on="user_id", how="inner")
    
    # 去重
    df = df.drop_duplicates(subset="user_id", keep="first")
    
    # 处理缺失值(数值型用中位数)
    num_cols = df.select_dtypes(include=[np.int64, np.float64]).columns.drop("target", errors="ignore")
    for col in num_cols:
        df[col].fillna(df[col].median(), inplace=True)
    
    # 处理异常值(3σ原则)
    for col in num_cols:
        mean_val = df[col].mean()
        std_val = df[col].std()
        upper_bound = mean_val + 3 * std_val
        lower_bound = mean_val - 3 * std_val
        df[col] = np.where(df[col] > upper_bound, upper_bound, df[col])
        df[col] = np.where(df[col] < lower_bound, lower_bound, df[col])
    
    return df

# 3. 特征工程与模型训练
def train_risk_models(df):
    # 划分特征与标签
    X = df.drop(["user_id", "target"], axis=1)
    y = df["target"]
    
    # 特征类型划分
    numeric_features = X.select_dtypes(include=[np.int64, np.float64]).columns.tolist()
    categorical_features = X.select_dtypes(include=[object]).columns.tolist()
    
    # 预处理 pipeline
    preprocessor = ColumnTransformer(
        transformers=[
            ("num", StandardScaler(), numeric_features),
            ("cat", OneHotEncoder(drop="first", sparse_output=False), categorical_features)
        ])
    
    # 数据划分
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.3, random_state=42, stratify=y
    )
    
    # 定义模型
    lr_model = Pipeline(steps=[("preprocessor", preprocessor), 
                               ("classifier", LogisticRegression(class_weight="balanced", random_state=42))])
    rf_model = Pipeline(steps=[("preprocessor", preprocessor), 
                               ("classifier", RandomForestClassifier(n_estimators=100, class_weight="balanced", random_state=42))])
    
    # 训练模型
    lr_model.fit(X_train, y_train)
    rf_model.fit(X_train, y_train)
    
    # 评估模型
    def evaluate(model, X_test, y_test, name):
        y_pred = model.predict(X_test)
        y_pred_proba = model.predict_proba(X_test)[:, 1]
        metrics = {
            "模型": name,
            "准确率": accuracy_score(y_test, y_pred).round(4),
            "精确率": precision_score(y_test, y_pred).round(4),
            "召回率": recall_score(y_test, y_pred).round(4),
            "F1": f1_score(y_test, y_pred).round(4),
            "AUC": roc_auc_score(y_test, y_pred_proba).round(4)
        }
        return metrics, y_pred_proba
    
    lr_metrics, lr_proba = evaluate(lr_model, X_test, y_test, "逻辑回归")
    rf_metrics, rf_proba = evaluate(rf_model, X_test, y_test, "随机森林")
    
    # 绘制ROC曲线
    plt.figure(figsize=(10, 6))
    fpr_lr, tpr_lr, _ = roc_curve(y_test, lr_proba)
    fpr_rf, tpr_rf, _ = roc_curve(y_test, rf_proba)
    plt.plot(fpr_lr, tpr_lr, label=f"逻辑回归 (AUC={lr_metrics['AUC']})", linewidth=2)
    plt.plot(fpr_rf, tpr_rf, label=f"随机森林 (AUC={rf_metrics['AUC']})", linewidth=2)
    plt.plot([0, 1], [0, 1], "k--", label="随机猜测")
    plt.xlabel("假正例率(FPR)")
    plt.ylabel("真正例率(TPR)")
    plt.title("信贷风控模型ROC曲线")
    plt.legend()
    plt.savefig("风控模型ROC曲线.png", dpi=300, bbox_inches="tight")
    plt.close()
    
    # 输出结果
    metrics_df = pd.DataFrame([lr_metrics, rf_metrics])
    print("模型性能对比:")
    print(metrics_df)
    
    return lr_model, rf_model, metrics_df

# 4. 主函数(执行流程)
if __name__ == "__main__":
    # 替换为实际数据路径
    BASE_PATH = "借款人基础信息表.csv"
    CREDIT_PATH = "征信特征表.csv"
    
    # 执行流程
    print("1. 数据加载与预处理...")
    df = load_and_preprocess_data(BASE_PATH, CREDIT_PATH)
    print(f"预处理后数据集形状:{df.shape}")
    
    print("\n2. 模型训练与评估...")
    lr_model, rf_model, metrics_df = train_risk_models(df)
    
    print("\n3. 模型保存(可选)...")
    # 若需保存模型,可使用joblib
    # import joblib
    # joblib.dump(rf_model, "信贷风控随机森林模型.pkl")
    print("流程执行完成!")

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐