拓冰建站拓冰建站
首页 / 资讯中心 / 正文

数据驱动的血小板库存优化:需求预测到动态库存策略

简介一份围绕血小板库存优化的数据驱动建模讲解资料面向医疗供应链研究者、血库管理者和数据分析从业者。内容以加拿大汉密尔顿四家医院2016—2018年临床输血数据为背景针对血小板价格昂贵、保质期短5—7天的特点完整呈现随机森林需求预测、滞后特征构造、模型评估以及综合考虑提前期与保质期的动态库存策略并延伸讨论牛鞭效应、敏感性分析和紧急订单减少策略。资源为单个PDF文件约822KB包含可运行的Python代码及逐段解释覆盖从数据预处理到库存策略类实现的关键模块能帮助读者快速掌握数据驱动库存决策的完整流程。已有62人学习下载适合想深入理解易腐品库存管理并动手复现论文方法的读者。1. 数据驱动的血小板库存优化模型从需求预测到动态库存策略血小板库存是所有医院供应链里最不适合“凭感觉备货”的物料保质期只有 5~7 天采血中心的供应节奏又相对固定而临床需求往往在手术排程变动、创伤患者集中或血液科治疗调整时突然上台阶。基于数据驱动的血小板库存优化模型把需求预测和临床特征塞进同一条决策链路昨天的手术量、血液科在床人数、血型比例、星期几这些信号先整理成特征再通过分位数预测得到明天的可能需求区间最后在“缺货风险”和“过期报废”之间选一个具体的订货量。整套模型可以用一份 Python 脚本落地不依赖厂商改造系统也适合血站做区域内的动态调配。下面我会从特征工程、预测训练、动态订货到回测验证完整解释每一步的取舍和代码含义。2. 需求预测建模把临床特征整理成可训练的特征矩阵2.1 血小板需求的三层波动日、周、突发血小板需求不是独立随机波动它至少有三层来源。第一层是星期和节假日效应周一的外科手术量大血小板申请往往在周一、周二出现峰长假后第一周也会积压一批择期手术。第二层是科室的慢性消耗血液肿瘤科、ICU 对血小板有相对稳定的基础量这类需求可以用滞后 7 天的移动平均抓住因为化疗方案通常以周为周期。第三层是突发冲击比如急诊创伤、消化道大出血或移植排期这类需求在数据上表现为明显的长尾。如果只用日均需求乘以某个安全系数那么长尾冲击一旦到来库存一定不够但如果直接按高峰日总量备货5 天后剩下的大量血小板只能报废。数据驱动方案的核心不是预测一个“平均值”而是预测整个需求分布尤其是 80%~95% 分位数让库存决策明确知道自己正在承担多大的缺货风险。这也是为什么我在这个项目中不会先做 ARIMA 或者 Prophet 做点预测而是直接用分位数回归树模型把临床特征作为外生输入。2.2 特征工程从 HIS、检验和血库日志里取什么常见的可落地数据源有三个HIS 手术排班表、LIS 检验申请记录、血库的每日发血退血日志。把这些按“医院 日期 血型 用血科室”粒度聚合就能得到一张需求时间序列表。真正让模型学到信号的是下面几个特征特征名来源示例值为什么有用当日已排择期手术台数HIS 手术表42血小板申请大多关联大型手术未来 3 天手术总时长HIS 手术表1310 分钟提前量更大的信号血液肿瘤科在床人数住院医嘱系统18慢性基础消耗的直接代理血小板申请滞后 1/7 天血库日志12 / 9捕捉近期趋势和周期星期几、是否节假日日历周一 / 1排除星期效应血型主数据A血小板必须 ABO 同型输注需要注意的是滞后特征必须按血型分组做不能把 A 型和 O 型的量混在一起。血小板输注讲究同型匹配实操中 O 型的紧平衡问题尤其突出混在一起只会让模型学到无意义的相关性。下面是一个典型的特征拼接代码片段import pandas as pd # df_daily: 每日各血型、科室的领用总量 # df_surgery: 每日各医院的手术时长和台次 df df_daily.merge(df_surgery, on[hospital, date], howleft) # 星期与节假日哑变量 df[dow] df[date].dt.dayofweek df[is_holiday] df[date].dt.date.isin(holiday_dates).astype(int) # 分血型的滞后特征 df[lag1] df.groupby([hospital, blood_type])[qty].shift(1) df[lag7] df.groupby([hospital, blood_type])[qty].shift(7) # 手术信号过去 7 天同科室的平均手术时长 df[surgery_7d_mean] ( df.groupby([hospital, dept])[surgery_minutes] .transform(lambda x: x.rolling(7, min_periods1).mean()) ) # 只保留有完整特征的样本 df df.dropna(subset[lag1, lag7])shift(1)和shift(7)是时间序列特征里最常用的两个滞后窗口前者描述昨天的需求惯性后者描述一周同天的基线水平。rolling(7, min_periods1)在头部样本不足 7 天时仍给出一个部分均值避免丢掉前几行。特征拼好之后还要注意按时间排序后切分不能用随机切分否则模型会偷看未来数据。2.3 用分位数损失训练预测模型我一般不会训练一个神经网络来处理这种规模的数据医院日粒度数据能上十万行就算多了LightGBM 的分位数目标函数更快、更容易解释、调参也更直接。分位数预测和普通回归的区别是损失函数普通回归最小化均方误差给的是条件均值分位数回归最小化 pinball loss给的是条件分位数。例如alpha0.8时模型会拟合出当前特征条件下 80% 概率满足需求的数值。import lightgbm as lgb features [dow, is_holiday, lag1, lag7, surgery_7d_mean, dept, blood_type] params_quantile { objective: quantile, alpha: 0.8, # 预测 80 分位数 metric: quantile, learning_rate: 0.05, num_leaves: 31, min_data_in_leaf: 20, # 每个叶子的最小样本数 seed: 42 } d_train lgb.Dataset(X_train[features], y_train, categorical_feature[dept, blood_type]) model_q80 lgb.train(params_quantile, d_train, num_boost_round300)代码里categorical_feature传了科室和血型LightGBM 会把这些当作分类特征处理而不是做哑变量展开样本少时可以避免稀疏问题。alpha0.8对应需求覆盖的期望概率。要做动态库存策略我建议至少训练 0.5、0.8、0.95 三个分位数这样既能拿到基线需求也能拿到一个极端缓冲值。这里唯一要留意的是LightGBM 每次只能训练一个 alpha实践中用循环训练三个模型即可对性能影响不大。3. 动态库存策略把预测分位数转换成每日订货量3.1 目标库存预测分位数就是安全库存需求预测模型输出的是“明天可能用掉多少”而不是“今天该备多少”。动态库存策略首先要建立一个基础库存上限也就是目标库存水平。常见做法是目标库存 需求分位数预测值 临床调整缓冲 拆零损耗预留其中需求分位数预测值用第 2 章的model_q80或model_q95给出临床调整缓冲由第 4 章的目标函数统一决定这样避免在库存策略里拍脑袋。血小板还有一个特殊约束每一袋是固定单位规格通常按 1 单位或 2 单位领用订货量必须取整到血站的包装规格。这个约束在代码里不能省否则计算出来的小数会误导采购量。3.2 订货量公式考虑可用库存、在途预约和过期风险实际每日补货不是简单地“用目标库存减去现有量”。血小板在途库存只占很少一部分但医院通常和血站有协定当天预约、次日送达所以“在途预约”必须预留。订货量公式如下订货量 max(0, 目标库存 - 当前可用库存 - 在途预约)这个公式看起来简单但关键在于“当前可用库存”不是账面库存而是要剔除已经过期、以及未来 24 小时内即将过期的部分。血小板效期最多 7 天如果账面还有 5 个单位但其中 4 个明天到期而这些血型明天还可能被大量用到那就可能出现“账面充足、实际缺货”的情况。所以我在常用代码里会把近效期库存单独算一列折减后再参与计算。3.3 代码实现动态安全系数与按血型分桶下面这段代码是库存策略的执行核心。它按血型循环处理因为不同血型的供应紧张程度不同O 型通常需要更高安全系数AB 型则接近零库存滚动操作。import numpy as np def calc_replenish(forecast_df, inventory_df, on_order_df, q_alpha0.8, expiring_threshold_days2): forecast_df: 包含 blood_type, date, pred_q inventory_df: 当前库存含 expiry_days 列 on_order_df: 在途预约按血型聚合 df forecast_df.copy() # 合并当前库存 df df.merge(inventory_df, on[blood_type], howleft) df df.merge(on_order_df, on[blood_type], howleft, suffixes(, _onorder)) # 折减近效期库存 floor_stock np.where( df[expiry_days] expiring_threshold_days, df[stock_qty] * 0.3, # 近效期只算三成可用 df[stock_qty] ) # 动态安全系数O 型提高 10%AB 型降低 10% clinical_factor np.select( [df[blood_type] O, df[blood_type] AB], [1.1, 0.9], default1.0 ) target_stock df[pred_q] * clinical_factor 2 # 2 单位最小物理库存 order_qty np.maximum( 0, np.ceil((target_stock - floor_stock - df[on_order_qty])) ) return df[[blood_type, date, target_stock, order_qty]]逻辑说明np.where对近效期库存做折减是血小板库存里最容易忽略的参数。把 2 天内效期的库存只按 30% 计算是很保守的口径适合回测初期确认缺货风险如果历史报废率很低可以放宽到 50%。np.select处理血型差异O 型因为通用性高、替换代价大要把安全库存上浮AB 型本身用量小库存周转慢下浮可以避免到期报废。最后np.ceil向上取整到单位袋数防止订货量出现小数。这套策略已经能应付大多数日常场景但真正的难点在下一章临床特征怎么进入“安全系数”和目标函数。血型上下浮是简单规则更精细的做法需要把临床特征和缺货/报废惩罚一起放进一个成本函数里用历史回放来优化。4. 结合临床特征的库存优化目标函数从预测到决策的闭环4.1 临床特征如何影响库存阈值第 3 章的clinical_factor是最朴素的临床特征映射。如果想把临床特征用得更充分比如未来 3 天的手术量或者 ICU 住院人数就不适合用固定系数而应该直接把这些特征加入目标函数。常见的做法是目标库存的基线由分位数预测决定临床特征作为一个线性修正项叠加上去。举个例子医院信息系统里能看到麻醉科已排的“心脏搭桥、肝移植”等高出血风险手术台数。这类手术一台就可能申请 8~10 单位血小板光靠历史销量的滞后特征模型很难事前捕捉到。我会把这些手术台数转成“预期额外需求”额外需求 高出血风险手术台数 × 平均每台历史血小板用量然后加进目标库存。这个计算发生在决策阶段而不是预测阶段好处是预测模型保持稳定临床变化通过可解释的规则影响库存不用频繁重训模型。4.2 平衡缺货与过期目标函数设计理想库存策略是在每个决策日同时考虑两笔成本缺货成本和过期成本。对血小板来说缺货可能导致一台手术延期或患者病情加重过期则直接损耗宝贵的血液资源。一般可以把单日决策目标写成成本 缺货惩罚 × max(0, 当日需求 - 可用库存) 过期惩罚 × max(0, 当日过期量)由于未来需求未知我们用第 2 章的预测分位数做模拟。决策变量是每个血型的订货量约束条件是不能超过血站日供应上限。这个目标函数虽然简单但它把安全库存的确定变成了一个可控的优化问题如果缺货惩罚远高于过期惩罚模型会选择更高的分位数反过来就会压低库存接受高周转。4.3 用网格搜索和模拟回放调优参数实际项目中我不会每次都跑一个复杂的强化学习模型而是先把惩罚系数和分位数档位做成参数网格用历史数据回放一遍选择总成本最低的一组参数。下面这个简化版模拟可以说明思路from itertools import product best_cost float(inf) best_params None for q_level, shortage_penalty, expiry_penalty in product( [0.7, 0.8, 0.9], [2.0, 3.0, 5.0], [0.5, 1.0, 1.5]): # 用每个 q_level 重新生成预测 pred_q train_quantile_model(hist_df, q_level) # 回放历史库存 sim simulate_inventory(hist_df, pred_q, shortage_penaltyshortage_penalty, expiry_penaltyexpiry_penalty) total_cost sim[shortage_cost].sum() sim[expiry_cost].sum() if total_cost best_cost: best_cost total_cost best_params (q_level, shortage_penalty, expiry_penalty)参数网格里q_level是预测分位数代表我们追求多高的“满足率”shortage_penalty和expiry_penalty是两类惩罚的相对权重。这里没有用scipy.optimize做连续搜索是因为目标函数不光滑而且回放本身带随机性网格搜索给出的离散最优值更稳。如果数据量大可以用optuna做贝叶斯搜索但参数空间不大时网格搜索就足够稳定且容易向医院解释。参数范围说明q_level0.7, 0.8, 0.9需求预测分位数shortage_penalty2.0 ~ 5.0单位缺货成本expiry_penalty0.5 ~ 1.5单位过期成本target_stock_min2 ~ 4 单位最小物理库存调参后要检查的不只是总成本还有分血型的最大缺货连续天数。血液病区如果连续两天缺乏某型血小板可能直接导致治疗中断这个业务约束经常比总成本更重要所以我通常会把“最长连续缺货不超过 2 天”作为一个硬条件。5. 回测验证与常见坑让模型在真实排班上可信5.1 滚动回测而非单次划分时序模型不能用随机切分验证血小板需求有强烈的星期效应随机切分等于让模型偷看未来。我会用滚动回测从训练集末尾开始每 7 天移动一次重新训练一次分位数模型然后预测接下来 7 天计算缺货和报废。这样最接近“每天早上重新预测、当天决定订货量”的真实流程。# 伪代码滚动回测主循环 for valid_start in range(history_end, hist_len, 7): train hist.iloc[:valid_start] valid hist.iloc[valid_start:valid_start 7] model_q train_quantile_model(train, alpha0.8) pred model_q.predict(valid[features]) # 模拟库存并累计成本 inventory_simulate(valid, pred, params)这里有一个值得注意的细节在每轮回测中滞后特征lag1和lag7必须重新从训练集尾部计算不能直接用整体数据的原始特征否则前一轮验证集的真实值会泄漏到下一轮。5.2 指标与分位数校准回测指标不要只盯准确率。对血小板库存核心指标是缺货率缺货天数 / 总天数、报废率过期单位数 / 总入库单位数、库存周转天数平均库存 / 日均用量。下面这张表是我的验收底线指标优秀可接受危险缺货率 2%2% ~ 5% 8%报废率 3%3% ~ 8% 15%周转变动 20%20% ~ 35% 35%5.3 分位数校准检查如果缺货率持续高于1-alpha说明分位数模型校准得不好。比如设了alpha0.8理论上 20% 的时间会缺货但实际缺货率达到 30%就需要检查是不是特征里缺少手术排程变量或者近效期折减系数过激进。我常用的检查方法是在验证集上计算“覆盖频次”实际需求小于等于预测分位数的样本占比应该接近该分位数水平。差太多时优先调整特征而不是继续调安全系数。最后一个容易踩的坑是把所有血型的模型混合训练后再分开预测。血型是强分类特征LightGBM 能够处理但 A 型样本可能是 O 型的 5 倍模型容易忽略 O 型的长尾。遇到这种情况我会对 O 型单独训练一个分位数模型或者调大min_data_in_leaf防止过拟合到 A 型主导的叶子节点。最终交付前把最后一轮回测的每日预测真值表和实际领用表并排贴到日报里让输血科医生能直观看到模型的预测依据再决定要不要下调近效期折减系数。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门