时间序列 | 从开始到结束日期自增扩充数据
糖尿病是全球最常见的慢性非传染性疾病之一。流行病学调查显示,我国约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(16, 34, 25),
Timestamp('2019-08-05 00:00:00'), datetime.time(16, 34, 42),
'长期医嘱', Timestamp('2019-08-27 00:00:00'),
datetime.time(10, 49, 26)]], 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(1, 0)
# 将原来的时间更换为新的时间
>>> 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
的填充和插值方式跟fillna
和reindex
的一样
>>> date_range_df = frame.resample('D').bfill()
>>> date_range_df
输出
最后在重置索引并重命名即可。
要点总结
构建自增时间序列 时间序列内容,即需要重复的医嘱单准备 医嘱开始时间准备,第一天与其后几天的时间不同 插值,根据实际情况使用前插值( .ffill()
)或后插值(.bfill()
)
当然,除了上述的两种方法,如果您有更好的方法,欢迎搭讪交流。
推荐阅读