来源:数据STUDIO
作者:云朵君
糖尿病是全球最常见的慢性非传染性疾病之一。流行病学调查显示,我国约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
# 纵向向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())
当然,除了上述的两种方法,如果您有更好的方法,欢迎搭讪交流。