hesim R包实战:微观模拟与概率敏感性分析在卫生经济学评价中的应用
简介hesim 是一套面向卫生经济评价与决策分析场景的 R 语言建模工具包适合卫生经济学研究者、药物经济学评价人员及具备一定 R 基础的数据分析者使用。它提供将统计分析与经济评估相集成的通用框架支持队列离散时间状态转换模型、N 状态分区生存模型以及个体级连续时间状态转换模型覆盖 Markov 与半 Markov 过程并借助 Rcpp 与 data.table 实现高效仿真、概率敏感性分析与患者异质性建模。资源包共 697 个文件约 9.72MB包含 60 个 R 源码、20 个 C 实现、102 个 rd 帮助文档、258 个 html 页面及若干 rmd 教程与 pdf 说明便于查阅函数用法与模型示例。目前已有 471 人学习下载。读者可据此掌握从参数分布或自举采样、PSA 不确定性传播到成本效益分析与决策不确定性呈现的完整流程并参考源码理解模型构建与计算加速思路。1. 从一张成本效果散点图说起hesim 到底解决什么问题如果你做过卫生经济学评价大概率经历过这样的场景模型跑完ICER 算出来是每 QALY 三万块决策者问「这个结论稳不稳」你只能硬着头皮说「我们做了单因素敏感性分析」。问题在于单因素分析一次只动一个参数而真实世界里成本、效用、转移概率是同时波动的。hesim 这个 R 包就是冲着这个痛点来的——它把微观模拟、离散事件模拟和分区生存模型统一到一套框架里让你能跑概率敏感性分析、算成本效果可接受曲线还能做个体层面的异质性建模。这个包适合两类人一类是做卫生技术评估的从业者手里有 Markova 或 TreeAge 的模型想迁移到开源环境另一类是流行病学或公共卫生背景的研究者需要把干预措施的成本效果算清楚但不想被商业软件锁死。它的核心价值在于「概率化」——不是给你一个点估计而是给你一个分布。你拿到的不只是「这个方案更划算」而是「在 95% 的模拟里这个方案有 78% 的概率更划算」。hesim 的底层逻辑是「先定义模型结构再生成模拟数据最后做决策分析」。它不要求你一开始就写对全部参数而是允许你用define_model()搭骨架用define_parameters()填血肉最后用run_model()跑出结果。这种分层设计的好处是你可以在参数层反复调整而不动模型结构也可以在模型层切换微观模拟和队列模拟而不重写参数。接下来我会按「装环境 → 搭模型 → 跑模拟 → 排错 → 进阶」的顺序把每个环节的代码和参数含义拆开讲。2. 环境准备与核心对象把 hesim 装进工作流2.1 安装与依赖检查hesim 在 CRAN 上有稳定版本但如果你要用最新的微观模拟功能建议从 GitHub 装开发版。我一般会先检查 R 版本低于 4.0 的话data.table和R6的某些特性会报错。# 检查 R 版本低于 4.0 建议先升级 getRversion() # 从 CRAN 安装稳定版 install.packages(hesim) # 如果需要开发版用 devtools 从 GitHub 装 # install.packages(devtools) # devtools::install_github(hesim-dev/hesim) # 加载核心包 library(hesim) library(data.table)装完之后别急着跑模型先确认几个关键依赖data.table用于高效处理模拟数据R6用于面向对象建模flexsurv用于参数生存模型拟合。如果flexsurv没装后面做生存分析会直接卡住。# 检查关键依赖是否可用 pkgs - c(data.table, R6, flexsurv, ggplot2) sapply(pkgs, requireNamespace, quietly TRUE)返回全是TRUE才算环境干净。有一个FALSE就去补装别跳过。2.2 理解 hesim 的三个核心对象hesim 的建模流程围绕三个对象展开hesim_data管数据params管参数model管结构。很多人第一次用觉得绕是因为把这三个混在一起写了。我习惯先画一张表把职责分清对象类型创建函数职责常见输入格式数据对象hesim_data()存储患者特征、策略、时间点data.table / data.frame参数对象params_surv()/params_lm()存储回归系数或分布参数列表或矩阵模型对象define_model()定义转移结构、干预策略公式或矩阵hesim_data()是最先要建的。它要求你提供三个要素strategies干预策略名称、patients患者层面的协变量、states健康状态。这三个要素的维度必须对齐否则后面run_model()会报「维度不匹配」——这是新手最常见的翻车点。# 定义三个干预策略 strategies - data.table( strategy_id 1:3, strategy_name c(标准治疗, 新药A, 新药B) ) # 定义患者特征这里模拟 1000 个患者 set.seed(42) patients - data.table( patient_id 1:1000, age rnorm(1000, mean 65, sd 10), sex rbinom(1000, 1, 0.5), risk_score runif(1000, 0, 1) ) # 定义健康状态 states - data.table( state_id 1:3, state_name c(稳定, 进展, 死亡) ) # 组装成 hesim_data 对象 hesim_dat - hesim_data( strategies strategies, patients patients, states states ) # 打印结构确认 print(hesim_dat)这段代码的逻辑是先把三个维度的数据分别建好再用hesim_data()打包。set.seed(42)是为了让患者特征可复现你做概率敏感性分析时如果每次患者样本都变结果就没法对比。age用正态分布、sex用二项分布、risk_score用均匀分布这是卫生经济评价里最常见的协变量生成方式。参数说明strategies的strategy_id必须是整数且从 1 开始patients的patient_id同理states的state_id也要连续。这三个 ID 是 hesim 内部索引的锚点断了就会报错。3. 模型搭建从转移概率到微观模拟3.1 用define_model()定义分区生存结构分区生存模型的核心是转移矩阵。hesim 允许你用两种方式定义一种是直接给转移概率矩阵另一种是用公式指定每个转移的回归关系。我一般先用矩阵方式跑通流程再换成公式方式做异质性分析。# 定义三个状态之间的转移矩阵 # 行是起点状态列是终点状态 # 这里假设不能从死亡回到其他状态 transition_matrix - matrix( c(0, 0.3, 0.1, # 从稳定出发留在稳定0去进展0.3去死亡0.1 0, 0, 0.2, # 从进展出发去稳定0留在进展0去死亡0.2 0, 0, 1), # 从死亡出发只能留在死亡 nrow 3, byrow TRUE ) # 用 define_model 定义模型结构 mod - define_model( transition_matrix transition_matrix, n_cycles 20, # 模拟 20 个周期 cycle_length 1, # 每个周期 1 年 starting_state 1 # 所有患者从稳定状态开始 ) # 查看模型摘要 summary(mod)transition_matrix的每一行代表一个起点状态每一列代表终点状态。第 1 行第 2 列的 0.3 表示从稳定到进展的概率是 0.3。注意第 3 行第 3 列的 1表示死亡状态是吸收态进去就出不来。n_cycles 20意味着模拟 20 年cycle_length 1表示每年一个周期。如果你的研究时限是 10 年但每半年一个周期就把n_cycles设成 20、cycle_length设成 0.5。提示转移矩阵的每一行概率之和必须等于 1否则run_model()会报「概率不合法」。我见过有人把 0.3 和 0.1 填进去忘了补留在原状态的概率结果跑出来患者凭空消失。3.2 用define_parameters()绑定成本和效用模型结构搭好后下一步是把成本和效用参数绑上去。hesim 的参数对象支持三种形式固定值、分布、回归方程。做概率敏感性分析时你至少要把关键参数设成分布。# 定义成本参数每个状态每年的成本 cost_params - params_lm( formula cost ~ 1, data data.frame( state_id c(1, 2, 3), cost c(5000, 15000, 0) # 稳定5000进展15000死亡0 ) ) # 定义效用参数每个状态的健康效用值 utility_params - params_lm( formula utility ~ 1, data data.frame( state_id c(1, 2, 3), utility c(0.8, 0.5, 0) # 稳定0.8进展0.5死亡0 ) ) # 把参数绑定到模型 mod - set_parameters( mod, cost cost_params, utility utility_params )params_lm()是线性模型参数容器这里虽然只填了固定值但它的结构支持后续替换成回归系数。formula cost ~ 1表示截距模型data里的state_id对应健康状态。成本参数里死亡状态设为 0 是惯例因为死者不产生医疗费用。效用参数里死亡状态也设为 0这是 QALY 计算的标准做法。如果你要做概率敏感性分析把固定值换成分布# 用正态分布替换固定值 cost_params_psa - params_lm( formula cost ~ 1, data data.frame( state_id c(1, 2, 3), mean c(5000, 15000, 0), sd c(500, 1500, 0) # 标准差设为均值的 10% ), uncertainty normal # 指定分布类型 )uncertainty normal告诉 hesim 在每次模拟中从正态分布抽样。sd设成均值的 10% 是经验做法如果你有文献来源的置信区间用(上限 - 下限) / 3.92反推标准差更严谨。3.3 跑微观模拟与结果提取参数绑好后就可以跑模型了。hesim 支持两种模拟方式run_model()跑队列模拟run_microsim()跑个体模拟。做异质性分析必须用后者。# 跑微观模拟1000 个患者100 次概率抽样 results - run_microsim( mod, n_patients 1000, n_sims 100, seed 123 ) # 提取成本效果结果 ce_results - summarize_ce( results, type icer, comparator 标准治疗 ) # 查看前几行 head(ce_results)n_patients 1000控制个体模拟的样本量n_sims 100控制概率敏感性分析的抽样次数。这两个参数直接决定运行时间——1000 患者乘 100 次模拟等于 10 万次个体轨迹普通笔记本大概跑 2 到 5 分钟。seed 123保证结果可复现。summarize_ce()的comparator参数指定基线策略ICER 是相对于它算的。跑完之后你会拿到一个包含strategy_id、cost、qaly、icer的 data.table。如果icer列出现NaN说明该策略在所有模拟中都不比基线更有效这是正常的不是 bug。4. 避坑与排查那些让我重跑一整天的问题4.1 转移概率矩阵行和不为 1现象run_model()报错Error: transition probabilities must sum to 1。原因手动填矩阵时漏了留在原状态的概率。比如从稳定出发你填了去进展 0.3、去死亡 0.1但忘了留在稳定应该是 0.6。解决用rowSums()检查每一行或者用normalize()函数自动归一化。# 检查行和 rowSums(transition_matrix) # 自动归一化慎用会掩盖逻辑错误 transition_matrix_norm - transition_matrix / rowSums(transition_matrix)我一般不建议自动归一化因为它会把你的逻辑错误悄悄修掉。手动检查每一行确认概率来源有依据。4.2 患者 ID 与策略 ID 不连续现象hesim_data()创建成功但run_microsim()报Error: patient_id must be consecutive integers。原因你从外部 CSV 读入患者数据时ID 可能跳号或者从 0 开始。解决用setorder()排序后重新生成连续 ID。# 重新生成连续 ID patients[, patient_id : .I] setorder(patients, patient_id).I是 data.table 的内置变量返回当前行的序号。这招对策略 ID 和状态 ID 同样适用。4.3 效用值超过 1 或为负现象QALY 算出来大于生命周期年限或者出现负值。原因效用参数没有做边界约束正态分布抽样时抽到了大于 1 或小于 0 的值。解决在params_lm()里加lower和upper约束或者用 Beta 分布替代正态分布。# 用 Beta 分布约束效用值在 0 到 1 之间 utility_params_beta - params_lm( formula utility ~ 1, data data.frame( state_id c(1, 2, 3), mean c(0.8, 0.5, 0), sd c(0.05, 0.08, 0) ), uncertainty beta )Beta 分布的参数化方式与正态不同hesim 内部会用矩估计法转换。如果你的效用均值接近 0 或 1Beta 分布比正态更合适。4.4 模拟次数太少导致 ICER 不稳定现象每次跑run_microsim()得到的 ICER 差异很大置信区间宽得没法用。原因n_sims设得太小概率空间没覆盖全。解决先跑 10 次看 ICER 的变异系数如果超过 20%加到 100 次以上。# 快速检查 ICER 稳定性 test_results - run_microsim(mod, n_patients 500, n_sims 10, seed 1) test_ce - summarize_ce(test_results, type icer, comparator 标准治疗) sd(test_ce$icer) / mean(test_ce$icer) # 变异系数变异系数低于 0.1 说明模拟次数够了高于 0.2 就继续加。别一上来就设 1000 次先跑小样本探路。4.5 生存模型外推超出数据范围现象用flexsurv拟合生存曲线后外推到 20 年时生存概率变成负数。原因参数生存模型在尾部可能不稳定尤其是 Gompertz 或 Weibull 分布。解决限制外推年限或者换用限制性立方样条。# 检查外推边界 fit - flexsurvreg(Surv(time, status) ~ 1, data survival_data, dist weibull) summary(fit, t c(1, 5, 10, 20)) # 看 20 年时的生存概率如果 20 年概率为负把模型时限缩到 15 年或者在define_model()里加max_cycles限制。5. 进阶技巧用概率敏感性分析做决策曲线5.1 算成本效果可接受曲线概率敏感性分析的最终产出不是 ICER 点估计而是成本效果可接受曲线CEAC。它回答的是「在 willingness-to-pay 阈值下每个策略成为最优的概率是多少」。# 定义支付意愿阈值范围 wtp_range - seq(0, 100000, by 5000) # 算 CEAC ceac - calc_ceac( results, wtp wtp_range, comparator 标准治疗 ) # 画图 library(ggplot2) ggplot(ceac, aes(x wtp, y prob, color strategy_name)) geom_line(size 1) labs(x 支付意愿阈值 (元/QALY), y 成为最优策略的概率) theme_minimal()calc_ceac()的内部逻辑是对每个 WTP 阈值算每个策略的净货币收益NMB WTP × QALY - Cost然后统计每个策略在 100 次模拟中 NMB 最高的频率。wtp_range从 0 到 10 万步长 5000覆盖了大多数中高收入地区的阈值范围。如果你的研究针对特定地区把上限调到当地人均 GDP 的 3 倍即可。5.2 用情景分析替代单因素敏感性分析单因素敏感性分析一次只动一个参数但决策者往往关心「如果新药价格降 20% 且效用提高 0.05 同时发生」会怎样。hesim 的情景分析可以同时改多个参数。# 定义情景新药价格降 20%效用提高 0.05 scenario_params - params_lm( formula cost ~ 1, data data.frame( state_id c(1, 2, 3), cost c(5000, 12000, 0) # 进展状态成本从 15000 降到 12000 ) ) # 把情景参数绑到模型 mod_scenario - set_parameters(mod, cost scenario_params) # 跑情景模拟 scenario_results - run_microsim(mod_scenario, n_patients 1000, n_sims 100, seed 123) scenario_ce - summarize_ce(scenario_results, type icer, comparator 标准治疗)对比ce_results和scenario_ce的 ICER 变化你就能量化「降价 提效」的联合影响。这比单因素分析更接近真实决策场景。5.3 一个我踩过的坑模拟种子与并行计算最后说一个血泪经验。hesim 支持并行计算加速但如果你在run_microsim()里设了seed又开了并行不同核心上的随机数序列会冲突导致结果不可复现。我当时的解决方案是要么关并行要么用future包显式管理随机数种子。# 方案一关并行老老实实跑 results - run_microsim(mod, n_patients 1000, n_sims 100, seed 123) # 方案二用 future 管理并行种子 library(future) plan(multisession, workers 4) results - run_microsim(mod, n_patients 1000, n_sims 100, seed 123, parallel TRUE) plan(sequential) # 跑完恢复默认从那以后我每次跑概率敏感性分析都强制走一遍「小样本探路 → 检查变异系数 → 确认种子 → 再放大样本」的流程再也没出现过结果对不上的情况。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

