2025MathorCup大数据竞赛B题思路模型详细分析:物流理赔风险识别及服务升级问题

2025MathorCup大数据竞赛B题思路模型详细分析:物流理赔风险识别及服务升级问题,完整论文模型见文末名片

2025年MathorCup赛道B(物流理赔风险识别)思路+模型+代码详解

咱们今天就盯着赛道B的三个问题往深了聊,所有内容都扣着你给的《2025年MathorCup大数据挑战赛-赛道B初赛.pdf》里的要求来------比如文件里明确的"索赔差额公式""三类诉求占比约束""运单特征列表",还有"严重超额样本少"的坑,咱们一步一步拆,从思路到代码都讲明白,不用花里胡哨的术语,就像平时练手一样。

一、先统一认知:B题的核心是"表格数据+业务逻辑"

首先得明确,B题所有任务都基于"结构化运单数据"(就是Excel/CSV表格),文件里的附表1列了所有可用特征------比如运单属性里的"线路类型""保价金额",寄收信息里的"始发城市""寄件人账号",运输情况里的"配送超时时长""异常原因"(比如Pickup Error、Damage这些),还有商品信息里的"商品类型"(Fresh、Electronic Product这些)。

代码工具也不用复杂,就用Python的pandas处理表格、sklearn做特征工程、xgboost/lightgbm建模------这些都是学生最常用的工具,不用搞深度学习(比如CNN、Transformer),纯属浪费时间,表格数据里树模型效果最好,还快。

二、问题1:风险标注模型(给附件1运单分三类)

文件里把这个问题的规则说死了:用"索赔差额"(实际赔付金额 索赔金额)和"实际赔付金额"分"合理诉求""诉求偏高""严重超额",还得满足三个硬约束

  1. 严重超额占比<3%,合理诉求≥85%;
  2. 实际赔付金额越高,需要更高的索赔差额才标"偏高/超额"(比如赔1000块的单,客户多要200可能算偏高;赔100块的,多要50就可能算超额);
  3. 合理诉求的索赔差额"密集",严重超额的"稀疏"。

1. 核心思路:先摸数据分布,再分档定阈值

别一上来就写代码,先打开附件1(假设是attachment1.csv),看看"实际赔付金额"和"索赔差额"长啥样------这步叫EDA(探索性数据分析) ,是关键。

先算索赔差额:用文件给的公式索赔差额 = 实际赔付金额 索赔金额(注意:客户要多了,这个值会是负数,比如实际赔500,客户要600,差额就是-100);

画直方图:看索赔差额的分布------合理诉求的柱子会堆在中间(比如-50~0之间),严重超额的会散在最左边(比如-200以下);

画散点图:横轴"实际赔付金额",纵轴"索赔差额"------你会发现,赔付金额高的点,纵轴得负很多才是超额,这就是文件说的"赔付越高,差额标准越松"。

然后分档定阈值 :按"实际赔付金额"分几档(比如按25%、50%、75%分位数分成4档:0100、100300、300~800、800+),每档单独算索赔差额的阈值,确保每档都符合"合理≥85%,超额<3%"。

2. 代码实现(手把手教)

第一步:加载数据+计算索赔差额
python 复制代码
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# 加载附件1(假设是CSV格式,实际按竞赛给的格式改,比如Excel)
df = pd.read_csv("attachment1.csv")

# 1. 计算索赔差额(严格按文件公式:实际赔付金额 索赔金额)
df["索赔差额"] = df["实际赔付金额"] df["索赔金额"]

# 看看数据基本情况,避免有缺失值(文件没说有,但得检查)
print("缺失值情况:")
print(df[["实际赔付金额", "索赔金额", "索赔差额"]].isnull().sum())
# 有缺失值的话,比如实际赔付金额缺失,用同商品类型的均值补(结合文件特征)
df["实际赔付金额"] = df.groupby("商品类型")["实际赔付金额"].transform(
    lambda x: x.fillna(x.mean())
)
第二步:EDA分析(画分布图)
python 复制代码
# 设置中文字体(不然乱码)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 1. 索赔差额直方图(看密集/稀疏情况)
plt.figure(figsize=(10, 6))
plt.hist(df["索赔差额"], bins=50, color="lightblue", edgecolor="black")
plt.axvline(x=df[df["索赔差额"].notna()]["索赔差额"].quantile(0.03), 
            color="red", linestyle="--", label="3%分位数(超额阈值参考)")
