随时间变化的生存回归

Cox的时间变化比例风险模型

通常,个体的协变量会随时间变化。一个例子是进入研究的医院患者,在未来的某个时间可能会接受心脏移植。我们想知道移植的效果,但如果我们在他们是否接受移植的条件下进行分析,我们必须小心。考虑到如果患者需要等待至少1年才能接受移植,那么在那一年之前去世的每个人都被视为未接受移植的患者,因此这会高估未接受移植的风险。

我们可以通过对Cox模型的修改,将随时间变化的变化纳入我们的生存分析中。一般的数学描述是:

\[h(t | x) = \overbrace{b_0(t)}^{\text{baseline}}\underbrace{\exp \overbrace{\left(\sum_{i=1}^n \beta_i (x_i(t) - \overline{x_i}) \right)}^{\text{log-partial hazard}}}_ {\text{partial hazard}}\]

注意时间变化的\(x_i(t)\)表示协变量可以随时间变化。这个模型在lifelines中实现为CoxTimeVaryingFitter。所需的数据集模式与之前的模型不同,因此我们将花一些时间描述它。

时变回归的数据集创建

lifelines 要求数据集采用所谓的格式。这意味着每一行代表一个状态变化,包括一个ID、左侧(不包含)时间点和右侧(包含)时间点。例如,以下数据集跟踪了三个独特的对象。

id

开始

停止

z

事件

1

0

8

1

0

2

0

5

0

0

2

5

8

0

1

3

0

3

1

0

3

3

12

1

1

在上述数据集中,startstop 表示边界,id 是每个主题的唯一标识符,event 表示主题在该时间段结束时是否死亡。例如,主题 ID 2 的变量 z=0 一直持续到并包括时间段 5 的结束(我们可以认为测量是在时间段结束时进行的),之后它被设置为 1。由于该行中的 event 为 1,我们得出结论,该主题在时间 8 时死亡,

这个所需的数据集可以从较小的数据集中构建。为此,我们可以使用lifelines中提供的一些辅助函数。通常,数据的格式看起来像是从关系数据库中导出的。你可能有一个包含id、存活时间和删失标志的“基础”表,以及可能的静态协变量。例如:

id

持续时间

事件

var1

1

10

0.1

2

12

0.5

我们将对这个数据集进行轻微的转换,将其修改为“长”格式。

import pandas as pd
from lifelines.utils import to_long_format

base_df = pd.DataFrame([
  {'id': 1, 'duration': 10, 'event': True, 'var1': 0.1},
  {'id': 2, 'duration': 12, 'event': True, 'var1': 0.5}
])

base_df = to_long_format(base_df, duration_col="duration")

新数据集如下所示:

id

开始

停止

var1

事件

1

0

10

0.1

2

0

12

0.5

你还将有一个引用未来测量的辅助数据集。这可能有两种“类型”。第一种是当你有一个随时间变化的变量时(例如:随时间给予不同的药物,或随时间测量体温)。第二种类型是基于事件的数据集:某个事件在未来的某个时间发生(例如:器官移植发生,或干预)。我们稍后会讨论第二种类型。第一种类型的数据集可能看起来像这样:

示例:

id

时间

var2

1

0

1.4

1

4

1.2

1

8

1.5

2

0

1.6

其中 time 是从进入事件开始的持续时间。在这里,我们看到受试者1在时间4结束时和时间8结束时其 var2 协变量发生了变化。我们可以使用 lifelines.utils.add_covariate_to_timeline() 将协变量数据集合并到原始数据集中。

from lifelines.utils import add_covariate_to_timeline

cv = pd.DataFrame([
  {'id': 1, 'time': 0, 'var2': 1.4},
  {'id': 1, 'time': 4, 'var2': 1.2},
  {'id': 1, 'time': 8, 'var2': 1.5},
  {'id': 2, 'time': 0, 'var2': 1.6},

])

df = add_covariate_to_timeline(base_df, cv, duration_col="time", id_col="id", event_col="event")

id

开始

停止

var1

var2

事件

1

0

4

0.1

1.4

1

4

8

0.1

1.2

1

8

10

0.1

1.5

2

0

12

0.5

1.6

从上述输出中,我们可以看到,受试者1在观察期间状态发生了两次变化,最终在时间10结束时失效。受试者2是一个删失案例,我们在时间12后失去了对他们的跟踪。

您可能希望添加多个协变量,因此上述内容可以像这样简化:

from lifelines.utils import add_covariate_to_timeline

df = base_df.pipe(add_covariate_to_timeline, cv1, duration_col="time", id_col="id", event_col="event")\
            .pipe(add_covariate_to_timeline, cv2, duration_col="time", id_col="id", event_col="event")\
            .pipe(add_covariate_to_timeline, cv3, duration_col="time", id_col="id", event_col="event")

如果你的数据集是第二种类型,即基于事件的,你的数据集可能看起来像下面这样,矩阵中的值表示自受试者出生以来的时间,NoneNaN 表示事件未发生(如果事件从未发生,受试者也可以被排除):

event_df = pd.DataFrame([
    {'id': 1, 'E1': 1.0},
    {'id': 2, 'E1': None},
    {'id': 3, 'E1': 3.0},
])

print(event_df)

"""
    id    E1
0   1     1.0
1   2     NaN
2   3     3.0
"""
...

最初,这不能添加到我们的基线DataFrame中。然而,使用lifelines.utils.covariates_from_event_matrix(),我们可以将这样的DataFrame转换为可以轻松添加的格式。

from lifelines.utils import covariates_from_event_matrix

cv = covariates_from_event_matrix(event_df, id_col="id")
print(cv)

"""
   id  duration  E1
0   1       1.0   1
1   2       inf   1
2   3       3.0   1
"""

base_df = pd.DataFrame([
    {'id': 1, 'duration': 10, 'event': True, 'var1': 0.1},
    {'id': 2, 'duration': 12, 'event': True, 'var1': 0.5}
])
base_df = to_long_format(base_df, duration_col="duration")


base_df = add_covariate_to_timeline(base_df, cv, duration_col="duration", id_col="id", event_col="event")
"""
   start   E1  var1  stop  id  event
0    0.0  NaN   0.1   1.0   1  False
1    1.0  1.0   0.1  10.0   1   True
2    0.0  NaN   0.5  12.0   2   True
"""

有关从SQL存储中提取此类数据集的示例以及其他辅助函数,请参见示例SQL查询和转换以获取随时间变化的数据

累计和

add_covariate_to_timeline()上,一个值得注意的额外标志是cumulative_sum标志。默认情况下它是False,但将其设置为True将在连接之前对协变量执行累积求和。如果协变量描述的是增量变化而不是状态更新,这将非常有用。例如,我们可能有对患者给药的测量值,我们希望协变量反映自开始以来我们给药的量。事件列也适合进行累积求和。相比之下,测量患者温度的协变量是状态更新,不应求和。参见随时间变化的协变量的累积求和示例以查看此示例。

延迟时变协变量

add_covariate_to_timeline() 还有一个选项可以延迟或移动协变量,使其变化比最初观察到的要晚。有人可能会问,为什么要延迟一个时间变化的协变量?这里有一个例子。考虑研究吸烟对死亡率的影响,我们手头有每个月吸烟量的时间变化观察数据。我们不知道的是,当受试者达到危重病水平时,他们会被送入医院,吸烟量降至零。有些人在医院中去世。如果我们天真地使用这个数据集,我们会看到吸烟会导致突然死亡,相反,吸烟有助于健康!这是一个反向因果关系的例子:即将发生的死亡事件实际上影响了协变量。

为了处理这个问题,你可以按时间段延迟观察。这可能会导致在观察窗口之外的行被丢弃。

from lifelines.utils import add_covariate_to_timeline

cv = pd.DataFrame([
    {'id': 1, 'time': 0, 'var2': 1.4},
    {'id': 1, 'time': 4, 'var2': 1.2},
    {'id': 1, 'time': 8, 'var2': 1.5},
    {'id': 2, 'time': 0, 'var2': 1.6},
])

base_df = pd.DataFrame([
    {'id': 1, 'duration': 10, 'event': True, 'var1': 0.1},
    {'id': 2, 'duration': 12, 'event': True, 'var1': 0.5}
])
base_df = to_long_format(base_df, duration_col="duration")

base_df = add_covariate_to_timeline(base_df, cv, duration_col="time", id_col="id", event_col="event", delay=5)\
            .fillna(0)

print(base_df)
"""
   start  var1  var2  stop  id  event
0      0   0.1   NaN   5.0   1  False
1      5   0.1   1.4   9.0   1  False
2      9   0.1   1.2  10.0   1   True
3      0   0.5   NaN   5.0   2  False
4      5   0.5   1.6  12.0   2   True
"""

拟合模型

一旦您的数据集处于正确的方向,我们可以使用CoxTimeVaryingFitter将模型拟合到您的数据。该方法与CoxPHFitter类似,只是我们需要告诉fit()关于额外的时间列。

将Cox模型拟合到数据涉及迭代梯度下降。lifelines 付出了额外的努力来帮助收敛,因此请注意出现的任何警告。修复任何警告通常有助于收敛。如需进一步帮助,请参阅 Cox比例风险模型中的收敛问题

from lifelines import CoxTimeVaryingFitter

ctv = CoxTimeVaryingFitter(penalizer=0.1)
ctv.fit(base_df, id_col="id", event_col="event", start_col="start", stop_col="stop", show_progress=True)
ctv.print_summary()
ctv.plot()

关于预测的简短说明

与其他回归模型不同,在时间变化的环境中进行预测并不简单。要进行预测,我们需要知道超出观察时间的协变量值,但如果我们知道这些,我们也就知道受试者是否仍然存活!然而,仍然可以计算已知观察值处受试者的风险值、基线累积风险率和基线生存函数。因此,尽管CoxTimeVaryingFitter提供了预测方法,但这些预测的含义存在逻辑上的限制。