医学数据挖掘基本素养:每一步都要校对数据!!!!!!!!!!!!!!!!!
看数据量、特征数据缺失情况、离散情况、差异情况、特征包含情况(频次、dosage和日剂量)、运行结果。要不然建模数据不好,得重新返工。
代码没思路,多看原始数据。逻辑顺,才能写代码顺。深刻理解原始数据,才能理清逻辑。才能决定手工操作方便还是代码方便,在用药数据重复和交叉的情况需要百度药物药效属性,才能决定该合并还是删除。不看数据,没有发言权!!!!!!
数据处理注意事项:
- 简略查看主要数据:用药和tdm。先提取用药数据,纳排后与tdm数据拼一块,查看数据量和patient_id个数,像日剂量计算、身高体重等字段可以先不加。分轻重缓急
- 明确限定:
- 时间限定
- id/case_no限定
- 关键词限定。str.contains('keyword')
- 结局限定
- 时间问题:所有变量都与结局相关。
- 数据类型:
- patient_id(患者id),case_no(住院记录id),这些id数据类型在读入时可能为int或float,造成merge无法匹配,应在读入时设置为dtype={'patient_id': str};
- age、weight等str类型需要转换为float类型,计算BMI;value=round(value,2),保留2为小数
- start_datetime或end_datetime等str还是timedelta时间戳类型,因为str无法进行时间加减datetime.timedelta(days=7)
- 时间格式:规范化为2018-01-01 18:46:23,而不是13/09/2018 18:46:23,因为python sort_values()方法按第一个数排序,会把12/04排在22/02前面!
- 明确数据对应关系:主键。选择纳排基准(patient_id或case_no),合并数据时,要明确id对应的用药、住院记录关系
- 一对一。一个患者对应一个id
- 按出院日剂量分组时。虽然病人可能存在多次入院,多次出院时剂量改变,但我们要研究他再次入院的话,只能以他第一次出院日剂量作为分组标准,分析他再次入院,否则无法明确分析不同日剂量组别的入院差异。因为他再次入院的记录可能按日剂量分到其他组了,这导致我们无法检测数再次入院。
- 一对多。一个患者可能对应多条住院记录case_no;一条住院记录可能对应多条用药记录
- 操作DataFrame数据之前:
- 删除空值
- 排序
- 删除异常值:文字、过大值(绝对值大于中位数100倍)
- 删除重复
- 保存DataFrame数据之前:
- 排序
- 重置索引。df=df.reset_index(drop=True)
- 输出数据统计。print(df.shape); print(df['patient_id'].nunique()); print(df['case_no'].nunique())
目录
1.3 检验原始数据test_record+test_result预处理
2. 纳排标准 <= 明确纳排基准patient_id,case_no
Medical DM数据处理流程:参考丙戊酸和甲氨蝶呤项目
1. 原始数据raw_data预处理
因为特定用药和联合用药都需要从doctor_order用药里面提取;tdm检测和其他检测都需要从df_test(df_test_record + df_test_result)提取;如果先简单预处理一下原始数据,后面直接用的话,会方便很多!不用提特定药的时候处理一次,提联合用药的时候再处理一次。
导入packages
# _*_ coding: utf-8 _*_
# @Time: 2021/10/27 17:51
# @Author: yuyongsheng
# @Software: PyCharm
# @Description:
# 导入程序包
import pandas as pd
pd.set_option('mode.chained_assignment', None)
import numpy as np
import os
project_path=os.getcwd()
导入自定义函数
字符串转换为时间格式
# 字符串转换为时间格式
import datetime
def str_to_datetime(x):
try:
a = datetime.datetime.strptime(x, "%d-%m-%Y %H:%M:%S")
return a
except:
return np.NaN
过滤文字
# 过滤文字
def filter_word(df,feature):
# 过滤文字!!!!!!!!!!!!!!!!!!!!!!!!!!
# re.sub('[\u4E00-\u9FA5]+','NaN',x))
# df=df[df[feature]!='NaN']
df=df[df[feature].str.contains('\d')]
return df
过滤特殊字符
# 过滤特殊字符
def filter_spec_character(df,feature):
df[feature]=df[feature].apply(lambda x: re.sub('<|>|>|<|\+|\/|-|cm|kg','',str(x)))
return df
过滤异常大值
# 过滤异常大值
def filter_huge_value(df,feature):
# 过滤异常大值!!!!!!!!!!!!!!!!!!!!!!!!!!
median_value=df[feature].median()
df[feature]=df[feature].apply(lambda x: np.nan if x<0 else x if float(x) <= 100 * median_value else 100*median_value)
df=df[df[feature].notnull()]
return df
随机森林插补
# 随机森林插补的值会变,这可能对模型效果有影响。
# 如果不能接受插补值变化,则将参数定死。
# 使用随机森林对缺失值进行插补
import pandas as pd
pd.set_option('mode.chained_assignment', None)
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV
def missing_value_interpolation(df,missing_list=[]):
df = df.reset_index(drop=True)
# 提取存在缺失值的列名
if not missing_list:
for i in df.columns:
if df[i].isnull().sum() > 0:
missing_list.append(i)
missing_list_copy = missing_list.copy()
# 用该列未缺失的值训练随机森林,然后用训练好的rf预测缺失值
for i in range(len(missing_list)):
name=missing_list[0]
df_missing = df[missing_list_copy]
# 将其他列的缺失值用0表示。
missing_list.remove(name)
for j in missing_list:
df_missing[j]=df_missing[j].astype('str').apply(lambda x: 0 if x=='nan' else x)
df_missing_is = df_missing[df_missing[name].isnull()]
df_missing_not = df_missing[df_missing[name].notnull()]
y = df_missing_not[name]
x = df_missing_not.drop([name],axis=1)
# 列出参数列表
tree_grid_parameter = {'n_estimators': list((10, 50, 100, 150, 200))}
# 进行参数的搜索组合
grid = GridSearchCV(RandomForestRegressor(),param_grid=tree_grid_parameter,cv=3)
#rfr=RandomForestRegressor(random_state=0,n_estimators=100,n_jobs=-1)
#根据已有数据去拟合随机森林模型
grid.fit(x, y)
rfr = RandomForestRegressor(n_estimators=grid.best_params_['n_estimators'])
# 定死随机森林参数,这样插补值不会变。
#rfr = RandomForestRegressor(n_estimators=300,
# random_state=3)
rfr.fit(x, y)
#预测缺失值
predict = rfr.predict(df_missing_is.drop([name],axis=1))
#填补缺失值
df.loc[df[name].isnull(),name] = predict
return df
1.1 用药原始数据doctor_order预处理
# 原始数据集预处理:调整时间格式;异常值删除需具体到特定药,最好不要笼统的删,因为此时剂量单位不统一;
# 而日剂量的计算和剂量单位统一,在具体到特定药后更简单
## 用药原始数据doctor_order处理
df_doctor_order=pd.read_csv(project_path+'/data/raw_data/2-doctor_order.csv')
print(df_doctor_order.shape)
print(df_doctor_order['patient_id'].nunique())
print(df_doctor_order['case_no'].nunique())
# 提取长期医嘱
df_doctor_order=df_doctor_order[df_doctor_order['long_d_order']=='长期医嘱']
print(df_doctor_order.shape)
print(df_doctor_order['patient_id'].nunique())
print(df_doctor_order['case_no'].nunique())
# 提取用药状态为停止的用药
df_doctor_order=df_doctor_order[df_doctor_order['statusdesc']=='停止']
print(df_doctor_order.shape)
print(df_doctor_order['patient_id'].nunique())
print(df_doctor_order['case_no'].nunique())
# 删除end_datetime为空的数据
df_doctor_order=df_doctor_order[df_doctor_order['end_datetime'].notnull()]
print(df_doctor_order.shape)
print(df_doctor_order['patient_id'].nunique())
print(df_doctor_order['case_no'].nunique())
# 并删除服药方式为“取药用”的样本
df_doctor_order=df_doctor_order[df_doctor_order['medication_way']!='取药用']
print(df_doctor_order.shape)
print(df_doctor_order['patient_id'].nunique())
print(df_doctor_order['case_no'].nunique())
# 删除用药剂量为空的数据
df_doctor_order=df_doctor_order[(df_doctor_order['dosage'].astype('str').notnull()) & (df_doctor_order['dosage'].astype('str')!='nan')]
df_doctor_order=df_doctor_order.reset_index(drop=True)
print(df_doctor_order.shape)
print(df_doctor_order['patient_id'].nunique())
print(df_doctor_order['case_no'].nunique())
# 删除重复数据
df_doctor_order=df_doctor_order.drop_duplicates(subset=['patient_id','case_no','drug_name','dosage','frequency','start_datetime','end_datetime'],keep='first')
df_doctor_order=df_doctor_order.reset_index(drop=True)
print(df_doctor_order.shape)
print(df_doctor_order['patient_id'].nunique())
print(df_doctor_order['case_no'].nunique())
# 提取doctor_order里面的有效字段
df_doctor_order=df_doctor_order[['patient_id','case_no','long_d_order','drug_name','amount','drug_spec','dosage','frequency','start_datetime','end_datetime']]
# 调整doctor_order开始服药时间和结束服药时间格式
df_doctor_order['start_datetime']=df_doctor_order['start_datetime'].apply(str_to_datetime)
df_doctor_order['end_datetime']=df_doctor_order['end_datetime'].apply(str_to_datetime)
print(df_doctor_order.shape)
print(df_doctor_order['patient_id'].nunique())
print(df_doctor_order['case_no'].nunique())
# 保存预处理后的原始用药数据doctor_order
writer=pd.ExcelWriter(project_path+'/data/pre_processed_raw_data/df_doctor_order.xlsx')
df_doctor_order.to_excel(writer)
writer.save()
# 计算用药时长
aaa=df_SUP_drug[df_SUP_drug['用药结束时间'].isnull()]
bbb=df_SUP_drug[df_SUP_drug['用药结束时间'].notnull()]
aaa['用药结束时间']=aaa['用药开始时间']
df_SUP_drug=pd.concat([aaa,bbb],axis=0)
df_SUP_drug=df_SUP_drug.sort_values(by=['受试者编号','用药开始时间'])
df_SUP_drug=df_SUP_drug.reset_index(drop=True)
df_SUP_drug['用药天数']=(pd.to_datetime(df_SUP_drug['用药结束时间']) - pd.to_datetime(df_SUP_drug['用药开始时间']))
df_SUP_drug['用药天数']=df_SUP_drug['用药天数'].apply(lambda x:x.days+1)
df_SUP_drug.to_excel(project_path+'/data/processed_data/df_1.3.4_用药时长.xlsx')
1.2 诊断原始数据diagnostic预处理
## 诊断原始数据diagnostic处理
df_diagnostic=pd.read_csv(project_path+'/data/raw_data/3-diagnostic_record.csv',dtype={'case_no':str}) # dtype可以防止某一列因为pandas读取导致数据类型改变
print(df_diagnostic.shape)
print(df_diagnostic['patient_id'].nunique())
print(df_diagnostic['case_no'].nunique())
print(df_diagnostic)
# 删除诊断为空的数据
df_diagnostic=df_diagnostic[(df_diagnostic['diagnostic_content'].notnull())& (df_diagnostic['diagnostic_content'].astype('str')!='nan')]
print(df_diagnostic.shape)
print(df_diagnostic['patient_id'].nunique())
print(df_diagnostic['case_no'].nunique())
# 删除住院记录case_no为空的记录
df_diagnostic=df_diagnostic[(df_diagnostic['case_no'].notnull()) & (df_diagnostic['case_no'].astype('str')!='nan')]
df_diagnostic=df_diagnostic.reset_index(drop=True)
print(df_diagnostic.shape)
print(df_diagnostic['patient_id'].nunique())
print(df_diagnostic['case_no'].nunique())
print(df_diagnostic)
# 删除重复数据
df_diagnostic=df_diagnostic.drop_duplicates(subset=['patient_id','case_no','record_date','diagnostic_type','diagnostic_content'],keep='first')
df_diagnostic=df_diagnostic.reset_index(drop=True)
print(df_diagnostic.shape)
print(df_diagnostic['patient_id'].nunique())
print(df_diagnostic['case_no'].nunique())
# 调整diagnostic里面的时间格式
df_diagnostic['record_date']=df_diagnostic['record_date'].astype('str').apply(str_to_datetime)
# 提取diagnostic里面的有效字段
df_diagnostic=df_diagnostic[['patient_id','case_no','record_date','diagnostic_type','diagnostic_content']]
print(df_diagnostic)
# 保存预处理后的原始诊断数据diagnostic
writer=pd.ExcelWriter(project_path+'/data/pre_processed_raw_data/df_diagnostic.xlsx')
df_diagnostic.to_excel(writer)
writer.save()
1.3 检验原始数据test_record+test_result预处理
## 检验原始数据test_record+test_result处理
#%%
# 提取df_test,它是由rest_record和test_result合并而成,十分重要!!包含:tdm和安全性指标。
# 检测记录test_record
df_test_record=pd.read_csv(project_path+'/data/raw_data/4-test_record.csv',dtype={'case_no':str})
df_test_record=df_test_record[['test_record_id','patient_id','case_no','test_date','clinical_diagnosis']]
print(df_test_record.shape)
print(df_test_record['patient_id'].nunique())
print(df_test_record['case_no'].nunique())
# 删除test_date为空的记录
df_test_record=df_test_record[df_test_record['test_date'].notnull()]
print(df_test_record.shape)
print(df_test_record['patient_id'].nunique())
print(df_test_record['case_no'].nunique())
# 删除住院号case_no为空的记录
df_test_record=df_test_record[df_test_record['case_no'].notnull()]
df_test_record=df_test_record.reset_index(drop=True)
print(df_test_record.shape)
print(df_test_record['patient_id'].nunique())
print(df_test_record['case_no'].nunique())
# 删除test_record重复数据
df_test_record=df_test_record.drop_duplicates(subset=['test_record_id','patient_id','case_no','test_date'],keep='first')
df_test_record=df_test_record.reset_index(drop=True)
print(df_test_record.shape)
print(df_test_record['patient_id'].nunique())
print(df_test_record['case_no'].nunique())
# 调整检测时间格式
df_test_record['test_date']=df_test_record['test_date'].astype('str').apply(str_to_datetime)
print(df_test_record)
# 保存预处理后的test_record
writer=pd.ExcelWriter(project_path+'/data/pre_processed_raw_data/df_test_record.xlsx')
df_test_record.to_excel(writer)
writer.save()
# 检测结果test_result
df_test_result=pd.read_csv(project_path+'/data/raw_data/4-test_result.csv')
df_test_result=df_test_result[['test_record_id','project_name','test_result','refer_scope','synonym']]
print(df_test_result.shape)
# 删除检测项目project_name为空的数据
df_test_result=df_test_result[df_test_result['project_name'].notnull()]
print(df_test_result.shape)
# 删除test_result为空的数据
df_test_result=df_test_result[df_test_result['test_result'].notnull()]
df_test_result=df_test_result.reset_index(drop=True)
print(df_test_result.shape)
# 删除<>号
df_test_result['test_result']=df_test_result['test_result'].astype('str').apply(lambda x:x.replace('<',''))
df_test_result['test_result']=df_test_result['test_result'].astype('str').apply(lambda x:x.replace('>',''))
print(df_test_result)
# 删除test_result重复数据
df_test_result=df_test_result.drop_duplicates(subset=['test_record_id','project_name','test_result'],keep='first')
df_test_result=df_test_result.reset_index(drop=True)
print(df_test_result.shape)
# 保存预处理后的test_result,数据太大无法保存
# writer=pd.ExcelWriter(project_path+'/data/pre_processed_raw_data/df_test_result.xlsx')
# df_test_result.to_excel(writer)
# writer.save()
# 基于唯一性的test_record_id,合并test_record和test_result
df_test=pd.merge(df_test_record,df_test_result,on=['test_record_id'],how='inner')
print(df_test)
1.4 住院记录df_inp
# 从inp_record表读入数据
try:
sql = 'select * from inp_record;'
df_inp_record = pd.read_sql(sql, conn)
except MySQLDB.err.ProgrammingError as e:
print('surgical_record Error is ' + str(e))
sys.exit()
print(df_inp_record.shape)
print(df_inp_record['patient_id'].nunique())
#用来判断某列是否有缺失值
df_inp_record.isnull().any()
# 查看重复数据
df_inp_record[df_inp_record.duplicated(subset=['patient_id','adm_date'],keep='first')]
df_inp_record.to_excel(project_path+'/data/pre_processed_raw_data/df_inp_record.xlsx')
1.5 medical_record个人史既往史
# 从medical_record表读入数据
try:
sql = 'select * from medical_record_zs;'
df_medical_record = pd.read_sql(sql, conn)
except MySQLDB.err.ProgrammingError as e:
print('surgical_record Error is ' + str(e))
sys.exit()
print(df_medical_record.shape)
print(df_medical_record['patient_id'].nunique())
# 删除既往史为空的记录
df_medical_record=df_medical_record[df_medical_record['record_content'].notnull()]
df_medical_record=df_medical_record.reset_index(drop=True)
print(df_medical_record.shape)
print(df_medical_record['patient_id'].nunique())
writer=pd.ExcelWriter(project_path+'/data/pre_processed_raw_data/df_既往史.xlsx')
df_medical_record.to_excel(writer)
writer.save()
2. 纳排标准 <= 明确纳排基准patient_id,case_no
condition:目标变量限定、目标用药限定、目标患者限定
在实际项目中,可以先简单的拼接一下目标用药、目标变量(tdm)、目标患者,看一下数据分布
目标变量限定:tdm
# 提取用药后不良反应发生前的甲氨蝶呤血药浓度检测
df_test_VPA=df_test[df_test['project_name'].str.contains('丙戊酸|VPA')]
# 排序
df_test_VPA=df_test_VPA.sort_values(['patient_id','case_no','test_date'])
df_test_VPA=df_test_VPA.reset_index(drop=True)
df_test_VPA['tdm_date']=df_test_VPA['test_date'].apply(lambda x: str(x).split(' ')[0])
df_test_VPA['test_result']=df_test_VPA['test_result'].astype('float')
目标用药限定:提取服用利伐沙班的患者
# 1.1 服用利伐沙班且出院记录中有房颤的患者
print('-------------------------提取服用利伐沙班的患者------------------------------')
# 提取服药利伐沙班的患者id
df_lfsb=df_doctor_order[df_doctor_order['drug_name'].str.contains('利伐沙班')]
df_lfsb=df_lfsb.reset_index(drop=True)
# 排序
df_lfsb=df_lfsb.sort_values(['patient_id','case_no','start_datetime'],ascending=[True,True,True])
df_lfsb=df_lfsb.reset_index(drop=True)
print(df_lfsb.shape)
print(df_lfsb['patient_id'].nunique())
print(df_lfsb['case_no'].nunique())
# 同一case_no,取第一次用药记录
df_MTX = df_MTX.sort_values(by=['patient_id','case_no','start_datetime'])
df_MTX = df_MTX.drop_duplicates(subset=['patient_id','case_no'],keep='first')
#%%
# 保存利伐沙班用药记录
df_lfsb.to_excel((project_path+'/data/processed_data/df_1.1_利伐沙班用药记录.xlsx')
目标患者限定:明确患特定疾病的患者
# 从diagnostic_record中提取双相情感障碍
df_BD=df_diagnostic_record[df_diagnostic_record['diagnostic_content'].str.contains('双相')]
# 纳入双相情感障碍
df_VPA_BD=df_VPA[df_VPA['patient_id'].isin(df_BD['patient_id'].unique())]
#%% md
## 纳入: 提取出院诊断房颤患者
#%%
# 1.2 根据郑-诊断.xlsx,提取出院诊断房颤患者case_no,已进行合并纳入
print('-------------------------提取出院诊断房颤患者------------------------------')
df_oup_fib=df_diagnostic[(df_diagnostic['diagnostic_type']=='出院诊断') & (df_diagnostic['diagnostic_content'].str.contains(
'房颤射消融术后|心房扑动射频消融术后|心房颤动|阵发性心房颤动|持续性心房颤动|阵发性房颤|频发房性早搏|阵发性心房扑动|心房扑动|持续性房颤|房颤伴快速心室率\
|房颤射频消融术后|射频消融术后|快慢综合征|左心耳封堵术后|阵发性心房纤颤|心房颤动伴快速心室率|房颤|心房颤动射频消融术后|射频消融+左心耳封堵术后|左心耳封闭术后\
|心房颤动射频消融术后+左心耳封堵术|动态心电图异常:阵发性房颤、偶发房性早搏、偶发室性早搏、T波间歇性异常改变|左心房房颤射频消融+左心耳切除术后|永久性房颤\
|阵发性房颤射频消融术后|冷冻射频消融术后|心房颤动药物复律后'))]
df_oup_fib=df_oup_fib.sort_values(by=['patient_id','case_no','record_date'],ascending=[True,True,True])
df_oup_fib=df_oup_fib.reset_index(drop=True)
print(df_oup_fib.shape)
print(df_oup_fib['patient_id'].nunique())
print(df_oup_fib['case_no'].nunique())
print(df_oup_fib)
#%%
# 保存出院诊断房颤患者
writer=pd.ExcelWriter(project_path+'/data/processed_data/df_1.2_出院诊断房颤患者记录.xlsx')
df_oup_fib.to_excel(writer)
writer.save()
纳入疾病诊断diagnosis,通常有单独的诊断文件,可能在inp文件中。
print(type(df_lfsb.loc[0,'case_no']))
print(type(df_oup_fib.loc[0,'case_no']))
#%% md
## 合并利伐沙班用药和出院房颤诊断
#%%
# 调整利伐沙班用药的case_no格式
df_lfsb['case_no']=df_lfsb['case_no'].astype('str')
# 出院诊断
df_oup_fib=df_oup_fib.drop(['patient_id'],axis=1)
#%%
df_lfsb_oup_fib=df_lfsb[df_lfsb['case_no'].isin(df_oup_fib.case_no.unique())]
#%%
print(df_lfsb_oup_fib.shape)
print(df_lfsb_oup_fib['patient_id'].nunique())
print(df_lfsb_oup_fib['case_no'].nunique())
#%%
print(df_lfsb_oup_fib)
排除:膜瓣置换手术
#%% md
## 排除:膜瓣置换手术和瓣膜性房颤
#%%
# 1.3 提取瓣膜性房颤患者:手术中有膜瓣置换、诊断中为瓣膜性房颤。
print('-------------------------排除房颤相关的手术-----------------------------')
# 根据郑-手术.xlsx,排除膜瓣置换手术
df_surgical_record=pd.read_csv(project_path+'/data/raw_data/1-surgical_record.csv')
# df_surgical_valve=df_surgical_record[df_surgical_record['surgery_name'].str.contains('心脏病损腔内消融术|心脏病损腔内冷冻消融术|心电生理测定(EPS)|左心耳堵闭术|左心耳切除术|左心封堵术')]
df_surgical_valve=df_surgical_record[df_surgical_record['surgery_name'].str.contains('瓣膜置换')]
print(df_surgical_valve.shape)
print(df_surgical_valve['patient_id'].nunique())
print(df_surgical_valve['case_no'].nunique())
print(df_surgical_valve)
#%%
# 排除瓣膜置换手术的case_no
df_lfsb_not_surgery=df_lfsb_oup_fib[~ df_lfsb_oup_fib['case_no'].isin(df_su