在熊猫数据框中组合具有重叠时间段的行

wal*_*box 8 dataframe python-3.x pandas

我正在研究处方习惯并拥有大量已售产品的数据框。

我试图通过计算产品将持续多长时间并添加 5 天的依从性、开始延迟等因素来计算购买的结束日期,从而将药物的购买转化为药物的疗程。

然后我想将处方与重叠的日期窗口结合起来,但我正在努力寻找一种有效的方法来做到这一点。我希望 groupby 是可能的,但我不知道如何做到这一点。

我知道如何迭代数据帧以创建一个包含相关信息的新数据帧,但这是一个缓慢的操作,我希望我能找到一个更优雅的解决方案。

ID      start       end         ingredient  days    dose    end
1000    2018-10-03  2018-10-18  Metron...   10.0    125.00 
1000    2018-10-13  2018-10-25  Metron...   7.0     125.00 
1001    2018-03-08  2018-03-20  Cefalexin   7.0     150.00
1001    2018-09-17  2018-10-05  Cefalexin   13.0    150.00
1002    2018-05-18  2018-05-30  Amoxiclav   7.0     75.00
1002    2018-05-25  2018-06-06  Amoxiclav   7.0     100.00 
1003    2018-07-01  2018-07-16  Amoxiclav   10.0    50.00
1003    2018-07-15  2018-07-30  Amoxiclav   10.0    50.00 
1003    2018-07-25  2018-08-09  Amoxiclav   10.0    50.00 
Run Code Online (Sandbox Code Playgroud)

我的预期结果如下:

ID      start       end         ingredient  days    dose
1000    2018-10-03  2018-10-25  Metron...   17.0    125.00
1001    2018-03-08  2018-03-20  Cefalexin   7.0     150.00
1001    2018-09-17  2018-10-05  Cefalexin   13.0    150.00
1002    2018-05-18  2018-05-30  Amoxiclav   7.0     75.00
1002    2018-05-25  2018-06-06  Amoxiclav   7.0     100.00 
1003    2018-07-01  2018-08-05  Amoxiclav   30.0    50.00
Run Code Online (Sandbox Code Playgroud)

1000的第二次购买正好是 10 天,因此结束日期与他们的第二次结束日期相同。

1001 没有重叠,所以保持原样。

1002 开始日期和结束日期重叠,但剂量有所变化,因此不应合并。

1003总共有 30 天。他们最终购买的开始日期晚于第一次购买的结束日期。他们的结束日期应该是他们第一次购买后的 35 天。这是一个可协商的标准,并且可以接受与最终购买的结束日期相匹配的结束日期。

我在这里吠错树了吗?这必须迭代完成吗?

Val*_*ino 8

我认为这里最大的问题是确定时间间隔何时重叠,其余的只是分组和相加。

首先,可以肯定的(如果尚未完成),以您的日期转换为datetime与天timedelta。这将有助于比较日期和持续时间并对其进行一些数学运算。

df['start'] = pd.to_datetime(df['start'])
df['end'] = pd.to_datetime(df['end'])
df['days'] = pd.to_timedelta(df['days'], unit='D')
Run Code Online (Sandbox Code Playgroud)

此代码产生您的预期结果:

def join_times(x):
    startdf = pd.DataFrame({'time':x['start'], 'what':1})
    enddf = pd.DataFrame({'time':x['end'], 'what':-1})
    mergdf = pd.concat([startdf, enddf]).sort_values('time')
    mergdf['running'] = mergdf['what'].cumsum()
    mergdf['newwin'] = mergdf['running'].eq(1) & mergdf['what'].eq(1)
    mergdf['group'] = mergdf['newwin'].cumsum()
    x['group'] = mergdf['group'].loc[mergdf['what'].eq(1)]
    res = x.groupby('group').agg({'days':'sum', 'start':'first'})
    res['end'] = res.apply(lambda x : x['start'] + x['days'] + pd.to_timedelta(5, unit='D'), axis=1)
    return res

ddf = df.groupby(['ID', 'ingredient', 'dose']).apply(join_times).reset_index().drop('group', axis=1)
Run Code Online (Sandbox Code Playgroud)

这是需要解释的。如您所见,我groupby用来识别子样本。然后工作由自定义join_times函数完成。

join_times函数在单个数据帧(列'time')开始和结束时间的同一列中连接在一起,按顺序排序。
第二列'what'用 +1 开始时间和 -1 结束时间标记。这些用于跟踪有多少间隔重叠(在列中'running'使用cumsum())。
然后建立一个布尔列'newwin'来标识一个新的非重叠时间间隔的开始,并建立一个列'group'来用相同的整数标记属于相同重叠时间间隔的行。

将 a'group'列添加到原始子样本中,复制先前构建的'group'列中的值。最后,我们可以为每个子样本确定哪些行有重叠。
所以我们可以groupby再次使用并对'days'列求和,保留列中的第一个日期'start'
'end'列的计算方法是将'start'持续时间'days'加上 5 天。

上面的代码,使用您的数据样本,给出:

     ID ingredient   dose    days      start        end
0  1000  Metron...  125.0 17 days 2018-10-03 2018-10-25
1  1001  Cefalexin  150.0  7 days 2018-03-08 2018-03-20
2  1001  Cefalexin  150.0 13 days 2018-09-17 2018-10-05
3  1002  Amoxiclav   75.0  7 days 2018-05-18 2018-05-30
4  1002  Amoxiclav  100.0  7 days 2018-05-25 2018-06-06
5  1003  Amoxiclav   50.0 30 days 2018-07-01 2018-08-05
Run Code Online (Sandbox Code Playgroud)

这是您的预期结果。由于groupby索引操作,列顺序不同。