群体药动学建模#

在本节中,我们将学习如何利用 Masmod 构建简单的群体药动学模型,并学习使用 Notebook 工具。

在开始前,推荐先阅读:Python 编程语言入门Python 数据科学入门Markdown 语法

定量药理学和群体药动学#

定量药理学(pharmacometrics)是一门结合传统药理学理论与数学模型而形成的新兴学科,其主要通过建模与模拟的方法来定量描述、阐释和预测药物在人体内的吸收、分布、代谢和排泄的特征、药物在人体内的药效特征及其两者的相关性。

群体药动学(population pharmacokinetics)是定量药理学的重要研究方向之一,与传统的标准两步法(standard two stage)相比,群体药动学利用构建模型的方法来有效整合多个临床研究的数据,在获取药物药动学参数群体典型值及其影响因素的同时,也能通过模型定量诠释药物在体内的药动学特征并进行模拟预测,能大大缩短临床研究所需的人力与时间成本,是模型引导的药物开发(model-informed drug development,MIDD)流程中的应用最广泛也是最关键的技术之一。

备注

若你想了解更多有关于群体药动学的知识,可参考:

若你想了解有关于 MIDD 的内容,可参考:

若你想深入学习群体药动学建模模拟方法,可参考:

  • 焦正. (2019). 基础群体药动学和药效学分析. 科学出版社

Notebook 的创建和使用#

我们的 Notebook 参考开源项目 Jupyter Notebook 开发。Jupyter Notebook 相关插件在 VSCode 中的下载量已达上亿数量级,是时下最为流行和便利的数据科学工具之一。

我们软件中的 Notebook 同样提供了这样一个交互式计算平台,允许创建一个包含文本、图形、代码的可交互式文档,为你带来全新的数据分析和建模模拟体验。

下文中我们将先简单学习如何使用软件中的 Notebook。

  1. 和新建分析一致,我们可以在新建分析页内选择 “通用 > Notebook” 来创建一个新的 Notebook(图 63)。

../_images/tutorial-new-notebook.png

图 63 “Notebook” 选项示意图#

  1. 在新建的 Notebook 中,我们便可以开始尝试在单元格中输入 Python 代码了。我们试着输入如下代码,并点击左侧的 单元格运行按钮 按钮以运行代码。运行后,你应该可以看到在单元格下打印出了 “Hello, World!”(图 64)。

print("Hello, World!")
Hello, World!
../_images/tutorial-poppk-notebook-start.png

图 64 Hello, World! 代码执行后示意图#

  1. 我们可以点击标签页顶部工具栏中的 “+ 代码” 按钮来新增一个代码单元格。也可以鼠标悬浮至此单元格下方,点击 “+ 代码” 按钮来新增(图 65)。在不处于单元格编辑状态时,也可以按下键盘 B 键来在下方新增一个单元格。接下来你可以在新增的单元格中输入代码并运行,也可以重复几次新增单元格的操作,来熟悉这一流程。

../_images/tutorial-poppk-notebook-new-cell.png

图 65 新增代码按钮位置示意图#

小技巧

一些在 Notebook 中常用的键盘快捷键:

  • A上方 新增一个同类型的单元格(不处于编辑状态时)

  • B下方 新增一个同类型的单元格(不处于编辑状态时)

  • Ctrl + Enter 运行代码或完成 Markdown 编辑

Masmod 入门#

Masmod 是一个基于 Python 编程语言的建模模拟框架,兼容 Python 数据科学生态,力图以最便捷与清晰的方式完成群体建模与模拟的工作流程。

接下来,我们将使用 Masmod 内置的血管内给药一室模型来完成对华法林示例数据集中的血药浓度数据完成群体药动学建模并采用 FOCEi(First Order Conditional Estimation with interaction)算法完成模型拟合。

  1. 首先我们需要导入 Masmod 与华法林示例数据,代码如下:

import polars as pl


from mas.model import *
from mas.datasets import warfarin
  1. 接下来我们定义一个血管内给药一室模型 Warfarin,同时定义模型中的群体药动学参数(例如清除率和表观分布容积)及其个体间变异:

class WarfarinPk(EvOneCmtLinear):
    def __init__(self) -> None:
        """ "定义模型参数"""
        super().__init__()
        self.tv_cl = theta(0.134, bounds=(0, None))
        self.tv_v = theta(7.81, bounds=(0, None))
        self.tv_ka = theta(0.571, bounds=(0, None))
        self.tv_alag = theta(0.823, bounds=(0, None))

        self.eta_cl = omega(0.049)
        self.eta_v = omega(0.0802)
        self.eta_ka = omega(0.047)
        self.eta_alag = omega(0.156)

        self.eps_prop = sigma(0.0104)
        self.eps_add = sigma(0.554)

    def pred(self) -> Expression:
        """ "定义模型结构"""
        cl = self.tv_cl * exp(self.eta_cl)
        v = self.tv_v * exp(self.eta_v)
        ka = self.tv_ka * exp(self.eta_ka)
        alag = self.tv_alag * exp(self.eta_alag)

        pred_res = self.pred_physio(cl=cl, v=v, ka=ka, alag1=alag, s2=v)
        ipred = pred_res.F

        y = ipred * (1 + self.eps_prop) + self.eps_add

        return y
  1. 接着我们筛选出示例数据中的血药浓度数据(即 DVID 不等于 2 的数据)

