数据科学在现代医学中的角色是什么?

数据科学在现代医学中的重要作用是什么?

介绍

随着人工智能的崛起,我们越来越依赖数据驱动的决策,以简化职业人士的生活。无论是供应链物流还是为客户批准贷款,数据都是关键。在医学领域利用数据科学的力量可以取得突破性的结果。通过分析大量的现代医学数据,数据科学家可以发现可以导致发现和治疗的模式。将数据科学整合到医疗领域中具有重塑医疗行业的潜力,这不仅是一个好主意,而且是必需品。

学习目标

在本文中,我们将探讨如何分析医学数据集,创建一个模型,该模型可以在面临特定诊断时预测患者应该服用哪种药物。听起来很有趣,所以让我们开始吧!

本文是数据科学博客马拉松的一部分。

数据集

我们将下载并使用来自Kaggle的开源数据集:

链接在这里

数据集包含:

  • 患者的年龄和性别
  • 患者的诊断
  • 用于治疗患者的抗生素
  • 抗生素的剂量(克)
  • 抗生素的应用途径
  • 抗生素的使用频率
  • 使用抗生素进行治疗的持续时间(天)
  • 抗生素的适应症

加载库和数据集

在这里,我们将导入我们的练习所需的相关库。然后将数据集加载到一个数据框中并查看几行。

import numpy as np # 线性代数import pandas as pd # 数据处理,CSV文件的I/O(例如,pd.read_csv)from wordcloud import WordCloud, STOPWORDS, ImageColorGeneratorimport matplotlib.pyplot as pltimport collectionsfrom sklearn.feature_extraction.text import CountVectorizerfrom sklearn.preprocessing import LabelEncoderfrom sklearn.model_selection import train_test_splitfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.metrics import confusion_matrix,classification_reportdf = pd.read_csv("/kaggle/input/hospital-antibiotics-usage/Hopsital Dataset.csv")print(df.shape)df.head()

输出:

列名相当直观,它们是有意义的。让我们通过查看一些统计数据来进一步了解。

基本统计信息

# "让我们看一些统计数据"df.info()

输出:

<class 'pandas.core.frame.DataFrame'>RangeIndex: 833 entries, 0 to 832Data columns (total 10 columns): #   Column              Non-Null Count  Dtype ---  ------              --------------  -----  0   Age                 833 non-null    object 1   Date of Data Entry  833 non-null    object 2   Gender              833 non-null    object 3   Diagnosis           833 non-null    object 4   Name of Drug        833 non-null    object 5   Dosage (gram)       833 non-null    object 6   Route               833 non-null    object 7   Frequency           833 non-null    object 8   Duration (days)     833 non-null    object 9   Indication          832 non-null    objectdtypes: object(10)memory usage: 65.2+ KB

观察结果:

  • 每个列都是字符串列
  • 让我们将年龄、剂量(克)和治疗持续时间(天)的列转换为数字。
  • 让我们还将数据输入日期转换为日期时间格式。
  • 指示有一个空值,其他列没有空值。

数据预处理

让我们清理一些列。在前一步中,我们看到所有列都是整数。所以,首先,我们将把年龄、剂量和疗程转换成数字值。同样地,我们将把数据输入日期转换为日期时间类型。而不是直接进行转换,我们将创建新的列,即我们将创建一个Age2列,它将是Age列的数值版本,依此类推。

df['Age2'] = pd.to_numeric(df['Age'],errors='coerce')df['Dosage (gram)2'] = pd.to_numeric(df['Dosage (gram)'],errors='coerce')df['Duration (days)2'] = pd.to_numeric(df['Duration (days)'],errors='coerce')df['Date of Data Entry2'] = pd.to_datetime(df['Date of Data Entry'],errors='coerce')df.info()

输出:

<class 'pandas.core.frame.DataFrame'>RangeIndex: 833 entries, 0 to 832Data columns (total 14 columns): #   Column               Non-Null Count  Dtype         ---  ------               --------------  -----          0   Age                  833 non-null    object         1   Date of Data Entry   833 non-null    object         2   Gender               833 non-null    object         3   Diagnosis            833 non-null    object         4   Name of Drug         833 non-null    object         5   Dosage (gram)        833 non-null    object         6   Route                833 non-null    object         7   Frequency            833 non-null    object         8   Duration (days)      833 non-null    object         9   Indication           832 non-null    object         10  Age2                 832 non-null    float64        11  Dosage (gram)2       831 non-null    float64        12  Duration (days)2     831 non-null    float64        13  Date of Data Entry2  831 non-null    datetime64[ns]dtypes: datetime64[ns](1), float64(3), object(10)memory usage: 91.2+ KB

转换后,我们看到有一些空值。让我们查看这些数据。

df[(df['Dosage (gram)2'].isnull())  | (df['Duration (days)2'].isnull())  | (df['Age2'].isnull())  | (df['Date of Data Entry2'].isnull())  ]#import csv

输出:

看起来数据集中有一些垃圾值。让我们把它们删除。

现在,我们还将把新列的值替换到旧列中,并且删除新创建的列

df = df[~((df['Dosage (gram)2'].isnull())  | (df['Duration (days)2'].isnull())  | (df['Age2'].isnull())  | (df['Date of Data Entry2'].isnull()))  ]df['Age'] = df['Age2'].astype('int')df['Dosage (gram)'] = df['Dosage (gram)2']df['Date of Data Entry'] = df['Date of Data Entry2']df['Duration (days)'] = df['Duration (days)2'].astype('int')df = df.drop(['Age2','Dosage (gram)2','Date of Data Entry2','Duration (days)2'],axis=1)print(df.shape)df.head()

输出:

查看一些统计数据

现在,我们将查看所有列的一些统计数据。我们将使用describe函数并传递include参数以获取所有列的值统计数据。让我们来看看

df.describe(include='all')

输出:

观察结果:

  • 我们可以看到年龄为1,最大值为90。
  • 同样地,通过观察数据输入列 – 我们知道它包含了2019年12月19日下午1点到7点之间的数据。
  • 性别有2个不同的值。
  • 剂量最小值为0.02克,最大重量为960克(似乎有些极端)。
  • 持续时间的值为1,最大值为28天。
  • 诊断、药物名称、途径、频率和指示列有不同的值。我们将对它们进行详细分析。

单变量分析 – 途径和频率列

在这里,我们将尝试分析途径(Route)和频率(Frequency)列。我们将使用value_counts()函数。

display(df['Route'].value_counts())print()df['Frequency'].value_counts()

输出:

RouteIV      534Oral    293IM        4Name: count, dtype: int64FrequencyBD     430TDS    283OD     110QID      8Name: count, dtype: int64

观察结果:

  • 药物治疗有3种不同的途径。最常用的途径是IV,最不常用的是IM。
  • 药物治疗有4种不同的频率。BD是最常用的药物治疗频率,最不常用的是QID。

单变量分析 – 诊断列

在这里,我们将分析诊断列。我们将使用value_counts()函数。

df['Diagnosis'].value_counts()

输出:

运行上述代码后,我们可以看到有263个不同的值。另外,我们可以看到每个患者可能有多个诊断(用逗号分隔)。因此,让我们尝试构建一个词云来更好地理解这一列。我们将尝试去除词云中的停用词,以过滤掉不必要的噪音。

text = " ".join(diagnosis for diagnosis in adf.Diagnosis)print ("所有诊断中共有{}个词。".format(len(text)))stopwords = set(STOPWORDS)# 生成词云图wordcloud = WordCloud(stopwords=stopwords, background_color="white").generate(text)# 显示生成的图像:# 使用matplotlib方法:plt.imshow(wordcloud, interpolation='bilinear')plt.axis("off")plt.show()

输出:

所有诊断中共有35359个词。

观察结果:

  • 所有诊断中共有35359个词。其中很多词可能是重复的。
  • 从词云中可以看出,胸部感染和结核肺似乎是更常见的诊断。
  • 我们还可以看到其他病症,如糖尿病、骨髓瘤等。

让我们来看一下前10个和后10个词/短语。我们将使用collections库中的Counter函数。

A = collections.Counter([i.strip().lower()   for i in text.split(',') if (i.strip().lower()) not in stopwords ])print('前10个词/短语')display(A.most_common(10))print('\n后10个词/短语')display(A.most_common()[-11:-1])

输出:

前 10 个词语/短语[('col', 77), ('chest infection', 68), ('ihd', 55), ('copd', 40), ('高血压', 38), ('ccf', 36), ('2 型糖尿病', 32), ('科奇肺', 28), ('ckd', 28), ('尿毒症性胃炎', 18)]最后 10 个词语/短语[('未控制良好的糖尿病合并尿路感染', 1), ('未控制良好的糖尿病引起感染性休克', 1), ('高血压肝炎', 1), ('尿路感染肝炎', 1), ('尿路感染尿路感染', 1), ('ccf ppt 由胸部感染导致的早期结肠炎', 1), ('手术脊髓', 1), ('血肿和截瘫', 1), ('带结肠炎的生育力划掉', 1), ('带有混乱的发热ccf', 1)]

观察:

  • 在前 10 个词语/短语中,结肠炎(col)胸部感染(chest infection)发生频率较高。77 位患者被诊断为结肠炎,68 位患者被诊断为胸部感染。
  • 在最后 10 个词语/短语中,我们看到每个列表中的词语/短语仅出现一次。

单变量 EDA 指示列

这里,我们将检查指示列。我们将使用 value_counts() 函数。

display(df['Indication'].value_counts())print()df['Indication'].value_counts(1)

输出:

指示胸部感染                92结肠炎                        32尿路感染                    302 型糖尿病                  25预防感染              22                           ..pad(lt u.l)                 1旧中风                   1晕厥发作              1欺骗感染              1蜈蚣咬伤             1Name: 计数, Length: 220, dtype: int64指示胸部感染                0.110843结肠炎                        0.038554尿路感染                    0.0361452 型糖尿病                  0.030120预防感染              0.026506                             ...   pad(lt u.l)                0.001205旧中风                 0.001205晕厥发作            0.001205欺骗感染            0.001205蜈蚣咬伤           0.001205Name: 比例, Length: 220, dtype: float64

我们看到指示列有 220 个不同的值。同时还存在一些拼写错误,比如 interstellar 和 internet。我们可以进行清理。在当前练习中,我们将考虑前 25% 的指示。

top_indications = df['Indication'].value_counts(1).reset_index()top_indications['cum_proportion'] = top_indications['proportion'].cumsum()top_indications = top_indications[top_indications['cum_proportion']<0.25]top_indications

输出:

我们将在双变量分析和建模练习中使用这个数据框。

单变量 EDA 药物名称列

这里,我们将审查该列。我们将使用 value_counts() 函数。

display(df['Name of Drug'].nunique())display(df['Name of Drug'].value_counts())

输出:

55药物名称头孢曲松                   221阿莫西林克拉维酸钾               162甲硝唑                        59头孢克洛                        58复方新诺明                      37克拉霉素                        32左氧氟沙星                      31阿莫西林+氟哌酸钠                 29头孢他啶                        24头孢吡肟                        14氯丁霉素                        12利福霉素                        10阿米卡星                          9头孢西丁                          9美罗培南                          8环丙沙星                          7庆大霉素                          5青霉素 V                          5利福平                            5阿齐霉素                          5西韦                             4米洛培坦                          4阿莫西林                          4链霉素                            4头孢他嗪                          4克拉替霉素                        4阿莫西林+氟洛沙星                 3头孢吡肟+舒巴坦                     3利奈唑胺                          3氧氟沙星                          3诺氟沙星                          3亚胺培南                          2氟哌酸                            2头孢曲松                          2头孢唑啉                          2头孢曲松+舒巴坦                   2头孢呋辛                          2哌拉西林+他唑巴坦               1阿莫西林+氟哌酸钠克拉维酸钾      1戊羟基异丙基黄嘌呤                  1甲硝唑                          1

观察:

  • 有55种独特的药物。
  • 再次发现拼写错误- ceftriaxone 与 ceftiaxone。独特药物计数可能较低。

让我们考虑这里的前5种药物。我们将创建一个名为cum_proportion的列,用于存储每种药物的累积比例。

top_drugs = (df['Name of Drug'].value_counts(1).reset_index())
top_drugs['cum_proportion'] = top_drugs['proportion'].cumsum()
top_drugs = top_drugs.head()
top_drugs

结果:

5种药物给了64.6%的病人。

注意:如果我们纠正拼写错误,这个比例可能超过64.6%。

我们将在后面的双变量分析和建模练习中使用这个数据框。

双变量分析指示与药物名称

在这里,我们将考虑我们创建的top_indications和top_drugs数据框,并尝试查看它们之间的分布,即我们比较前5种药物名称与前25%的指示。我们将使用pivot_table()函数。

(df[(df['Indication'].isin(top_indications['Indication'])) & (df['Name of Drug'].isin(top_drugs['Name of Drug']))].pivot_table(index='Indication',columns='Name of Drug',values='Age',aggfunc='count'))

结果:

观察:

  • 对于胸部感染,最常推荐的药物是co-amoxiclav,其次是ceftriaxone。
  • 对其他指示也可以得出类似的观察。

注意:这些药物是根据患者的年龄、性别、诊断和病史等多个因素开具的处方。

双变量分析指示与年龄

在这里,我们将尝试了解一些情况是否出现在年长的患者身上。我们将考虑top_indications,并检查年龄的平均值和中位数。

(df[df['Indication'].isin(top_indications['Indication'])].groupby('Indication')['Age'].agg(['mean','median','count']))

结果:

观察:

  • 指示- col预防感染年轻患者中更常见。
  • uti中年患者中观察到。
  • 指示:胸部感染2型糖尿病年长患者中更常见。

双变量分析药物名称与年龄

在这里,我们将尝试了解特定药物是否用于年长患者。我们将考虑top_drugs,并检查年龄的平均值和中位数。

(df[df['Name of Drug'].isin(top_drugs['Name of Drug'])].groupby('Name of Drug')['Age'].agg(['mean','median','count']).sort_values(by='median'))

结果:

观察:

  1. 年轻患者开的处方有septrincefixime
  2. 中年患者开的处方有ceftriaxone
  3. 年长患者开的处方有metronidazoleco-amoxiclav

建模方法

在这里,我们将尝试帮助药剂师或开药医生。问题陈述是根据诊断、年龄和性别列确定给患者开具哪种药物。

一些考虑和假设:

  1. 在这个练习中 – 我们只考虑前5种药物,并将其余的标记为其他。
  2. 所以,对于(诊断,年龄,性别)的每个值 – 目标是识别出将被推荐的药物。 基准模型是(1/6)= 16.67%的准确率。

让我们看看我们能否试图超越它。 我们将为建模过程创建数据帧的副本。

adf = df.copy()adf['Output'] = np.where(df['Name of Drug'].isin(top_drugs['Name of Drug']),                    df['Name of Drug'],'其他')adf['Output'].value_counts()

输出:

Output其他            294ceftriaxone      221co-amoxiclav     162metronidazole     59cefixime          58septrin           37Name: count, dtype: int64

我们之前也看到了这个分布。 这意味着类别不是均匀分布的。 在初始化模型时,我们需要记住这一点。

特征工程

我们将尝试捕捉诊断栏中更频繁的单词/ ngram。 我们将使用Count Vectorizer模块。

vectorizer = CountVectorizer(max_features = 150,stop_words = 'english',              ngram_range = (1,3))X = vectorizer.fit_transform(adf['Diagnosis'].str.lower())vectorizer.get_feature_names_out()

输出:

array(['脓肿', '急性', '急性胆管炎', '房颤', '急性肾损伤',       '急性肾损伤慢性肾损伤', '急性肾损伤慢性肾损伤转移性肾损伤', '酒精性', '贫血', '冠状动脉介入术', '咬伤',       '出血', '骨折', '癌', '肺炎', '充血性心力衰竭', '充血性心力衰竭胸部感染',       '充血性心力衰竭胸部感染充血性心力衰竭', '充血性心力衰竭胸部感染充血性心力衰竭肺结核',       '脑', '脑梗塞', '胸痛', '胸部感染',       '胸部感染预', '慢性', '慢性肾损伤', '慢性肾损伤胸部感染',       '慢性胆管炎', '慢性胆管炎肝转移癌', '慢性胆管炎肝转移癌门脉高压',       '慢性阻塞性肺病', '慢性阻塞性肺病胸部感染', '亚健康',       '亚健康过度', '亚健康过度呕吐', '糖尿病',       '疾病', '疾病肾', '疾病肾损伤', '糖尿病冠心病',       '糖尿病冠心病严重肾功能不全', '水肿', '积液', '脑病', '过度',       '过度呕吐', '过度呕吐尿毒症', '衰竭', '发热', '胃炎', '胃炎丙肝', '胃炎丙肝急性肾损伤', '胆管',       '胆管癌', '胆管癌急性肾损伤', '胆管癌肺结核', '心脏', '肝硬化', '肝硬化脑病', '肝炎', '高血压',       '高血压疾病', '高血压疾病肾', '冠心病', '功能不全',       '功能不全肺病', '功能不全肺病结核病', '增加', '增加肝肾功能', '梗死', '感染', '感染预',       '感染预糖尿病', '结核病', '结核病肺部',       '结核病肺部慢性阻塞性肺病', '结核病肺部慢性阻塞性肺病充血性心力衰竭',       '左侧', '腿', '肝功能', '肺', '肺部慢性阻塞性肺病', '肺部慢性阻塞性肺病胸痛',       '肺部慢性阻塞性肺病胸痛', '骨髓', '多发',       '多发骨髓瘤', '多发骨髓瘤慢性肾损伤', '骨髓瘤', '骨髓瘤慢性肾损伤', '新的', '旧的', '胸腔', '胸腔积液',       '肺炎', '门脉', '门脉高压', '预',       '预糖尿病', '肺', '肺水肿', '肾',       '肾损伤', '肾损伤肝', '肾损伤肝结核', '转移性肾', '右', '肺静脉', '肺静脉期', '肺静脉期高血压', '败血症', '败血症休克', '严重', '严重贫血', '严重贫血多发', '休克',       '侧', '蛇', '蛇咬伤', '期', '期高血压',       '期高血压疾病', '中风', '结核', '类型', '类型糖尿病',       '类型糖尿病冠心病', '尿毒', '尿毒胃炎',       '尿毒胃炎丙肝', '尿毒胃炎丙肝急性肾损伤', '尿路感染', '尿路感染类型', '尿路感染类型糖尿病',       '呕吐', '呕吐尿毒', '呕吐尿毒胃炎'],      dtype=object)

上面的列表展示了在删除停用词后,在诊断列中观察到的前150个ngrams。

数据集创建

在这里,我们将创建一个包含刚刚创建的特征和年龄以及性别列作为输入的单个数据帧。我们将使用Label Encoder将药物名称转换为数值。

feature_df = pd.DataFrame(X.toarray(),columns=vectorizer.get_feature_names_out())feature_df['Age'] = adf['Age'].fillna(0).astype('int')feature_df['Gender_Male'] = np.where(adf['Gender']=='Male',1,0)le = LabelEncoder()feature_df['Output'] = le.fit_transform(adf['Output'])

现在,我们将进行训练集和测试集的划分。我们将保留20%的数据作为测试集。我们将使用random_state参数来确保可重现性。

X_train, X_test, y_train, y_test = train_test_split(  feature_df.drop('Output',axis=1).fillna(-1),   feature_df['Output'],   test_size=0.2, random_state=42)

建模

在这里,我尝试使用了随机森林模型。您也可以尝试其他模型。我们将使用random_state参数来确保可重现性。由于我们之前看到的类别分布不均匀,我们将使用class_weight参数。

clf = RandomForestClassifier(max_depth=6, random_state=0,   class_weight='balanced')clf.fit(X_train, y_train)

让我们看看训练数据集的准确性和其他指标。

# 在X_train数据上的准确性final_accuracy = clf.score(X_train, y_train)print("final_accuracy is : ",final_accuracy)# 创建混淆矩阵以确定和可视化准确性得分clf_predict = clf.predict(X_train)print(classification_report(y_train, clf_predict,                            target_names=list(mapping_df['Actual_Name'])))

输出:

final_accuracy is :  0.411144578313253               precision    recall  f1-score   support        Other       0.84      0.30      0.44       236     cefixime       0.16      0.86      0.27        49  ceftriaxone       0.74      0.26      0.39       176 co-amoxiclav       0.48      0.47      0.48       127metronidazole       0.39      0.59      0.47        49      septrin       0.45      0.93      0.61        27     accuracy                           0.41       664    macro avg       0.51      0.57      0.44       664 weighted avg       0.64      0.41      0.43       664

类似地,让我们看看测试数据集的准确性和其他指标。

# 在X_test数据上的准确性final_accuracy = clf.score(X_test, y_test)print("final_accuracy is : ",final_accuracy)# 创建混淆矩阵以确定和可视化准确性得分clf_predict = clf.predict(X_test)print(classification_report(y_test, clf_predict,                            target_names=list(mapping_df['Actual_Name'])))

输出:

final_accuracy is :  0.38323353293413176               precision    recall  f1-score   support        Other       0.71      0.38      0.49        58     cefixime       0.08      0.56      0.14         9  ceftriaxone       0.36      0.09      0.14        45 co-amoxiclav       0.64      0.71      0.68        35metronidazole       0.31      0.40      0.35        10      septrin       0.44      0.40      0.42        10     accuracy                           0.38       167    macro avg       0.42      0.42      0.37       167 weighted avg       0.53      0.38      0.41       167

关键观察:

  • 模型在训练数据上的准确率为41.11%,在测试数据上为38.32%
  • 如果我们看f1-score,我们会发现在Other药物名称上,训练数据集的值为0.44,测试数据集的值为0.49。
  • 我们还可以看到cefiximeceftriaxone的测试f1-score较低。虽然cefixime只有9个样本,但ceftriaxone有45个样本。所以,我们需要分析这些数据点以了解改善我们的特征集的范围。

结论

在这篇文章中,我们对一个医疗数据集进行了全面的分析。然后,我们清洗了数据集。我们观察了基本统计信息、分布甚至制作了一个词云来了解列的情况。然后,我们创建了一个问题陈述,以帮助药剂师或开药医生使用现代医学。问题是基于诊断、年龄和性别列来确定给患者哪种药物。

主要观点

  • 我们考虑了诊断列中的前150个词语/双字组合/三字组合作为建模练习。可以说我们剖析了诊断列并创建了150个特征。
  • 我们还有年龄和性别特征。所以,一共有152个特征。
  • 利用这152个特征,我们尝试确定将会开具哪种药物。
  • 基准/随机模型的准确率是16.67%。我们的模型在训练数据上的准确率为41.11%,在测试数据上为38.32%。这相对于基准模型来说是一个显著的提升。

尝试提高模型性能的一些方法:

  1. 尝试使用TF-IDF或嵌入来提取特征。
  2. 尝试不同的模型并使用网格搜索和交叉验证来优化准确性。
  3. 尝试预测更多的药物。

感谢阅读我的文章。请随时在LinkedIn上与我联系讨论此事。

本文中显示的媒体不归Analytics Vidhya所有,而是由作者自行决定使用。