查看原文
其他

时间序列 | 从开始到结束日期自增扩充数据

云朵君 数据STUDIO 2022-04-28

糖尿病是全球最常见的慢性非传染性疾病之一。流行病学调查显示,我国约11%的成年人患有糖尿病,而在住院患者中这一比例更高。

住院期间将长期服用药物,医院系统在检测到医嘱优先级别为长期医嘱时,会根据医嘱单上医嘱开始日期及时间,每天按时自动创建当日医嘱单,在没有停止或更改的情况下,其医嘱内容与上一天医嘱内容一致。患者根据每天的医嘱单上的内容按时按量服用药物,直至医生停止患者用药。

由于是重复内容,系统为节约存储空间,并未记录每天自动创建的重复医嘱单。但在做数据分析时,需要进行临床场景重现。

需求描述

有如下数据,columns = ['医嘱日期', '医嘱时间', '医嘱开始日期', '医嘱开始时间','医嘱优先级', '停止日期', '停止时间', '项目名称']

现要求从医嘱开始日期到停止日期,按照日期自增逻辑扩充数据,其中自增的日期的医嘱开始时间为当日的01:00:00。结果如下图:



方法一,表格合并

先上代码

def long_advice(item):
    # 逐条处理,传入Series
    # 构建医嘱单内容表
    item_df1 = pd.DataFrame(data=np.reshape(item.values,(1,-1)),columns=item.index)
    item_df2 = item_df1.copy()
    item_df2['医嘱开始时间'] = parse('01:00:00').time()
    item_df = pd.concat([item_df1, item_df2]).drop(columns='医嘱开始日期').reset_index(drop=True)
    
    # 构建时间序列索引表
    # 扩展的医嘱日期的医嘱时间为01:00:00,医嘱开始日期的医嘱时间为原有的医嘱时间
    date_range_left = pd.DataFrame(
        data=parse('01:00:00').time(),
        index=pd.date_range(start=item.医嘱开始日期, end=item.停止日期),
        columns= ['医嘱开始时间']
                                  ).reset_index().rename(columns={'index':'医嘱开始日期'})
    date_range_left.loc[0,'医嘱开始时间']= item.医嘱时间
    
    # 以时间序列索引表为左表,以时间序列内容表为右表
    date_range_df = pd.merge(date_range_left
                         ,item_df
                         ,on = '医嘱开始时间'
                         ,how='left')
    return  date_range_df

步骤详解

导入Python包

import pandas as pd
import numpy as np
from datetime import datetime
from dateutil.parser import parse

查看原始数据

# 前面步骤略,直接从主题开始
>>> item
医嘱日期      2019-08-05 00:00:00
医嘱时间                 16:34:25
医嘱开始日期    2019-08-05 00:00:00
医嘱开始时间               16:34:42
医嘱优先级                    长期医嘱
停止日期      2019-08-27 00:00:00
停止时间                 10:49:26
项目名称           格华止(500mg×30片)
Name: 0, dtype: object

pd.Series转pd.DataFrame

# 纵向向array转横向array
>>> np.reshape(item.values,(1,-1))
array([[Timestamp('2019-08-05 00:00:00'), datetime.time(163425),
        Timestamp('2019-08-05 00:00:00'), datetime.time(163442),
        '长期医嘱', Timestamp('2019-08-27 00:00:00'),
        datetime.time(104926)]], dtype=object)

>>> item_df1 = pd.DataFrame(data=np.reshape(item.values,(1,-1)),columns=item.index)

# 或者
>>> pd.DataFrame(item).T

输出

构建医嘱单内容表

# 首先创建副本,避免更改原表
>>> item_df2 = item_df1.copy()

# 创建datetime.time()格式的'01:00:00'
>>> parse('01:00:00').time()
datetime.time(10)

# 将原来的时间更换为新的时间
>>> item_df2['医嘱开始时间'] = parse('01:00:00').time()

# 合并两表
>>> item_df = pd.concat([item_df1, item_df2]
                       ).drop(columns='医嘱开始日期').reset_index(drop=True)

输出

至此医嘱单内容已创建完毕,接下来需要创建自增的时间序列,并以时间序列做主表,以医嘱单内容表做从表,进行表与表之间的连接。

构建时间序列索引表