pk_data = warfarin.filter(pl.col("DVID") != 2)

pk_data.head(10)
shape: (10, 11)
IDTIMEAMTDVDVIDMDVWTAGESEXDOSEEVID
i64f64f64f64i64i64f64i64i64f64i64
00.0100.0null0166.7501100.01
00.5null0.01066.7501100.00
01.0null1.91066.7501100.00
02.0null3.31066.7501100.00
03.0null6.61066.7501100.00
06.0null9.11066.7501100.00
09.0null10.81066.7501100.00
012.0null8.61066.7501100.00
024.0null5.61066.7501100.00
036.0null4.01066.7501100.00
  1. 数据筛选完成后,我们就可以将模型与数据结合起来组成群体模型了:

pop_model = PopModel(mod=WarfarinPk, data=pk_data)
  1. 群体模型定义完成后,我们采用 FOCEi 算法拟合模型:

fit_res = pop_model.fit(FOCEi(print=10))
🔧 CXX compiler C:\ProgramData\mingw64\bin\g++.EXE
📦 Compiling build target...
🔗 Linking dynamic library...
✅ Compilation Finished
✈️ First Order Conditional Estimation
Using BFGS...
* maxiter = 9999
* xtol = 1e-06
* ftol = 1e-06
* lower_bounds = 
* upper_bounds = 
* log_level = 0
* print = 10
* print_type = 0
* verbose = 1
* p_small = 0.1
* rel_step_size = 0.001

#0    ofv: 347.7917575
x: 1.0000e-01  1.0000e-01  1.0000e-01  1.0000e-01  1.0000e-01  1.0000e-01  1.0000e-01  1.0000e-01  1.0000e-01  1.0000e-01  
Gradients: -10.5092 -8.33744 -96.9252 30.7996 -27.5129 17.5438 -33.1212 -11.5115 43.1845 122.184 

Funcalls: 8
#1    ofv: 252.9367067
x: 1.5161e-01  1.4094e-01  5.7596e-01  -5.1245e-02 2.3511e-01  1.3849e-02  2.6265e-01  1.5653e-01  -1.1206e-01 -5.0000e-01 
Gradients: 58.5006 19.4823 -12.6546 0.438415 -20.9679 14.0305 -33.114 -4.19792 7.4639 42.0374 

Funcalls: 17
#11   ofv: 219.1455368
x: 6.6127e-02  1.1409e-01  1.1175e+00  1.3436e-01  3.6191e-01  -1.8678e-01 1.6618e+00  -3.4381e-02 -4.8288e-02 -1.0445e+00 
Gradients: -12.2346 -8.00718 1.12182 2.07945 -0.317653 -3.44941 4.27612 -2.38963 -2.99215 -5.93228 

Funcalls: 109
#21   ofv: 218.0048506
x: 8.5522e-02  1.2083e-01  9.4660e-01  8.7388e-02  3.6554e-01  -1.5218e-01 1.4398e+00  1.1196e-01  -6.5216e-02 -9.5924e-01 
Gradients: -0.424662 -0.426146 -0.204735 -0.723757 -0.0638195 -0.0733563 -0.0561034 -0.0253637 0.0186558 -0.117254 

Funcalls: 223
converged = True
n_iter = 24
x_opt = 8.6125e-02  1.2129e-01  9.6338e-01  1.0150e-01  3.6619e-01  -1.5174e-01 1.4491e+00  9.8515e-02  -6.5824e-02 -9.5708e-01 
f_opt = 217.9974604
fun_calls = 266

Computing Covariance...
Covariance computation succeed
  1. 最后,我们来查看模型拟合结果:

fit_res

Estimation Summary

OFVConvergedTotal IterationsCompilation TimeEstimation TimeCovariance Time
217.997True ✅240:00:10.150:00:01.270:00:01.60

Parameter Estimation

ParameterEstimateShrinkage(%)RSE(%)
Theta
tv_cl0.1325.218
tv_v7.9784.075
tv_ka1.35431.919
tv_alag0.82419.057
Omega(SD)
eta_cl0.2890.00014.186
eta_v0.2203.99313.348
eta_ka0.83550.45319.177
eta_alag0.39459.24528.372
Sigma(SD)
eps_prop0.08614.95214.144
eps_add0.25915.63523.639