plt.axvline(x=df[df["索赔差额"].notna()]["索赔差额"].quantile(0.85), 
            color="green", linestyle="--", label="85%分位数(合理阈值参考)")
plt.xlabel("索赔差额")
plt.ylabel("运单数量")
plt.title("附件1索赔差额分布(红色=超额参考,绿色=合理参考)")
plt.legend()
plt.savefig("索赔差额分布.png")  # 保存图,论文里要用
plt.close()

# 2. 实际赔付金额vs索赔差额散点图(看分档必要性)
plt.figure(figsize=(10, 6))
plt.scatter(df["实际赔付金额"], df["索赔差额"], alpha=0.5, color="orange")
plt.xlabel("实际赔付金额")
plt.ylabel("索赔差额")
plt.title("实际赔付金额与索赔差额关系(看分档需求)")
plt.savefig("赔付金额vs差额.png")
plt.close()
第三步:分档定阈值+标注类别
python 复制代码
# 1. 给实际赔付金额分档(按分位数分4档,文件没说档数,合理就行)
df["赔付金额档位"] = pd.qcut(
    df["实际赔付金额"], 
    q=[0, 0.25, 0.5, 0.75, 1],  # 25%、50%、75%分位数
    labels=["0~100", "100~300", "300~800", "800+"]  # 实际值按你数据改,比如你数据25%分位数是120,就标0~120
)

# 2. 每档单独算阈值(核心:确保合理≥85%,超额<3%)
def get_threshold_by_bin(bin_data):
    # bin_data是某一档的所有数据
    total = len(bin_data)
    # 严重超额阈值:取3%分位数(≤这个值的是超额,占比≈3%)
    excess_threshold = bin_data["索赔差额"].quantile(0.03)
    # 合理诉求阈值:取85%分位数(≥这个值的是合理,占比≈85%)
    reasonable_threshold = bin_data["索赔差额"].quantile(0.85)
    return excess_threshold, reasonable_threshold

# 按档位分组,计算每档阈值
bin_thresholds = {}
for bin_name, bin_data in df.groupby("赔付金额档位"):
    excess_th, reasonable_th = get_threshold_by_bin(bin_data)
    bin_thresholds[bin_name] = (excess_th, reasonable_th)
    print(f"档位{bin_name}:严重超额阈值={excess_th:.2f},合理诉求阈值={reasonable_th:.2f}")

# 3. 给每单标注类别(严格按文件三类)
def label_risk(row):
    bin_name = row["赔付金额档位"]
    excess_th, reasonable_th = bin_thresholds[bin_name]
    diff = row["索赔差额"]
    if diff <= excess_th:
        return "严重超额"
    elif diff >= reasonable_th:
        return "合理诉求"
    else:
        return "诉求偏高"

df["风险标注结果"] = df.apply(label_risk, axis=1)

# 4. 验证占比是否符合文件要求
label_count = df["风险标注结果"].value_counts(normalize=True) * 100
print("\n风险标注占比(需符合文件:合理≥85%,超额<3%):")
print(label_count)
# 若不符合,微调阈值(比如把超额阈值再调低,让超额占比<3%)

# 5. 保存标注结果(文件要求输出附件1的标注结果)
df[["运单号", "实际赔付金额", "索赔金额", "索赔差额", "赔付金额档位", "风险标注结果"]].to_csv(
    "attachment1_风险标注结果.csv", index=False
)

3. 关键注意事项(紧扣文件)

别一刀切:必须分档,因为文件明确"实际赔付金额越高,需要更高索赔差额才标偏高/超额",不分档会不符合业务逻辑;

占比硬约束:要是标注后严重超额占了4%,就把该档的"超额阈值"再调低(比如从-150改成-180),直到占比<3%;

保留运单号:文件后续要求"不改变表格中的运单号",所以所有操作都要带着"运单号"列,别丢了。

三、问题2:预测附件2的实际赔付金额(回归任务)

文件要求:用附件1的数据建模型,预测附件2每单的"实际赔付金额",要说明评估指标,结果填到result文件,不改运单号。这是典型的表格数据回归任务,核心是"特征工程"(文件里的特征要用好)。

1. 核心思路:特征工程>模型

