轻松学医疗保健分析(二)
原文:
annas-archive.org/md5/d23e06d989b2f11c724cc2cf921f15d7译者:飞龙
第六章:测量医疗质量
本章面向所有读者,旨在向你展示美国当前基于价值的项目下,医疗提供者是如何被评估和奖励/处罚的。我们将查看从网络下载的实际提供者数据示例,并使用 Python 对这些数据进行整理,以提取我们需要的信息。通过本章学习后,你将能够定位到感兴趣的项目中基于提供者的数据,并使用pandas进行操作,识别出表现优秀的提供者以及那些可能从分析解决方案中受益的提供者。
医疗措施简介
医疗措施是对提供者的护理活动进行的计算,旨在显示护理人员提供的质量水平。随着提供者越来越多地根据他们提供的服务质量而非数量获得奖励,医疗措施在决定哪些护理提供者会受到奖励或处罚方面发挥着越来越重要的作用。医疗保险和医疗补助服务中心(CMS)是美国的联邦级机构之一,发布标准化的医疗措施;此外,各州也会发布相应的措施。提供者使用自己患者的数据计算这些措施,然后将计算结果提交给发布机构进行审计。结果在一定程度上决定了提供者从机构获得的报销金额。
医疗中的典型措施通常是与护理质量相关的比率或百分比。一个措施通常由两个部分组成:分子和分母。分母是指在特定时间范围内,提供者所接诊的符合资格的患者群体或接诊次数的量化。确定分母通常需要通过应用纳入标准和/或排除标准来筛选整个提供者群体,进而得到所需的测量群体或接诊池。一旦确定了分母,分子便是根据分母中获得某些积极或消极结果或事件的项数来计算的。
这个结果或事件通常由基础和/或临床研究建议作为患者护理的推荐部分(或不良护理的标志)。最后,将分子除以分母得到最终的百分比。这个百分比可以单独使用,也可以与其他措施结合,融入更复杂的公式和加权方案,以确定总体质量分数。
例如,一个机构希望评估某州门诊设施提供的糖尿病护理质量,可以通过调查文献中的糖尿病护理建议开始制定衡量标准。除其他事项外,糖尿病患者应该每年接受多次足部检查(检查溃疡和神经损伤)和糖化血红蛋白(HgbA1c)检测(检查血糖是否升高)。为了计算分母,纳入标准是患者在过去一年中至少接受过一次糖尿病(ICD 编码)诊断。该机构只希望考虑标准的成人群体;因此,18 岁以下的儿童和 65 岁以上的老年人将被排除在外。一个诊所可能有 4,000 名患者,其中 500 人符合这些标准;那么 500 就是该指标的分母。接下来需要计算两个分子:
-
这些患者中,在过去一年中接受了至少三次足部检查的数量
-
这些患者中,在过去一年中接受了至少两次 HgbA1c 检测的数量
例如,假设我们诊所的数字分别为 350 和 400。最终的指标是 350/500 = 0.70,代表糖尿病足部检查的表现,400/500 = 0.80,代表糖尿病血液检查的表现。然后,这些可以平均得出 0.75,作为该诊所糖尿病护理的总体评分。
衡量标准存在一些问题;没有一个标准能够避免漏洞,这些漏洞允许提供者操控他们的衡量分数,而不真正提高护理质量。此外,许多标准可能不公平地惩罚那些患者可能违背医疗建议或拒绝适当治疗的提供者。然而,如果要奖励护理质量,就必须有一种量化护理质量的方法,而在医疗领域,衡量标准是实现这一目标的重要手段。
美国医疗保险基于价值的项目
在第二章《医疗基础》中,我们讨论了按服务项目付费(FFS)报销模式在医学中的应用,在这种模式下,医生根据提供的护理量而非护理的价值获得报酬。最近,有一个推动趋势,旨在根据护理质量而非护理数量来奖励提供者。
为了促进从按服务项目付费(FFS)到基于价值的报销模式的转变,CMS 实施了基于价值的项目。这些项目根据医疗服务提供者为医疗保险患者提供的护理质量进行奖励或惩罚。2018 年,共有八个此类项目,具体如下:
-
医院基于价值的采购(HVBP)项目
-
医院再入院减少(HRR)项目
-
医院获得性疾病(HAC)项目
-
终末期肾病(ESRD)质量倡议项目
-
熟练护理设施基于价值的项目 (SNFVBP)
-
居家健康价值基础项目(HHVBP)
-
替代支付模型(APMs)
-
基于绩效的激励支付系统(MIPS)
在接下来的部分中,我们将详细介绍这些项目。
医院基于价值的采购(HVBP)项目
HVBP 项目根据医院提供的医疗服务质量对其进行奖励,激励支付给接受医保患者的医院。HVBP 项目通过 2010 年的《平价医疗法案》设立,并于 2012 年开始实施。
领域与测量标准
在 2018 年,HVBP 项目大约包含 20 项测量标准,涵盖医院护理质量的四个不同领域。该列表不断扩展,到 2023 年预计将包括约 25 项测量标准。让我们在此看看每个领域和测量标准。
临床护理领域
临床护理领域通过使用死亡率等指标来主要评估临床护理质量。死亡率指特定疾病患者的死亡率。该领域使用了五项死亡率测量指标(列举如下)。第六项测量标准是全髋关节/膝关节置换(即关节置换)手术的并发症率:
-
MORT-30-AMI:急性心肌梗死患者的 30 天死亡率
-
MORT-30-HF:心力衰竭患者的 30 天死亡率
-
MORT-30-PN:肺炎患者的 30 天死亡率
-
THA/TKA:发生并发症的全髋关节置换/全膝关节置换手术数量
-
MORT-30-COPD:慢性阻塞性肺病(COPD)患者的 30 天死亡率
-
MORT-30-CABG:接受冠状动脉旁路移植手术(CABG)的患者 30 天死亡率
以患者和护理人员为中心的护理体验领域
以患者和护理人员为中心的护理体验领域的测量标准是通过医院消费者评估医疗服务和系统(HCAHPS)调查获得的。HCAHPS 调查在患者出院后不久,对随机抽取的医保患者进行。超过十个问题集中在以下八个测量指标上:
-
与护士的沟通
-
与医生的沟通
-
医院员工的响应性
-
药物沟通
-
医院环境的清洁度和安静度
-
出院信息
-
医院整体评分
-
三项护理过渡
安全领域
该领域的测量标准评估医院的安全性,特别是不良事件和院内感染等问题。该领域的所有测量标准都将在后续的 HAC 项目部分中描述(PC-01 测量标准除外,具体描述如下):
-
AHRQ 复合指标(PSI-90):有关详细描述,请参见 HAC 项目部分。
-
导尿管相关尿路感染(CAUTI):有关详细描述,请参见 HAC 项目部分。
-
中心静脉导管相关血流感染(CLABSI):有关详细描述,请参见 HAC 项目部分。
-
艰难梭状芽孢杆菌感染(CDI):有关详细描述,请参阅 HAC 计划部分。
-
耐甲氧西林金黄色葡萄球菌感染(MRSA):有关详细描述,请参阅 HAC 计划部分。
-
手术部位感染(SSI):有关详细描述,请参阅 HAC 计划部分。
-
PC-01 – 39 周未满时的选择性分娩:指南建议妊娠尽可能接近 40 周时分娩。
效率和成本削减领域
本领域的四项指标检查与每家医院相关的护理成本。其中一项指标(MSPB)与每位患者的总体支出有关;其余三项指标涉及三种特定疾病的支出:
-
每位受益人 Medicare 支出(MSPB)
-
急性心肌梗死(AMI)支付
-
心力衰竭(HF)支付
-
肺炎(PN)支付
医院再入院减少(HRR)计划
测量住院病人护理质量的另一种方式是通过使用住院病人初次(起始)就诊时诊断为特定疾病的患者的再入院率。如果病人在医院获得了针对这些特定疾病的适当护理,那么预期再入院率应该等于或低于可接受的水平。高于基准再入院率的医院将面临较低的赔偿。因此,HRR 计划于 2012 年启动。该计划为减少住院病人在 30 天内因以下疾病再入院率的医院提供激励支付(最高可达其来自 Medicare 的住院支付的 3%):
-
急性心肌梗死(AMI)
-
心力衰竭(HF)
-
肺炎(PN)
-
慢性阻塞性肺疾病(COPD)
-
全髋/ 膝关节置换术(THA/ TKA)
-
冠状动脉旁路移植手术(CABG)
医院获得性疾病(HAC)计划
衡量住院病人护理质量的另一种方法是考虑该医院发生的院内感染或医源性疾病的数量。医源性疾病是指由医学检查或治疗引起的疾病,而院内感染则是指源自医院的疾病(通常是感染)。院内感染通常对多种抗生素具有耐药性,且相当难以治疗。
在 2014 年启动的 HACRP 计划下,如果医院的患者感染医院获得性感染的风险较高,则医院将面临总 Medicare 支付的 1%的处罚。更具体地说,医院如果符合特定得分阈值(基于患者发生五种常见医院获得性感染的频率以及其 AHRQ 患者安全指标(PSI)90 综合指标的表现),将有资格获得 Medicare 报销的 1%的减少。
HAC 项目包括六项措施,涵盖了两大护理领域。六项措施中有五项与医院患者的感染率相关。第六项措施是一个综合性指标,评估各种不利的患者安全事件。
我们现在将更详细地了解这些领域和措施。
医疗获得性感染领域
以下是五种医疗获得性感染:
-
导尿管相关尿路感染 (CAUTI):CAUTI 发生在使用不当(无菌)技术将尿管插入尿道时,导致细菌在尿路中繁殖。
-
中心静脉导管相关血流感染 (CLABSI):类似地,当中心静脉导管不当插入体内时,细菌便可进入血液并定植(败血症)。
-
艰难梭状芽孢杆菌感染 (CDI):住院治疗的病人在抗生素治疗后,原本的肠道菌群被消除,从而容易受到艰难梭状芽孢杆菌的感染,细菌在肠道内定植。医疗人员的卫生条件差和手部洗净不当是额外的风险因素。
-
耐甲氧西林金黄色葡萄球菌(MRSA)感染:MRSA 是一种常见且特别具有毒性的金黄色葡萄球菌株,通常感染皮肤和血液,并且对多种抗生素具有耐药性。它常常在医院传播,通过快速治疗和护理可以避免传播。
-
手术部位感染(SSI):由于手术过程中或术后灭菌技术不当,导致伤口或手术部位感染。
患者安全领域
PSI 90 是由医疗研究与质量局(AHRQ)发布的患者安全/并发症指标。2017 年,它通过 10 项指标衡量了医院的患者安全和并发症发生率:
-
PSI 03:压疮发生率:压疮是由于病人长时间保持同一姿势卧床所形成的皮肤损伤。它常常被用作衡量医院护理质量/忽视情况的指标。
-
PSI 06:医源性气胸发生率:气胸是指肺壁破裂,导致空气积聚在肺部周围的腔隙中,从而妨碍患者正常呼吸。有些气胸是由医院手术引起的,这些被称为医源性气胸。
-
PSI 08:住院跌倒伴髋部骨折发生率:跌倒在老年患者中常见,尤其是在手术或治疗后。采取一些预防措施可以防止患者跌倒,未能做到这一点的医院常被认为提供了低质量的护理。
-
PSI 09:围手术期出血或血肿发生率:此指标衡量患者在医院接受手术时发生过量出血的情况。
-
PSI 10: 术后急性肾损伤率:在手术或操作后,患者因血流减少或 X 光对比剂的影响而面临肾脏损伤的风险。
-
PSI 11: 术后呼吸衰竭发生率:手术后,患者可能出现呼吸衰竭,这是一种危及生命的病症,需要将患者置于麻醉状态下的呼吸机上,并在重症监护病房(ICU)进行持续监护。通过指导患者进行正确的呼吸练习,可以减少呼吸衰竭事件的发生。
-
PSI 12: 术后肺栓塞(PE)或深静脉血栓(DVT)发生率:深静脉血栓是在下肢肌肉的静脉中形成的血块。肺栓塞是指血块通过血液循环进入肺部,导致生命威胁的并发症。许多 DVT 可以通过在住院期间使用肝素和其他治疗方法,鼓励患者保持活动来预防。
-
PSI 13: 术后脓毒症发生率:该指标衡量了接受手术的患者术后发生感染的频率。脓毒症是一种危及生命的病症,表现为细菌感染血液,影响器官功能。
-
PSI 14: 术后伤口裂开率:伤口裂开是指手术部位未能正确闭合或愈合,通常是手术操作不当或术后营养不良的表现。
-
PSI 15: 未识别的腹腔/盆腔意外穿刺/撕裂率:此指标衡量在腹部或盆腔手术过程中,意外穿刺/撕裂发生的频率。
更多信息请访问www.qualityindicators.ahrq.gov/News/PSI90_Factsheet_FAQ.pdf。
终末期肾病(ESRD)质量激励计划
ESRD 质量激励计划衡量了 Medicare ESRD 患者在透析中心接受的护理质量。共有 16 项指标:11 项临床指标和 5 项报告指标,具体如下:
-
NHSN 血流感染在血液透析门诊患者中的发生率:不当的消毒技术可能导致血液透析时发生感染。该指标通过对比实际发生的感染数(分子)和预期感染数(分母),来评估感染的发生情况。
-
ICH CAHPS:该指标通过评估患者问卷调查反馈,审视透析中心提供护理的质量。
-
标准化再入院率:标准化再入院率是实际发生的非计划性 30 天再入院次数与预期非计划性 30 天再入院次数的比值。
-
Kt/V 透析充分性措施 – 血液透析:Kt/V 是一个公式,用于量化透析治疗的充分性。四项 Kt/V 措施检查有多少治疗会话符合不同透析患者群体的 Kt/V 阈值:
-
Kt/V 透析充分性措施 – 腹膜透析
-
Kt/V 透析充分性措施 – 儿童血液透析
-
Kt/V 透析充分性措施 – 儿童腹膜透析
-
-
标准化输血比率:此措施比较透析患者中实际与预期的红细胞输血数量(输血是透析的不良后果)。
-
血管通路 – 动静脉瘘:血管通路措施量化了是否为患者提供了适当的血液通路。动静脉瘘措施评估了使用两根针头进行血液通路的动静脉瘘部位数量。
-
血管通路 – 导管:导管措施确定有多少导管在患者体内存在超过 90 天,这是一种感染风险。
-
高钙血症:该措施衡量患者经历高钙血症的月数,这是一种透析的不良反应。
-
矿物质代谢报告:这五项报告措施检查每个设施如何报告透析患者护理的各个方面。措施包括矿物质代谢报告、贫血管理报告、疼痛评估、抑郁症筛查和个人流感疫苗接种报告:
-
贫血管理报告
-
疼痛评估和跟踪报告
-
临床抑郁症筛查和跟踪报告
-
个人流感疫苗接种报告
-
熟练护理设施价值导向项目(SNFVBP)
SNFVBP 计划定于 2019 年开始。该计划将基于两项与结果相关的措施,部分决定政府向 SNF 支付的医疗保险报销:
-
30 天内全因再入院率
-
30 天内可能可预防的再入院率
这些标准适用于入住 SNF 的居民,且他们已被转院到其他医院。当该项目启动时,SNF 可能会通过与机器学习专家合作,预测哪些患者有再入院风险,从而获益。
有关 SNFVBP 的更多信息,请访问以下链接:www.cms.gov/Medicare/Quality-Initiatives-Patient-Assessment-Instruments/Value-Based-Programs/Other-VBPs/SNF-VBP.html。
家庭健康价值导向项目(HHVBP)
HHVBP 计划于 2016 年 1 月在美国 50 个州中的 9 个州启动。它根据护理质量向获得 Medicare 认证的家庭健康机构 (HHAs)提供支付调整。该项目将使用 22 项措施来评估 HHAs 提供的护理质量。这些措施包括调查、过程和结果措施,并包括急诊利用和非计划性住院的相关措施。
优质激励支付系统(MIPS)
MIPS 是一个面向个体和团体门诊医师实践的基于价值的项目。该项目始于 2017 年,并通过 2015 年的《MACRA 法案》实施。与 APMs 项目一起,MIPS 构成了 Medicare 的质量支付项目(QPP)。它替代并整合了之前的基于价值的项目,如医师质量报告系统(PQRS)和**价值调整(VM)**项目。如果提供者的账单金额或 Medicare 患者数量达到一定要求,必须参与 MIPS。在 MIPS 中,提供者根据四个类别进行评估:
-
质量
-
推进护理信息
-
改善活动
-
成本
2017 年,确定实践最终 MIPS 得分的细分标准如下:60%为质量,25%为推进护理信息,15%为改善活动。从 2018 年开始,成本也将影响最终的 MIPS 得分。
让我们详细了解这四个绩效类别。
质量
在质量类别中,提供者从包含 271 项措施的列表中选择六项措施(截至 2018 年)。措施的例子包括急性外耳道炎(耳部感染):局部治疗和静脉曲张治疗伴大隐静脉消融:结果调查。所有医学专业都有代表,提供者可以选择最适合自己的措施。然后,提供者根据措施的规范收集并提交相关数据。
推进护理信息
这一类别包括与推进健康信息技术相关的措施。此类别包含 15 项措施。措施的例子包括核对患者信息、向数据注册处报告临床数据,以及电子处方。
改善活动
在这一类别中,提供者必须证明他们已采取措施来改善实践中的护理协调、患者参与和患者安全等方面。提供者必须证明他们在 3 个月内完成了最多四项措施。
成本
对于最终类别,护理成本将从索赔数据中确定,提供者提供最具效率的护理将获得奖励。该类别将从 2018 年开始计入 MIPS 最终得分。
其他基于价值的项目
除了上述由 CMS 管理的基于价值的项目外,还有其他由其他机构管理的附加项目。我们来看看这些项目。
医疗效果数据与信息集(HEDIS)
HEDIS 用于衡量健康保险计划的质量,由国家质量保证委员会(NCQA)管理。HEDIS 包括约 90 个衡量标准,涵盖几乎所有医学专业。许多衡量标准与之前讨论过的指标,或者与 MIPS 临床护理类别中的 271 个衡量标准有相似之处。
州级指标
在 2018 年,几乎每个州都有某种形式的基于价值的程序和激励措施。通常,这些计划适用于 Medicaid 患者,因为 Medicaid 通常由州级管理。许多州也采用联邦发布的指标,并对其进行调整以适应自己的需求。例如,乔治亚州资助了 Georgia Families 计划(dch.georgia.gov/georgia-families),该计划允许乔治亚州的 Medicaid 患者选择健康保险计划。它通过使用 HEDIS 指标设定目标并衡量效果。
使用 Python 比较透析设施
在前一节中,我们概述了 CMS 实施的基于价值的激励计划。其中一个计划是 ESRD 质量激励计划,该计划根据透析设施为患有 ESRD 的 Medicare 患者提供的护理质量进行财务奖励。我们描述了 16 项评估每个 ESRD 病例的指标。
在本节中,我们将下载 CMS 发布的关于美国透析中心绩效的数据。我们将使用 Python 命令处理这些数据,以提取我们可以用来找出哪些中心表现良好,哪些中心可能从分析解决方案中受益的信息。通过适当的市场营销和销售努力的精准定位,将提高分析解决方案的效率。
下载数据
要下载透析设施对比数据,请完成以下步骤。
-
在页面上找到标有“DOWNLOAD CSV FLAT FILES (REVISED) NOW”的蓝色按钮。(为了获取正确的年份,你可能需要选择“GET ARCHIVED DATA”按钮。)点击该按钮,
.zip文件将开始下载。 -
使用适当的 Windows/Mac 程序或 Linux 命令解压
.zip文件。 -
注意文件名为
ESRD QIP - Complete QIP Data - Payment Year 2018.csv的目录和路径。
将数据导入到你的 Jupyter Notebook 会话中
要将.csv文件导入 Jupyter Notebook 会话,请像我们在第一章《医疗分析简介》中所做的那样打开 Jupyter Notebook 程序。打开一个新笔记本。然后,在第一个单元格中输入以下内容(用你的文件路径替换这里显示的路径),并点击播放按钮:
import pandas as pd
df = pd.read_csv(
'C:\\Users\\Vikas\\Desktop\\Bk\\Data\\DFCompare_Revised_FlatFiles\\' +
'ESRD QIP - Complete QIP Data - Payment Year 2018.csv', header=0
)
上述代码使用了pandas库的read_csv()函数将.csv文件导入为 DataFrame。header参数告诉笔记本该文件的第一行包含列名。
注意到反斜杠是成对出现的。这是因为\是 Python 中的转义字符。同时注意到文件名太长,无法放在一行内。在 Python 中,只要换行由括号和其他特定标点符号包围,语句就可以跨越多行,而不需要特殊处理。
探索数据的行与列
让我们来探索数据。在下一个单元格中,输入以下内容:
print('Number of rows: ' + str(df.shape[0]))
print('Number of columns: ' + str(df.shape[1]))
输出结果如下:
Number of rows: 6825
Number of columns: 153
在 2018 年文件中,应有 6,825 行和 153 列。每一行对应美国的一个透析设施。这里我们使用了 DataFrame 的shape属性,它返回一个元组,包含行数和列数。
我们还可以通过使用head()函数来查看 DataFrame 的可视化效果。head()函数接受一个参数n,用于指定打印 DataFrame 的前几行。在下一个单元格中,输入以下内容并按下播放按钮:
print(df.head(n=5))
输出结果如下:
Facility Name CMS Certification Number (CCN) \
0 CHILDRENS HOSPITAL DIALYSIS 12306
1 FMC CAPITOL CITY 12500
2 GADSDEN DIALYSIS 12501
3 TUSCALOOSA UNIVERSITY DIALYSIS 12502
4 PCD MONTGOMERY 12505
...
你应该能够看到前五行中的一些列,如设施名称、地址和衡量得分。head()函数会打印出列的简要列表,从.csv文件的开始和结束处各选择一些列,并用省略号分隔。
让我们获取所有153列的完整列表。输入以下内容并按下Enter:
print(df.columns)
输出结果如下:
Index(['Facility Name', 'CMS Certification Number (CCN)', 'Alternate CCN 1',
'Address 1', 'Address 2', 'City', 'State', 'Zip Code', 'Network',
'VAT Catheter Measure Score',
...
'STrR Improvement Measure Rate/Ratio',
'STrR Improvement Period Numerator',
'STrR Improvement Period Denominator', 'STrR Measure Score Applied',
'National Avg STrR Measure Score', 'Total Performance Score',
'PY2018 Payment Reduction Percentage', 'CMS Certification Date',
'Ownership as of December 31, 2016', 'Date of Ownership Record Update'],
dtype='object', length=153)
这里,我们使用 DataFrame 的columns属性,它会以列表的形式提供 DataFrame 的列名。不幸的是,pandas又一次将输出进行了缩略,因此我们无法看到所有153列。为了查看所有列,我们需要更明确地使用for循环逐个打印每一列:
for column in df.columns:
print(column)
输出结果如下:
Facility Name
CMS Certification Number (CCN)
Alternate CCN 1
Address 1
Address 2
City
State
Zip Code
Network
VAT Catheter Measure Score
...
现在你将看到所有153列的名称。使用滚动条浏览所有列。你会发现每个 16 个衡量指标都与多个列相关联,此外还有像人口统计数据和总性能得分等附加列。
现在我们对数据集有了一个大致的概览,可以继续进行更深入的分析。
地理数据探索
在本节余下的部分,我们将使用许多类似 SQL 的操作来处理数据。这里是一些基本操作的 SQL 和pandas之间的转换表:
| 操作 | SQL 语法 | pandas函数 | SQL 示例 | pandas示例 |
|---|---|---|---|---|
| 选择列 | SELECT | [[]] | SELECT col1, col2, FROM df; | df[['col1','col2']] |
| 选择行 | WHERE | loc(), iloc() | SELECT * FROM df WHERE age=50; | df.loc[df['age']==50] |
| 按列排序 | ORDER BY | sort_values() | SELECT * FROM df ORDER BY col1; | df.sort_values('col1') |
| 按列聚合 | GROUP BY | groupby() | SELECT COUNT(*) FROM df GROUP BY col1; | df.groupby('col1').size() |
| 限制行数 | LIMIT | head() | SELECT * FROM df LIMIT 5; | df.head(n=5) |
考虑到这些转换,我们可以开始按地理位置探索数据。
首先,6,825 个透析设施已经是一个庞大的数量。让我们尝试通过州来缩小范围。首先,我们统计每个州的透析设施数量:
"""Equivalent SQL: SELECT COUNT(*)
FROM df
GROUP BY State;
"""
df_states = df.groupby('State').size()
print(df_states)
输出如下:
State
AK 9
AL 170
AR 69
AS 1
AZ 120
CA 625
CO 75
CT 49
DC 23
DE 27
...
你应该能看到一个包含 50 行的表格(每个州一行,每行显示相关的计数)。
现在让我们按降序对这些行进行排序:
"""Equivalent SQL: SELECT COUNT(*) AS Count
FROM df
GROUP BY State
ORDER BY Count ASC;
"""
df_states = df.groupby('State').size().sort_values(ascending=False)
print(df_states)
输出如下:
State
CA 625
TX 605
FL 433
GA 345
OH 314
IL 299
PA 294
NY 274
NC 211
MI 211
...
让我们进一步优化查询,将输出限制为 10 个州:
"""Equivalent SQL: SELECT COUNT(*) AS Count
FROM df
GROUP BY State
ORDER BY Count DESC
LIMIT 10;
"""
df_states = df.groupby('State').size().sort_values(ascending=False).head(n=10)
print(df_states)
根据结果,加利福尼亚州是拥有最多透析中心的州,其次是德克萨斯州。如果我们想要根据州来筛选透析设施,可以通过选择适当的行来实现:
"""Equivalent SQL: SELECT *
FROM df
WHERE State='CA';
"""
df_ca = df.loc[df['State'] == 'CA']
print(df_ca)
根据总表现显示透析中心
几乎所有对这种以提供者为中心的数据的探索都会包括根据质量得分分析设施。接下来我们将深入探讨这一点。
首先,让我们统计不同透析设施所获得的得分:
print(df.groupby('Total Performance Score').size())
输出如下:
Total Performance Score
10 10
100 30
11 2
12 2
13 1
14 3
15 1
...
95 15
96 2
97 11
98 8
99 12
No Score 276
Length: 95, dtype: int64
需要注意的一点是,Total Performance Score 列的格式是字符串而不是整数格式,因此为了进行数值排序,我们必须先将该列转换为整数格式。其次,在运行前面的代码后,你会注意到 276 个透析设施在 Total Performance Score 列中的值为 No Score。这些行在转换为整数格式之前必须被删除,以避免出现错误。
在以下代码中,我们首先删除了 No Score 行,然后使用 pandas 的 to_numeric() 函数将字符串列转换为整数列:
df_filt= df.loc[df['Total Performance Score'] != 'No Score']
df_filt['Total Performance Score'] = pd.to_numeric(
df_filt['Total Performance Score']
)
现在,我们创建一个新的 DataFrame,仅选择我们感兴趣的几个列并进行排序,将表现最差的中心排在最前面。例如,这样的代码块对识别表现最差的透析中心非常有帮助。我们展示前五个结果:
df_tps = df_filt[[
'Facility Name',
'State',
'Total Performance Score'
]].sort_values('Total Performance Score')
print(df_tps.head(n=5))
输出如下:
Facility Name State \
5622 462320 PRIMARY CHILDREN'S DIALYSIS CENTER UT
698 PEDIATRIC DIALYSIS UNIT AT UCSF CA
6766 VITAL LIFE DIALYSIS CENTER FL
4635 BELMONT COURT DIALYSIS - DOYLESTOWN CAMPUS PA
3763 WOODMERE DIALYSIS LLC NY
Total Performance Score
5622 5
698 7
6766 8
4635 8
3763 9
另外,如果我们希望分析每个州透析中心的平均总表现,可以通过结合使用 numpy.mean() 和 groupby() 来实现:
import numpy as np
df_state_means = df_filt.groupby('State').agg({
'Total Performance Score': np.mean
})
print(df_state_means.sort_values('Total Performance Score', ascending=False))
输出如下:
Total Performance Score
State
ID 73.178571
WY 71.777778
HI 70.500000
UT 70.421053
CO 70.173333
WA 70.146067
ME 70.058824
OR 70.046154
KS 69.480769
AZ 68.905983
...
根据这个查询的结果,爱达荷州和怀俄明州的透析中心表现最好。你也可以通过以下修改添加一列,显示每个州透析中心的数量:
import numpy as np
df_state_means = df_filt.groupby('State').agg({
'Total Performance Score': np.mean,
'State': np.size
})
print(df_state_means.sort_values('Total Performance Score', ascending=False))
输出如下:
Total Performance Score State
State
ID 73.178571 28
WY 71.777778 9
HI 70.500000 26
UT 70.421053 38
CO 70.173333 75
WA 70.146067 89
ME 70.058824 17
OR 70.046154 65
KS 69.480769 52
AZ 68.905983 117
...
结果表明,在只考虑拥有至少 100 个透析中心的州时,亚利桑那州的总表现最佳。
对透析中心的替代分析
本节中介绍的代码可以调整用于对透析中心进行各种不同类型的分析。例如,您可能希望根据透析中心所有者来衡量平均表现,而不是按 State 来衡量。这可以通过在最近的示例中更改分组的列来实现。或者,您也许想查看单个指标,而不是 Total Performance Score,这同样可以通过仅更改代码中的一列来完成。
现在,我们已经使用透析中心分析了服务提供商的表现,接下来我们将查看一个更复杂的数据集——住院医院表现数据集。
比较医院
在前面的示例中,我们使用 Python 分析了透析中心的表现。透析中心只是医疗服务提供者池中的一小部分——该池还包括医院、门诊诊所、疗养院、住院康复设施和临终关怀服务等。例如,当您从data.medicare.gov下载透析设施比较数据时,您可能会注意到这些其他设施的表现数据。现在我们将研究一个更复杂的设施类型:住院医院。医院比较数据集包含了 CMS 基于价值的三个项目的数据。它是一个庞大的数据集,我们将使用这些数据展示一些高级的 Python 和 pandas 特性。
下载数据
要下载医院比较数据集,请完成以下步骤:
-
在页面上找到标有“DOWNLOAD CSV FLAT FILES (REVISED) NOW” 的蓝色按钮。(如果要获取正确的年份,您可能需要选择“GET ARCHIVED DATA”按钮)。点击该按钮,
.zip文件将开始下载。 -
使用适当的 Windows/Mac 程序或 Linux 命令提取
.zip文件。 -
请注意包含已提取
.csv文件的路径。
将数据导入到您的 Jupyter Notebook 会话中
请注意,提取的医院比较文件夹包含 71 个文件,其中绝大多数是 .csv 文件。这是很多表格!让我们将一些表格导入到 Jupyter Notebook 中:
import pandas as pd
pathname = 'C:\\Users\\Vikas\\Desktop\\Bk\\Data\\Hospital_Revised_Flatfiles\\'
files_of_interest = [
'hvbp_tps_11_07_2017.csv',
'hvbp_clinical_care_11_07_2017.csv',
'hvbp_safety_11_07_2017.csv',
'hvbp_efficiency_11_07_2017.csv',
'hvbp_hcahps_11_07_2017.csv'
]
dfs = {
foi: pd.read_csv(pathname + foi, header=0) for foi in files_of_interest
}
上面的代码将与 HVBP 测量相关的表格加载到 Python 会话中。共有五个表格,其中四个表格对应该测量的四个领域,一个表格代表整体评分。
请注意,替代显式地创建和导入五个数据框,我们使用列表推导式创建了一个数据框字典。我们在 Python 章节中已经讲解了字典、列表和推导式。这在当前和即将到来的单元中节省了大量的输入工作。
探索表格
接下来,让我们探索表格,并检查每个表格中的行和列数:
for k, v in dfs.items():
print(
k + ' - Number of rows: ' + str(v.shape[0]) +
', Number of columns: ' + str(v.shape[1])
)
输出结果如下:
hvbp_tps_11_07_2017.csv - Number of rows: 2808, Number of columns: 16
hvbp_clinical_care_11_07_2017.csv - Number of rows: 2808, Number of columns: 28
hvbp_safety_11_07_2017.csv - Number of rows: 2808, Number of columns: 64
hvbp_efficiency_11_07_2017.csv - Number of rows: 2808, Number of columns: 14
hvbp_hcahps_11_07_2017.csv - Number of rows: 2808, Number of columns: 73
在前一个单元中,我们使用了字典的items()方法来遍历字典中的每个键-数据框对。
所有的表格都有相同的行数。由于每一行都对应着一个医院,因此可以安全地假设所有表格中的医院是相同的(我们稍后将验证这一假设)。
由于表格之间的分离,任何我们进行的分析都有其局限性。由于所有医院都是(假设)相同的,我们可以将所有列合并为一个表格。我们将使用pandas的merge()函数来实现这一点。使用pandas的merge()类似于使用SQL JOIN(你在第四章中学习了SQL JOIN,计算基础 – 数据库)。合并通过指定两个表格中共有的 ID 列来进行,这样就可以根据该列匹配行。为了查看五个 HVBP 表格中是否有共同的 ID 列,我们可以打印出每个表格的列名:
for v in dfs.values():
for column in v.columns:
print(column)
print('\n')
如果你浏览结果,你会注意到所有表格中都有Provider Number列。Provider Number是一个独特的标识符,可以用来链接这些表格。
合并 HVBP 表格
让我们尝试合并两个表格:
df_master = dfs[files_of_interest[0]].merge(
dfs[files_of_interest[1]],
on='Provider Number',
how='left',
copy=False
)
print(df_master.shape)
输出如下:
(2808, 43)
我们的合并操作似乎成功了,因为df_master中的列数是前两个数据框列数的总和,减去一(on列没有被复制)。我们来看一下新数据框的列:
print(df_master.columns)
输出如下:
Index(['Provider Number', 'Hospital Name_x', 'Address_x', 'City_x', 'State_x',
'Zip Code', 'County Name_x',
'Unweighted Normalized Clinical Care Domain Score',
'Weighted Normalized Clinical Care Domain Score',
'Unweighted Patient and Caregiver Centered Experience of Care/Care Coordination Domain Score',
'Weighted Patient and Caregiver Centered Experience of Care/Care Coordination Domain Score',
'Unweighted Normalized Safety Domain Score',
'Weighted Safety Domain Score',
'Unweighted Normalized Efficiency and Cost Reduction Domain Score',
'Weighted Efficiency and Cost Reduction Domain Score',
'Total Performance Score', 'Hospital Name_y', 'Address_y', 'City_y',
'State_y', 'ZIP Code', 'County Name_y',
'MORT-30-AMI Achievement Threshold', 'MORT-30-AMI Benchmark',
'MORT-30-AMI Baseline Rate', 'MORT-30-AMI Performance Rate',
'MORT-30-AMI Achievement Points', 'MORT-30-AMI Improvement Points',
'MORT-30-AMI Measure Score', 'MORT-30-HF Achievement Threshold',
'MORT-30-HF Benchmark', 'MORT-30-HF Baseline Rate',
'MORT-30-HF Performance Rate', 'MORT-30-HF Achievement Points',
'MORT-30-HF Improvement Points', 'MORT-30-HF Measure Score',
'MORT-30-PN Achievement Threshold', 'MORT-30-PN Benchmark',
'MORT-30-PN Baseline Rate', 'MORT-30-PN Performance Rate',
'MORT-30-PN Achievement Points', 'MORT-30-PN Improvement Points',
'MORT-30-PN Measure Score'],
dtype='object')
重复的列(Hospital Name、Address、City等)在合并后的表格中添加了后缀_x和_y,以指示它们来自哪个表格,确认了合并操作成功。
让我们使用一个for循环将其余的三个表格与df_master合并:
for df in dfs.values():
df.columns = [col if col not in ['Provider_Number'] else 'Provider Number'
for col in df.columns]
for num in [2,3,4]:
df_master = df_master.merge(
dfs[files_of_interest[num]],
on='Provider Number',
how='left',
copy=False
)
print(df_master.shape)
输出如下:
(2808, 191)
在这一单元中,首先我们使用一个循环将所有列名从Provider_Number重命名为Provider Number,以便我们可以清晰地连接表格。
然后我们使用一个循环将每个剩余的表格与df_master合并。最终表格中的列数等于所有表格的列数总和,减去四。
为了确认合并是否成功,我们可以打印出新表格的列:
for column in df_master.columns:
print(column)
滚动输出确认了五个表格中的所有列都已包含。
我们将留给你使用比较透析设施部分的代码示例,进行更多的分析。
总结
在本章中,我们回顾了一些当今塑造美国医疗行业的突出基于价值的项目。我们已经看到这些项目如何通过使用度量标准来量化提供者的表现。此外,我们下载了用于比较透析设施和医院的数据,并通过一些 Python 代码示例来展示如何分析这些数据。
有人可能会争辩,书中这一章的分析可以通过使用诸如 Microsoft Excel 之类的电子表格应用程序来完成,而不必编程。在第七章,在医疗保健中构建预测模型,我们将使用医疗保健数据集训练预测模型,以预测急诊科的出院状态。正如你将看到的,这种类型的分析几乎肯定需要编写代码。
参考文献
Data.Medicare.gov(2018)。于 2018 年 4 月 28 日访问自data.medicare.gov。
MIPS 概述(2018)。于 2018 年 4 月 28 日访问自qpp.cms.gov/mips/overview。
什么是基于价值的项目?(2017 年 11 月 9 日)。美国医疗保险和医疗补助服务中心。于 2018 年 4 月 28 日访问自www.cms.gov/Medicare/Quality-Initiatives-Patient-Assessment-Instruments/Value-Based-Programs/Value-Based-Programs.html。
第七章:在医疗领域构建预测模型
本章面向所有读者,是本书的核心内容之一。我们将通过示例数据和机器学习问题,演示如何为医疗领域构建预测模型。我们将一次处理一个特征的数据预处理。到本章结束时,你将理解如何准备并拟合一个机器学习模型到临床数据集。
医疗预测分析简介
在第一章《医疗分析简介》中,我们讨论了分析的三个子组成部分:描述性分析、预测性分析和规范性分析。预测性分析和规范性分析是医疗领域提升护理质量、降低成本和改善结果的核心。这是因为如果我们能够预测未来可能发生的不良事件,我们就可以将我们有限的资源转向预防这些不良事件的发生。
我们可以预测(并且随后预防)的医疗领域中的一些不良事件有哪些?
-
死亡:显然,任何可以预防或预测的死亡都应该避免。一旦预测到死亡的发生,预防措施可能包括将更多护士指派给该患者、为该案例聘请更多顾问,或尽早与家属沟通可选方案,而不是等到最后一刻。
-
不良临床事件:这些事件并不等同于死亡,但会大大增加发病率和死亡率的可能性。发病率指的是并发症,而死亡率则指死亡。不良临床事件的例子包括心脏病发作、心力衰竭加重、慢性阻塞性肺病(COPD)加重、肺炎和跌倒。那些可能发生不良事件的患者,可能是需要更多护理或预防性治疗的候选人。
-
再入院:再入院并不会直接对患者构成明显的威胁,但它们是昂贵的,因此应尽量避免可预防的再入院。此外,减少再入院是美国医疗保险和医疗补助服务中心(CMS)的一个重要激励目标,正如我们在第六章《衡量医疗质量》中看到的那样。预防性措施包括为高风险患者安排社会工作者和个案管理人员,确保他们在门诊提供者那里跟进,并购买所需的处方。
-
高利用率:预测那些可能再次发生大量医疗支出的患者,可能通过为他们的团队分配更多护理成员并确保频繁的门诊检查和随访,从而降低成本。
既然我们已经回答了“是什么?”的问题,下一个问题是,“怎么做?”换句话说,我们如何预测哪些护理提供者可以采取行动?
-
首先,我们需要数据:医疗服务提供者应当将其历史患者数据发送给你。数据可以是索赔数据、临床记录、电子健康记录(EHR)记录的导出,或这些数据的某种组合。无论数据类型如何,它最终应该能够转化为表格格式,其中每一行代表一个患者/就诊,每一列代表该患者/就诊的某个特征。
-
使用部分数据,我们训练一个预测模型:在第三章《机器学习基础》中,我们学习了在训练预测模型时到底在做什么,以及整个建模流程如何运作。
-
使用部分数据,我们测试模型的表现:评估我们模型的性能对于设定医疗服务提供者对模型准确性的预期非常重要。
-
然后,我们将模型部署到生产环境中,并为患者提供定期的实时预测:在这一阶段,应当有来自医疗服务提供者的数据定期流入到分析公司。公司随后会定期提供这些患者的预测结果。
在本章的剩余部分,我们将介绍如何构建医疗健康预测模型。首先,我们将描述我们的模拟建模任务。接着,我们将描述并获取公开可用的数据集。然后,我们将对数据集进行预处理,并使用不同的机器学习算法训练预测模型。最后,我们将评估我们模型的表现。虽然我们不会使用我们的模型对实时数据进行实际预测,但我们会描述实现这一目标所需的步骤。
我们的建模任务——预测急诊室患者的出院状态
每年,全国有数百万患者使用急诊科设施。这些设施的资源必须得到妥善管理——如果在某个时间点出现大量患者涌入,医院的工作人员和可用病房应当相应增加。资源与患者涌入之间的不匹配可能会导致浪费资金和提供不理想的护理。
在这个背景下,我们介绍了我们的示例建模任务——预测急诊室就诊患者的出院状态。出院状态指的是患者是被住院还是被送回家。通常,更严重的病例会被住院。因此,我们试图在患者住院期间尽早预测急诊就诊的结果。
使用这样的模型,医院的工作流程和资源流动都可以得到极大改善。许多先前的学术研究已经探讨了这个问题(例如,见 Cameron 等人,2015 年)。
你可能会想,为什么我们没有选择其他建模任务,比如再入院建模或预测充血性心力衰竭(CHF)加重。首先,公开可用的临床数据非常有限。我们选择的数据集是急诊科(ED)数据集;目前没有可以免费下载安装的住院数据集。尽管如此,我们选择的任务仍然能够展示如何构建预测性医疗模型。
获取数据集
在本节中,我们将提供逐步的说明,教你如何获取数据及其相关文档。
NHAMCS 数据集概览
我们为本书选择的数据集是 国家医院门诊医疗护理调查(NHAMCS)公开使用数据的一部分。该数据集由美国 疾病控制与预防中心(CDC)发布并维护。该数据集的主页是 www.cdc.gov/nchs/ahcd/ahcd_questionnaires.htm。
-
NHAMCS 数据是基于调查的数据;它通过向曾在医院就诊的病人和医疗服务提供者发送调查问卷进行收集。
-
数据文件为定宽格式。换句话说,它们是文本文件,每行在一个独立的行上,每列的字符数是固定的。每个特征的字符长度信息可以在相应的 NHAMCS 文档中找到。
-
数据文件分为不同的集,具体取决于数据是否来自门诊就诊或急诊科就诊。我们将在本章中使用急诊科格式。
-
数据附带了有关每个特征内容的详细文档。
-
每一行数据代表一次独立的急诊科病人就诊。
请参阅以下表格,了解我们将在本书中使用的 NHAMCS 急诊科数据文件的概要:
| 文件名 | 数据类型和年份 | 行数(就诊次数) | 列数(特征数量) | 广泛特征类别 |
|---|---|---|---|---|
| ED2013 | 急诊科就诊;2013 | 24,777 | 579 | 就诊日期和信息、人口统计学、烟草使用、到达方式、支付方式、生命体征、分诊、急诊科关系、就诊原因、伤害、诊断、慢性病、执行的服务、见诊的医务人员、处置、住院、插补数据、急诊科信息、社会经济数据 |
下载 NHAMCS 数据
原始数据文件和相关文档可以通过 CDC NHAMCS 主页访问:www.cdc.gov/nchs/ahcd/ahcd_questionnaires.htm(如下截图)。我们建议将所有文件下载到一个专门用于本书及其相关文件的目录中。此外,请记住文件下载到的目录位置:
下载 ED2013 文件
ED2013 文件包含原始数据。要下载它:
-
访问 CDC NHAMCS 主页:
www.cdc.gov/nchs/ahcd/ahcd_questionnaires.htm。 -
向下滚动页面,找到《公共数据文件:可下载数据文件》标题。
-
点击 NHAMCS 链接。它将把您带到 CDC 的 FTP 网站(ftp://ftp.cdc.gov/pub/Health_Statistics/NCHS/Datasets/NHAMCS)。该网站如下图所示。
-
找到名为
ED2013.zip的文件。点击它,文件将开始下载。 -
在文件资源管理器中找到该文件并解压。解压后的目录中,您应看到一个名为
ED2013的无扩展名文件。这就是数据文件。 -
将 ED2013 数据文件移动到与书本相关的文件目录中:
下载调查项目列表 – body_namcsopd.pdf
-
访问 CDC NHAMCS 主页:
www.cdc.gov/nchs/ahcd/ahcd_questionnaires.htm。 -
向下滚动页面,找到《调查项目列表,1973-2012》标题。
-
点击标签为 NAMCS 和 NHAMCS 调查内容手册[修订版 11/2012]的链接。
-
链接应将您带到位于
www.cdc.gov/nchs/data/ahcd/body_namcsopd.pdf的 PDF 页面。这是调查项目的列表。 -
使用浏览器下载文件。然后使用文件资源管理器将其移动到目标目录。
下载文档文件 – doc13_ed.pdf
-
访问 CDC NHAMCS 主页:
www.cdc.gov/nchs/ahcd/ahcd_questionnaires.htm。 -
向下滚动页面,找到《可下载文档》标题。
-
点击 NHAMCS(1992-2014)链接。它将把您带到 CDC 的文档 FTP 网站(ftp://ftp.cdc.gov/pub/Health_Statistics/NCHS/Dataset_Documentation/NHAMCS)。该网站如下图所示。
-
找到名为
doc13_ed.pdf的文件。点击它,PDF 将在浏览器中打开。此 PDF 文件包含 ED2013 数据文件的文档。 -
使用浏览器下载文件。然后使用文件资源管理器将其移动到目标目录:
启动 Jupyter 会话
接下来,我们将启动一个 Jupyter 会话,以便将数据导入 Python 并构建一个机器学习模型。第一章《医疗分析入门》中提供了创建新的 Jupyter Notebook 的详细示例。以下是步骤:
-
在您的计算机上找到 Jupyter 应用程序并启动它。
-
在默认浏览器中新打开的 Jupyter 标签页中,导航到您希望保存 Notebook 的目录。
-
在控制台的右上角找到“New”下拉菜单,点击它并选择 Python 3。
-
你应该看到一个名为 Untitled 的新笔记本。
-
要重命名笔记本,请点击左上角笔记本的名称。一个光标应该会出现,输入所需的名称。我们将笔记本命名为
ED_predict。
你现在可以将数据集导入到 Jupyter 中了。
导入数据集
在加载数据集之前,有一些关于数据的重要事实需要确认:
-
数据是固定宽度格式,这意味着没有分隔符。列宽需要手动指定。
-
没有包含列名的标题行。
-
如果你使用文本编辑器打开数据文件,你会看到包含数字的行。
因为导入 .fwf 文件需要列宽,我们必须先将这些列宽导入到我们的会话中。因此,我们创建了一个名为 ED_metadata.csv 的辅助 .csv 文件,包含每列的宽度、名称和变量类型。我们的数据只有 579 列,所以创建这样的文件只花了几个小时。如果你的数据集更大,可能需要依赖自动宽度检测方法和/或更多团队成员来完成创建数据架构的繁重工作。
加载元数据
在第一个单元格中,让我们导入元数据并打印一个小的预览:
import pandas as pd
pd.set_option('mode.chained_assignment',None)
HOME_PATH = 'C:\\Users\\Vikas\\Desktop\\Bk\\health-it\\ed_predict\\data\\'
df_helper = pd.read_csv(
HOME_PATH + 'ED_metadata.csv',
header=0,
dtype={'width': int, 'column_name': str, 'variable_type': str}
)
print(df_helper.head(n=5))
你应该看到以下输出:
width column_name variable_type
0 2 VMONTH CATEGORICAL
1 1 VDAYR CATEGORICAL
2 4 ARRTIME NONPREDICTIVE
3 4 WAITTIME CONTINUOUS
4 4 LOV NONPREDICTIVE
因此,ED_metadata.csv 文件仅仅是一个包含宽度、列名和变量类型的逗号分隔值文件,具体细节可参考文档。此文件可以从本书的代码仓库下载。
在下一个单元格中,我们将导入的 pandas DataFrame 的列转换为单独的列表:
width = df_helper['width'].tolist()
col_names = df_helper['column_name'].tolist()
var_types = df_helper['variable_type'].tolist()
加载 ED 数据集
接下来,我们将固定宽度数据文件的内容导入 Python,作为由字符串列组成的 pandas DataFrame,使用前一个单元格中创建的 widths 列表。然后,我们使用 col_names 列表命名列:
df_ed = pd.read_fwf(
HOME_PATH + 'ED2013',
widths=width,
header=None,
dtype='str'
)
df_ed.columns = col_names
让我们打印数据集的预览,以确认它已正确导入:
print(df_ed.head(n=5))
输出应类似于以下内容:
VMONTH VDAYR ARRTIME WAITTIME LOV AGE AGER AGEDAYS RESIDNCE SEX ... \
0 01 3 0647 0033 0058 046 4 -07 01 2 ...
1 01 3 1841 0109 0150 056 4 -07 01 2 ...
2 01 3 1333 0084 0198 037 3 -07 01 2 ...
3 01 3 1401 0159 0276 007 1 -07 01 1 ...
4 01 4 1947 0114 0248 053 4 -07 01 1 ...
RX12V3C1 RX12V3C2 RX12V3C3 RX12V3C4 SETTYPE YEAR CSTRATM CPSUM PATWT \
0 nan nan nan nan 3 2013 20113201 100020 002945
1 nan nan nan nan 3 2013 20113201 100020 002945
2 nan nan nan nan 3 2013 20113201 100020 002945
3 nan nan nan nan 3 2013 20113201 100020 002945
4 nan nan nan nan 3 2013 20113201 100020 002945
EDWT
0 nan
1 nan
2 nan
3 nan
4 nan
[5 rows x 579 columns]
查看文档中列值及其含义,确认数据已正确导入。nan 值对应数据文件中的空白空间。
最后,作为另一个检查,让我们统计数据文件的维度,并确认有 24,777 行和 579 列:
print(df_ed.shape)
输出应类似于以下内容:
(24777, 579)
现在数据已正确导入,让我们设置响应变量。
创建响应变量
在某些情况下,我们尝试预测的响应变量可能已经是一个单独且定义明确的列。在这些情况下,在将数据拆分为训练集和测试集之前,只需将响应变量从字符串转换为数字类型即可。
在我们的特定建模任务中,我们试图预测哪些前来急诊科的患者最终会住院。在我们的案例中,住院包括:
-
那些被接收进住院病房以便进一步评估和治疗的人
-
那些被转送到其他医院(无论是精神病医院还是非精神病医院)以便进一步治疗的人
-
那些被接收进入观察单元以进一步评估的人(无论他们最终是被接收还是在观察单元停留后被出院)
因此,我们必须进行一些数据处理,将所有这些不同的结果汇总成一个单一的响应变量:
response_cols = ['ADMITHOS','TRANOTH','TRANPSYC','OBSHOS','OBSDIS']
df_ed.loc[:, response_cols] = df_ed.loc[:, response_cols].apply(pd.to_numeric)
df_ed['ADMITTEMP'] = df_ed[response_cols].sum(axis=1)
df_ed['ADMITFINAL'] = 0
df_ed.loc[df_ed['ADMITTEMP'] >= 1, 'ADMITFINAL'] = 1
df_ed.drop(response_cols, axis=1, inplace=True)
df_ed.drop('ADMITTEMP', axis=1, inplace=True)
让我们详细讨论一下之前的代码示例:
-
第一行通过名称标识我们希望包含在最终目标变量中的列。如果这些列中的任何一列的值为
1,那么目标值应为1。 -
在第 2 行,我们将列从字符串类型转换为数值类型。
-
在第 3 到第 5 行,我们创建了一个名为
ADMITTEMP的列,它包含了五个目标列的逐行求和。然后我们创建了最终的目标列ADMITFINAL,当ADMITTEMP大于或等于1时,将其设置为1。 -
在第 6 到第 7 行,我们删除了五个原始响应列以及
ADMITTEMP列,因为我们现在已经有了最终的响应列。
将数据拆分为训练集和测试集
现在我们已经有了响应变量,下一步是将数据集分为训练集和测试集。在数据科学中,训练集是用来确定模型系数的数据。在训练阶段,模型会将预测变量的值与响应值一起考虑,以“发现”指导预测新数据的规则和权重。然后使用测试集来衡量我们模型的表现,正如我们在第三章中讨论的,机器学习基础。典型的拆分比例是 70%-80%用于训练数据,20%-30%用于测试数据(除非数据集非常大,在这种情况下可以分配较小比例的数据用于测试集)。
一些实践者还会有一个验证集,用于训练模型参数,例如随机森林模型中的树大小或正则化逻辑回归中的套索参数。
幸运的是,scikit-learn 库提供了一个非常方便的函数 train_test_split(),它在给定测试集百分比时会自动处理随机拆分。要使用这个函数,我们必须首先将目标变量从其他数据中分离出来,具体做法如下:
def split_target(data, target_name):
target = data[[target_name]]
data.drop(target_name, axis=1, inplace=True)
return (data, target)
X, y = split_target(df_ed, 'ADMITFINAL')
运行前面的代码后,y 保存了我们的响应变量,X 保存了我们的数据集。我们将这两个变量传递给 train_test_split() 函数,同时设置 test_size 为 0.25,并指定一个随机状态以确保结果可重现:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=1234
)
结果是一个 2 x 2 的拆分:X_train、X_test、y_train 和 y_test。我们现在可以使用 X_train 和 y_train 来训练模型,使用 X_test 和 y_test 来测试模型的性能。
一个重要的事情是要记住,在预处理阶段,任何对训练集所做的变换,在测试时也必须对测试集执行,否则模型对新数据的输出将是错误的。
作为一个简单检查,并且为了检测目标变量的不平衡情况,让我们检查响应变量中的正负响应数量:
print(y_train.groupby('ADMITFINAL').size())
输出如下:
ADMITFINAL
0 15996
1 2586
dtype: int64
我们的结果表明,测试集中的大约 1/7 的观测值具有正响应。虽然这不是一个完美平衡的数据集(在这种情况下,比例应该是 1/2),但它也不至于不平衡到需要进行任何上采样或下采样的数据处理。我们继续进行预测变量的预处理。
预测变量的预处理
让我们看看在医疗数据中常见的一些特定的预测变量组。
访问信息
ED2013 数据集中的第一个特征类别包含有关就诊时间的信息。包括月份、星期几和到达时间等变量。还包括等待时间和就诊时长变量(均以分钟为单位)。
月份
让我们更详细地分析VMONTH预测变量。以下代码打印出训练集中的所有值及其计数:
print(X_train.groupby('VMONTH').size())
输出如下:
VMONTH
01 1757
02 1396
03 1409
04 1719
05 2032
06 1749
07 1696
08 1034
09 1240
10 1306
11 1693
12 1551
dtype: int64
我们现在可以看到,月份被编号为01到12,正如文档中所说的,每个月都有代表。
数据预处理的一部分是进行特征工程——也就是说,以某种方式组合或转化特征,生成比之前更具预测性的新的特征。例如,假设我们有一个假设,急诊科患者在冬季月份的住院率较高。我们可以创建一个名为WINTER的预测变量,它是VMONTH预测变量的组合,只有当患者是在 12 月、1 月、2 月或 3 月就诊时,值为1。我们在下面的单元中做到了这一点。稍后,我们可以在评估变量重要性时检验这个假设,进行机器学习建模时:
def is_winter(vmonth):
if vmonth in ['12','01','02','03']:
return 1
else:
return 0
X_train.loc[:,'WINTER'] = df_ed.loc[:,'VMONTH'].apply(is_winter)
X_test.loc[:,'WINTER'] = df_ed.loc[:,'VMONTH'].apply(is_winter)
作为一个非正式的测试,让我们打印出WINTER变量的分布,并确认它是前四个月冬季月份的总和:
X_train.groupby('WINTER').size()
输出如下:
WINTER
0 12469
1 6113
dtype: int64
果然,我们得到了6113 = 1551 + 1757 + 1396 + 1409,确认我们正确地做了特征工程。在本章中,我们将看到其他特征工程的例子。
星期几
作为数据导入正确性的一个简单检查,让我们还探讨一下VDAYR变量,它表示患者就诊发生的星期几:
X_train.groupby('VDAYR').size()
输出如下:
VDAYR
1 2559
2 2972
3 2791
4 2632
5 2553
6 2569
7 2506
dtype: int64
正如我们预期的那样,存在七个可能的值,且这些观察值相对均匀地分布在所有可能值中。我们可以尝试制作一个WEEKEND特征,但特征工程可能非常耗时和占用内存,且通常收益甚微。这个练习我们留给读者去做。
到达时间
到达时间是数据中另一个包含的就诊信息变量。然而,在原始形式下,它可能没有什么用处,因为它是一个介于 0 到 2,359 之间的整数。让我们创建一个NIGHT变量,只有当患者在晚上 8 点到早上 8 点之间到达时,才为正。我们创建这个变量的原因是假设那些在非正常时间到达急诊科的患者病情更严重,因此更有可能被收治入院。我们可以使用以下代码来创建NIGHT变量:
def is_night(arrtime):
arrtime_int = int(arrtime)
if ((arrtime_int >= 0) & (arrtime_int < 800)):
return 1
elif ((arrtime_int >= 2000) & (arrtime_int < 2400)):
return 1
else:
return 0
X_train.loc[:,'NIGHT'] = df_ed.loc[:,'ARRTIME'].apply(is_night)
X_test.loc[:,'NIGHT'] = df_ed.loc[:,'ARRTIME'].apply(is_night)
X_train.drop('ARRTIME', axis=1, inplace=True)
X_test.drop('ARRTIME', axis=1, inplace=True)
在前面的例子中,我们首先编写一个函数,如果患者的到达时间在晚上 8 点到早上 8 点之间,则返回1,否则返回0。然后我们使用 pandas 的apply()函数将这个函数“应用”到ARRTIME列,生成NIGHT列。接着,我们删除原始的ARRTIME列,因为它的原始形式没有用处。
等待时间
在急诊科的等待时间是另一个可能与目标变量相关的就诊信息变量。假设患有更严重疾病的患者可能在分诊护士眼中表现得更加有症状,因此可能会被分配更高的分诊分数,从而导致其等待时间比那些病情较轻的患者更短。
在文档中,提到WAITTIME变量在空白和不可用时分别可能取值-9和-7。每当一个连续变量有这样的占位符值时,我们必须进行某种填补操作,以去除占位符值。否则,模型会认为患者的等待时间是-7分钟,从而导致整个模型的调整不利。
在这种情况下,均值填补是合适的操作。均值填补将这些负值替换为数据集其余部分的均值,这样在建模过程中,这些观察值就不会对该变量的系数产生影响。
为了执行均值填补,首先将列转换为数值类型:
X_train.loc[:,'WAITTIME'] = X_train.loc[:,'WAITTIME'].apply(pd.to_numeric)
X_test.loc[:,'WAITTIME'] = X_test.loc[:,'WAITTIME'].apply(pd.to_numeric)
接下来,我们编写一个名为mean_impute_values()的函数,该函数移除-7和-9的值,并将它们替换为该列的均值。我们将这个函数设计为通用型,以便以后在预处理其他列时使用:
def mean_impute_values(data,col):
temp_mean = data.loc[(data[col] != -7) & (data[col] != -9), col].mean()
data.loc[(data[col] == -7) | (data[col] == -9), col] = temp_mean
return data
X_train = mean_impute_values(X_train,'WAITTIME')
X_test = mean_impute_values(X_test,'WAITTIME')
然后我们在数据上调用这个函数,完成这个过程。接下来,我们将确认该函数是否已正确应用,但在此之前,让我们再看一下几个其他变量。
其他就诊信息
这个数据集中的最后一个访问信息变量是访问时长变量(LOV)。然而,访问时长只有在整个急诊访问结束后才能确定,而那时是否接收住院或者出院的决定已经做出。因此,必须删除那些在预测时无法获得的变量,出于这个原因,我们需要删除LOV。我们可以按照以下代码执行:
X_train.drop('LOV', axis=1, inplace=True)
X_test.drop('LOV', axis=1, inplace=True)
现在我们已经完成了访问信息的处理,接下来让我们转向人口统计变量。
人口统计变量
在医疗领域,人口统计变量通常与结果相关联。年龄、性别和种族是医疗领域中的主要人口统计变量。在这个数据集中,还包含了族裔和居住类型变量。让我们按如下方式整理这些变量。
年龄
随着人们年龄的增长,我们可以预期他们的健康状况会变差,并且更频繁地住院。这个假设将在我们查看模型的变量重要性结果后进行验证。
数据集中有三个变量反映年龄。AGE是一个整数值,表示以年为单位的年龄。AGEDAYS是一个整数值,如果患者不到 1 岁,表示以天为单位的年龄。AGER是年龄变量,但它已经被转换为分类变量。我们将AGE变量转换为数值类型,保持AGER变量不变,并删除AGEDAYS变量,因为在绝大多数情况下它不适用:
X_train.loc[:,'AGE'] = X_train.loc[:,'AGE'].apply(pd.to_numeric)
X_test.loc[:,'AGE'] = X_test.loc[:,'AGE'].apply(pd.to_numeric)
X_train.drop('AGEDAYS', axis=1, inplace=True)
X_test.drop('AGEDAYS', axis=1, inplace=True)
性别
在医疗领域,通常发现女性的预期寿命较长,整体健康状况也优于男性,因此我们要将SEX变量纳入我们的模型。它已经是分类变量,因此可以保持不变。
种族和族裔
数据中还包含了种族(西班牙裔/拉丁裔与非西班牙裔/拉丁裔)和族裔信息。通常,容易处于低社会经济地位的种族在医疗中的结果较差。让我们保持未填充的种族和族裔变量(ETHUN和RACEUN)不变。我们可以删除冗余的RACER变量以及填充后的种族和族裔变量(ETHIM和RACERETH):
X_train.drop(['ETHIM','RACER','RACERETH'], axis=1, inplace=True)
X_test.drop(['ETHIM','RACER','RACERETH'], axis=1, inplace=True)
其他人口统计信息
患者居住地包含在数据中。由于它是分类变量,因此无需做任何更改。
让我们查看目前为止的结果,并使用head()函数打印前五行:
X_train.head(n=5)
在输出中横向滚动时,你应该确认我们的所有转换和变量删除操作已正确执行。
分诊变量
分诊变量对急诊科建模任务非常重要。分诊包括根据患者的初始表现和生命体征为患者分配风险评分。通常由专业分诊护士完成,涵盖了主观和客观信息。分诊评分通常从 1(危急)到 5(非急诊)。IMMEDR变量(文档中的第 34 项)是本数据集中的分诊评分。我们当然会包括它。
我们可以将一些其他变量归类为分诊变量,包括患者是否通过急救医疗服务(ARREMS)到达(通常与较差的结果相关)以及患者是否在过去 72 小时内已经接受并出院(SEEN72)。我们也将在我们的模型中包括这些变量。
财务变量
患者的支付方式通常包含在医疗数据集中,且通常某些支付方式与更好或更差的结果相关。没有预期支付来源(NOPAY)、或通过医疗补助(PAYMCAID)或医疗保险(PAYMCARE)支付的患者,通常比拥有私人保险(PAYPRIV)或自行支付(PAYSELF)的患者健康状况差。让我们包括所有财务变量,除了PAYTYPER变量,它只是其他支付变量的非二元扩展:
X_train.drop('PAYTYPER', axis=1, inplace=True)
X_test.drop('PAYTYPER', axis=1, inplace=True)
生命体征
生命体征是医疗建模中患者信息的重要来源,原因有很多:
-
它们易于收集。
-
它们通常在临床接诊初期可用。
-
它们是客观的。
-
它们是患者健康的数值指标。
本数据集中包括的生命体征有体温、脉搏、呼吸频率、血压(收缩压和舒张压)、血氧饱和度百分比,以及是否使用氧气。身高和体重通常也被归类为生命体征,但它们不包括在我们的数据中。让我们逐一查看每项生命体征。
体温
体温通常在患者接诊初期使用温度计测量,可以记录为摄氏度或华氏度。98.6°F(37.1°C)的体温通常被认为是正常体温。显著高于此范围的体温可以称为发热或高热,通常反映感染、炎症或环境过度暴露于阳光下。低于正常体温一定程度的情况被称为低体温,通常反映暴露于寒冷的环境。体温偏离正常值越多,通常意味着疾病越严重。
在我们的数据集中,TEMPF温度被乘以 10 并以整数形式存储。同时,一些值为空(用-9表示),我们必须填补这些空值,因为温度是一个连续变量。接着,我们首先将温度转换为数值类型,使用我们之前写的mean_impute_values()函数填补TEMPF中的缺失值,然后使用 lambda 函数将所有温度值除以10:
X_train.loc[:,'TEMPF'] = X_train.loc[:,'TEMPF'].apply(pd.to_numeric)
X_test.loc[:,'TEMPF'] = X_test.loc[:,'TEMPF'].apply(pd.to_numeric)
X_train = mean_impute_values(X_train,'TEMPF')
X_test = mean_impute_values(X_test,'TEMPF')
X_train.loc[:,'TEMPF'] = X_train.loc[:,'TEMPF'].apply(lambda x: float(x)/10)
X_test.loc[:,'TEMPF'] = X_test.loc[:,'TEMPF'].apply(lambda x: float(x)/10)
让我们打印出此列的 30 个值,以确认我们的处理是否正确:
X_train['TEMPF'].head(n=30)
输出结果如下:
15938 98.200000
5905 98.100000
4636 98.200000
9452 98.200000
7558 99.300000
17878 99.000000
21071 97.800000
20990 98.600000
4537 98.200000
7025 99.300000
2134 97.500000
5212 97.400000
9213 97.900000
2306 97.000000
6106 98.600000
2727 98.282103
4098 99.100000
5233 98.800000
5107 100.000000
18327 98.900000
19242 98.282103
3868 97.900000
12903 98.600000
12763 98.700000
8858 99.400000
8955 97.900000
16360 98.282103
6857 97.100000
6842 97.700000
22073 97.900000
Name: TEMPF, dtype: float64
我们可以看到温度现在是浮动类型,并且它们没有被乘以 10。同时,我们看到均值98.282103已经代替了之前为空的值。接下来我们来看下一个变量。
脉搏
脉搏测量患者心跳的频率。正常范围为 60-100。脉搏超过100被称为心动过速,通常表明潜在的心脏功能障碍、血容量减少或感染(败血症)。脉搏低于60被称为心动过缓。
我们必须使用均值插补法来填补缺失值。首先,我们将脉搏转换为数值类型:
X_train.loc[:,'PULSE'] = X_train.loc[:,'PULSE'].apply(pd.to_numeric)
X_test.loc[:,'PULSE'] = X_test.loc[:,'PULSE'].apply(pd.to_numeric)
然后,我们编写一个类似于mean_impute_values()的mean_impute_vitals()函数,只是占位符值从-7和-9变为-998和-9:
def mean_impute_vitals(data,col):
temp_mean = data.loc[(data[col] != 998) & (data[col] != -9), col].mean()
data.loc[(data[col] == 998) | (data[col] == -9), col] = temp_mean
return data
X_train = mean_impute_vitals(X_train,'PULSE')
X_test = mean_impute_vitals(X_test,'PULSE')
呼吸频率
呼吸频率表示一个人每分钟的呼吸次数。18-20 次被认为是正常的。呼吸急促(异常增高的呼吸频率)在临床实践中常见,通常表明体内缺氧,通常由于心脏或肺部问题。缓呼吸是指异常低的呼吸频率。
在以下代码中,我们将RESPR变量转换为数值类型,然后对缺失值进行均值插补:
X_train.loc[:,'RESPR'] = X_train.loc[:,'RESPR'].apply(pd.to_numeric)
X_test.loc[:,'RESPR'] = X_test.loc[:,'RESPR'].apply(pd.to_numeric)
X_train = mean_impute_values(X_train,'RESPR')
X_test = mean_impute_values(X_test,'RESPR')
血压
血压测量血液对血管壁施加的单位面积的压力。血压由两个数字组成——收缩压(心跳收缩期的血压)和舒张压(心跳舒张期的血压)。正常的血压通常在收缩压 110 到 120 mmHg 之间,舒张压在 70 到 80 mmHg 之间。升高的血压称为高血压。高血压最常见的原因是原发性高血压,主要是遗传因素(但是多因素的)。低血压称为低血压。高血压和低血压都有复杂的病因,通常难以识别。
在我们的数据集中,收缩压和舒张压分别位于单独的列中(BPSYS和BPDIAS)。首先,我们处理收缩压,将其转换为数值类型,并像其他列一样进行均值插补:
X_train.loc[:,'BPSYS'] = X_train.loc[:,'BPSYS'].apply(pd.to_numeric)
X_test.loc[:,'BPSYS'] = X_test.loc[:,'BPSYS'].apply(pd.to_numeric)
X_train = mean_impute_values(X_train,'BPSYS')
X_test = mean_impute_values(X_test,'BPSYS')
收缩压稍微复杂一些。998 这个值表示压力为 PALP,意味着它低到无法通过血压计检测,但又足够高,能通过触诊(触摸)感受到。我们将其转换为数值类型后,会将 PALP 值替换为数值 40:
X_train.loc[:,'BPDIAS'] = X_train.loc[:,'BPDIAS'].apply(pd.to_numeric)
X_test.loc[:,'BPDIAS'] = X_test.loc[:,'BPDIAS'].apply(pd.to_numeric)
我们编写了一个新函数 mean_impute_bp_diast(),它将 PALP 值转换为 40,并将缺失值填充为均值:
def mean_impute_bp_diast(data,col):
temp_mean = data.loc[(data[col] != 998) & (data[col] != -9), col].mean()
data.loc[data[col] == 998, col] = 40
data.loc[data[col] == -9, col] = temp_mean
return data
X_train = mean_impute_values(X_train,'BPDIAS')
X_test = mean_impute_values(X_test,'BPDIAS')
血氧饱和度
血氧饱和度是衡量血液中氧气水平的指标,通常以百分比形式表示,值越高越健康。我们将其转换为数值类型并进行均值填充,过程如下:
X_train.loc[:,'POPCT'] = X_train.loc[:,'POPCT'].apply(pd.to_numeric)
X_test.loc[:,'POPCT'] = X_test.loc[:,'POPCT'].apply(pd.to_numeric)
X_train = mean_impute_values(X_train,'POPCT')
X_test = mean_impute_values(X_test,'POPCT')
让我们通过选择这些列并使用 head() 函数来检查目前为止我们所做的生命体征转换:
X_train[['TEMPF','PULSE','RESPR','BPSYS','BPDIAS','POPCT']].head(n=20)
输出如下:
| TEMPF | PULSE | RESPR | BPSYS | BPDIAS | ||
|---|---|---|---|---|---|---|
| 15938 | 98.200000 | 101.000000 | 22.0 | 159.000000 | 72.000000 | 98.000000 |
| 5905 | 98.100000 | 70.000000 | 18.0 | 167.000000 | 79.000000 | 96.000000 |
| 4636 | 98.200000 | 85.000000 | 20.0 | 113.000000 | 70.000000 | 98.000000 |
| 9452 | 98.200000 | 84.000000 | 20.0 | 146.000000 | 72.000000 | 98.000000 |
| 7558 | 99.300000 | 116.000000 | 18.0 | 131.000000 | 82.000000 | 96.000000 |
| 17878 | 99.000000 | 73.000000 | 16.0 | 144.000000 | 91.000000 | 99.000000 |
| 21071 | 97.800000 | 88.000000 | 18.0 | 121.000000 | 61.000000 | 98.000000 |
| 20990 | 98.600000 | 67.000000 | 16.0 | 112.000000 | 65.000000 | 95.000000 |
| 4537 | 98.200000 | 85.000000 | 20.0 | 113.000000 | 72.000000 | 99.000000 |
| 7025 | 99.300000 | 172.000000 | 40.0 | 124.000000 | 80.000000 | 100.000000 |
| 2134 | 97.500000 | 91.056517 | 18.0 | 146.000000 | 75.000000 | 94.000000 |
| 5212 | 97.400000 | 135.000000 | 18.0 | 125.000000 | 71.000000 | 99.000000 |
| 9213 | 97.900000 | 85.000000 | 18.0 | 153.000000 | 96.000000 | 99.000000 |
| 2306 | 97.000000 | 67.000000 | 20.0 | 136.000000 | 75.000000 | 99.000000 |
| 6106 | 98.600000 | 90.000000 | 18.0 | 109.000000 | 70.000000 | 98.000000 |
| 2727 | 98.282103 | 83.000000 | 17.0 | 123.000000 | 48.000000 | 92.000000 |
| 4098 | 99.100000 | 147.000000 | 20.0 | 133.483987 | 78.127013 | 100.000000 |
| 5233 | 98.800000 | 81.000000 | 16.0 | 114.000000 | 78.000000 | 97.311242 |
| 5107 | 100.000000 | 95.000000 | 24.0 | 133.000000 | 75.000000 | 94.000000 |
| 18327 | 98.900000 | 84.000000 | 16.0 | 130.000000 | 85.000000 | 98.000000 |
查看前面的表格,似乎一切正常。我们可以看到每列的填充均值(值具有额外的精度)。现在我们来处理数据中的最后一个生命体征——疼痛级别。
疼痛级别
疼痛是人体出现问题的常见表现,疼痛程度通常会在每次医疗访谈中被询问,无论是初诊病史和体格检查,还是每日 SOAP 记录。疼痛程度通常用从 0(无痛)到 10(无法忍受)的等级来报告。我们首先将PAINSCALE列转换为数值类型:
X_train.loc[:,'PAINSCALE'] = X_train.loc[:,'PAINSCALE'].apply(pd.to_numeric)
X_test.loc[:,'PAINSCALE'] = X_test.loc[:,'PAINSCALE'].apply(pd.to_numeric)
现在,我们需要为疼痛值的均值填充编写一个单独的函数,因为它使用-8作为占位符,而不是-7:
def mean_impute_pain(data,col):
temp_mean = data.loc[(data[col] != -8) & (data[col] != -9), col].mean()
data.loc[(data[col] == -8) | (data[col] == -9), col] = temp_mean
return data
X_train = mean_impute_pain(X_train,'PAINSCALE')
X_test = mean_impute_pain(X_test,'PAINSCALE')
综合来看,生命体征为患者健康提供了一个重要的整体图像。最终,我们将看到在进行变量重要性分析时,这些变量所发挥的关键作用。
现在,我们可以进入下一个变量类别。
就诊原因代码
就诊原因变量编码了患者就诊的原因,可以视为就诊的主要诉求(我们在第二章,医疗基础中讨论了主要诉求)。在这个数据集中,这些原因使用一个名为门诊就诊原因分类的代码集进行编码(更多信息请参考 2011 年文档的第 16 页和附录二;附录第一页的截图已提供在本章结尾)。虽然确切的代码在患者就诊初期可能无法确定,但我们在这里包括它,因为:
-
它反映了患者就诊初期可用的信息。
-
我们想展示如何处理一个编码变量(其他所有编码变量出现在患者就诊的较晚阶段,无法用于本建模任务):
编码变量需要特别注意,原因如下:
-
表格中常常会有多个条目对应多个代码,原因-就诊代码也不例外。注意,这个数据集包含了三个 RFV 列(
RFV1、RFV2和RFV3)。例如,哮喘的代码可能会出现在这三列中的任何一列。因此,单独对这些列进行独热编码是不够的。我们必须检测每个代码在任何这三列中的出现情况,并且必须编写一个特殊的函数来处理这个问题。 -
代码是分类变量,但这些数字本身通常没有实际意义。为了便于解释,我们必须相应地命名这些列,使用适当的描述。为此,我们整理了一份特殊的
.csv文件,包含了每个代码的描述(可在本书的 GitHub 仓库中下载)。 -
一种可能的输出格式是每个代码对应一列,其中
1表示该代码的存在,0表示其不存在(如 Futoma 等,2015 年所做)。然后可以进行任何所需的组合/转换。我们在此采用了这种格式。
不再赘述,让我们开始转换我们的就诊原因变量。首先,我们导入 RFV 代码描述:
rfv_codes_path = HOME_PATH + 'RFV_CODES.csv'
rfv_codes = pd.read_csv(rfv_codes_path,header=0,dtype='str')
现在我们将开始处理 RFV 代码。
首先,为了正确命名列,我们从re模块导入sub()函数(re代表正则表达式)。
然后,我们编写一个函数,扫描任何给定的 RFV 列,检查是否存在指定的代码,并返回包含新列的数据集。如果代码存在,则列值为1,如果代码不存在,则列值为0。
接下来,我们使用for循环遍历.csv文件中的每个代码,有效地为每个可能的代码添加一个二进制列。我们对训练集和测试集都执行此操作。
最后,我们删除原始的 RFV 列,因为我们不再需要它们。完整的代码如下:
from re import sub
def add_rfv_column(data,code,desc,rfv_columns):
column_name = 'rfv_' + sub(" ", "_", desc)
data[column_name] = (data[rfv_columns] == rfv_code).any(axis=1).astype('int')
return data
rfv_columns = ['RFV1','RFV2','RFV3']
for (rfv_code,rfv_desc) in zip(
rfv_codes['Code'].tolist(),rfv_codes['Description'].tolist()
):
X_train = add_rfv_column(
X_train,
rfv_code,
rfv_desc,
rfv_columns
)
X_test = add_rfv_column(
X_test,
rfv_code,
rfv_desc,
rfv_columns
)
# Remove original RFV columns
X_train.drop(rfv_columns, axis=1, inplace=True)
X_test.drop(rfv_columns, axis=1, inplace=True)
让我们用head()函数查看一下转换后的数据集:
X_train.head(n=5)
注意,现在有 1,264 列。虽然完整的 DataFrame 已被截断,但如果你水平滚动,你应该能看到一些新的rfv_列被添加到 DataFrame 的末尾。
伤害代码
伤害代码也包含在数据中。虽然就诊原因代码适用于所有就诊,但伤害代码仅在患者遭受了身体伤害、中毒或医疗治疗的副作用(包括自杀未遂)时适用。由于伤害的确切原因可能在进行全面检查之前无法得知,而且该检查通常在决定是否住院后才会进行,因此我们将删除伤害代码变量,因为它们可能包含在预测时不可用的未来信息。不过,如果你希望在建模任务中使用这些代码,请记住,编码数据可以像之前所示的那样进行处理。有关伤害变量的更多细节,请参阅文档:
inj_cols = [
'INJURY','INJR1','INJR2','INJPOISAD','INJPOISADR1',
'INJPOISADR2','INTENT','INJDETR','INJDETR1','INJDETR2',
'CAUSE1','CAUSE2','CAUSE3','CAUSE1R','CAUSE2R','CAUSE3R'
]
X_train.drop(inj_cols, axis=1, inplace=True)
X_test.drop(inj_cols, axis=1, inplace=True)
诊断代码
数据集还包含 ICD-9-DM 代码,用于分类与每次就诊相关的诊断。请注意,数据集中有三个诊断代码列。这与我们在就诊原因代码部分提到的编码变量一致。由于 ICD-9 代码通常在进行检查并确定症状原因后分配给就诊,因此我们将需要将其从这个建模任务中排除:
diag_cols= [
'DIAG1','DIAG2','DIAG3',
'PRDIAG1','PRDIAG2','PRDIAG3',
'DIAG1R','DIAG2R','DIAG3R'
]
X_train.drop(diag_cols, axis=1, inplace=True)
X_test.drop(diag_cols, axis=1, inplace=True)
病史
正如我们在第二章《医疗基础》中讨论的那样,患有慢性疾病的人通常比没有慢性疾病的人健康状况较差,健康结果也较差。数据集中包括了每次就诊时 11 种常见慢性疾病的相关信息。这些疾病包括癌症、脑血管疾病、慢性阻塞性肺病、需要透析的疾病、充血性心力衰竭、痴呆、糖尿病、心肌梗死历史、肺栓塞或深静脉血栓历史,以及 HIV/AIDS。由于以前就诊的患者通常能够获取其病史信息,并且这些信息通常在患者分诊时就已明确,因此我们决定在此包含这些变量。因为这些变量已经是二元的,所以不需要进一步处理。
还有一个连续变量,名为 TOTCHRON,用于统计每位患者的慢性病总数,我们将其进行均值填充,如下所示:
X_train.loc[:,'TOTCHRON'] = X_train.loc[:,'TOTCHRON'].apply(pd.to_numeric)
X_test.loc[:,'TOTCHRON'] = X_test.loc[:,'TOTCHRON'].apply(pd.to_numeric)
X_train = mean_impute_values(X_train,'TOTCHRON')
X_test = mean_impute_values(X_test,'TOTCHRON')
测试
医学测试虽然重要,但发生在预测时间之后,因此必须在此用例中省略。它们可能会用于其他建模任务,如再入院预测或死亡率:
testing_cols = [
'ABG','BAC','BLOODCX','BNP','BUNCREAT',
'CARDENZ','CBC','DDIMER','ELECTROL','GLUCOSE',
'LACTATE','LFT','PTTINR','OTHERBLD','CARDMON',
'EKG','HIVTEST','FLUTEST','PREGTEST','TOXSCREN',
'URINE','WOUNDCX','URINECX','OTHRTEST','ANYIMAGE',
'XRAY','IVCONTRAST','CATSCAN','CTAB','CTCHEST',
'CTHEAD','CTOTHER','CTUNK','MRI','ULTRASND',
'OTHIMAGE','TOTDIAG','DIAGSCRN'
]
X_train.drop(testing_cols, axis=1, inplace=True)
X_test.drop(testing_cols, axis=1, inplace=True)
程序
我们省略了程序,因为与测试类似,它们通常发生在预测时间之后:
proc_cols = [
'PROC','BPAP','BLADCATH','CASTSPLINT','CENTLINE',
'CPR','ENDOINT','INCDRAIN','IVFLUIDS','LUMBAR',
'NEBUTHER','PELVIC','SKINADH','SUTURE','OTHPROC',
'TOTPROC'
]
X_train.drop(proc_cols, axis=1, inplace=True)
X_test.drop(proc_cols, axis=1, inplace=True)
药物编码
数据包括关于急诊科所给予的药物和/或出院时开出的药物的充足信息。实际上,最多可提供 12 种药物的信息,分布在不同的列中。显然,药物的给药是在决定是否接纳患者之后,因此我们不能在此用例中使用这些列。
尽管如此,如果你希望在自己的预测建模中使用此类信息,我们鼓励你查阅文档并阅读有关药物编码系统的相关内容:
med_cols = [
'MED1','MED2','MED3','MED4','MED5',
'MED6','MED7','MED8','MED9','MED10',
'MED11','MED12','GPMED1','GPMED2','GPMED3',
'GPMED4','GPMED5','GPMED6','GPMED7','GPMED8',
'GPMED9','GPMED10','GPMED11','GPMED12','NUMGIV',
'NUMDIS','NUMMED',
]
X_train.drop(med_cols, axis=1, inplace=True)
X_test.drop(med_cols, axis=1, inplace=True)
提供者信息
提供者列指示参与医疗接触的医疗提供者类型。我们已省略以下变量:
prov_cols = [
'NOPROVID','ATTPHYS','RESINT','CONSULT','RNLPN',
'NURSEPR','PHYSASST','EMT','MHPROV','OTHPROV'
]
X_train.drop(prov_cols, axis=1, inplace=True)
X_test.drop(prov_cols, axis=1, inplace=True)
处置信息
由于处置变量与结果直接相关,因此我们不能将其保留在数据中。我们在此省略它们(请回顾我们在创建最终目标列后已删除了若干处置变量):
disp_cols = [
'NODISP','NOFU','RETRNED','RETREFFU','LEFTBTRI',
'LEFTAMA','DOA','DIEDED','TRANNH','OTHDISP',
'ADMIT','ADMTPHYS','BOARDED','LOS','HDDIAG1',
'HDDIAG2','HDDIAG3','HDDIAG1R','HDDIAG2R','HDDIAG3R',
'HDSTAT','ADISP','OBSSTAY','STAY24'
]
X_train.drop(disp_cols, axis=1, inplace=True)
X_test.drop(disp_cols, axis=1, inplace=True)
填充列
这些列表示包含填充数据的列。大部分情况下,我们在数据中已经包括了未填充的对应列,因此不需要填充列,所以我们将其删除:
imp_cols = [
'AGEFL','BDATEFL','SEXFL','ETHNICFL','RACERFL'
]
X_train.drop(imp_cols, axis=1, inplace=True)
X_test.drop(imp_cols, axis=1, inplace=True)
识别变量
当结合在一起时,识别变量为每次接触提供唯一的键。虽然在许多情况下这很有用,但幸运的是,pandas DataFrame 已经为每一行唯一分配了一个整数,因此我们可以删除 ID 变量:
id_cols = [
'HOSPCODE','PATCODE'
]
X_train.drop(id_cols, axis=1, inplace=True)
X_test.drop(id_cols, axis=1, inplace=True)
电子病历状态栏
数据集中包含数十列指示患者就诊时所在医疗机构的技术水平。我们在第二章《EHR 技术与有意义的使用》部分中讨论了这一点,医疗基础。由于这些列是按医院而非就诊事件来评估的,因此我们将忽略这些列:
emr_cols = [
'EBILLANYE','EMRED','HHSMUE','EHRINSE','EDEMOGE',
'EDEMOGER','EPROLSTE','EPROLSTER','EVITALE','EVITALER',
'ESMOKEE','ESMOKEER','EPNOTESE','EPNOTESER','EMEDALGE',
'EMEDALGER','ECPOEE','ECPOEER','ESCRIPE','ESCRIPER',
'EWARNE','EWARNER','EREMINDE','EREMINDER','ECTOEE',
'ECTOEER','EORDERE','EORDERER','ERESULTE','ERESULTER',
'EGRAPHE','EGRAPHER','EIMGRESE','EIMGRESER','EPTEDUE',
'EPTEDUER','ECQME','ECQMER','EGENLISTE','EGENLISTER',
'EIMMREGE','EIMMREGER','ESUME','ESUMER','EMSGE',
'EMSGER','EHLTHINFOE','EHLTHINFOER','EPTRECE','EPTRECER',
'EMEDIDE','EMEDIDER','ESHAREE','ESHAREEHRE','ESHAREWEBE',
'ESHAREOTHE','ESHAREUNKE','ESHAREREFE','LABRESE1','LABRESE2',
'LABRESE3','LABRESE4','LABRESUNKE','LABRESREFE','IMAGREPE1',
'IMAGREPE2','IMAGREPE3','IMAGREPE4','IMAGREPUNKE','IMAGREPREFE',
'PTPROBE1','PTPROBE2','PTPROBE3','PTPROBE4','PTPROBUNKE',
'PTPROBREFE','MEDLISTE1','MEDLISTE2','MEDLISTE3','MEDLISTE4',
'MEDLISTUNKE','MEDLISTREFE','ALGLISTE1','ALGLISTE2','ALGLISTE3',
'ALGLISTE4','ALGLISTUNKE','ALGLISTREFE','EDPRIM','EDINFO',
'MUINC','MUYEAR'
]
X_train.drop(emr_cols, axis=1, inplace=True)
X_test.drop(emr_cols, axis=1, inplace=True)
详细的药物信息
这些列包含更详细的药物信息,包括药物类别的编码信息。我们必须忽略这些列,因为它们表示的是未来信息。然而,这些信息在其他机器学习问题中可能非常有用:
drug_id_cols = [
'DRUGID1','DRUGID2','DRUGID3','DRUGID4','DRUGID5',
'DRUGID6','DRUGID7','DRUGID8','DRUGID9','DRUGID10',
'DRUGID11','DRUGID12'
]
drug_lev1_cols = [
'RX1V1C1','RX1V1C2','RX1V1C3','RX1V1C4',
'RX2V1C1','RX2V1C2','RX2V1C3','RX2V1C4',
'RX3V1C1','RX3V1C2','RX3V1C3','RX3V1C4',
'RX4V1C1','RX4V1C2','RX4V1C3','RX4V1C4',
'RX5V1C1','RX5V1C2','RX5V1C3','RX5V1C4',
'RX6V1C1','RX6V1C2','RX6V1C3','RX6V1C4',
'RX7V1C1','RX7V1C2','RX7V1C3','RX7V1C4',
'RX8V1C1','RX8V1C2','RX8V1C3','RX8V1C4',
'RX9V1C1','RX9V1C2','RX9V1C3','RX9V1C4',
'RX10V1C1','RX10V1C2','RX10V1C3','RX10V1C4',
'RX11V1C1','RX11V1C2','RX11V1C3','RX11V1C4',
'RX12V1C1','RX12V1C2','RX12V1C3','RX12V1C4'
]
drug_lev2_cols = [
'RX1V2C1','RX1V2C2','RX1V2C3','RX1V2C4',
'RX2V2C1','RX2V2C2','RX2V2C3','RX2V2C4',
'RX3V2C1','RX3V2C2','RX3V2C3','RX3V2C4',
'RX4V2C1','RX4V2C2','RX4V2C3','RX4V2C4',
'RX5V2C1','RX5V2C2','RX5V2C3','RX5V2C4',
'RX6V2C1','RX6V2C2','RX6V2C3','RX6V2C4',
'RX7V2C1','RX7V2C2','RX7V2C3','RX7V2C4',
'RX8V2C1','RX8V2C2','RX8V2C3','RX8V2C4',
'RX9V2C1','RX9V2C2','RX9V2C3','RX9V2C4',
'RX10V2C1','RX10V2C2','RX10V2C3','RX10V2C4',
'RX11V2C1','RX11V2C2','RX11V2C3','RX11V2C4',
'RX12V2C1','RX12V2C2','RX12V2C3','RX12V2C4'
]
drug_lev3_cols = [
'RX1V3C1','RX1V3C2','RX1V3C3','RX1V3C4',
'RX2V3C1','RX2V3C2','RX2V3C3','RX2V3C4',
'RX3V3C1','RX3V3C2','RX3V3C3','RX3V3C4',
'RX4V3C1','RX4V3C2','RX4V3C3','RX4V3C4',
'RX5V3C1','RX5V3C2','RX5V3C3','RX5V3C4',
'RX6V3C1','RX6V3C2','RX6V3C3','RX6V3C4',
'RX7V3C1','RX7V3C2','RX7V3C3','RX7V3C4',
'RX8V3C1','RX8V3C2','RX8V3C3','RX8V3C4',
'RX9V3C1','RX9V3C2','RX9V3C3','RX9V3C4',
'RX10V3C1','RX10V3C2','RX10V3C3','RX10V3C4',
'RX11V3C1','RX11V3C2','RX11V3C3','RX11V3C4',
'RX12V3C1','RX12V3C2','RX12V3C3','RX12V3C4'
]
addl_drug_cols = [
'PRESCR1','CONTSUB1','COMSTAT1','RX1CAT1','RX1CAT2',
'RX1CAT3','RX1CAT4','PRESCR2','CONTSUB2','COMSTAT2',
'RX2CAT1','RX2CAT2','RX2CAT3','RX2CAT4','PRESCR3','CONTSUB3',
'COMSTAT3','RX3CAT1','RX3CAT2','RX3CAT3','RX3CAT4','PRESCR4',
'CONTSUB4','COMSTAT4','RX4CAT1','RX4CAT2','RX4CAT3',
'RX4CAT4','PRESCR5','CONTSUB5','COMSTAT5','RX5CAT1',
'RX5CAT2','RX5CAT3','RX5CAT4','PRESCR6','CONTSUB6',
'COMSTAT6','RX6CAT1','RX6CAT2','RX6CAT3','RX6CAT4','PRESCR7',
'CONTSUB7','COMSTAT7','RX7CAT1','RX7CAT2','RX7CAT3',
'RX7CAT4','PRESCR8','CONTSUB8','COMSTAT8','RX8CAT1',
'RX8CAT2','RX8CAT3','RX8CAT4','PRESCR9','CONTSUB9',
'COMSTAT9','RX9CAT1','RX9CAT2','RX9CAT3','RX9CAT4',
'PRESCR10','CONTSUB10','COMSTAT10','RX10CAT1','RX10CAT2',
'RX10CAT3','RX10CAT4','PRESCR11','CONTSUB11','COMSTAT11',
'RX11CAT1','RX11CAT2','RX11CAT3','RX11CAT4','PRESCR12',
'CONTSUB12','COMSTAT12','RX12CAT1','RX12CAT2','RX12CAT3',
'RX12CAT4'
]
X_train.drop(drug_id_cols, axis=1, inplace=True)
X_train.drop(drug_lev1_cols, axis=1, inplace=True)
X_train.drop(drug_lev2_cols, axis=1, inplace=True)
X_train.drop(drug_lev3_cols, axis=1, inplace=True)
X_train.drop(addl_drug_cols, axis=1, inplace=True)
X_test.drop(drug_id_cols, axis=1, inplace=True)
X_test.drop(drug_lev1_cols, axis=1, inplace=True)
X_test.drop(drug_lev2_cols, axis=1, inplace=True)
X_test.drop(drug_lev3_cols, axis=1, inplace=True)
X_test.drop(addl_drug_cols, axis=1, inplace=True)
其他信息
最后,数据集末尾有几个与我们目的无关的列,因此我们将它们删除:
design_cols = ['CSTRATM','CPSUM','PATWT','EDWT']
X_train.drop(design_cols, axis=1, inplace=True)
X_test.drop(design_cols, axis=1, inplace=True)
最终的预处理步骤
现在我们已经完成了所有变量组的处理,几乎可以开始构建我们的预测模型了。但首先,我们必须将所有类别变量扩展为二进制变量(也叫做独热编码或 1-of-K 表示法),并将数据转换为适合输入到scikit-learn方法的格式。接下来我们就来做这个。
独热编码
许多scikit-learn库的分类器要求类别变量进行独热编码。独热编码,或1-of-K 表示法,是指将一个有多个可能值的类别变量记录为多个变量,每个变量有两个可能的值。
例如,假设我们在数据集中有五个患者,我们希望对表示主要就诊诊断的列进行独热编码。在进行独热编码之前,这一列看起来是这样的:
patient_id | primary_dx |
|---|---|
| 1 | copd |
| 2 | hypertension |
| 3 | copd |
| 4 | chf |
| 5 | asthma |
在进行独热编码后,这一列将被拆分成K列,其中K是可能值的数量,每一列的值为 0 或 1,具体取决于该观察值是否对应该列的值:
patient_id | primary_dx_copd | primary_dx_hypertension | primary_dx_chf | primary_dx_asthma |
|---|---|---|---|---|
| 1 | 1 | 0 | 0 | 0 |
| 2 | 0 | 1 | 0 | 0 |
| 3 | 1 | 0 | 0 | 0 |
| 4 | 0 | 0 | 1 | 0 |
| 5 | 0 | 0 | 0 | 1 |
请注意,我们已经将上一列的字符串转换为整数表示。这是有道理的,因为机器学习算法是基于数字而不是单词来训练的!这就是为什么独热编码是必要的原因。
scikit-learn在其preprocessing模块中有一个OneHotEncoder类。然而,pandas有一个get_dummies()函数,能够用一行代码完成独热编码。我们将使用pandas函数。在此之前,我们必须确定数据集中哪些列是类别变量,然后传递给该函数。我们通过使用元数据来识别类别列,并查看这些列与我们数据中剩余列的交集:
categ_cols = df_helper.loc[
df_helper['variable_type'] == 'CATEGORICAL', 'column_name'
]
one_hot_cols = list(set(categ_cols) & set(X_train.columns))
X_train = pd.get_dummies(X_train, columns=one_hot_cols)
我们还必须对测试数据进行独热编码:
X_test = pd.get_dummies(X_test, columns=one_hot_cols)
最后要提到的是,测试集可能包含在训练数据中未曾见过的类别值。这可能会导致在使用测试集评估模型性能时出现错误。为防止这种情况,您可能需要编写一些额外的代码,将测试集中缺失的列设置为零。幸运的是,在我们的数据集中我们不需要担心这个问题。
数字转换
现在让我们将所有列转换为数字格式:
X_train.loc[:,X_train.columns] = X_train.loc[:,X_train.columns].apply(pd.to_numeric)
X_test.loc[:,X_test.columns] = X_test.loc[:,X_test.columns].apply(pd.to_numeric)
NumPy 数组转换
最后的步骤是获取将直接传入机器学习算法的 pandas 数据框的 NumPy 数组。首先,我们保存最终的列名,这将在稍后评估变量重要性时帮助我们:
X_train_cols = X_train.columns
X_test_cols = X_test.columns
现在,我们使用pandas数据框的values属性来访问每个数据框的底层 NumPy 数组:
X_train = X_train.values
X_test = X_test.values
现在,我们已经准备好构建模型。
构建模型
在本节中,我们将构建三种类型的分类器并评估它们的性能:逻辑回归分类器、随机森林和神经网络。
逻辑回归
我们在第三章中讨论了逻辑回归模型的直觉和基本概念,机器学习基础。为了在我们的训练集上构建一个模型,我们使用以下代码:
from sklearn.linear_model import LogisticRegression
clfs = [LogisticRegression()]
for clf in clfs:
clf.fit(X_train, y_train.ravel())
print(type(clf))
print('Training accuracy: ' + str(clf.score(X_train, y_train)))
print('Validation accuracy: ' + str(clf.score(X_test, y_test)))
coefs = {
'column': [X_train_cols[i] for i in range(len(X_train_cols))],
'coef': [clf.coef_[0,i] for i in range(len(X_train_cols))]
}
df_coefs = pd.DataFrame(coefs)
print(df_coefs.sort_values('coef', axis=0, ascending=False))
在for循环之前,我们导入LogisticRegression类,并将clf设置为LogisticRegression实例。训练和测试过程发生在for循环中。首先,我们使用fit()方法拟合模型(例如,确定最优的系数),使用训练数据。接下来,我们使用score()方法评估模型在训练数据和测试数据上的表现。
在for循环的后半部分,我们打印出每个特征的系数值。通常,系数离零更远的特征与结果的相关性最强(无论是正相关还是负相关)。然而,我们在训练之前没有对数据进行缩放,因此有可能更重要的预测变量由于没有适当缩放而会有较低的系数。
代码的输出应如下所示:
<class 'sklearn.linear_model.logistic.LogisticRegression'>
Training accuracy: 0.888978581423
Validation accuracy: 0.884261501211
coef column
346 2.825056 rfv_Symptoms_of_onset_of_labor
696 1.618454 rfv_Adverse_effect_of_drug_abuse
95 1.467790 rfv_Delusions_or_hallucinations
108 1.435026 rfv_Other_symptoms_or_problems_relating_to_psy...
688 1.287535 rfv_Suicide_attempt
895 1.265043 IMMEDR_01
520 1.264023 rfv_General_psychiatric_or_psychological_exami...
278 1.213235 rfv_Jaundice
712 1.139245 rfv_For_other_and_unspecified_test_results
469 1.084806 rfv_Other_heart_disease
...
首先,让我们讨论一下训练集和测试集的表现。它们非常接近,这表明模型没有过拟合训练集。准确率约为 88%,与研究中的表现(Cameron 等人,2015 年)相当,用于预测急诊科状态。
查看系数后,我们可以确认它们符合直觉。系数最高的特征与怀孕中的分娩开始相关;我们都知道,分娩通常会导致住院。许多与严重精神疾病相关的特征几乎总是会导致住院,因为患者对自己或他人构成风险。IMMEDR_1特征的系数也很高;请记住,这个特征对应的是分诊量表中的 1,表示最紧急的情况:
...
898 -0.839861 IMMEDR_04
823 -0.848631 BEDDATA_03
625 -0.873828 rfv_Hand_and_fingers
371 -0.960739 rfv_Skin_rash
188 -0.963524 rfv_Earache_pain_
217 -0.968058 rfv_Soreness
899 -1.019763 IMMEDR_05
604 -1.075670 rfv_Suture__insertion_removal
235 -1.140021 rfv_Toothache
30 -1.692650 LEFTATRI
相反,滚动到页面底部会显示一些与入院呈负相关的特征。比如牙痛和需要拆除缝线的情况出现在这里,它们不太可能导致入院,因为这些情况并不是紧急问题。
我们已经训练了第一个模型。让我们看看一些更复杂的模型是否能带来改进。
随机森林
scikit-learn的一个便捷特性是,许多分类器具有相同的方法,因此可以互换使用模型。在下面的代码中,我们看到,当我们使用RandomForestClassifier类的fit()和score()方法来训练模型和评估其性能时:
from sklearn.ensemble import RandomForestClassifier
clfs_rf = [RandomForestClassifier(n_estimators=100)]
for clf in clfs_rf:
clf.fit(X_train, y_train.ravel())
print(type(clf))
print('Training accuracy: ' + str(clf.score(X_train, y_train)))
print('Validation accuracy: ' + str(clf.score(X_test, y_test)))
imps = {
'column': [X_train_cols[i] for i in range(len(X_train_cols))],
'imp': [clf.feature_importances_[i] for i in range(len(X_train_cols))]
}
df_imps = pd.DataFrame(imps)
print(df_imps.sort_values('imp', axis=0, ascending=False))
输出应该类似于以下内容:
<class 'sklearn.ensemble.forest.RandomForestClassifier'>
Training accuracy: 1.0
Validation accuracy: 0.885391444713
column imp
1 AGE 0.039517
13 PULSE 0.028348
15 BPSYS 0.026833
12 TEMPF 0.025898
16 BPDIAS 0.025844
0 WAITTIME 0.025111
14 RESPR 0.021329
17 POPCT 0.020407
29 TOTCHRON 0.018417
896 IMMEDR_02 0.016714
...
这次的验证准确率与逻辑回归模型相似,约为 88%。然而,训练数据上的准确率为 100%,这表明我们对模型进行了过拟合。我们将在本章末尾讨论一些改善模型的潜在方法。
查看特征重要性时,结果再次符合逻辑。这次,生命体征似乎是最重要的预测因子,还有年龄预测因子。滚动到页面底部显示了那些对预测没有影响的特征。请注意,与回归系数不同,在随机森林中,变量重要性的衡量标准不仅仅是系数的大小,还包括变量为正的频率;这可能解释了为什么IMMEDR_02的重要性排名高于IMMEDR_01。
神经网络
最后,我们来到了神经网络模型。请注意,我们的训练集只有大约 18,000 个样本;最成功的神经网络模型(例如,“深度学习”模型)通常使用数百万甚至数十亿个样本。尽管如此,让我们看看我们的神经网络模型的表现如何。对于神经网络,建议对数据进行适当缩放(例如,使其标准分布,均值为 0,标准差为 1)。我们使用StandardScaler类来完成这一操作:
from sklearn.preprocessing import StandardScaler
from sklearn.neural_network import MLPClassifier
# Scale data
scaler = StandardScaler()
scaler.fit(X_train)
X_train_Tx = scaler.transform(X_train)
X_test_Tx = scaler.transform(X_test)
# Fit models that require scaling (e.g. neural networks)
hl_sizes = [150,100,80,60,40,20]
nn_clfs = [MLPClassifier(hidden_layer_sizes=(size,), random_state=2345, verbose=True) for size in hl_sizes]
for num, nn_clf in enumerate(nn_clfs):
print(str(hl_sizes[num]) + '-unit network:')
nn_clf.fit(X_train_Tx, y_train.ravel())
print('Training accuracy: ' + str(nn_clf.score(X_train_Tx, y_train)))
print('Validation accuracy: ' + str(nn_clf.score(X_test_Tx, y_test)))
一旦你运行上述单元格,你将看到迭代完成,一旦迭代未能带来模型的改进,训练将停止,并打印出准确率。在我们的运行中,具有 150 个隐藏层单元的模型的验证准确率为 87%,高于其他隐藏层大小。
使用模型进行预测
我们已经完成了数据预处理、模型的构建和评分。AUC 与之前学术研究中预测 ED 结果的报告相似(见 Cameron 等,2015 年)。
下一步是保存并部署模型,并使用它进行实时预测。幸运的是,scikit-learn 库中的所有分类器都包含几个用于进行预测的函数:
-
对于大多数分类器,
predict()函数接受一个包含未标记数据的矩阵 X 作为输入,并简单地返回类预测结果,不包含其他信息。 -
predict_proba()函数接受一个包含未标记数据的矩阵 X 作为输入,并返回观察值属于各个类别的概率。这些概率对于每个观察值应该加起来等于1。 -
predict_log_proba()函数与predict_proba()函数类似,只不过它返回的是观察值属于每个类别的对数概率。
记住以下重要事实:在进行预测时,未标记的数据必须以与训练数据预处理相同的方式进行预处理。这包括:
-
列的添加与删除
-
列变换
-
缺失值的填充
-
缩放和中心化
-
独热编码
仅仅是一个没有正确预处理的列,可能会对模型预测产生极大的负面影响。
改进我们的模型
尽管在本章中我们构建了一个初步模型,其性能与学术研究中所报告的类似,但仍有改进的空间。以下是一些改善模型的想法,我们留给读者去实现这些建议以及其他可能的技巧或方法,以提高性能。你的性能能达到多高呢?
首先,当前的训练数据有大量的列。几乎总是会执行某种特征选择,尤其是对于逻辑回归和随机森林模型。对于逻辑回归,常见的特征选择方法包括:
-
使用具有最高系数的若干个预测变量
-
使用具有最低 p-值的若干个预测变量
-
使用 lasso 正则化并移除系数变为零的预测变量
-
使用贪心算法,例如前向或后向逐步逻辑回归,根据规则系统地移除/添加预测变量
-
使用暴力算法,例如最佳子集逻辑回归,测试给定数量的预测变量的所有排列/组合。
对于随机森林,使用变量重要性并选择具有最高重要性的若干个预测变量是非常常见的,进行网格搜索也是如此。
神经网络有其独特的改进技术。
-
一方面,更多的数据总是好的,特别是在神经网络中。
-
使用的具体优化算法可能会影响性能。
-
在这个例子中,我们的神经网络只有一个隐藏层。在行业中,具有多个隐藏层的模型变得越来越常见(尽管它们可能需要较长时间进行训练)。
-
使用的具体非线性激活函数也可能影响模型的性能。
总结
在本章中,我们构建了一个用于预测急诊科结果的预测模型。尽管医疗领域中有许多机器学习问题,但本练习展示了在预处理医疗数据、训练和评分模型以及使用无标签数据进行预测时通常会遇到的问题。本章标志着本书编码部分的结束。
既然我们已经亲自看到了预测模型的构建,接下来合乎逻辑的问题是,预测模型与传统统计风险评分在临床结果预测方面相比表现如何。我们将在下一章探讨这个问题。
参考文献及进一步阅读
Cameron A, Rodgers K, Ireland A 等(2015)。一项简单工具用于预测分诊时的入院情况。Emerg Med J 2015;32:174-179。
Futoma J, Morris J, Lucas J(2015)。预测早期住院再入院的模型比较。生物医学信息学杂志 56: 229-238。
第八章:医疗保健预测模型 – 综述
本章面向所有读者,将传统的风险评分模型(该模型通常用于医疗保健领域)与类似于第七章《医疗保健中的预测模型》一章中介绍的机器学习模型理论及特性相结合。如果你来自数据科学背景,本章将是一个很好的入门,帮助你了解一些广泛使用的临床风险评分,并指出哪些特征应该包含在你的模型中,无论是一般模型还是特定疾病的模型。如果你来自医疗保健背景,本章将回顾一些临床风险评分,并解释机器学习算法如何增强传统的风险评估。
医疗保健预测分析 – 最前沿
正如我们在第三章《机器学习基础》中提到的那样,医疗保健对复杂风险因素评估并不陌生。几乎每种主要疾病,都有几个风险评分模型被广泛应用于医生评估患病风险或患病后导致的致病/死亡风险。当我们使用“风险评分”一词时,我们通常指的是标准表格,在这些表格中,风险因素被赋予点值,所有风险因素的点数相加,给出基于总和的整体风险。这些评分系统在医学中广泛使用;有趣的是,其中许多基于涉及逻辑回归模型的研究(类似于第七章《医疗保健中的预测模型》中开发的模型)。过去几十年最关键的问题是,机器学习能否改善我们预测个体是否患病、该疾病需要多少护理以及患者是否会在特定时间内因该疾病而死亡的能力。
本章将解决这个问题。我们通过几个在发达国家已开发风险评分的主要致病和死亡原因来组织本章内容。这些领域包括整体心血管风险、充血性心力衰竭、癌症以及全因再入院。前三个领域是主要的死亡原因,第四个领域是衡量医疗质量的常见方式。随后,我们将探讨相关的机器学习文献,看看机器学习是否改善了该疾病的传统风险评估。通过本章的学习,你将对机器学习如何提升疾病预测有一个透彻的理解。
整体心血管风险
我们首先从整体心血管风险评估开始,因为它是个人健康中最重要的领域之一,而且心血管风险因素的探索有着悠久而丰富的历史。
心血管风险是指患上心血管疾病的风险。心血管疾病(CVD)是指由于动脉变窄和/或堵塞,导致血液供应到组织的循环系统功能障碍,这一过程被称为动脉粥样硬化。它涵盖了一系列心脏疾病,包括以下几种:
-
冠状动脉疾病(CAD):当供应心脏血液的血管因动脉粥样硬化而变窄时,发生冠状动脉疾病。冠状动脉疾病是致命的,因为它可能导致冠状动脉的突然堵塞,这被称为心肌梗死(或心脏病发作)。
-
充血性心力衰竭(CHF):这是指心脏无法将血液泵送到身体其他部位。它是由冠状动脉疾病长期影响心脏所致。其发生比心肌梗死更为渐进,但其过程往往以突然恶化和住院为特征,最终导致死亡。
-
外周血管疾病(PVD):当供应手臂或腿部的动脉变窄并发生阻塞时,称为外周血管疾病,这可能导致诸如疼痛(称为跛行)等问题症状,甚至可能导致截肢。
-
脑血管疾病,即供应大脑的血管发生动脉粥样硬化:这使个体更容易发生缺血性和出血性中风。中风是指血液供应到大脑被切断,这可能导致死亡,也可能导致严重的后遗症。
现在你知道了什么是心血管疾病(CVD),你还应该了解它对人类的破坏性影响。全球范围内,心血管疾病是导致发病率和死亡率的主要原因(Weng 等,2017)。仅充血性心力衰竭(CHF)就与 3-5%的住院人数相关,并且是医疗专业人员住院的主要原因,占发达国家医疗支出的高达 2%(Tripoliti 等,2016)。
尽管自 20 世纪初以来,心血管疾病一直是美国残疾和死亡的主要原因,但在 1940 年代,人们仍然不知道其成因。事实上,在那个时期,关于心血管疾病(以及一般疾病)的风险和预防几乎没有了解。当时,人们认为心血管疾病是任何患病者的命运,与生活方式的选择无关。
弗雷明汉风险评分
1948 年,美国国家心脏研究所与波士顿大学合作,启动了一个雄心勃勃的项目——Framingham 心脏研究。其目标是找出导致 CVD 的因素。1948 年,来自马萨诸塞州 Framingham 镇的 5209 名男女被招募参与,这些人当时尚未明显受到 CVD 的影响(Framingham 心脏研究,2018a)。每隔 2 年,这些人都要接受详细的病史记录、体检和实验室检测。随着时间的推移,新的患者群体不断加入,受试者也继续每 2 年返回进行评估,直到今天。
通过这项长期的前瞻性研究,心血管疾病(CVD)的风险因素首次被确定。吸烟与 CVD 之间的关联首次在 1960 年被报道(Framingham 心脏研究,2018b)。随后,胆固醇、高血压和糖尿病最终也与 CVD 有关联。从 1990 年代开始,开始发布特定类型 CVD(例如,心肌梗死、外周血管疾病、慢性心力衰竭)的风险评分。2008 年,发布了普遍适用的 Framingham 风险评分。Framingham 风险评分根据五个主要 CVD 风险因素:年龄、高血压、胆固醇水平、吸烟状态和糖尿病,给个体分配一个在 10 年内发生 CVD 事件的风险评分。以下是女性的一般心血管风险评分标准摘要(D'Agostino 等,2008)。
男性的标准与女性类似,但分值略有不同:
| 分数 | 年龄(岁) | HDL 胆固醇 | 总胆固醇 | 未经治疗的 SBP | 治疗中的 SBP | 吸烟者 | 糖尿病患者 |
|---|---|---|---|---|---|---|---|
| -3 | <120 | ||||||
| -2 | 60+ | ||||||
| -1 | 50-59 | <120 | |||||
| 0 | 30-34 | 45-49 | <160 | 120-129 | 否 | 否 | |
| 1 | 35-44 | 160-190 | 130-139 | ||||
| 2 | 35-39 | <35 | 140-149 | 120-129 | |||
| 3 | 200-239 | 130-139 | 是 | ||||
| 4 | 40-44 | 240-279 | 150-159 | 是 | |||
| 5 | 45-49 | 280+ | 160+ | 140-149 | |||
| 6 | 150-159 | ||||||
| 7 | 50-54 | 160+ | |||||
| 8 | 55-59 | ||||||
| 9 | 60-64 | ||||||
| 10 | 65-69 | ||||||
| 11 | 70-74 | ||||||
| 12 | 75+ |
如今,当我们去看医生时,这五个风险因素(其中四个是可以预防的)依然是医生们不断讲解的内容。
为了计算总评分,将六个风险因素(年龄、HDL 水平、总胆固醇、未经治疗的 SBP、治疗中的 SBP、吸烟者、糖尿病患者)的数值加在一起。下表显示了分值如何与 10 年风险评分对应(D'Agostino 等,2008):
| 分数 | 风险(%) | 分数 | 风险(%) | 分数 | 风险(%) |
|---|---|---|---|---|---|
| <-2 | <1 | 6 | 3.3 | 14 | 11.7 |
| -1 | 1.0 | 7 | 3.9 | 15 | 13.7 |
| 0 | 1.2 | 8 | 4.5 | 16 | 15.9 |
| 1 | 1.5 | 9 | 5.3 | 17 | 18.5 |
| 2 | 1.7 | 10 | 6.3 | 18 | 21.5 |
| 3 | 2.0 | 11 | 7.3 | 19 | 24.8 |
| 4 | 2.4 | 12 | 8.6 | 20 | 28.5 |
| 5 | 2.8 | 13 | 10.0 | 21 + | > 30 |
你可能会问,“这种疯狂背后有什么方法?”为了制定这些评分,研究的作者使用了 Cox 比例风险回归模型,它类似于逻辑回归,不同之处在于它不是确定变量与二元结果之间的关系,而是确定变量与事件发生前的时间量之间的关系。他们甚至计算了风险评分的C统计量(类似于第三章中讨论的曲线下面积,机器学习基础),结果为 0.76 到 0.79。这是一个非常好的评分,只需要通过患者病史、体检和一些简单的血液测试就能得到。
心血管风险与机器学习
如你所知,科学进步从不自满。当取得一个结果时,人们总会开始思考如何在此基础上进行改进。心血管风险评估也不例外。曾经被提出的关键问题包括以下几点:
-
有哪些其他的风险因素与《弗雷明汉风险评分》中的五个风险因素一样(或甚至更为重要)?
-
更新的机器学习算法能否超越统计模型,如回归分析,提供更高的辨识度和性能?
一项来自英国诺丁汉大学的研究探讨了这些问题(Weng et al., 2017)。这是一项前瞻性研究,监测了 2005 年到 2015 年间 378,256 名患者的电子病历。研究者使用了《弗雷明汉风险评分》中的 8 个风险因素的数据,以及从先前文献和与医生的咨询中认为与心血管风险相关的 22 个额外变量。这 22 个额外变量包括社会经济状态;包括肾脏病、关节炎和房颤等其他疾病的病史;如 C 反应蛋白和伽玛谷氨酰转移酶等较新的实验室检查;以及种族等信息。他们在患者数据上训练了四种类型的机器学习算法——逻辑回归、随机森林、神经网络和梯度提升机。四种算法的性能都优于基准风险预测算法;事实上,神经网络算法预测出了比现有算法多出 355 例正确的心血管事件。在查看重要变量时,虽然许多与《弗雷明汉标准》相似,但也有许多是新的。种族出现在所有算法的前三个重要变量中。社会经济状态(汤森德贫困指数)出现在四种算法的前十名中。慢性肾病也被发现与心血管风险相关。对于心血管风险,显然机器学习加深了我们对预测心脏事件的理解。
充血性心力衰竭
在总体心血管风险部分提到的所有心脏事件中,CHF 值得专门单独设立一个部分。这是由于三个主要原因:
-
CHF 是发达国家住院的最常见原因
-
其管理成本非常高,占总医疗支出的 2%左右
-
其诊断成本也非常高,需要进行昂贵的超声心动图检查,并由专业人员和医生进行解读和解释(Tripoliti 等,2016 年)
诊断充血性心力衰竭(CHF)
虽然在具有特定症状、风险因素、心电图表现和实验室结果的患者中,CHF 是可能的,但最终诊断只能通过超声心动图或心脏 MRI 来确立。超声心动图需要专业人员来进行测试,然后由专科医生(通常是心脏病专家或放射科医生)来解读结果,并评估心脏的泵血功能。这通常是通过估算射血分数(EF)来完成的,射血分数是指左心室在收缩过程中排出血液的比例。65%的 EF 被认为是正常的,40%表示心力衰竭,10-15%则见于 CHF 的晚期阶段。下图取自超声心动图,展示了心脏的四个腔室。你可以想象,使用声波产生的模糊图像来量化心脏功能可能是不可靠的:
心脏 MRI 虽然更昂贵,但在测量 EF 方面更为准确,并被视为 CHF 诊断的金标准;然而,它需要心脏病专家花费最多 20 分钟来解读单个扫描。在下图中,我们看到:
-
A) 展示心脏和用于心脏 MRI 成像平面的插图。
-
B) 心脏的三维血管造影图(血管造影是一种在注入染料后拍摄图像以更好地可视化血管的检查)。
-
C)、D) 和 E) 分别为正常、损伤和缺血性左心室的心脏 MRI 图像:
鉴于 CHF 诊断的复杂性,我们再次提出之前的问题:通过使用机器学习算法,是否可以发现新的风险因素,并在 CHF 诊断中实现更好的表现?
使用机器学习进行 CHF 检测
改进 CHF 检测的一个方法是避免使用昂贵且耗时的影像学检查。一项来自韩国启明大学的最新研究使用粗糙集、决策树和逻辑回归算法,并将其表现与医生的心脏病诊断进行比较,后者被作为金标准(Son 等,2012 年)。
粗糙集与最佳子集逻辑回归相似(例如,测试变量的子集以评估其信息量,并选择最具信息量的子集作为最终决策规则)。算法仅基于人口统计特征和实验室检查结果进行训练。该模型在区分充血性心力衰竭(CHF)与非 CHF 相关的呼吸急促时,达到了超过 97%的敏感性和 97%的特异性。这与人类表现极为接近,并且所用的数据、时间和资源大大减少。
我们应该提到的第二种方法是使用自动化算法读取用于诊断 CHF 的超声心动图和心脏 MRI 扫描。这一问题是 2015 年数据科学大赛的主题,该比赛由数据科学竞赛网站 Kaggle 和咨询公司 Booz Allen Hamilton 赞助。欲了解有关此竞赛的更多信息,请访问 Kaggle 竞赛网站:www.kaggle.com/c/second-annual-data-science-bowl。这显示了机器学习在医疗保健领域引起的关注。
机器学习在 CHF 中的其他应用
虽然人类疾病的检测和诊断始终是一个难题,但机器学习在 CHF 管理中的其他应用也不容忽视。这些应用在希腊 Ioannina 大学的一篇综述论文中有详细记录(Tripoliti 等,2017 年)。除了 CHF 检测外,该论文中涉及的与 CHF 管理相关的其他问题包括严重性评估、CHF 亚型分类、恶化和住院再入预测,以及死亡率。
癌症
在第二章,医疗基础设施中,我们列举了通过机器学习抗击癌症的几个原因,尽管癌症具有显著的全球发病率、死亡率和情感后果。 本节中,我们将更深入地探讨有关癌症的一些重要概念和背景,抗击癌症的潜在机器学习应用,癌症风险和生存模型中的重要特征,以及在乳腺癌领域已有的一些研究成果。
什么是癌症?
癌症可以描述为异常细胞的生长和繁殖。这些细胞与正常细胞的区别在于它们具有更强的繁殖能力,并且能够从正常细胞中夺取重要资源,如血液和营养。癌症通常分为良性和恶性两种类型。良性癌症意味着它局限在身体的某个局部区域,而恶性癌症则意味着它具有扩散到其他组织的能力。恶性癌症如果不治疗几乎总是会导致死亡,许多情况下即便治疗也难以避免死亡。死亡的进程依赖于多个因素,包括癌症的类型、发现时的临床分期、肿瘤的病理等级以及临床风险因素。良性癌症不像恶性癌症那样严重,尽管它们可能引发症状,且仍有可能导致死亡(例如,一种引起大脑高压的良性听神经瘤)。治疗通常包括化疗、放疗、生物制剂和/或肿瘤及周围组织和器官的外科切除。
正如你所知,癌症通常根据其首次出现的身体部位来分类。美国四种最致命的癌症类型是肺癌、乳腺癌、前列腺癌和结肠癌。
癌症的机器学习应用
有两篇综合性综述论文详细记录和总结了过去三十年内进行的癌症相关机器学习研究。第一篇综述论文来自加拿大阿尔伯塔大学,提供了 2006 年之前的相关研究的详细回顾(Cruz 和 Wishart, 2006)。第二篇则是来自希腊伊奥尼纳大学的较新论文(Kourou 等,2015)。这两篇论文很好地分解了癌症机器学习领域的子问题,并且与第二章《医疗基础》一章中讨论的一些子问题相匹配。这些问题包括:
-
癌症的早期检测/筛查:机器学习模型能否训练出来以识别在症状出现之前就有高风险发展为癌症的个体?
-
癌症诊断:机器学习如何帮助肿瘤学家/放射科医生做出癌症的明确诊断,并分类癌症的分期和分级?
-
癌症复发:对于已经通过初期治疗成功治愈的癌症患者,癌症复发的可能性有多大?
-
预期寿命和生存率:哪些患者的癌症可能导致死亡?哪些患者可能在五年后仍然存活?十年后呢?
-
肿瘤药物敏感性:哪些肿瘤可能对特定的癌症治疗(例如化疗、放疗、生物制剂或手术)产生反应?
癌症的重要特征
一些变量对于癌症相关机器学习的应用尤为重要。让我们现在来看一下这些变量。
常规临床数据
在所有患者的电子病历中常规可获得的临床数据尤为宝贵,因为这些数据成本低廉,且可以轻松纳入回顾性建模研究。这样的临床数据汇总在下表中。需要注意的是,这些规则有许多例外;例如,虽然高龄是大多数癌症的一个重要危险因素,但骨癌和一些白血病往往最常见于儿童(National Cancer Institute, 2015)。国家癌症研究所是有关癌症临床危险因素更详细分解的优秀资源(www.cancer.gov):
| 危险因素 | 增加癌症风险 | 减少癌症风险 |
|---|---|---|
| 老年 | × | |
| 家族史阳性 | × | |
| 高脂肪饮食 | × | |
| 高纤维饮食 | × | |
| 高水果和蔬菜饮食 | × | |
| 肥胖 | × | |
| 烟草使用 | × | |
| 酒精使用 | × | |
| 日晒 | × |
癌症特异性临床数据
在肿瘤的原发部位被确定后,几乎每个肿瘤都可以根据临床和病理学进一步分为亚型。肿瘤的临床分期对肿瘤的扩展程度进行分类。TNM 分期系统是最常见的;在这个系统下,分期是根据以下三个因素确定的:
-
肿瘤的大小(T)
-
附近淋巴结的受累情况(N)
-
肿瘤向其他部位的转移(M)
通常,生存率是根据临床分期来给出的,而临床分期是根据 TNM 分期标准来确定的,这对每个肿瘤有所不同。
肿瘤的病理分级关注的是其细胞特征。病理学家在设置肿瘤分级时所关注的事项包括肿瘤细胞外观与正常细胞外观的相似性(分化)和正常细胞器官的存在(National Cancer Institute, 2015)。
成像数据
来自 X 光、CT 扫描和 MRI 的放射影像学发现也可以在与癌症严重程度和预后相关的模型中使用。稍后在本节中,我们将探讨一项乳腺癌研究,该研究训练了一个神经网络,分析乳腺 X 光片特征,如乳腺肿瘤微钙化的存在和形状,以及周围皮肤特征(Ayer et al., 2010)。
基因组数据
尽管基因结果在前瞻性研究中并不总是容易获得,回顾性研究中也没有这些数据,但在有的情况下它们是重要的预测因素。具体特征包括患者 DNA 中单核苷酸多态性(SNPs;点突变)的存在,以及某些基因的存在(如遗传性乳腺癌中的 BRCA1)(Morin et al. Harrison's, 2005)。
蛋白质组数据
与肿瘤相关的特定蛋白质的存在也会影响风险。重要蛋白质的例子包括肿瘤相关生物标志物(例如,胰腺癌中的 CA 19-9)和激素(例如,乳腺癌中的 HER-2/neu)(Longo 等,2005)。
一个例子——乳腺癌预测
让我们看看机器学习如何增强乳腺癌的筛查和诊断——乳腺癌是全球最常见的癌症之一。
乳腺癌的传统筛查
乳腺癌筛查是一个复杂的问题。风险评分通常表现不佳;关于 Gail 模型在乳腺癌风险预测中的表现的文献综述发现,该模型的 AUC 值通常在 0.55 到 0.65 之间(一个随机分类器的 AUC 值为 0.5)(Wang 等,2018 年)。
乳腺癌筛查的下一种最低侵入性测试是临床乳腺检查或自我乳腺检查。乳腺检查的敏感性和特异性因年龄、乳腺密度和研究环境而异;有研究发现,敏感性最低可低至 57%,尽管同一研究发现特异性为 88%(Baines 等,1989)。因为好的筛查测试具有较高的敏感性,普遍认为仅凭体检进行乳腺癌筛查是不充分的。
影像学检查是乳腺癌筛查中的下一种最低侵入性测试。用于乳腺癌筛查的影像学方式包括乳腺 X 光检查、MRI 和超声波。2016 年,美国预防服务工作组(USPSTF)建议 50 岁及以上女性每两年做一次乳腺 X 光检查(见下图,图示为乳腺 X 光检查,显示出一个被诊断为胶样癌的白色区域;美国国家癌症研究所,1990 年),因为该年龄段的乳腺 X 光检查具有较高的敏感性(77%至 95%)和特异性(94%至 97%),并且它们对患者的潜在危害较低(美国预防服务工作组,2016 年)。
活检是乳腺癌检测中最具侵入性的选项,实际上,当筛查测试结果为阳性时,活检通常用于确诊。
乳腺癌筛查与机器学习
乳腺癌研究中的机器学习文献非常丰富(Cruz 和 Wishart,2006 年;Kourou 等,2015 年)。在这里,我们总结了一项研究,强调了机器学习在乳腺癌诊断中的潜力,尤其是当它与乳腺 X 光检查和电子病历(EMR)结合使用时。该研究来自威斯康星大学麦迪逊分校,包含了 48,744 张乳腺 X 光检查图像(Ayer 等,2010 年)。
对每个乳腺 X 光检查,收集了 36 个类别变量的信息,包括临床数据(年龄、既往病史、家族史)和乳腺 X 光发现,如肿瘤质量特征、周围皮肤和乳头特征、淋巴结检查以及钙化特征。一个由 1,000 个隐藏层组成的人工神经网络经过训练,并将生成的标签与良性与恶性的真实标签进行了比较。八名放射科医师也审核了不同数量的乳腺 X 光图像,并将其分类为良性或恶性。神经网络的总 AUC 为 0.965,而放射科医师的 AUC 为 0.939。结合我们在最后一章中将阅读的其他研究,这项研究展示了机器学习如何作为对抗癌症的有效工具:
预测再入院
预测所有原因的患者再入院可能性超出了典型临床医生的知识范围,因为它与特定的器官系统或疾病无关。然而,在医疗保健领域,这正变得越来越重要,因为可以预防的住院再入院是美国及其他国家医疗支出增加的主要原因之一。我们已经讨论了预测医院再入院的动机和理由,以及美国政府的医院再入院减少计划(HRRP),该内容在第六章,衡量医疗质量中有详细介绍。现在,让我们回顾一下机器学习算法如何用来增强简单的再入院风险评分。
LACE 和住院评分
最著名的再入院风险评分是 LACE 评分,该评分由加拿大研究人员于 2010 年开发(van Walraven 等人,2010)。"LACE"代表用于计算该评分的四个预测因子,完整的评分计算范围为 0-19,具体见下表。查尔森合并症指数是一个根据患者过往病史中某些疾病的存在为其打分的评分系统,包括心肌梗死、癌症、CHF(充血性心力衰竭)和 HIV 感染:
| 成分 | 属性 | 数值 | 分数 |
|---|---|---|---|
| L | 住院天数 | <1 | 0 |
| 1 | 1 | ||
| 2 | 2 | ||
| 3 | 3 | ||
| 4-6 | 4 | ||
| 7-13 | 5 | ||
| >13 | 7 | ||
| A | 急性(入院紧急性) | 是 | 3 |
| C | 合并症(查尔森合并症指数) | 0 | 0 |
| 1 | 1 | ||
| 2 | 2 | ||
| 3 | 3 | ||
| >3 | 5 | ||
| E | 最近 6 个月的急诊就诊 | 0 | 0 |
| 1 | 1 | ||
| 2 | 2 | ||
| 3 | 3 | ||
| <3 | 4 |
为了得出这个指数,研究作者将认为与再入院风险相关的十二个以上的变量输入到一个多变量逻辑回归模型中,针对 2,393 名患者得出了这四个显著的预测变量。然后,他们开发了评分系统,并使用来自加拿大患者数据库的 100 万个记录进行外部验证。他们报告的C统计量预测 30 天内的早期死亡或紧急再入院为 0.684。
另一个医院再入院风险评分的例子是较近期开发的 HOSPITAL 评分(Donze 等,2013;Donze 等,2016)。同样,“HOSPITAL”代表该评分中使用的七个预测因子,具体列在以下表格中:
| 组件 | 属性 | 值 | 分数 |
|---|---|---|---|
| H | 出院时血红蛋白 < 12 g/dL | 是 | 1 |
| O | 肿瘤科出院 | 是 | 2 |
| S | 出院时钠 < 135 mmol/L | 是 | 1 |
| P | 住院期间是否有手术 | 是 | 1 |
| IT | 入院类型:紧急 | 是 | 1 |
| A | 前一年住院次数 | 0-1 | 0 |
| 2-5 | 2 | ||
| >5 | 5 | ||
| L | 住院超过 5 天 | 是 | 2 |
HOSPITAL 评分范围为 0-13 分。该评分是根据与医院再入院独立相关的七个因素得出的,还使用了多变量逻辑回归模型(Donze 等,2013)。该评分通过四个国家的 117,065 个出院数据进行外部验证,作者报告了 C 统计量为 0.72(Donze 等,2016)。
再入院建模
让我们看看最近的两项研究,这些研究应用了机器学习来识别再入院风险问题。
第一项研究来自杜克大学(Futoma 等,2014)。该研究分析了来自新西兰的 330 万次住院数据。对于每次住院,使用了人口统计信息、背景信息、诊断相关组(DRG)代码和国际疾病分类(ICD)诊断与手术代码。然后,将该矩阵输入到六种不同的机器学习算法中:
-
逻辑回归
-
使用多步骤变量选择的逻辑回归
-
带惩罚的逻辑回归
-
随机森林
-
支持向量机
-
深度学习
在前五种方法中,随机森林的表现最佳,达到了 AUC 0.684。深度学习模型被用来预测五个患者群体的再入院:肺炎、慢性阻塞性肺疾病(COPD)、充血性心力衰竭(CHF)、心肌梗死(MI)和全髋/膝关节置换术。肺炎群体的表现最佳,AUC 为 0.734。需要注意的是,未对 LACE 或 HOSPITAL 风险评分进行直接比较。
第二项研究来自 Advocate Health Care 医院系统(Tong 等人,2016)。研究包括 162,466 例入院(例如初始)病例。对于每一例入院,收集了 19 个数据元素,包括四个 LACE 评分组件和其他 15 个电子病历变量,如既往病史、先前临床接触、就业状态和实验室结果。研究人员在这些数据上训练了三种不同的机器学习模型:
-
带有逐步前进-后退变量选择的逻辑回归
-
带有 Lasso 正则化的逻辑回归
-
提升法
他们发现,当使用 80,000 例入院病例作为训练/验证集时,LACE 模型的 AUC 约为 0.65,而三种机器学习模型的 AUC 均约为 0.73。结果表明,LACE 评分中使用的四个变量之外的其他变量具有重要意义,并进一步加强了机器学习在医疗保健中的应用。
其他条件和事件
还有许多其他疾病,机器学习算法已被用于改善风险评估和预测,超越传统的风险评分。慢性阻塞性肺病(COPD)、肺炎、脓毒症、中风和痴呆症只是其中的一些例子。
摘要
在本章中,我们回顾了不同类型的医疗保健模型及其使用的特征,以实现效果。我们在讨论中省略了一些医疗保健领域的最新趋势,包括使用新兴技术,如社交媒体、物联网和深度学习算法,进一步推动医疗预测的界限。我们将在下一章中讨论这些趋势。
参考文献与进一步阅读
Ayer T, Alagoz O, Chhatwal J, Shavlik JW, Kahn Jr. CE, Burnside ES (2010)。使用人工神经网络重新审视乳腺癌风险评估:区分度和校准。Cancer 116 (14): 3310-3321。
Baines CJ (1989)。乳房自我检查。Cancer 64(12 Suppl): 2661-2663。
Cruz JA, Wishart DS (2006)。机器学习在癌症预测与预后中的应用。Cancer Informatics 2: 59-77。
D'Agostino RB, Vasan RS, Pencina MJ, Wolf PA, Cobain M, Massaro JM, Kannel WB (2008)。初级保健中的一般心血管风险评估:Framingham 心脏研究。Circulation 117 (6): 743-753。
Donze J, Aujesky D, Williams D, Schnipper JL (2013)。医学患者的 30 天潜在可避免再入院:预测模型的推导与验证。JAMA Intern Med 173(8): 632-638。
Donze JD, Williams MV, Robinson EJ 等人(2016)。"HOSPITAL"评分在国际上预测医疗患者 30 天潜在可避免再入院的有效性。JAMA Intern Med 176(4): 496-502。
Framingham Heart Study (2018a)。Framingham Heart Study 的历史。www.framinghamheartstudy.org/fhs-about/history/。访问时间:2018 年 6 月 16 日。
Framingham 心脏研究 (2018b). 研究里程碑。www.framinghamheartstudy.org/fhs-about/research-milestones/。访问日期:2018 年 6 月 16 日。
Futoma J, Morris J, Lucas J (2015). 早期医院再入院预测模型比较。生物医学信息学期刊 56: 229-238。
Kourou K, Exarchos TP, Exarchos KP, Karamouzis MV, Fotiadis DI (2015). 机器学习在癌症预后和预测中的应用。计算与结构生物技术期刊 13: 8-17。
Lenes K (2005). 文件:心脏超声图像 4 腔.jpg。commons.wikimedia.org/wiki/File:Echocardiogram_4chambers.jpg。访问日期:2018 年 6 月 23 日。
Longo DL (2005). "癌症患者的处理方法" 见 Kasper DL, Braunwald E, Fauci AS, Hauser SL, Longo DL, Jameson JL 主编,哈里森内科学原理,第 16 版。纽约:McGraw-Hill。
Morin PJ, Trent JM, Collins FS, Vogelstein B (2005). "癌症遗传学。" 见 Kasper DL, Braunwald E, Fauci AS, Hauser SL, Longo DL, Jameson JL 主编,哈里森内科学原理,第 16 版。纽约:McGraw-Hill。
美国国家癌症研究所 (1990). 显示癌症的乳腺 X 光片。未知摄影师,美国放射学会。commons.wikimedia.org/wiki/File:Mammogram_showing_cancer.jpg。访问日期:2018 年 6 月 23 日。
美国国家癌症研究所 (2015a). 乳腺癌筛查(PDQ®)-健康专业版。www.cancer.gov/types/breast/hp/breast-screening-pdq#section/all。访问日期:2018 年 6 月 23 日。
美国国家癌症研究所 (2015b). 癌症的风险因素。www.cancer.gov/about-cancer/causes-prevention/risk。访问日期:2018 年 6 月 23 日。
美国国家心肺血液研究所 (2013). 文件:Cardiac_Mri.jpg。commons.wikimedia.org/wiki/File:Cardiac_mri.jpg。访问日期:2018 年 6 月 23 日。
Son C, Kim Y, Kim H, Park H, Kim M (2012). 基于粗集和决策树方法的充血性心力衰竭早期诊断决策模型。生物医学信息学期刊 45: 999-1008。
Tong L, Erdmann C, Daldalian M, Li J, Esposito T (2016). 30 天全因非择期再入院风险预测模型方法比较。BMC 医学研究方法论 2016(16): 26。
Tripoliti EE, Papadopoulos TG, Karanasiou GS, Naka KK, Fotiadis DI (2017). 心力衰竭:通过机器学习技术诊断、严重程度评估及不良事件预测。计算与结构生物技术期刊 15: 26-47。
美国预防服务工作组 (2016). 最终建议声明:乳腺癌:筛查。 www.uspreventiveservicestaskforce.org/Page/Document/RecommendationStatementFinal/breast-cancer-screening1。访问日期:2018 年 6 月 23 日。
van Walraven C, Dhalla IA, Bell C, Etchells E, Stiell IG, Zarnke K, Austin PC, Forster AJ (2010). 推导和验证一个预测早期死亡或在出院后重新入院的指数,以便预测从医院到社区的出院情况。加拿大医学会期刊 182(6): 551-557。
Wang X, Huang Y, Li L, Dai H, Song F, Chen K (2018). Gail 模型在预测乳腺癌风险中的表现评估:一项系统评价和元分析,结合试验顺序分析。乳腺癌研究 20: 18。
Weng SF, Reps J, Kai J, Garibaldi JM, Qureshi N (2017). 机器学习能否通过常规临床数据提高心血管风险预测? PLOS One 12(4): e0174944. doi.org/10.1371/journal.pone.0174944