OSGB转3DTiles命令行转换管线:大场景倾斜摄影Cesium加载实践

OSGB转3DTiles命令行转换管线:大场景倾斜摄影Cesium加载实践

简介:面向GIS与三维可视化开发者的多功能转换工具,可将OpenSceneGraph的OSGB二进制模型转为Cesium 3D Tiles标准瓦片,适合需要在Web端流畅加载大规模倾斜摄影或人工模型的场景。工具不依赖CesiumLab,能够独立完成数据切片、格式封…

2026/10/14 10:34:11 阅读更多 →
AI 辅助架构设计:小白程序员必备,收藏学大模型必备指南!

AI 辅助架构设计:小白程序员必备,收藏学大模型必备指南!

本文探讨了AI在系统架构设计中的应用。AI能快速生成架构图和设计规格,但架构师仍需明确系统边界、核验参考依据、记录决策理由,并调整自身能力以适应AI辅助的工作方式。AI可以加速方案和代码生成,但设计依据、验收标准和验证方法仍需架构师把…

2026/10/14 10:34:11 阅读更多 →
Spring Boot Actuator 生产级监控实战:从端点配置到 Prometheus 闭环

Spring Boot Actuator 生产级监控实战:从端点配置到 Prometheus 闭环

1. 从一次线上告警说起:为什么我们需要 Actuator去年冬天的一个凌晨,我被一条告警短信吵醒——某核心接口的 P99 响应时间突然从 80ms 飙到了 2.3s。爬起来连上服务器,第一反应是看日志,但日志里只有零星的业务报错,根…