文件里的附表1给了19个特征(比如"是否为c2c""保价金额""异常原因""商品类型"),这些都是建模的关键,思路分三步:

  1. 特征筛选:删没用的(比如"运单号",唯一值,无意义),留所有业务相关特征(比如"保价金额"------保价高的肯定赔得多,符合文件里"保价金额是申报价值"的定义);
  2. 特征处理
    分类特征:比如"商品类型"(Fresh/Electronic)、"异常原因"(Damage/Lost),用One-Hot编码(模型不认文字);
    缺失值:比如"保价金额"缺失,用同"商品类型+线路类型"的均值补(结合文件里的业务关联);
    异常值:比如实际赔付金额10万(明显是珠宝类高价商品),用99%分位数"盖帽"(避免带偏模型);
  3. 模型选择:用XGBoost/LightGBM(树模型抗干扰强,还能输出特征重要性,方便论文分析------比如"保价金额"是Top1特征,符合业务常识)。

2. 代码实现

第一步:加载数据+特征筛选
python 复制代码
# 加载附件1(带风险标注的,用来训练)和附件2(要预测的)
df_train = pd.read_csv("attachment1_风险标注结果.csv")  # 附件1,有实际赔付金额(标签)
df_test = pd.read_csv("attachment2.csv")  # 附件2,要预测实际赔付金额

# 1. 筛选特征(按文件附表1,删没用的,留业务相关的)
useless_features = ["运单号", "赔付金额档位", "风险标注结果"]  # 运单号要保留,最后输出用,先不删
feature_cols = [col for col in df_train.columns if col not in useless_features + ["实际赔付金额"]]
print("建模特征(来自文件附表1):", feature_cols)

# 2. 分离X(特征)和y(标签:实际赔付金额)
X_train = df_train[feature_cols].copy()
y_train = df_train["实际赔付金额"].copy()
X_test = df_test[feature_cols].copy()
test_order_id = df_test["运单号"].copy()  # 保留运单号,最后输出
第二步:特征工程(关键!)
python 复制代码
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer

# 1. 区分特征类型(按文件附表1)
# 分类特征:比如商品类型、异常原因、进线渠道、是否为c2c(0/1也是分类)
cat_features = ["商品类型", "异常原因", "进线渠道", "是否为c2c", "是否生鲜妥投及时", "寄件是否内部", "新旧程度", "进线人身份", "寄件B/C"]
# 数值特征:比如保价金额、配送超时时长、妥投到进线时长、网点统计特征(始发网点发单量等)
num_features = [col for col in feature_cols if col not in cat_features]

# 2. 分类特征处理:填充缺失值(用众数)+ One-Hot编码
cat_transformer = Pipeline(steps=[
    ("imputer", SimpleImputer(strategy="most_frequent")),  # 分类特征缺失用众数补
    ("onehot", OneHotEncoder(handle_unknown="ignore"))  # 忽略测试集新类别
])

# 3. 数值特征处理:填充缺失值(用均值)+ 标准化(让模型收敛快)
num_transformer = Pipeline(steps=[
    ("imputer", SimpleImputer(strategy="mean")),  # 数值特征缺失用均值补
    ("scaler", StandardScaler())  # 标准化
])

# 4. 合并所有特征处理(ColumnTransformer按特征类型分别处理)
preprocessor = ColumnTransformer(
    transformers=[
        ("num", num_transformer, num_features),
        ("cat", cat_transformer, cat_features)
    ])
第三步:建模+训练+评估
python 复制代码
from sklearn.model_selection import train_test_split
from xgboost import XGBRegressor
from sklearn.metrics import mean_absolute_error, mean_squared_error

# 1. 拆分训练集和验证集(用附件1的80%训练,20%验证,别全用)
X_train_split, X_val_split, y_train_split, y_val_split = train_test_split(
    X_train, y_train, test_size=0.2, random_state=2025
)

# 2. 建模型 pipeline(预处理+XGB回归)
model = Pipeline(steps=[
    ("preprocessor", preprocessor),
    ("regressor", XGBRegressor(
        n_estimators=100,  # 树的数量,可调
        max_depth=5,       # 树深度,防过拟合
        learning_rate=0.1, # 学习率
        random_state=2025
    ))
])

# 3. 训练模型
model.fit(X_train_split, y_train_split)

# 4. 验证模型(文件要求说明评估指标)
y_val_pred = model.predict(X_val_split)
mae = mean_absolute_error(y_val_split, y_val_pred)
rmse = np.sqrt(mean_squared_error(y_val_split, y_val_pred))
print(f"\n模型评估(附件1验证集):")
print(f"MAE(平均绝对误差):{mae:.2f} 元")  # 直观,比如平均差25元
print(f"RMSE(均方根误差):{rmse:.2f} 元")  # 对大误差敏感,符合文件"控制成本"需求

