【问题标题】:How to use groupby and filter data frame to create a new column如何使用 groupby 和 filter 数据框创建新列
【发布时间】:2018-05-04 14:57:18
【问题描述】:

假设我有一个数据集,其中包含入住 ICU 的患者的心率时间序列。

我想添加一些纳入标准,例如,我只想考虑心率 >= 90 的患者在 ICU 停留至少一小时。如果一小时后第一次测量的心率(从 >=90 值开始)未知,我们假设它高于 90 并包括入住 ICU。

应从与“至少 1 小时”时间跨度相对应的第一次测量开始包括该 ICU 停留的条目。

请注意,一旦包含入住 ICU 时间,即使心率在某个时候回落到 90 以下,也不会再被排除在外。

所以下面的数据框,其中“Icustay”对应于入住 ICU 的唯一 ID,“Hours”表示自进入 ICU 以来所花费的时间

   Heart Rate  Hours  Icustay  Inclusion Criteria
0          79    0.0     1001                   0
1          91    1.5     1001                   0
2         NaN    2.7     1001                   0
3          85    3.4     1001                   0
4          90    0.0     2010                   0
5          94   29.4     2010                   0
6          68    0.0     3005                   0

应该变成

   Heart Rate  Hours  Icustay  Inclusion Criteria
0          79    0.0     1001                   0
1          91    1.5     1001                   1
2         NaN    2.7     1001                   1
3          85    3.4     1001                   1
4          90    0.0     2010                   1
5          94   29.4     2010                   1
6          68    0.0     3005                   0

我已经为此编写了代码,并且可以正常工作。但是它非常慢,在处理我的整个数据集时,每个患者最多可能需要几秒钟(实际上我的数据集包含的数据比只有 6 个字段还多,但为了更好的可读性,我已经对其进行了简化)。由于有 40,000 名患者,我想加快速度。

这是我目前正在使用的代码,以及我上面介绍的玩具数据集。

import numpy as np
import pandas as pd

d = {'Icustay': [1001, 1001, 1001, 1001, 2010, 2010, 3005], 'Hours': [0, 1.5, 2.7, 3.4, 0, 29.4, 0],
     'Heart Rate': [79, 91, np.NaN, 85, 90, 94, 68], 'Inclusion Criteria':[0, 0, 0, 0, 0, 0, 0]}
all_records = pd.DataFrame(data=d)


for curr in np.unique(all_records['Icustay']):
    print(curr)
    curr_stay = all_records[all_records['Icustay']==curr]
    indexes = curr_stay['Hours'].index
    heart_rate_flag = False
    heart_rate_begin_time = 0
    heart_rate_begin_index = 0
    for i in indexes:
        if(curr_stay['Heart Rate'][i] >= 90 and not heart_rate_flag):
            heart_rate_flag = True
            heart_rate_begin_time = curr_stay['Hours'][i]
            heart_rate_begin_index = i
        elif(curr_stay['Heart Rate'][i] < 90):
            heart_rate_flag = False
        elif(heart_rate_flag and curr_stay['Hours'][i]-heart_rate_begin_time >= 1.0):
            all_records['Inclusion Criteria'].iloc[indexes[indexes>=heart_rate_begin_index]] = 1
            break

请注意,数据集按患者和小时数排序。

有没有办法加快速度?我曾考虑过像 group by 这样的内置函数,但我不确定它们在这种特殊情况下是否会有所帮助。

【问题讨论】:

  • 感谢您提出详尽的问题。如果在同一 ICU 住院期间心率回到 90 以下会怎样?
  • 如果患者在某个时间点满足标准,则纳入标准在患者剩余的住院期间保持为 1,即使心率回落到 90 以下。我将编辑我的问题以使更清晰
  • 我对时间跨度有点困惑。您说:The entries of that ICU stay should be included starting from the first measurement corresponding to the ”at least 1 hour” timespan.,但在您的示例中,您包括了在 ICU 停留2010 的第一次观察,即使在 Hours = 0.0 观察到。
  • “至少 1 小时”是指纳入标准的时间跨度,而不是整个 ICU 停留时间。因此,对于 2010 年的 ICU 住宿,心率高于 90 的时间至少有 1 小时,该时间段从 0.0 时间开始,这就是它被包括在内的原因
  • 我一直在查看数据以寻找错误的场景,但它似乎总是按预期工作,谢谢!

标签: python python-3.x pandas numpy


【解决方案1】:

这看起来有点难看,但它避免了循环和apply(本质上只是一个循环)。我没有在大型数据集上进行过测试,但我怀疑它会比你当前的代码快很多。

首先,创建一些额外的列,其中包含下一行/上一行的详细信息,因为这可能与您的某些条件相关:

all_records['PrevHeartRate'] = all_records['Heart Rate'].shift()
all_records['NextHours'] = all_records['Hours'].shift(-1)
all_records['PrevICU'] = all_records['Icustay'].shift()
all_records['NextICU'] = all_records['Icustay'].shift(-1)

接下来,创建一个包含每个 id 的第一个合格记录的 DataFrame(由于涉及的逻辑量,现在这非常混乱):

first_per_id = (all_records[((all_records['Heart Rate'] >= 90) |
                            ((all_records['Heart Rate'].isnull()) & 
                            (all_records['PrevHeartRate'] >= 90) &
                            (all_records['Icustay'] == all_records['PrevICU']))) &
                            ((all_records['Hours'] >= 1) |
                            ((all_records['NextHours'] >= 1) &
                            (all_records['NextICU'] == all_records['Icustay'])))]
                .drop_duplicates(subset='Icustay', keep='first')[['Icustay']]
                .reset_index()
                .rename(columns={'index': 'first_index'}))

这给了我们:

   first_index  Icustay
0            1     1001
1            4     2010

您现在可以从原始 DataFrame 中删除所有新列:

all_records.drop(['PrevHeartRate', 'NextHours', 'PrevICU', 'NextICU'], axis=1, inplace=True)

然后我们可以将它与原始 DataFrame 合并:

new = pd.merge(all_records, first_per_id, how='left', on='Icustay')

给予:

   Heart Rate  Hours  Icustay  Inclusion Criteria  first_index
0        79.0    0.0     1001                   0          1.0
1        91.0    1.5     1001                   0          1.0
2        97.0    2.7     1001                   0          1.0
3         NaN    3.4     1001                   0          1.0
4        90.0    0.0     2010                   0          4.0
5        94.0   29.4     2010                   0          4.0
6        68.0    0.0     3005                   0          NaN

从这里我们可以将“first_index”(这是该 id 的第一个合格索引)与实际索引进行比较:

new['Inclusion Criteria'] = new.index >= new['first_index']

这给出了:

       Heart Rate  Hours  Icustay  Inclusion Criteria  first_index
0        79.0    0.0     1001               False          1.0
1        91.0    1.5     1001                True          1.0
2        97.0    2.7     1001                True          1.0
3         NaN    3.4     1001                True          1.0
4        90.0    0.0     2010                True          4.0
5        94.0   29.4     2010                True          4.0
6        68.0    0.0     3005               False          NaN

从这里开始,我们只需要整理一下(将结果列转换为整数,并删除first_index列):

new.drop('first_index', axis=1, inplace=True)
new['Inclusion Criteria'] = new['Inclusion Criteria'].astype(int)

给出最终期望的结果:

       Heart Rate  Hours  Icustay  Inclusion Criteria
0        79.0    0.0     1001                   0
1        91.0    1.5     1001                   1
2        97.0    2.7     1001                   1
3         NaN    3.4     1001                   1
4        90.0    0.0     2010                   1
5        94.0   29.4     2010                   1
6        68.0    0.0     3005                   0

【讨论】:

    【解决方案2】:

    您可以在 pandas 中使用 groupbyapply 函数。这也应该更快。

    ## fill missing values 
    all_records['Heart Rate'].fillna(90, inplace=True)
    
    ## use apply
    all_records['Inclusion Criteria'] = all_records.groupby('Icustay').apply(lambda x: (x['Heart Rate'].ge(90)) & (x['Hours'].ge(0))).values.astype(int)
    
    print(all_records)
    
       Heart Rate  Hours  Icustay  Inclusion Criteria
    0        79.0    0.0     1001                   0
    1        91.0    1.5     1001                   1
    2        97.0    2.7     1001                   1
    3        90.0    3.4     1001                   1
    4        90.0    0.0     2010                   1
    5        94.0   29.4     2010                   1
    6        68.0    0.0     3005                   0
    

    【讨论】:

    • 谢谢,遗憾的是,这并不能解决问题(我刚刚编辑以明确问题),因为它排除了在 ICU 停留满足纳入标准后心率低于 90 的情况。很抱歉没有在原始问题中说得更清楚
    猜你喜欢
    • 1970-01-01
    • 2018-12-08
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多