随时间变化的生存回归¶
Cox的时间变化比例风险模型¶
通常,个体的协变量会随时间变化。一个例子是进入研究的医院患者,在未来的某个时间可能会接受心脏移植。我们想知道移植的效果,但如果我们在他们是否接受移植的条件下进行分析,我们必须小心。考虑到如果患者需要等待至少1年才能接受移植,那么在那一年之前去世的每个人都被视为未接受移植的患者,因此这会高估未接受移植的风险。
我们可以通过对Cox模型的修改,将随时间变化的变化纳入我们的生存分析中。一般的数学描述是:
注意时间变化的\(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 |
真 |
在上述数据集中,start 和 stop 表示边界,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")
如果你的数据集是第二种类型,即基于事件的,你的数据集可能看起来像下面这样,矩阵中的值表示自受试者出生以来的时间,None 或 NaN 表示事件未发生(如果事件从未发生,受试者也可以被排除):
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提供了预测方法,但这些预测的含义存在逻辑上的限制。