# 5. 用全量附件1训练模型(最后预测用)
model.fit(X_train, y_train)

# 6. 预测附件2的实际赔付金额
df_test["预测实际赔付金额"] = model.predict(X_test)

# 7. 保存结果(文件要求填到result文件,不改变运单号)
result_df = pd.DataFrame({
    "运单号": test_order_id,
    "预测实际赔付金额": df_test["预测实际赔付金额"].round(2)  # 保留2位小数,符合金额格式
})
result_df.to_csv("result_问题2_预测赔付金额.csv", index=False)

3. 关键注意事项(紧扣文件)

特征别漏:文件附表1里的"网点统计特征"(比如始发网点万单理赔率)很重要------理赔率高的网点,赔付金额可能更高,别漏了;

评估指标:必须说MAE和RMSE,文件没指定,但这俩最符合"理赔成本控制"的业务目标(大误差会导致多赔或少赔);

结果格式:预测金额保留2位小数(金额格式),运单号顺序别乱,文件后续要合并问题2和3的结果。

四、问题3:风险分类预测+方法对比(B题最难)

文件要求:用问题1的标注规则(附件1的三类结果)建分类模型,预测附件2的风险类别;还要说清"严重超额样本少"的处理方法,对比"直接分类"和"先预测赔付再分类(问题2方法)"的优劣势。

1. 核心思路:先解决不平衡,再建模对比

文件明确"严重超额占比通常<3%",样本极不平衡------直接建模会漏判超额单(模型只认多数类"合理诉求"),所以第一步是处理样本不平衡,然后建分类模型,最后对比两种方法。

两种方法的逻辑:

直接分类:用附件1的"特征+风险标注结果"建分类模型,直接预测附件2的类别;

先预测赔付再分类:用问题2预测的"实际赔付金额",结合问题1的分档阈值,给附件2标类别(比如预测赔付500,按300~800档的阈值标类别)。

2. 代码实现

第一步:处理样本不平衡+建分类模型
python 复制代码
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, f1_score
from imblearn.over_sampling import SMOTE  # 处理不平衡的SMOTE算法
from sklearn.model_selection import cross_val_score

# 1. 准备分类数据(X用问题2的特征,y用问题1的风险标注结果)
X_classify = X_train.copy()  # 和问题2的特征一样
y_classify = df_train["风险标注结果"].copy()

# 2. 处理"严重超额样本少"(文件核心难点)
# 方法1:SMOTE过采样(造严重超额的假样本,平衡三类)
smote = SMOTE(random_state=2025, sampling_strategy="minority")  # 只给少数类(严重超额)过采样
X_resampled, y_resampled = smote.fit_resample(preprocessor.fit_transform(X_classify), y_classify)
print(f"过采样前样本数:{y_classify.value_counts()}")
print(f"过采样后样本数:{pd.Series(y_resampled).value_counts()}")  # 三类样本数接近

# 3. 建分类模型(用随机森林,简单易解释)
classifier = RandomForestClassifier(
    n_estimators=100,
    max_depth=6,
    class_weight="balanced",  # 再给少数类加权重,双重保险
    random_state=2025
)

# 4. 交叉验证(看模型稳定性,避免过拟合)
cv_scores = cross_val_score(
    classifier, X_resampled, y_resampled, 
    cv=5, scoring="f1_macro"  # 用F1,不用准确率(不平衡数据准确率骗人)
)
print(f"\n5折交叉验证F1分数:{cv_scores.mean():.4f} ± {cv_scores.std():.4f}")

# 5. 训练模型+预测附件2
classifier.fit(X_resampled, y_resampled)
# 先对附件2的特征做预处理(和训练时一致)
X_test_processed = preprocessor.transform(X_test)
df_test["直接分类_风险预测结果"] = classifier.predict(X_test_processed)

# 6. 评估模型(用附件1的验证集)
X_val_processed = preprocessor.transform(X_val_split)
y_val_pred_class = classifier.predict(X_val_processed)
print("\n附件1验证集分类评估(重点看严重超额的F1):")
print(classification_report(df_train.loc[X_val_split.index, "风险标注结果"], y_val_pred_class))
第二步:实现"先预测赔付再分类"(问题2方法)
python 复制代码
# 1. 用问题2的预测结果(附件2的预测实际赔付金额)
df_test["预测赔付金额档位"] = pd.qcut(
    df_test["预测实际赔付金额"], 
    q=[0, 0.25, 0.5, 0.75, 1],
    labels=["0~100", "100~300", "300~800", "800+"]  # 和问题1的档位一致
)

