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

时间:2019-09-05 11:08:25

标签: python-3.x pandas dataframe

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

我正在尝试通过计算产品将持续多长时间并添加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 

我的预期结果如下:

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

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

1001没有重叠,因此保持原样。

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

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

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

1 个答案:

答案 0 :(得分:0)

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

首先,请确保(如果尚未完成)将日期转换为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')

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

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)

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

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

'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

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