从医嘱开始日期到停止日期创建pd.date_range() 索引,以医嘱开始时间等于'01:00:00' 为内容创建DataFrame,并重置索引并重命名,还原医嘱开始当日的开始时间。因为只要自增的那部分日期的医嘱时间为'01:00:00' ,而开始的第一天还是按照原来的开始时间。

>>> date_range_left = pd.DataFrame(data=parse('01:00:00').time(),
                                   index=pd.date_range(start=item.医嘱开始日期, end=item.停止日期),
                                   columns= ['医嘱开始时间']
                                  ).reset_index().rename(columns={'index':'医嘱开始日期'})
>>> date_range_left.loc[0,'医嘱开始时间']= item.医嘱开始时间
>>> date_range_left

输出

这里主要用到了pd.date_range() 方法,可参考《时间序列》

合并时间序列索引表与医嘱单内容表

>>> date_range_df = pd.merge(date_range_left
                             , item_df
                             , on='医嘱开始时间'
                             , how='left')

至此方法一已完成。


方法二,时间戳重采样

既然方法一已经提到用时间序列内pd.date_range() 方法,何不直接用升采用及插值的方法完成。

需要了解pandas里使用时间序列处理数据问题,可移步至《时间序列》

上代码

def long_advice_2(item):
    # 逐条处理,传入Series
    # 构建医嘱单内容表
    item_df1 = pd.DataFrame(data=np.reshape(item.values,(1,-1)),columns=item.index)
    item_df2 = item_df1.copy()
    item_df2['医嘱开始时间'] = parse('01:00:00').time()
    item_df2['医嘱开始日期'] = item_df2['停止日期']
    item_df = pd.concat([item_df1, item_df2]).reset_index(drop=True)
    # 构建时间序列,将起始时间转换为 DatetimeIndex(['2019-08-05', '2019-08-27'], dtype='datetime64[ns]', freq=None)
    frame = pd.DataFrame(item_df.drop(columns=['医嘱开始日期']).values,
             index=pd.to_datetime(item_df.医嘱开始日期.values),
             columns=item_df.drop(columns=['医嘱开始日期']).columns)
    
    # 时间戳重采样,resampling的填充和插值方式跟fillna和reindex的一样
    date_range_df = frame.resample('D').bfill().reset_index().rename(columns={'index':'医嘱开始日期'})
    return date_range_df

构建医嘱单内容表

其中构建医嘱单内容表与前面类似,其不同之处为保留医嘱开始日期,将第二个开始日期替换为停止日期,以便后面转换为pd.date_range()日期范围。

>>> item_df1 = pd.DataFrame(data=np.reshape(item.values,(1,-1)),columns=item.index)
>>> item_df2 = item_df1.copy()
>>> item_df2['医嘱开始时间'] = parse('01:00:00').time()
>>> item_df2['医嘱开始日期'] = item_df2['停止日期']
>>> item_df = pd.concat([item_df1, item_df2]).reset_index(drop=True)
>>> item_df

输出

构建时间序列

>>> # DataFrame的轴索引或列的日期转换为DatetimeIndex()
>>> pd.to_datetime(item_df.医嘱开始日期.values)
DatetimeIndex(['2019-08-05''2019-08-27'], dtype='datetime64[ns]', freq=None)

>>> frame = pd.DataFrame(item_df.drop(columns=['医嘱开始日期']).values,
                 index=pd.to_datetime(item_df.医嘱开始日期.values),
                 columns=item_df.drop(columns=['医嘱开始日期']).columns)
>>> frame 

输出

升采样及插值

时间戳重采样,resampling的填充和插值方式跟fillnareindex的一样

>>> date_range_df = frame.resample('D').bfill()
>>> date_range_df

输出

最后在重置索引并重命名即可。

要点总结

  • 构建自增时间序列
  • 时间序列内容,即需要重复的医嘱单准备
  • 医嘱开始时间准备,第一天与其后几天的时间不同
  • 插值,根据实际情况使用前插值(.ffill())或后插值(.bfill()

当然,除了上述的两种方法,如果您有更好的方法,欢迎搭讪交流。


推荐阅读

时间序列 | pandas时间序列基础

时间序列 | 重采样及频率转换

时间序列 | 时期(Period)及其算术运算

时间序列 | 字符串和日期的相互转换

-- 数据STUDIO -- 

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存