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

相关推荐
热爱运维的小七7 小时前
从传统架构到云原生,如何应对数据增长挑战?
数据库·it运维·devops·1024程序员节
huangyuchi.7 小时前
【Linux网络】套接字Socket编程预备
linux·运维·服务器·端口号·linux网络·套接字·1024程序员节
稍带温度的风7 小时前
node 后端服务 PM2 相关命令
node.js·pm2·1024程序员节
More more7 小时前
Element Plus 表格table中,el-image实现图片预览无层级显示问题
1024程序员节
Brandon汐7 小时前
MySQL的安装与卸载
1024程序员节
胡耀超7 小时前
5、服务器互连技术(小白入门版)
服务器·网络·ai·网络拓扑·gpu·pcie·1024程序员节
DKunYu7 小时前
2.2softmax回归
pytorch·python·深度学习·1024程序员节
努力进修7 小时前
用 Excalidraw+cpolar 做会议协作,像素级还原实体白板体验
cpolar·1024程序员节·excalidraw
victory04317 小时前
whisper 模型处理音频办法与启示
1024程序员节