2026/10/14 10:34:11 阅读更多 →

最新新闻

视觉SLAM框架详解:从传感器到地图与轨迹的完整链路

视觉SLAM框架详解:从传感器到地图与轨迹的完整链路

我最初接触《视觉SLAM十四讲》第二版时,完全是被那句“SLAM是同时定位与建图”给劝退的。这六个字拆开都认识,合在一起就不知道它在讲什么。直到把第一章的框架图反复看了四五遍,又动手把书上的代码跑通,才算真正建立起对SLAM的宏…

2026/10/14 11:19:26 阅读更多 →
大模型API成本优化:从Token消耗原理到企业级降本实践

大模型API成本优化:从Token消耗原理到企业级降本实践

我们直接把话挑明:大模型API的账单,是每个做AI应用的人迟早要面对的一道坎。模型越强,token越贵,哪怕你只是调个接口做点小功能,一个月下来账单也可能让人肉疼。我自己在好几个项目里摸爬滚打,踩过不少坑之…

2026/10/14 11:19:26 阅读更多 →
嵌入式开发十大黄金认证:职业进阶与薪资提升指南

嵌入式开发十大黄金认证:职业进阶与薪资提升指南

1. 程序员考证的价值与现状分析在技术行业摸爬滚打十几年,我见过太多同行对考证这件事的误解。有人把证书当救命稻草,也有人完全不屑一顾。但现实情况是,在嵌入式这个细分领域,某些权威认证确实能成为职业发展的加速器。就拿我带的…

