五一数学建模B题复盘:用Python搞定快递需求预测与成本优化(附完整代码)
数学建模竞赛实战:Python实现快递需求预测与成本优化全流程解析
去年参加五一数学建模竞赛的经历让我深刻体会到,从问题理解到代码落地的过程中藏着无数"魔鬼细节"。本文将围绕快递需求分析赛题,用Python工具链完整还原特征工程、时序预测、分类建模、路径优化四大核心环节,特别分享那些在比赛文档里不会写的实操技巧。
1. 数据清洗与特征工程实战
拿到快递运输数据的第一件事不是急着建模,而是理解数据背后的业务逻辑。我们使用的数据集包含发货城市、收货城市、日期、运输量等字段,但原始数据就像未经雕琢的玉石——需要先解决几个关键问题:
import pandas as pd # 读取数据时的常见陷阱 raw_data = pd.read_excel('附件1.xlsx', parse_dates=['日期']) # 必须显式指定日期解析 print(f"时间范围: {raw_data['日期'].min()} 至 {raw_data['日期'].max()}")典型数据问题及处理方案:
| 问题类型 | 检测方法 | 处理方案 | 代码示例 |
|---|---|---|---|
| 缺失值 | isna().sum() | 时间序列线性插值 | data['运输量'].interpolate(method='time') |
| 异常值 | 3σ原则或IQR | 盖帽法处理 | np.clip(data['运输量'], lower, upper) |
| 日期不连续 | 生成完整日期范围 | 重新采样填充 | pd.date_range(start, end).difference(data['日期']) |
提示:快递数据中0值需要谨慎处理——可能是无需求也可能是数据缺失,建议结合业务判断
构建有效的特征需要从时间和空间两个维度思考:
# 时间特征扩展示例 data['星期'] = data['日期'].dt.dayofweek data['是否周末'] = data['星期'].isin([5,6]).astype(int) data['月份'] = data['日期'].dt.month # 空间特征构建 city_pairs = data.groupby(['发货城市','收货城市'])['运输量'].mean().reset_index() data = data.merge(city_pairs, on=['发货城市','收货城市'], suffixes=('','_均值'))2. 运输量预测模型构建
预测特定城市对的日运输量是个典型的时序回归问题。我们测试了多种算法后发现LightGBM+时序特征的组合在验证集上表现最优。以下是关键实现步骤:
2.1 数据准备与验证策略
由于数据具有明显的时间属性,绝对不能使用随机划分:
from sklearn.model_selection import TimeSeriesSplit tss = TimeSeriesSplit(n_splits=5) for train_idx, test_idx in tss.split(X): X_train, X_test = X.iloc[train_idx], X.iloc[test_idx] y_train, y_test = y.iloc[train_idx], y.iloc[test_idx]2.2 特征重要性分析
构建完模型后,用SHAP值分析发现影响预测的关键因素:
import lightgbm as lgb import shap model = lgb.LGBMRegressor() model.fit(X_train, y_train) explainer = shap.TreeExplainer(model) shap_values = explainer.shap_values(X_test) shap.summary_plot(shap_values, X_test)预测效果对比表:
| 模型 | MAE | RMSE | R² | 训练时间(s) |
|---|---|---|---|---|
| SVR | 45.2 | 68.7 | 0.81 | 120 |
| 随机森林 | 39.8 | 62.3 | 0.84 | 85 |
| LightGBM | 32.1 | 54.6 | 0.88 | 15 |
注意:实际比赛中需要预测多天的数据时,建议采用滚动预测策略,用前一天的预测结果作为后一天的特征
3. 运输通断分类模型
判断某条线路是否能正常发货本质上是个二分类问题,但样本极度不均衡——正常线路占比超过90%。我们采用了一套组合方案:
from imblearn.over_sampling import SMOTE from sklearn.ensemble import GradientBoostingClassifier # 处理样本不均衡 smote = SMOTE(random_state=42) X_res, y_res = smote.fit_resample(X_train, y_train) # 使用类别权重 model = GradientBoostingClassifier( loss='deviance', max_depth=5, n_estimators=200, min_samples_leaf=50 )分类阈值优化技巧:
from sklearn.metrics import precision_recall_curve probs = model.predict_proba(X_test)[:, 1] precision, recall, thresholds = precision_recall_curve(y_test, probs) # 找到满足业务需求的最佳阈值 optimal_idx = np.argmax(precision * recall) optimal_threshold = thresholds[optimal_idx]4. 运输路径成本优化
这是典型的带约束的整数规划问题,我们选择PuLP库实现。核心在于如何将实际问题转化为数学模型:
from pulp import * # 初始化问题 prob = LpProblem("Transport_Cost_Minimization", LpMinimize) # 决策变量:路径选择 x = LpVariable.dicts("route", [(i,j,k) for i in origins for j in destinations for k in paths[(i,j)]], cat='Binary') # 目标函数:总成本最小化 prob += lpSum([fixed_cost[k]*x[(i,j,k)] + variable_cost[(i,j,k)]*demand[(i,j)] for (i,j,k) in x]) # 约束条件:每个OD对不超过5条路径 for i in origins: for j in destinations: prob += lpSum([x[(i,j,k)] for k in paths[(i,j)]]) <= 5 # 求解 status = prob.solve(PULP_CBC_CMD(msg=False))优化效果对比:
| 日期 | 原始方案成本 | 优化后成本 | 降本比例 |
|---|---|---|---|
| 4-23 | ¥184,520 | ¥152,380 | 17.4% |
| 4-24 | ¥192,650 | ¥158,920 | 17.5% |
| 4-25 | ¥178,930 | ¥147,210 | 17.7% |
在实现过程中发现几个关键点:
- 将固定成本分摊到单位运输量可以简化模型
- 提前过滤掉运输量为0的OD对能大幅减少计算量
- 对无法满足的约束条件需要设计fallback机制
5. 完整项目架构设计
为保证代码可维护性和复现性,推荐以下项目结构:
/project │── /data # 原始数据 │── /notebooks # 探索性分析 │── /src │ ├── features # 特征工程 │ ├── models # 各问题模型 │ └── utils # 工具函数 │── config.yaml # 参数配置 │── pipeline.py # 主流程控制关键依赖版本:
# requirements.txt pandas==1.5.3 numpy==1.23.5 scikit-learn==1.2.2 lightgbm==3.3.5 pulp==2.7.0在最终提交的代码中,我特别添加了以下保障措施:
- 所有随机种子固定
- 关键步骤的输入输出检查
- 运行时长预估和进度条
- 中间结果的自动缓存
比赛结束后复盘,最大的收获不是奖项而是这种从实际问题到数学建模再到代码实现的完整闭环经验。下次如果再遇到类似问题,我会更关注数据中的异常模式挖掘,尝试将图神经网络应用到运输网络建模中。