# 2. 用问题1的阈值,给附件2标类别(先预测赔付再分类)
def label_by_pred_payout(row):
    bin_name = row["预测赔付金额档位"]
    # 用问题1算好的阈值(bin_thresholds是问题1里的字典)
    excess_th, reasonable_th = bin_thresholds[bin_name]
    # 索赔差额=预测实际赔付金额 客户索赔金额(附件2里有"索赔金额")
    diff = row["预测实际赔付金额"] row["索赔金额"]
    if diff <= excess_th:
        return "严重超额"
    elif diff >= reasonable_th:
        return "合理诉求"
    else:
        return "诉求偏高"

df_test["先预测赔付_风险预测结果"] = df_test.apply(label_by_pred_payout, axis=1)
第三步:合并结果+对比两种方法
python 复制代码
# 1. 合并问题2和3的结果(文件要求放一个result文件)
final_result = pd.DataFrame({
    "运单号": test_order_id,
    "预测实际赔付金额": df_test["预测实际赔付金额"].round(2),
    "直接分类_风险类别": df_test["直接分类_风险预测结果"],
    "先预测赔付_风险类别": df_test["先预测赔付_风险预测结果"]
})
final_result.to_csv("result_问题2+3_最终结果.csv", index=False)

# 2. 对比两种方法的优劣势(紧扣文件业务,论文里要写)
print("\n两种方法优劣势对比(基于文件业务目标:控制成本+减少人工):")
print("1. 直接分类(问题3方法):")
print("   优势:无误差传递(不用先预测赔付,少一次错),严重超额识别准(SMOTE处理后F1高),控成本好;")
print("   劣势:解释性差(业务人员问为啥是超额,只能说模型算的,不符合文件"清晰策略"需求);")
print("2. 先预测赔付再分类(问题2方法):")
print("   优势:符合业务流程(文件里理赔就是先算该赔多少,再判合理,解释性强),合理诉求识别准;")
print("   劣势:误差传递(赔付预测错了,分类也错),严重超额漏判率高(比如赔付预测低了,按低档位阈值标,漏超额);")

3. 关键注意事项(紧扣文件)

不平衡处理:必须说SMOTE+类别权重,文件明确"严重超额样本占比严重不均衡",这是得分点;

对比别空谈:要结合文件里的"控制理赔成本""减少人工审核"------比如直接分类控成本好,先预测赔付减少人工争议;

结果合并:文件要求"问题2、3的结果放在一个result文件",所以最后要合并成一个CSV,别分开。

五、最后给学生的实战建议

  1. 别等附件1/2: 竞赛里附件会晚给,但可以先写好代码框架(比如特征处理、分档函数),等数据来了改改列名就能用;
  2. 紧扣文件: 所有步骤都要对应文件里的规则(比如索赔差额公式、占比约束),别自己造逻辑;
  3. 论文要配图: 问题1的分布图、问题2的特征重要性图、问题3的分类混淆矩阵,这些能让论文更充实,符合竞赛要求。

B题不难,但"细"------把文件里的特征、规则吃透,代码一步一步来,拿分很稳;要是脱离文件瞎建模,再复杂也没用。

相关推荐
学传打活3 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续3)
微信公众平台·1024程序员节·汉字·昆仑正义文化
学传打活9 天前
【边打字.边学昆仑正义文化】_进阶篇5 宇宙星系的运转(续1)
微信公众平台·1024程序员节·汉字·昆仑正义文化
fangzhanpeng16816 天前
快速掌握C#语言基础知识点(1.基础数据类型)
开发语言·c#·1024程序员节
无己心19 天前
面向多平台 AI 搜索引擎的适配层架构设计
前端·人工智能·搜索引擎·ai·1024程序员节
u01030552725 天前
AI驱动的安卓UI自动生成功能实现
人工智能·1024程序员节
u01030552725 天前
长株潭智能体赋能腾讯元器链接共享
1024程序员节
云贝贝贝1 个月前
OceanBase认证体系介绍与备考指南
运维·服务器·oceanbase·1024程序员节
u0103055271 个月前
Java图像处理实战指南
人工智能·1024程序员节
u0103055271 个月前
Java AWT鼠标事件全解析
人工智能·1024程序员节
u0103055271 个月前
Java实用类应用精讲
人工智能·1024程序员节