2026/10/14 11:19:26 阅读更多 →
两款很赞的学习类小程序

两款很赞的学习类小程序

两款很赞的学习类小程序【小鹿啃单词】【高频Java面试题宝典】最近学习时寻找学习类软件,发现了两款感觉非常不错的学习类小程序,记录一下。 【小鹿啃单词】 超轻量会上瘾的背英语单词小程序,免费免登录没广告,秒开秒用。涵盖小…

2026/10/14 11:19:26 阅读更多 →
数据分析,除了Excel数据透视表,还有什么工具?——2026 专业精选推荐榜

数据分析,除了Excel数据透视表,还有什么工具?——2026 专业精选推荐榜

透视表是 Excel 的看家本领,但一旦需求越过"单表汇总"这道线——多表关联、异常排查、定期月报、要给领导交一份能改的 Word/PPT——它就开始吃力。本文以企业办公人群的真实场景为准,用 5 个专业维度筛出 5 类值得长期用的数据分析工具&#…

2026/10/14 11:19:26 阅读更多 →
数据库安全加固实战:五大数据库风险排查与加固指南

数据库安全加固实战:五大数据库风险排查与加固指南

简介:一份面向数据库运维与安全管理技术人员的数据库安全加固手册,围绕 MySQL、Oracle、SQL Server、PostgreSQL、Redis 五种主流数据库,逐项梳理用户与密码配置、权限管理、通信加密、日志审计、文件权限等关键安全控制点,并给出…

2026/10/14 11:18:26 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →