薬剤師のプログラミング学習日記

プログラミングやコンピュータに関する記事を書いていきます

薬物動態パラメータを変化させた場合の血中濃度推移

点滴時間、半減期、分布容積の薬物動態パラメータを変化させたときの血中濃度推移の動きをプログラムを書いて確かめてみます。モデルは1-コンパートメント点滴静注モデルです。


点滴時間の変化による影響

f:id:enokisaute:20200307131448g:plain

点滴時間を長くすることでCmax(最大血中濃度)とCmin(最小血中濃度)の振れ幅は小さくなります(Cmaxは下がり、Cminは上がる)。
薬物の中には、急激な血中濃度の上昇により副作用が発現するものがあり、これを避けるため、点滴時間を長く取るようになっているものもあります。
また、Cminが上昇するため、抗菌薬の一部にある一定レベル以上の血中濃度を保つことで効果が最大限発揮される時間依存性薬物などの場合は、点滴時間が長い方が効果的であるといえます。

半減期と分布容積の変化による影響

f:id:enokisaute:20200307131733g:plain

半減期の変化による影響

t_{1/2} = \frac{0.693}{k_e}より、半減期と消失速度定数keは反比例の関係にあります。半減期が長い薬物のkeは小さく、半減期が短い薬物のkeは大きい、です。keが血中濃度推移に与える影響としては、keが小さく(=半減期が長く)なると、体内の薬物量の減少のしかたが緩やかになるため、定常状態の血中濃度が高くなります。ま、当たり前と言えば当り前のことです。

分布容積の変化による影響

分布容積Vdは、『ある薬物が体内に均等に分布すると仮定したとき、薬物が溶解している体積の量』のことです。血中濃度とは反比例の関係にあります。分布容積が大きくなる(小さくなる)と、血中濃度は下がります(上がります)が、定常状態に到達するまでの時間には影響しません。

matplotlibでインタラクティブなグラフを描く

ここからはプログラミング的な話になります。上図のどちらも同じようなコードになるので、ここでは最初の点滴時間を変化させたときのプログラムについて書いていきます。

ウィジェットと言われる部品(のようなもの)を組み込むことで動きのあるグラフを作ることができます。これにより、マウス操作でパラメータの値を変えたときに、グラフがどう変化するかをリアルタイムで見ることができるようになります。
今回のグラフそのものは1-コンパートメント点滴静注の繰り返し投与ですが、これに点滴時間を0.5~投与間隔時間まで変化させることのできるスライダーを加えます。

matplotlib.widgetsを使う

matplotlibで使うことのできるウィジェットにはスライダーやラジオボタンなどいろいろありますが、今回はスライダーを使います。

今までのグラフと異なる点を大まかに言うと、

  1. スライダーのインスタンスを生成する
  2. コールバック関数(マウスによるスライダーの操作が発生したときに行う処理)を呼び出しするメソッドを書く
  3. コールバック関数を書く

の3点です。

import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

まずはモジュールのインポートです。また、途中省いていますが、このあとBloodConc2クラスのインスタンスを生成して血中濃度のリストを得ておくところまでは同じです。

    # subplot()でFigure(図そのもの), Axes(Figureの要素の座標軸)オブジェクトを得る
    fig, ax = plt.subplots(figsize=(10, 6))
    # スライダーを配置するためAxes下の余白を広げる. デフォルトは0.1
    plt.subplots_adjust(bottom=0.2)
    # プロットされたデータを表すオブジェクトを取得しておく
    graph, = plt.plot(time, conc, color='blue', alpha=0.8)

続いてのコードです。今までは単純にmatplotlib.pyplotモジュールのplotを呼び出すだけでしたが、グラフとスライダーのレイアウトで細かい設定をするため、
先にplt.subplots()でfigure, axesといったオブジェクトを得ています(figsize=(10, 6)のところで図のサイズ(縦横)を設定)。あと、plt.plot()の戻り値の先頭の要素のみをgraphで受け取っており、これをコールバック関数で更新します。

    # スライダーの値をマウスで変更したときに行う処理
    def divt_update(slider_val):
        T = slider_val
        bc2 = BloodConc2(x0, vd, k, T, interval)
        conc2 =[]
        print('点滴時間: ', T)
        print('css_ave: ', bc2.compute_cssave())
        print('css_max: ', bc2.calc_cssmax())
        print('css_min: ', bc2.calc_cssmin())
        print('-' * 30)
        for i in range(1, n + 1):
            conc2.extend(bc2.get_section_conc(i))
        # 血中濃度の値を更新
        graph.set_ydata(conc2)
        # 再描画
        fig.canvas.draw_idle()
 

    # figure内でのaxes(ここではグラフ)の座標を取得
    ax_pos = ax.get_position()
    # Sliderの配置を設定する. axes([左, 下, 幅, 高さ]). 上のグラフと左位置と幅を揃える
    divt_slider_pos = plt.axes([ax_pos.x0, ax_pos.y0 - 0.15, ax_pos.width, 0.03])
    # スライダーインスタンスを生成(配置する座標軸, ラベル, 最小値, 最大値, スライダー初期位置, ステップの刻み幅)
    divt_slider = Slider(divt_slider_pos, '点滴時間(hr)', 0.5, 12, valinit=1.0, valstep=0.5)
    # スライダーの値の変更時に新しいスライダーの値でdivt_updateを呼び出す
    divt_slider.on_changed(divt_update)
    # グラフを表示する
    plt.show()
 

こちらに先に説明した3点のコードが含まれています。def divt_update(slider_val)がコールバック関数で、ここでスライダーの現在の値を点滴時間Tとして取得しています。その新たなTを元にBloodConc2のインスタンスを生成し*1、血中濃度を再計算、グラフの値を更新、再描画といった処理を行なっています。

最後にその下の部分でスライダーのインスタンス生成とそのインスタンスのon_changedメソッドにコールバック関数を渡すコードを書いています。

なお、今回の記事はこちらのページを参考にさせてもらいました。
matplotlib の figure(図) と axes(座標軸)-python | コード7区
matplotlib.widgets — Matplotlib 3.1.0 documentation

変化させた点滴時間毎にCss_ave, Css_max, Css_minを画面に出力するようにしてみました。Css_maxとCss_minの差がだんだん小さくなってくるのがわかります。

点滴時間:  0.5
Css_ave:  11.09765416068434
Css_max:  15.164576241758036
Css_min:  7.804466121696154
------------------------------
点滴時間:  1.0
Css_ave:  11.09765416068433
Css_max:  14.948723217342526
Css_min:  7.918810278283177
------------------------------
.
.



最後に、(点滴時間を変化させる方の)コード全文を載せておきます。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from scipy import integrate

 
class BloodConc:
    def __init__(self, x0, vd, ke, T, interval):
        self.k0 = x0 / T          # 点滴速度
        self.vd = vd              # 分布容積
        self.k = ke               # 消失速度定数
        self.T = T                # 点滴時間
        self.interval = interval  # 投与間隔
        # 点滴開始~終了時間の経過時間
        self.td = np.linspace(0, T, num=int(T / 0.1), endpoint=False)
        # 点滴終了後~次回投与直前まで
        self.ta = np.linspace(0, interval - T,
                        num=int((interval - T) / 0.1), endpoint=False)
 
    # (1):点滴開始~終了直前
    def __calc_during_div(self, n):
        c = (self.k0 / self.vd / self.k) * ((1 - np.exp(-self.k * self.T))
             * (1 - np.exp(-(n - 1) * self.k * self.interval))
             / (1 - np.exp(-self.k * self.interval))
             * np.exp(-self.k * (self.td + self.interval - self.T))
             + (1 - np.exp(-self.k * self.td)))
        return c.tolist()
 
    # (2):点滴終了時~次回投与直前
    def __calc_after_div(self, n):
        c = (self.k0 / self.vd / self.k) * ((1 - np.exp(-self.k * self.T))
             * (1 - np.exp(-n * self.k * self.interval))
             / (1 - np.exp(-self.k * self.interval))
             * np.exp(-self.k * self.ta))
        return c.tolist()
 
    # (1)+(2):点滴開始~次回投与直前
    def get_section_conc(self, n):
        c1 = self.__calc_during_div(n)
        c2 = self.__calc_after_div(n)
        c1.extend(c2)
        return c1
 
 
# 上のBloodConcを継承する. 今回コンストラクタはオーバーライドしない.
class BloodConc2(BloodConc):
    def compute_cssave(self):
        # 点滴開始~点滴終了までの区間で定積分
        auc1 = integrate.quad(
            lambda t: (self.k0 / self.vd / self.k) * ((1 - np.exp(-self.k * self.T))
            / (1 - np.exp(-self.k * self.interval))
            * np.exp(-self.k * (t + self.interval - self.T)) + (1 - np.exp(-self.k * t))),
            0, self.T)
 
        # 点滴終了~次回時までの区間で定積分
        auc2 = integrate.quad(
            lambda t: (self.k0 / self.vd / self.k) * (1 - np.exp(-self.k * self.T))
            / (1 - np.exp(-self.k * self.interval)) * np.exp(-self.k * (t - self.T)),
            self.T, self.interval)
 
        return (auc1[0] + auc2[0]) / self.interval
 
    def calc_cssmax(self):
        return (self.k0 / self.vd / self.k) * (1 - np.exp(-self.k * self.T)) / \
               (1 - np.exp(-self.k * self.interval))
 
    def calc_cssmin(self):
        return self.calc_cssmax() * np.exp(-self.k * (self.interval - self.T))
 
 
if __name__ == '__main__':
    x0 = 100                  # 投与量(mg)
    vd = 13                   # 分布容積(L)
    t_half = 12               # 半減期(hr)
    k = np.log(2) / t_half    # 消失速度定数(hr^-1)
    T = 1                     # 点滴時間(hr)
    interval = 12             # 投与間隔(hr)
    days = 4                  # 何日投与するか
 
    # 描画期間の投与回数
    n = int(days * 24 / interval)
    bc = BloodConc2(x0, vd, k, T, interval)
    conc = []
 
    for i in range(1, n + 1):
        conc.extend(bc.get_section_conc(i))
 
    period = 24 * days
    time = np.linspace(0, period, period / 0.1, endpoint=False)
 
    # subplot()でFigure(図), Axes(座標軸)インスタンスを得る
    fig, ax = plt.subplots(figsize=(10, 6))
    # スライダーを配置するためAxes下の余白を広げる. デフォルトは0.1
    plt.subplots_adjust(bottom=0.2)
    # プロットされたデータを表すオブジェクトを取得しておく
    graph, = plt.plot(time, conc, color='blue', alpha=0.8)
    # 軸ラベルを表示
    plt.ylabel('血中濃度[μg/mL]')
    plt.xlabel('時間[hr]')
    # 目盛りを投与間隔毎にする
    plt.xticks(np.arange(0, time[-1] + 12, 12))
    plt.xlim(0,)
    plt.ylim(0, 20)
    plt.grid(linestyle='dashed')
 
    # スライダーの値をマウスで変更したときに行う処理
    def divt_update(slider_val):
        T = slider_val
        bc2 = BloodConc2(x0, vd, k, T, interval)
        conc2 =[]
        print('点滴時間: ', T)
        print('css_ave: ', bc2.compute_cssave())
        print('css_max: ', bc2.calc_cssmax())
        print('css_min: ', bc2.calc_cssmin())
        print('-' * 30)
        for i in range(1, n + 1):
            conc2.extend(bc2.get_section_conc(i))
        # グラフの値を更新
        graph.set_ydata(conc2)
        # 再描画
        fig.canvas.draw_idle()
 
 
    # figure内でのaxes(上のグラフ)の座標を取得
    ax_pos = ax.get_position()
    # Sliderの配置を設定する. axes([左, 下, 幅, 高さ]). 上のグラフと左位置と幅を揃える
    divt_slider_pos = plt.axes([ax_pos.x0, ax_pos.y0 - 0.15, ax_pos.width, 0.03])
    # スライダーインスタンスを生成(配置する座標軸, ラベル, 最小値, 最大値, スライダー初期位置, ステップの刻み幅)
    divt_slider = Slider(divt_slider_pos, '点滴時間(hr)', 0.5, 12, valinit=1.0, valstep=0.5)
    # スライダーの値の変更時に新しいスライダーの値でdivt_updateを呼び出す
    divt_slider.on_changed(divt_update)
    # グラフを表示する
    plt.show()
 

参考文献

・4ステップ 臨床力UPエクササイズ4 TDM領域
・徹底解説 薬物動態の数学ー微積分と対数、非線形ー

*1:本当は新たにインスタンスを作るのではなく、先に作ったインスタンスの変数を代入で更新するだけにしたかったのですが、コンストラクタ内でTを使った変数を作っていたためできなかった。面倒なので書き直しはせずにこのままにしておきます。