[EDA]AMP®-Parkinson‘s Disease Progression Prediction

news2024/11/24 17:56:37


翻译自:AMP - EDA + Models

1.数据集观察

加载四个excel文件

import pandas as pd
train_clinical_data = pd.read_csv('input/train_clinical_data.csv')
train_peptides = pd.read_csv('input/train_peptides.csv')
train_protiens = pd.read_csv('input/train_proteins.csv')
supplemental_clinical_data = pd.read_csv('input/supplemental_clinical_data.csv')

1.1.含脑脊液样本的临床记录数据 来自train_clinical_data.csv

print(f'患者人数:{train_clinical_data["patient_id"].nunique()}')
print(f'月份数:{train_clinical_data["visit_month"].nunique()}')
train_clinical_data 
# visit_id 访问的id
# patient_id 患者的id
# updrs_[1-4] 患者评分
# upd23b患者在updors期间是否服用..,主要影响第三部分分数

在这里插入图片描述
1.2.肽水平的质谱数据 来自train_peptides.csv

print(f'患者人数:{train_peptides["patient_id"].nunique()}')
print(f'月份数:{train_peptides["visit_month"].nunique()}')
print(f'蛋白质数目:{train_peptides["UniProt"].nunique()}')
print(f'氨基酸序列数:{train_peptides["Peptide"].nunique()}')
train_peptides
# UniProt 相关蛋白质的UniProt代码,每种蛋白质中通常有几种肽
# Peptide 肽中包含的氨基酸序列
# PeptideAbundance 样品中肽的频率

在这里插入图片描述
1.3.从肽水平数据汇总的蛋白表达频率数据 来自train_proteins.csv

print(f'患者人数:{train_protiens["patient_id"].nunique()}')
print(f'月份数:{train_protiens["visit_month"].nunique()}')
print(f'蛋白质数目:{train_protiens["UniProt"].nunique()}')
train_protiens
# NPX 蛋白质在样品中出现的频率

在这里插入图片描述
1.4.没有任何相关脑脊液样本的临床记录 来自supplemental_clinical_data

print(f'患者人数:{supplemental_clinical_data["patient_id"].nunique()}')
print(f'月份数:{supplemental_clinical_data["visit_month"].nunique()}')
# 该数据旨在提供有关帕金森病典型进展的其他背景信息,使用与train_clinical_data.csv相同的列。
supplemental_clinical_data

在这里插入图片描述
将含脑脊液的的样本与支持样本拼接

combined = pd.concat([train_clinical_data, supplemental_clinical_data]).reset_index(drop=True)
print(f'患者人数:{combined["patient_id"].nunique()}')
print(f'月份数:{combined["visit_month"].nunique()}')
combined

在这里插入图片描述

总结

1.train_clinical_data
此数据集表示患者在特定月份的统一帕金森病评定量表1-4部分的得分,其中upd23b_clinical_state_on_medication是类别特征,visit_monthupdrs_[1-4]是连续特征。
数据集包含2615行数据,248个患者和17个月份。
2.train_peptides
此数据集表示特定患者在特定月份的脑脊液中所见的肽频率,此数据集基于visit_id连接到train_clinical_data的数据集,其中UniProtPeptide是类别特征,PeptideAbundance是连续特征。
数据集包含981834行数据,248个患者,227种蛋白质,968种肽。
3.train_proteins
此数据集代表特定患者在特定月份在脑脊液中看到的蛋白质表达频率,此数据集基于visit_id连接到train_clinical_data数据集,其中UniProt是类别特征,NPX是连续特征。
数据集包含232741行数据,248个患者,227种蛋白质。
4.supplemental_clinical_data是补充性临床数据,该数据集代表了补充信息,没有任何来自脑脊液的蛋白质或肽的数据。其中upd23b_clinical_state_on_medication是类别特征,visit_monthupdrs_[1-4]是连续特征。
此数据集包含2223行数据,771个患者。
总计
4838次独立访问
1019名患者
18个月份

2.NULL值

from matplotlib import pyplot as plt
import seaborn as sns

# 记录train_clinical_data的缺失信息
train_clinical_data['null_count'] = train_clinical_data.isnull().sum(axis=1)
# 按照null_count字段进行分组,然后计算每组visit_id字段的数量
counts_train_clinical_data = train_clinical_data.groupby('null_count')['visit_id'].count().to_dict()
null_train_clinical_data = {"{} Null Value(s)".format(k) : v for k, v in counts_train_clinical_data.items()}
print(f'train_clinical_data: {null_train_clinical_data}')

# supplemental_clinical_data
supplemental_clinical_data["null_count"] = supplemental_clinical_data.isnull().sum(axis=1)
counts_supplemental_clinical_data = supplemental_clinical_data.groupby("null_count")["visit_id"].count().to_dict()
null_supplemental_clinical_data = {"{} Null Value(s)".format(k) : v for k, v in counts_supplemental_clinical_data.items()}
print(f'supplemental_clinical_data: {null_supplemental_clinical_data}')

# train_protiens
train_protiens["null_count"] = train_protiens.isnull().sum(axis=1)
counts_train_protiens = train_protiens.groupby("null_count")["visit_id"].count().to_dict()
null_train_protiens = {"{} Null Value(s)".format(k) : v for k, v in counts_train_protiens.items()}
print(f'train_protiens: {null_train_protiens}')

# train_peptides
train_peptides["null_count"] = train_peptides.isnull().sum(axis=1)
counts_train_peptides = train_peptides.groupby("null_count")["visit_id"].count().to_dict()
null_train_peptides = {"{} Null Value(s)".format(k) : v for k, v in counts_train_peptides.items()}
print(f'train_peptides: {null_train_peptides}')

fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(15, 15)) # fig 是整个图形的对象,axs 是一个包含了每个子图对象的数组
axs = axs.flatten() # axs = axs.flatten() 是将 axs 数组展平,使得我们可以方便地使用单个索引来访问每个子图
_ = axs[0].pie(
    x=list(null_train_clinical_data.values()), 
    autopct=lambda x: f'{x * sum(null_train_clinical_data.values())/100:.0f} = {x:.2f}',
    explode=[0.05] * len(null_train_clinical_data.keys()), # explode 参数表示距离饼图中心的偏移量
    labels=null_train_clinical_data.keys(), 
    colors=sns.color_palette("Set2")[0:len(null_train_clinical_data.keys())],
)
_ = axs[0].set_title("Null Values Per Row in Clinical Data", fontsize=15)

_ = axs[1].pie(
    x=list(null_supplemental_clinical_data.values()), 
    autopct=lambda x: "{:,.0f} = {:.2f}%".format(x * sum(null_supplemental_clinical_data.values())/100, x),
    explode=[0.05] * len(null_supplemental_clinical_data.keys()), 
    labels=null_supplemental_clinical_data.keys(), 
    colors=sns.color_palette("Set2")[0:len(null_supplemental_clinical_data.keys())],
)
_ = axs[1].set_title("Null Values Per Row in Supplemental Clinical Data", fontsize=15)

_ = axs[2].pie(
    x=list(null_train_protiens.values()), 
    autopct=lambda x: "{:,.0f} = {:.2f}%".format(x * sum(null_train_protiens.values())/100, x),
    explode=[0.05] * len(null_train_protiens.keys()), 
    labels=null_train_protiens.keys(), 
    colors=sns.color_palette("Set2")[0:len(null_train_protiens.keys())],
)
_ = axs[2].set_title("Null Values Per Row in Protein Data", fontsize=15)

_ = axs[3].pie(
    x=list(null_train_peptides.values()), 
    autopct=lambda x: "{:,.0f} = {:.2f}%".format(x * sum(null_train_peptides.values())/100, x),
    explode=[0.05] * len(null_train_peptides.keys()), 
    labels=null_train_peptides.keys(), 
    colors=sns.color_palette("Set2")[0:len(null_train_peptides.keys())],
)
_ = axs[3].set_title("Null Values Per Row in Peptide Data", fontsize=15)

在这里插入图片描述
在这里插入图片描述
由扇形图可以看到涉及肽和蛋白质的数据无空值,但涉及到临床数据时是有空值的。
接下来看一下train_clinical_data的缺失信息

# 选取所有具有相同非空值数量的行,并将每行中的所有空值求和
# isnull().sum() 函数计算选定行中的所有空值数, .index[:-1] 则返回一个包含除目标列外的所有列索引的列表
null_count_labels = [train_clinical_data[(train_clinical_data["null_count"] == x)].isnull().sum().index[:-1] for x in range(1, 6)]
null_count_values = [train_clinical_data[(train_clinical_data["null_count"] == x)].isnull().sum().values[:-1] for x in range(1, 6)]

fig, axs = plt.subplots(nrows=1, ncols=4, figsize=(20, 5))
fig.suptitle("Null Values for Clinical Data", fontsize=20)

axs = axs.flatten()

for x in range(0, 4):
    ax = axs[x]
    labels = null_count_labels[x]
    _ = sns.barplot(x=labels, y=null_count_values[x], ax=ax)
    _ = ax.set_title("Number of Rows With {} Null(s)".format(x + 1), fontsize=15)
    _ = ax.set_ylabel("# of Nulls" if x == 0 else "")
    _ = ax.set_xlabel("")
    _ = ax.set_xticks([z for z in range(len(labels))], labels, rotation=90)
    for p in ax.patches:
        height = p.get_height()
        ax.text(x=p.get_x()+(p.get_width()/2), y=height, s="{:d}".format(int(height)), ha="center")

在这里插入图片描述
分析
当一行有1个空值时,通常对应于特征upd23b_clinical_state_on_medication,由OnOff组成,该字段的空值无法确定它们是否表示患者停止服药,或者评估是否未能捕捉到患者的服药状态。updrs_3出现了7次,updrs_4出现了21次,根据 Goetz 等人 (2008) 的说法,UPDRS 评估的第 3 部分涉及运动评估,最低得分为 0。UPDRS 评估的第 4 部分涉及运动并发症,同样最低得分为 0。 这些列表明评估未执行。 这一点很重要,因为 0 分表示患者已接受评估并被认为具有正常反应,所以不能做简单的补0操作。
当一行有2个空值时,通常对应于特征upd23b_clinical_state_on_medicationupdrs_4,少部分出现在updrs_2updrs_3,对于 UPDRS 第 2 部分,评估涉及日常生活的运动体验。
有 10 个实例的行包含 3 个空值。 在每个实例中,该行都缺少来自updrs_3updrs_4upd23b_clinical_state_on_medication的信息。 同样,缺失值不能假定为 0。
仅出现具有 4 个空值的行的单个实例。 访问时似乎只执行了 UPDRS 第 3 部分评估。 同样,空值不能解释为 0,因为基于 0 的分数表示正常响应。
supplemental_clinical_data的缺失信息

null_count_labels = [supplemental_clinical_data[(supplemental_clinical_data["null_count"] == x)].isnull().sum().index[:-1] for x in range(1, 6)]
null_count_values = [supplemental_clinical_data[(supplemental_clinical_data["null_count"] == x)].isnull().sum().values[:-1] for x in range(1, 6)]

fig, axs = plt.subplots(nrows=1, ncols=4, figsize=(20, 5))
fig.suptitle("Null Values for Supplemental Data", fontsize=20)

axs = axs.flatten()

for x in range(0, 4):
    ax = axs[x]
    labels = null_count_labels[x]
    _ = sns.barplot(x=labels, y=null_count_values[x], ax=ax)
    _ = ax.set_title("Number of Rows With {} Null(s)".format(x + 1), fontsize=15)
    _ = ax.set_ylabel("# of Nulls" if x == 0 else "")
    _ = ax.set_xlabel("Feature")
    _ = ax.set_xticks([z for z in range(len(labels))], labels, rotation=90)
    for p in ax.patches:
        height = p.get_height()
        ax.text(x=p.get_x()+(p.get_width()/2), y=height, s="{:d}".format(int(height)), ha="center")

在这里插入图片描述
分析
当一行中有1个空值或2个空值时,它会出现在 updrs_4upd23b_clinical_state_on_medication 中。
当存在3个空值时,趋势与train_clinical_data略有不同。 在这种情况下,与train_clinical_data相比,补充数据更有可能缺少updrs_1updrs_2。 最后,当有4个缺失值时,我们在updrs_1updrs_2updrs_4upd23b_clinical_state_on_medication 中看到相同的缺失值,补充数据中 4 个空值行的数量明显多于临床数据。
处理空值时必须小心
是否应该使用空值来表示错过评估,或者是否可以将它们设置为另一个值,这并不明显。

  • 对于 UPDRS 评估,将值设置为 0 可能是错误的,因为这表示“正常”结果。
  • 对于 upd23b_clinical_state_on_medication 特性,唯一有效的设置是 OnOff,因此 null 的影响是未定义的。

3.重复行

重复可能会影响我们的学习方法,导致对重复信息的预测偏差。

import numpy as np
titles = ["Peptide Data", "Protein Data", "Clinical Data", "Supplemental Data"]
value_counts = []

# pivot_table 透视表
# 这行代码使用了 pandas library 中的 pivot_table 函数,
# 将 train_peptides dataframe 以 UniProt, Peptide 和 PeptideAbundance 列作为 index,调用 size 函数,
# 得到一个 Series 对象,其中每行表示相应的行组合在 train_peptides dataframe 中出现的次数
duplicates = train_peptides.pivot_table(index=[
    'UniProt', 'Peptide', 'PeptideAbundance',
], aggfunc="size")
unique, counts = np.unique(duplicates, return_counts=True)
value_counts.append(dict(zip(unique, counts)))

duplicates = train_protiens.pivot_table(index=[
    'UniProt', 'NPX',
], aggfunc="size")
unique, counts = np.unique(duplicates, return_counts=True)
value_counts.append(dict(zip(unique, counts)))

duplicates = train_clinical_data.pivot_table(index=[
    'visit_month', 'updrs_1', 'updrs_2', 'updrs_3', 'updrs_4', 'upd23b_clinical_state_on_medication'
], aggfunc="size")
unique, counts = np.unique(duplicates, return_counts=True)
value_counts.append(dict(zip(unique, counts)))

duplicates = supplemental_clinical_data.pivot_table(index=[
    'visit_month', 'updrs_1', 'updrs_2', 'updrs_3', 'updrs_4', 'upd23b_clinical_state_on_medication'
], aggfunc="size")
unique, counts = np.unique(duplicates, return_counts=True)
value_counts.append(dict(zip(unique, counts)))

fig, axs = plt.subplots(nrows=1, ncols=4, figsize=(20, 4))

axs = axs.flatten()

for x in range(4):
    ax = axs[x]
    _ = sns.barplot(x=list(value_counts[x].keys())[1:], y=list(value_counts[x].values())[1:], ax=ax)
    _ = ax.set_title("Duplicate Counts in {}".format(titles[x], fontsize=15))
    _ = ax.set_ylabel("Count")
    _ = ax.set_xlabel("Number of Times Row is Duplicated")
    for p in ax.patches:
        height = p.get_height()
        ax.text(x=p.get_x()+(p.get_width()/2), y=height, s="{:d}".format(int(height)), ha="center")

在这里插入图片描述
关于重复行的主要观察

  • 就原始副本而言:
    • 在临床数据中:
      • 同一行数据出现两次的情况有 2 次。
      • 重复数据占所有临床数据的 0.15%。
    • 在补充数据中:
      • 同一行数据出现两次的情况有 7 次。
      • 重复数据占所有补充数据的 0.63%。
    • 在蛋白质数据中:
      • 同一行数据出现两次的情况有 400 个。
      • 重复项占所有蛋白质数据的 0.35%。
    • 在肽数据中:
      • 同一行数据出现两次的情况有 1,765 个。
      • 同一行数据出现 3 次的情况有 2 次。
      • 重复项占所有肽数据的 0.36%。
  • 总体而言,对于临床和补充数据,重复数据可能影响很小或没有影响。

4.统计分析

首先,让我们将临床数据与补充数据进行比较,看看我们有什么样的差异。

print('train_clinical_data')
features = ['visit_month', 'updrs_1', 'updrs_2', 'updrs_3', 'updrs_4',]
# .T: 转置整个数据表,行列互换。
# .style: 对 DataFrame 进行美化,返回一个 Styler 对象,进而可以链式描述所需的格式。
# 对 mean 的 cells 标成条形图
# 对 std 的 cells 进行 cmap='Reds' 颜色的背景渐变,即数字越大的单元格颜色越深
# 对 50 percentile 的 cells 进行 cmap='coolwarm' 颜色的背景渐变,即数字越大/小的单元格颜色越深/浅
train_clinical_data[features].describe().T.style.bar(subset=['mean'], color='#7BCC70')\
    .background_gradient(subset=['std'], cmap='Reds')\
    .background_gradient(subset=['50%'], cmap='coolwarm')

在这里插入图片描述

print('supplemental_clinical_data')
features = ['visit_month', 'updrs_1', 'updrs_2', 'updrs_3', 'updrs_4',]
supplemental_clinical_data[features].describe().T.style.bar(subset=['mean'], color='#7BCC70')\
    .background_gradient(subset=['std'], cmap='Reds')\
    .background_gradient(subset=['50%'], cmap='coolwarm')

在这里插入图片描述
补充数据显示就诊主要发生在 0 到 36 个月之间,而临床数据显示就诊发生在 0 到 108 个月之间。
接下来比较一下按月就诊的分布情况:

train_clinical_data["origin"] = "Clincial Data"
supplemental_clinical_data["origin"] = "Supplemental Data"
combined = pd.concat([train_clinical_data, supplemental_clinical_data]).reset_index(drop=True)
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 5))
sns.set_style('darkgrid')
_ = sns.histplot(data=combined, x="visit_month", hue="origin", kde=True, ax=ax, element="step")
_ = ax.set_title("Visit Month by Data Source", fontsize=15)
_ = ax.set_ylabel("Count")
_ = ax.set_xlabel("Visit Month")

在这里插入图片描述
我们还可以进行快速目视检查,看看临床数据和补充数据在 UPDRS 评分方面是否存在差异。 对于下图,趋势线是核密度估计值,因此趋势线考虑了原始计数的差异。

train_clinical_data["origin"] = "Clincial Data"
supplemental_clinical_data["origin"] = "Supplemental Data"

combined = pd.concat([train_clinical_data, supplemental_clinical_data]).reset_index(drop=True)

features = ["updrs_1", "updrs_2", "updrs_3", "updrs_4"]
labels = ["UPDRS Part 1", "UPDRS Part 2", "UPDRS Part 3", "UPDRS Part 4"]

fig, axs = plt.subplots(nrows=4, ncols=1, figsize=(15, 25))

sns.set_style('darkgrid')

axs = axs.flatten()

sns.set_style('darkgrid')

for x, feature in enumerate(features):
    ax = axs[x]
    _ = sns.histplot(data=combined, x=feature, hue="origin", kde=True, ax=ax, element="step")
    _ = ax.set_title("{} Scores by Data Source".format(labels[x]), fontsize=15)
    _ = ax.set_ylabel("Count")
    _ = ax.set_xlabel("{} Score".format(labels[x]))

在这里插入图片描述
在这里插入图片描述
UPDRS 第 1 部分和第 4 部分分数在临床和补充数据源之间的分布似乎非常相似。
我们可以通过执行对抗性验证来粗略衡量临床数据和补充数据之间的差异。 对抗性验证的目标是查看分类器是否可以区分两个数据集。 我们将使用 ROC AUC 分数来告知我们差异。 如果两组看起来非常相似,分类器将无法区分它们,因此 ROC AUC 分数将为 0.5。 如果它们很容易区分, 那么 ROC AUC 分数将接近 1。

from sklearn.model_selection import KFold
from sklearn.metrics import roc_auc_score
from lightgbm import LGBMClassifier
from lightgbm import early_stopping, log_evaluation
from sklearn.metrics import confusion_matrix
from sklearn.metrics import roc_curve
from sklearn.preprocessing import LabelEncoder

# 打标签
train_clinical_data['origin'] = 0
supplemental_clinical_data['origin'] = 1
combined = pd.concat([train_clinical_data, supplemental_clinical_data]).reset_index(drop=True)
features = ['visit_month', 'updrs_1', 'updrs_2', 'updrs_3', 'updrs_4', 'upd23b_clinical_state_on_medication']
le = LabelEncoder() # LabelEncoder 将分类变量映射为从 0 开始连续的整数,一个类别对应一个标签
combined['upd23b_clinical_state_on_medication'] = le.fit_transform(combined['upd23b_clinical_state_on_medication'])

n_folds = 5
skf = KFold(n_splits=n_folds, random_state=2023, shuffle=True)
train_oof_preds = np.zeros((combined.shape[0]))
train_oof_probas = np.zeros((combined.shape[0]))

for fold, (train_index, test_index) in enumerate(skf.split(combined, combined['origin'])):
    # 对 k-fold 交叉验证器生成的每一组训练和测试集的索引进行循环
    print(f'-------> Fold {fold + 1} <--------')
    x_train, x_valid = pd.DataFrame(combined.iloc[train_index]), pd.DataFrame(combined.iloc[test_index])
    y_train, y_valid = combined["origin"].iloc[train_index], combined["origin"].iloc[test_index]
    # 在整个数据集中取出了需要使用的特征进行后续的训练和测试
    x_train_features = pd.DataFrame(x_train[features])
    x_valid_features = pd.DataFrame(x_valid[features])
    
    model = LGBMClassifier(
        random_state=2023, # 模型中用于产生随机数的种子
        objective="binary", # 模型采用二分类任务,优化目标为最大化 AUC
        metric="auc", # 模型在训练过程中衡量效果的指标是 AUC
        n_jobs=-1, # 并行运算线程数,-1 表示使用所有 CPU 核心
        n_estimators=2000, # 分类器中选择的树形成员数目
        verbose=-1, # 控制输出消息的详细程度。-1 表示不输出  
        max_depth=3, # 单棵树的最大深度
    )
    
    model.fit(
        x_train_features[features], # 训练集的特征数据
        y_train, # 训练集的目标变量
        eval_set=[(x_valid_features[features], y_valid)], # 测试集和标签
        callbacks=[ # 在模型 fit 的过程中执行的操作
            early_stopping(50, verbose=False), # 函数基于评价指标的变化来判断模型是否过拟合,并在 50 棵树后没有进一步提升时停止训练
            log_evaluation(2000), # 在训练过程中每隔 2000 棵树记录下模型在验证集上的评价指标
        ]
    )
    
    oof_preds = model.predict(x_valid_features[features]) # 方法返回一个类别的预测结果
    oof_probas = model.predict_proba(x_valid_features[features])[:,1] #  predict_proba 方法返回类别为正类的概率值,而不是简单的类别
    train_oof_preds[test_index] = oof_preds
    train_oof_probas[test_index] = oof_probas
    print(": AUC ROC = {}".format(roc_auc_score(y_valid, oof_probas)))
    
auc_vanilla = roc_auc_score(combined["origin"], train_oof_probas)
fpr, tpr, _ = roc_curve(combined["origin"], train_oof_probas)
print("--> Overall results for out of fold predictions")
print(": AUC ROC = {}".format(auc_vanilla))

confusion = confusion_matrix(combined["origin"], train_oof_preds)

fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(15, 8))

labels = ["Clincial Data", "Supplemental Data"]

_ = sns.heatmap(confusion, annot=True, fmt=",d", ax=axs[0], xticklabels=labels, yticklabels=labels)
_ = axs[0].set_title("Confusion Matrix (@ 0.5 Probability)", fontsize=15)
_ = axs[0].set_ylabel("Actual Class")
_ = axs[0].set_xlabel("Predicted Class")

_ = sns.lineplot(x=[0, 1], y=[0, 1], linestyle="--", label="Indistinguishable Datasets", ax=axs[1])
_ = sns.lineplot(x=fpr, y=tpr, ax=axs[1], label="Adversarial Validation Classifier")
_ = axs[1].set_title("ROC Curve", fontsize=15)
_ = axs[1].set_xlabel("False Positive Rate")
_ = axs[1].set_ylabel("True Positive Rate")

在这里插入图片描述
AUC ROC 分数为 0.939,我们可以看到分类器可以轻松区分两个数据集。 这表明它们在本质上非常不同,正如比赛的一部分所指出的那样。 混合这两个数据集时应谨慎,因为它们在本质上非常不同。
蛋白质数据

print('Protein Data')
train_protiens[["NPX"]].describe().T.style.bar(subset=['mean'], color='#7BCC70')\
    .background_gradient(subset=['std'], cmap='Reds')\
    .background_gradient(subset=['50%'], cmap='coolwarm')

在这里插入图片描述

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 5))
sns.set_style('darkgrid')
# sns.kdeplot() 是一个在 seaborn 库中用于绘制单变量分布的函数。它接收单个一维数组的数据作为输入,并创建表示该数组数据分布的 Kernel Density Estimate(KDE)图
_ = sns.kdeplot(train_protiens["NPX"], shade=True, color="r", ax=ax, label="Normalized Protein Expression", log_scale=True)
_ = ax.set_title("Logarithmic Normalized Protein Expression (Kernel Density Estimate)", fontsize=15)

在这里插入图片描述
正如我们所见,实际的蛋白质表达频率存在很多差异。 我们将在下面的第 2 节中更多地研究各种蛋白质的分布及其与 UPDRS 分数的关联。 目前,我们的主要观察结果是标准化的蛋白质表达是高度可变的,如特征的最小值、最大值和标准差所示。
肽数据

train_peptides[["PeptideAbundance"]].describe().T.style.bar(subset=['mean'], color='#7BCC70')\
    .background_gradient(subset=['std'], cmap='Reds')\
    .background_gradient(subset=['50%'], cmap='coolwarm')

在这里插入图片描述
同样,我们看到肽的丰度存在很大差异。 最小值、最大值和标准差告诉我们,肽丰度可能会因我们正在查看的特定肽而有很大差异。 同样,我们可以绘制核密度估计值,让我们了解大部分值存在的位置。

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 5))
sns.set_style('darkgrid')
_ = sns.kdeplot(train_peptides["PeptideAbundance"], shade=True, color="r", ax=ax, label="Peptide Abundance", log_scale=True)
_ = ax.set_title("Logarithmic Peptide Abundance (Kernel Density Estimate)", fontsize=15)

在这里插入图片描述
在下面,我们将再次查看肽数据 - 特别是肽序列以及它们与 UPDRS 分数的关系。
关于统计细分的主要观察

  • 临床数据和补充数据有非常不同的月份范围:
    • 这是通过查看它们的统计属性、直方图和对抗性验证来证实的。
  • 蛋白质表达数据具有广泛的价值,因此需要进一步细分为子组以提供信息。
  • 肽丰度频率具有广泛的值,因此需要进一步细分为子组以提供信息。

5.特征工程

访问月份会对所有不同的数据集产生影响,并随后通过我们拥有的许多不同特征产生影响。 让我们依次看看它们。
访问月与UPDRS
对于每个访问月,我们都会观察到目标特征 - UPDRS分数。 根据 Holden 等人 (2018) 的说法,UPDRS每个部分的发现都取决于患者是否正在服药。 我们应该将UPDRS评分观察结果细分为服用药物的组和未服用药物的组。 出于此探索的目的,在有关药物状态的临床数据中发现的空值将被视为off

train_clincial_data_copy = train_clinical_data.copy()
train_clincial_data_copy["upd23b_clinical_state_on_medication"] = train_clincial_data_copy["upd23b_clinical_state_on_medication"].fillna("Off")

fig, axs = plt.subplots(nrows=4, ncols=1, figsize=(15, 25))

sns.set_style('darkgrid')

axs = axs.flatten()

for x, feature in enumerate(["updrs_1", "updrs_2", "updrs_3", "updrs_4"]):
    ax = axs[x]
    data = train_clincial_data_copy[(train_clincial_data_copy["upd23b_clinical_state_on_medication"] == "Off")]
    _ = sns.boxplot(data=data, x="visit_month", y=feature, ax=ax)
    _ = sns.pointplot(data=data, x="visit_month", y=feature, color="r", ci=None, linestyles=[":"], ax=ax)
    _ = ax.set_title("UPDRS Part {} Scores by Month while OFF Medication".format(x+1), fontsize=15)
    _ = ax.set_xlabel("Visit Month")
    _ = ax.set_ylabel("Score")

在这里插入图片描述
在这里插入图片描述
停药时的一些一般观察:

  • 每个 UPDRS 部分及其各自的访问月份之间存在大量差异和异常值。
  • 总的来说,在 UPDRS 第 1 至第 3 部分中,分数的趋势线保持相对平稳。
    • 对于 UPDRS 第 4 部分,我们看到分数逐渐增加。
train_clincial_data_copy = train_clinical_data.copy()
train_clincial_data_copy["upd23b_clinical_state_on_medication"] = train_clincial_data_copy["upd23b_clinical_state_on_medication"].fillna("Off")

fig, axs = plt.subplots(nrows=4, ncols=1, figsize=(15, 25))

sns.set_style('darkgrid')

axs = axs.flatten()

for x, feature in enumerate(["updrs_1", "updrs_2", "updrs_3", "updrs_4"]):
    ax = axs[x]
    data = train_clincial_data_copy[(train_clincial_data_copy["upd23b_clinical_state_on_medication"] == "On")]
    _ = sns.boxplot(data=data, x="visit_month", y=feature, ax=ax)
    _ = sns.pointplot(data=data, x="visit_month", y=feature, color="r", ci=None, linestyles=[":"], ax=ax)
    _ = ax.set_title("UPDRS Part {} Scores by Month while ON Medication".format(x+1), fontsize=15)
    _ = ax.set_xlabel("Visit Month")
    _ = ax.set_ylabel("Score")

在这里插入图片描述
在这里插入图片描述
服药时的一些一般观察:

  • 每个 UPDRS 部分及其各自的访问月份之间存在大量差异和异常值。
  • 总的来说,在 UPDRS 第 1、2 和 4 部分中,分数的趋势线保持相对平坦。
  • 对于 UPDRS 第 3 部分,我们看到分数逐渐增加。
    正如 Holden 等人 (2018) 所提到的,UPDRS 的最高得分为 272。在 UPDRS 的先前版本中,UPDRS 得分随着时间的推移呈线性增长。 我们应该查看 UPDRS 分数的总和,看看该数据中的分数是否随时间增加。
train_clinical_data["updrs_sum"] = train_clinical_data["updrs_1"] + train_clinical_data["updrs_2"] + train_clinical_data["updrs_3"] + train_clinical_data["updrs_4"]
train_clincial_data_copy = train_clinical_data.copy()
train_clincial_data_copy["upd23b_clinical_state_on_medication"] = train_clincial_data_copy["upd23b_clinical_state_on_medication"].fillna("Off")

fig, axs = plt.subplots(nrows=2, ncols=1, figsize=(15, 12.5))

axs = axs.flatten()

sns.set_style('darkgrid')

data = train_clincial_data_copy[(train_clincial_data_copy["upd23b_clinical_state_on_medication"] == "Off")]
ax = axs[0]
_ = sns.boxplot(data=data, x="visit_month", y="updrs_sum", ax=ax)
_ = sns.pointplot(data=data, x="visit_month", y="updrs_sum", color="r", ci=None, linestyles=[":"], ax=ax)
_ = ax.set_title("UPDRS Sum of Scores by Month while OFF Medication".format(x+1), fontsize=15)
_ = ax.set_xlabel("Visit Month")
_ = ax.set_ylabel("Score")

data = train_clincial_data_copy[(train_clincial_data_copy["upd23b_clinical_state_on_medication"] == "On")]
ax = axs[1]
_ = sns.boxplot(data=data, x="visit_month", y="updrs_sum", ax=ax)
_ = sns.pointplot(data=data, x="visit_month", y="updrs_sum", color="r", ci=None, linestyles=[":"], ax=ax)
_ = ax.set_title("UPDRS Sum of Scores by Month while ON Medication".format(x+1), fontsize=15)
_ = ax.set_xlabel("Visit Month")
_ = ax.set_ylabel("Score")

在这里插入图片描述
根据停药时所有 UPDRS 评分的总和,我们看到随着就诊月份的增加呈上升趋势,这表明总体而言,疾病正在进展。 在服药期间,趋势线保持相对平稳,直到月数 > 96,总分有所增加。 同样,这表明正在发生疾病进展。 如果我们结合 ON 和 OFF 药物状态:

train_clinical_data["updrs_sum"] = train_clinical_data["updrs_1"] + train_clinical_data["updrs_2"] + train_clinical_data["updrs_3"] + train_clinical_data["updrs_4"]
train_clincial_data_copy = train_clinical_data.copy()
train_clincial_data_copy["upd23b_clinical_state_on_medication"] = train_clincial_data_copy["upd23b_clinical_state_on_medication"].fillna("Off")

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 5))

sns.set_style('darkgrid')

_ = sns.boxplot(data=train_clincial_data_copy, x="visit_month", y="updrs_sum", ax=ax)
_ = sns.pointplot(data=train_clincial_data_copy, x="visit_month", y="updrs_sum", color="r", ci=None, linestyles=[":"], ax=ax)
_ = ax.set_title("UPDRS Sum of Scores by Month", fontsize=15)
_ = ax.set_xlabel("Visit Month")
_ = ax.set_ylabel("Score")

在这里插入图片描述
总体而言,我们看到了 UPDRS 分数增加的趋势。 这一观察很重要,因为它表明我们的分数应该会随着时间的推移而增加,而不是减少。 这可以用作后处理检查,以确保我们的机器学习算法做出的预测是有意义的。
访问月份与蛋白质数据
在不深入研究实际蛋白质数据的情况下,我们应该检查一下是否存在关于蛋白质数据按月细分的一般趋势。

train_protiens_copy = train_protiens.copy()
train_protiens_copy["log_NPX"] = np.log(train_protiens_copy["NPX"])

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(15, 5))

sns.set_style('darkgrid')

_ = sns.boxplot(data=train_protiens_copy, x="visit_month", y="log_NPX", ax=ax)
_ = sns.pointplot(data=train_protiens_copy, x="visit_month", y="log_NPX", color="r", ci=None, linestyles=[":"], ax=ax)
_ = ax.set_title("NPX by Month", fontsize=15)
_ = ax.set_xlabel("Visit Month")
_ = ax.set_ylabel("NPX")

在这里插入图片描述
不出所料,我们在每个月类别中都看到了稳定数量的蛋白质表达。 蛋白质表达频率存在很大差异,但总的来说,我们看到几个月内重复出现相同的平均总 NPX 值。 这可能意味着实际表达的蛋白质会有差异,而不是它们的绝对数量。
我们可以检查“UniProt”蛋白质在几个月内是否有任何显着增加或减少。 查看所有 227 种蛋白质将具有挑战性。 为此,我们将研究在几个月内显着增加或减少的蛋白质。 我们将检查所有 227 种蛋白质的蛋白质计数,然后挑选出与它们的平均值相比标准偏差非常大的蛋白质。 对于此 EDA,我们将查看标准偏差超过平均值 25% 的任何蛋白质表达数据。

unique_proteins = train_protiens["UniProt"].unique()
unique_months = train_protiens["visit_month"].unique()

protein_dict = dict()
for protein in unique_proteins:
    if protein not in protein_dict:
        protein_dict[protein] = {
            "months": unique_months,
            "count_NPX": [train_protiens[(train_protiens["UniProt"] == protein) & (train_protiens["visit_month"] == month)]["NPX"].count() for month in unique_months],
            "total_NPX": [train_protiens[(train_protiens["UniProt"] == protein) & (train_protiens["visit_month"] == month)]["NPX"].sum() for month in unique_months],
            "avg_NPX": [0 * len(unique_months)],
        }

for protein in unique_proteins:
    protein_dict[protein]["avg_NPX"] = [float(total) / count for total, count in zip(protein_dict[protein]["total_NPX"], protein_dict[protein]["count_NPX"])]
    
for protein in unique_proteins:
    protein_dict[protein]["min_NPX"] = min(protein_dict[protein]["avg_NPX"])
    protein_dict[protein]["max_NPX"] = max(protein_dict[protein]["avg_NPX"])
    
for protein in unique_proteins:
    protein_dict[protein]["mean"] = sum(protein_dict[protein]["avg_NPX"]) / len(protein_dict[protein]["months"])
    protein_dict[protein]["std"] = sum([(total_NPX - protein_dict[protein]["mean"]) ** 2 for total_NPX in protein_dict[protein]["avg_NPX"]]) / (len(unique_months) - 1)
    protein_dict[protein]["std"] = protein_dict[protein]["std"] ** 0.5
    
proteins_with_large_std = [protein for protein in unique_proteins if protein_dict[protein]["std"] > (protein_dict[protein]["mean"] * .25)]
proteins_of_interest_by_month = {
    "UniProt": [],
    "Visit Month": [],
    "Average NPX": [],
}
for protein in proteins_with_large_std:
    for month_index, month in enumerate(unique_months):
        proteins_of_interest_by_month["UniProt"].append(protein)
        proteins_of_interest_by_month["Visit Month"].append(month)
        value = protein_dict[protein]["avg_NPX"][month_index]
        value /= protein_dict[protein]["max_NPX"]
        proteins_of_interest_by_month["Average NPX"].append(value)
        
df = pd.DataFrame(proteins_of_interest_by_month)
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(20, 8))

sns.set_style('darkgrid')

_ = sns.lineplot(data=df, x="Visit Month", y="Average NPX", hue="UniProt", style="UniProt", ax=ax)
_ = ax.set_title("Average NPX per Protein by Month", fontsize=15)
_ = ax.set_xlabel("Visit Month")
_ = ax.set_ylabel("Average NPX")
_ = plt.legend(ncol=5)

在这里插入图片描述
这里有一些有趣的观察结果。 首先,在第 30、54 和 96 个月,我们看到所有感兴趣的蛋白质的平均水平出现向上和向下的峰值。 这是否与药物治疗状态或 UPDRS 评分等临床数据相关,我们将在下面直接研究蛋白质特征时更详细地研究。 现在,我们只是好奇蛋白质和访问月份是否存在相关性,这显然是存在的。
第二个观察结果是几种蛋白质既有向上运动也有向下运动。 这意味着蛋白质可能与 UPDRS 评分呈正相关或负相关。
第三个也是最后一个观察结果是,我们只关注每月差异很大的蛋白质表达频率(标准差 > 平均值的 25%)。 机器学习算法可以从中学习其他更微妙的表达频率变化。
蛋白质 UniProt 和 NPX
虽然我们已经开始检查“visit_month”对各种蛋白质水平的影响,但我们也可以查看蛋白质表达水平本身,看看它们与 UPDRS 分数之间是否存在相关性。 有大量的蛋白质 (227),因此在强调有趣的相关性之前,我们将首先全面了解一下。

proteins = []
protein_dict = {}
for index, row in train_protiens.iterrows():
    protein = row["UniProt"]
    if protein not in protein_dict:
        protein_dict[protein] = {}
        proteins.append(protein)
    protein_dict[protein][row["visit_id"]] = row["NPX"]
    
peptides = []
peptide_dict = {}
for index, row in train_peptides.iterrows():
    peptide = row["Peptide"]
    if peptide not in peptide_dict:
        peptide_dict[peptide] = {}
        peptides.append(peptide)
    peptide_dict[peptide][row["visit_id"]] = row["PeptideAbundance"]
    
train_copy = train_clinical_data.copy()
features = []
features.extend(proteins)

# Set missing values to null so our correlation matrix won't include 0 values in the correlation calculation
train_copy[features] = train_copy[features].replace(0.0, np.nan)

features.extend(["updrs_1", "updrs_2", "updrs_3", "updrs_4"])

correlation_matrix = train_copy[features].corr(method="spearman")

from matplotlib.colors import SymLogNorm

fig, axs = plt.subplots(nrows=8, ncols=1, figsize=(20, 40))

axs = axs.flatten()

_ = sns.heatmap(
    correlation_matrix.iloc[-4:,0:30],
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0, square=True, linewidths=.1, cbar=False, ax=axs[0], annot=True,
)
_ = axs[0].set_title("Spearman Correlation Matrix", fontsize=15)

_ = sns.heatmap(
    correlation_matrix.iloc[-4:,30:60],
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0, square=True, linewidths=.1, cbar=False, ax=axs[1], annot=True,
)

_ = sns.heatmap(
    correlation_matrix.iloc[-4:,60:90],
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0, square=True, linewidths=.1, cbar=False, ax=axs[2], annot=True,
)

_ = sns.heatmap(
    correlation_matrix.iloc[-4:,90:120],
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0, square=True, linewidths=.1, cbar=False, ax=axs[3], annot=True,
)

_ = sns.heatmap(
    correlation_matrix.iloc[-4:,120:150],
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0, square=True, linewidths=.1, cbar=False, ax=axs[4], annot=True,
)

_ = sns.heatmap(
    correlation_matrix.iloc[-4:,150:180],
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0, square=True, linewidths=.1, cbar=False, ax=axs[5], annot=True,
)

_ = sns.heatmap(
    correlation_matrix.iloc[-4:,180:210],
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0, square=True, linewidths=.1, cbar=False, ax=axs[6], annot=True,
)

_ = sns.heatmap(
    correlation_matrix.iloc[-4:,210:227],
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0, square=True, linewidths=.1, cbar=False, ax=axs[7], annot=True,
)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
有很多蛋白质需要用相关矩阵来检查。 让我们首先定义我们认为具有某种显着相关性(正相关或负相关)的东西。 0.1 或以下的值可能与 UPDRS 目标分数几乎没有相关性,并且可能只是噪音。 快速扫描显示有几个候选者可能对我们的回归没有用:

  • O00533
  • O14498
  • O15240
  • O15394
  • O43505
  • O60888
  • P00738
  • P01034
  • P01042
  • P01717
  • P02452
  • P02649
  • P02751
  • P02753
  • P02787
  • P04075
  • P04156
  • P04180
  • P04216
  • P05060
  • P05067
  • P05155
  • P05408
  • P05452
  • P06396
  • P07195
  • P07225
  • P07602
  • P07711
  • P07858
  • P08133
  • P08253
  • P08571
  • P09104
  • P09486
  • P09871
  • P10645
  • P11142
  • P13521
  • P13591
  • P13611
  • P13987
  • P14313
  • P14618
  • P17174
  • P19021
  • P23083
  • P23142
  • P39060
  • P40925
  • P43121
  • P49908
  • P54289
  • P55290
  • P61278
  • P61769
  • P61916
  • P98160
  • Q02818
  • Q06481
  • Q08380
  • Q12907
  • Q13332
  • Q14118
  • Q14508
  • Q14515
  • Q15904
  • Q16610
  • Q6UXB8
  • Q7Z3B1
  • Q8NBJ4
  • Q92520
  • Q92823
  • Q96KN2
  • Q99435
  • Q99674
  • Q9BY67
  • Q9NQ79
  • Q9NYU2
  • Q9UHG2
  • P01594
  • Q13449
  • Q99829

有些蛋白质与 updrs_4 弱相关:

  • P00746

  • P02749

  • P02774

  • P04211

  • P04217

  • P05155

  • P06681

  • P19827

  • P20774

  • P31997

  • P61626

  • Q96BZ4

  • Q96PD5
    挑战将是我们如何使用这些知识。 我们的相关性分析之所以有效,是因为我们能够忽略缺失的值。 为了让机器学习回归发挥作用,我们需要满足以下条件之一才能使用数据:

  • 每次访问的每种蛋白质类型都有完整的记录。

  • 找出一种估算缺失值的方法。

  • 使用隐式处理缺失数据的机器学习算法。

让我们来看看我们的蛋白质在访问中是如何出现的。 具体来说,我们知道有 2,615 次独立访问。 问题是在给定总访问次数的情况下,我们看到的每种蛋白质有多少。 如果我们只看到一种蛋白质三四次,即使它与 UPDRS 分数相关,也可能不会有太大帮助。

protein_counts = {}

for protein in proteins:
    protein_counts[protein] = float(len(train_copy[(train_copy[protein] > 0.0)][protein])) / len(train_copy[protein]) * 100

protein_counts = dict(sorted(protein_counts.items(), key=lambda x:x[1], reverse=True))
protein_counts
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(20, 45))

_ = sns.barplot(y=list(protein_counts.keys()), x=list(protein_counts.values()), ax=ax)
_ = ax.set_title("% of Visits Containing Specified Protein", fontsize=15)
_ = ax.set_ylabel("Protein")
_ = ax.set_xlabel("% of Visits")
_ = ax.set_xlim([0, 100])

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
似乎对于所有蛋白质数据,蛋白质测量数据最多存在于我们记录的访问的40%。跟踪趋势会有些问题。我们没有特定蛋白质测量值的情况将压倒我们确实有蛋白质测量的例子,从而产生混淆效应。更成问题的是这些测量值来自访问月份。

protein_month_counts = {}

for protein in proteins:
    protein_month_counts[protein] = {month: 0 for month in range(109)}
    for x in range(109):
        protein_month_counts[protein][x] = len(train_copy[(train_copy[protein] > 0.0) & (train_copy["visit_month"] == x)][protein])
from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(20, 5))

_ = sns.barplot(x=list(protein_month_counts["P00450"].keys()), y=list(protein_month_counts["P00450"].values()), ax=ax)
_ = ax.set_title("Samples Available by Visit Month for Protein P00450", fontsize=15)
_ = ax.set_ylabel("# Samples")
_ = ax.set_xlabel("Visit Month")
_ = ax.xaxis.set_major_locator(MultipleLocator(6))
_ = ax.xaxis.set_major_formatter('{x:.0f}')
_ = ax.xaxis.set_minor_locator(MultipleLocator(3))

在这里插入图片描述

import warnings # Python 通过调用 warnings 模块中定义的 warn () 函数来发出警告
warnings.filterwarnings('ignore') # 警告过滤器
train_copy = train_copy.fillna(0)
train_copy["missing_all"] = train_copy[proteins].apply(lambda x: 1 if sum([y for y in x]) == 0 else 0, axis=1)

missing_month_counts = [train_copy[(train_copy["visit_month"] == x)]["missing_all"].sum() / float(train_copy[(train_copy["visit_month"] == x)]["patient_id"].count()) * 100 for x in range(109)]
missing_month_labels = [x for x in range(109)]

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(20, 5))

_ = sns.barplot(x=missing_month_labels, y=missing_month_counts, ax=ax)
_ = ax.set_title("% of Patients Missing Protein Data by Visit Month", fontsize=15)
_ = ax.set_ylabel("% of Patients")
_ = ax.set_xlabel("Visit Month")
_ = ax.xaxis.set_major_locator(MultipleLocator(3))
_ = ax.xaxis.set_major_formatter('{x:.0f}')
_ = ax.xaxis.set_minor_locator(MultipleLocator(3))

在这里插入图片描述
正如我们所看到的,在第3、9、18、30、42、54和96个月,我们缺乏研究中几乎所有患者的蛋白质数据。
脑脊液肽作为潜在的帕金森病生物标志物 (2015)

Shi 等人(2015 年)的研究专门研究了脑脊液蛋白和肽,它们是帕金森病的潜在指标。 在确定的那些人中,以下内容在我们可用的培训数据中可用:

  • 蛋白质:
    • P00450 - 铜蓝蛋白 (CP)
    • P07333 - 巨噬细胞集落刺激因子 1 受体 (CSF1R)
    • P10451 - 骨桥蛋白 (SPP1)
    • P01033 - 金属蛋白酶抑制剂 1 (TIMP1)
    • P01008 - 抗凝血酶-III (SERPINC1)
    • P02647 - 载脂蛋白 A-I (APOA1)
    • P01024 - 补码 C3 (C3)
      *Q92876-激肽释放酶 6 (KLK6)
      *肽:
    • GAYPLSIEPIGVR - 与铜蓝蛋白 (CP) 相关
    • EPGLC(UniMod_4)TWQSLR - 与蛋白质金属蛋白酶抑制剂 1 (TIMP1) 相关
    • WQEEMELYR - 与蛋白质载脂蛋白 A-I (APOA1) 相关
    • QPSSAFAAFVK - 与蛋白质补体 C3 (C3) 相关
    • GLVSWGNIPC(UniMod_4)GSK - 与蛋白质 Kallikrein-6 (KLK6) 相关

我们应该检查这些蛋白质水平如何影响 UPDRS 分数。 让我们首先检查这些肽和蛋白质水平与各种 UPDRS 分数的相关性。

proteins = ["P00450", "P07333", "P10451", "P01033", "P01008", "P02647", "P01024", "Q92876"] # 蛋白质
peptides = ["GAYPLSIEPIGVR", "EPGLC(UniMod_4)TWQSLR", "WQEEMELYR", "QPSSAFAAFVK", "GLVSWGNIPC(UniMod_4)GSK"] # 肽

protein_dict = {}
for index, row in train_protiens.iterrows():
    protein = row["UniProt"]
    if protein not in protein_dict:
        protein_dict[protein] = {}
    protein_dict[protein][row["visit_id"]] = row["NPX"]
    
peptide_dict = {}
for index, row in train_peptides.iterrows():
    peptide = row["Peptide"]
    if peptide not in peptide_dict:
        peptide_dict[peptide] = {}
    peptide_dict[peptide][row["visit_id"]] = row["PeptideAbundance"]
    
train_copy = train_clinical_data.copy()
for protein in proteins:
    train_copy[protein] = train_copy["visit_id"].apply(lambda visit_id: 0 if visit_id not in protein_dict[protein] else protein_dict[protein][visit_id])
    
for peptide in peptides:
    train_copy[peptide] = train_copy["visit_id"].apply(lambda visit_id: 0 if visit_id not in peptide_dict[peptide] else peptide_dict[peptide][visit_id])
    
features = []
features.extend(proteins)
features.extend(peptides)

# Set missing values to null so our correlation matrix won't include 0 values in the correlation calculation
train_copy[features] = train_copy[features].replace(0.0, np.nan)

features.extend(["updrs_1", "updrs_2", "updrs_3", "updrs_4"])

correlation_matrix = train_copy[features].corr(method="spearman")

from matplotlib.colors import SymLogNorm

f, ax = plt.subplots(figsize=(20, 20))
_ = sns.heatmap(
    correlation_matrix.iloc[13:17,0:13], 
    cmap=sns.diverging_palette(230, 20, as_cmap=True), 
    center=0,
    square=True, 
    linewidths=.1, 
    cbar=False,
    ax=ax,
    annot=True,
)
_ = ax.set_title("Spearman Correlation Matrix for Peptides and Protiens of Interest vs UPDRS scores", fontsize=15)
# 感兴趣的肽和蛋白与 UPDRS 分数的斯皮尔曼相关矩阵

在这里插入图片描述

  • 蛋白质 P07333updrs_3 呈弱负相关
  • GLVSWGNIPC(UniMod_4)GSKupdrs_1 呈弱负相关。
    这些观察结果与论文中的发现一致。 简而言之,作者指出,没有任何一种单独的蛋白质或肽与 UPDRS 评分有明显的相关性——只有与其他肽和蛋白质结合时才会出现更强的信号。 该论文概述了一个与整体疾病严重程度有很强相关性的 2 肽模型。 不幸的是,训练数据集没有论文中描述的蛋白质和肽的正确组合。 尽管如此,上述两个发现可能足以提供最小的提升。

6.模型训练

我们要构建的第一个模型是一个简单的模型——我们将绝对不使用蛋白质或肽数据。 本质上,我们最终只会使用有关访问月份的先前信息来预测未来。
train_clinical_data

# 构建数据集
train_dict = {}

for index, row in train_clinical_data.iterrows():
    patient_id = row["patient_id"]
    visit_month = row["visit_month"]
    if patient_id not in train_dict:
        train_dict[patient_id] = {}
    train_dict[patient_id][visit_month] = {
           "updrs_1": row["updrs_1"],
           "updrs_2": row["updrs_2"],
           "updrs_3": row["updrs_3"],
           "updrs_4": row["updrs_4"],
    }
    
train = train_clinical_data.copy()
train["month_offset"] = 0

for index, row in train_clinical_data.iterrows():
    visit_id = row["visit_id"]
    patient_id = row["patient_id"]
    visit_month = row["visit_month"]
    month_offsets = [6, 12, 24]
    for month_offset in month_offsets:
        new_visit_month = visit_month + month_offset
        if new_visit_month in train_dict[patient_id]:
            new_row = {
                "visit_id": visit_id,
                "visit_month": visit_month,
                "month_offset": month_offset,
                "patient_id": patient_id,
                "updrs_1": train_dict[patient_id][new_visit_month]["updrs_1"],
                "updrs_2": train_dict[patient_id][new_visit_month]["updrs_2"],
                "updrs_3": train_dict[patient_id][new_visit_month]["updrs_3"],
                "updrs_4": train_dict[patient_id][new_visit_month]["updrs_4"],
                "upd23b_clinical_state_on_medication": row["upd23b_clinical_state_on_medication"],
            }
            train = train.append(new_row, ignore_index=True)
train

在这里插入图片描述

from catboost import CatBoostRegressor
from sklearn.model_selection import KFold

# 0% 的 SMAPE 表示模型的预测完全准确,100% 的 SMAPE 表示模型的预测非常差,因为它低估或高估了实际值的 100%
def smape(y_true, y_pred):
    denominator = (y_true + np.abs(y_pred)) / 200.0 # 分母
    diff = np.abs(y_true - y_pred) / denominator
    diff[denominator == 0] = 0.0
    return np.mean(diff)

features = ['visit_month', 'month_offset']

train_copy = train.copy()
train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]] = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].fillna(0)

n_folds = 5
skf = KFold(n_splits=n_folds, random_state=2023, shuffle=True)
train_oof_preds = np.zeros((train.shape[0], 4))
smape_scores = []

for fold, (train_index, test_index) in enumerate(skf.split(train_copy, train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]])):
    print(f"-------> Fold {fold + 1} <--------")
    x_train, x_valid = pd.DataFrame(train_copy.iloc[train_index]), pd.DataFrame(train_copy.iloc[test_index])
    y_train, y_valid = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[train_index], train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[test_index]

    x_train_features = pd.DataFrame(x_train[features])
    x_valid_features = pd.DataFrame(x_valid[features])
    
    model = CatBoostRegressor(
        eval_metric="MultiRMSE",
        loss_function="MultiRMSE",
        random_state=2023,
        num_boost_round=5000,
        od_type="Iter",
        od_wait=200,
        use_best_model=True,
        verbose=0,    
    )
    
    model.fit(
        x_train_features[features], 
        y_train,
        eval_set=[(x_valid_features[features], y_valid)],
        verbose=0,
        early_stopping_rounds=200,
        use_best_model=True,
    )

    oof_preds = model.predict(x_valid_features[features])
    train_oof_preds[test_index] = np.rint(oof_preds)

    reshaped_truth = y_valid.to_numpy().reshape(-1, 1)
    new_preds = np.rint(oof_preds)
    reshaped_preds = new_preds.reshape(-1, 1)

    local_smape = smape(reshaped_truth.flatten(), reshaped_preds.flatten())
    smape_scores.append(local_smape)
    print(": SMAPE = {}".format(local_smape))
    
smape_baseline = np.mean(smape_scores)
print("--> Overall results for out of fold predictions")
print(": SMAPE = {}".format(smape_baseline))

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5))

data = pd.DataFrame({"Fold": [x + 1 for x in range(n_folds)], "SMAPE": smape_scores})
_ = sns.lineplot(x="Fold", y="SMAPE", data=data, ax=ax)
_ = ax.set_title("SMAPE per Fold", fontsize=15)
_ = ax.set_ylabel("SMAPE")
_ = ax.set_xlabel("Fold #")

在这里插入图片描述
正如我们所见,我们得到的 SMAPE 分数为 95.7,这不是很好。 然而,我们可以对此分数进行改进。
总的来说,由于缺失值和空值,我们的模型目前无法了解有关 UPDRS 4 的足够信息。 处理此问题的一种简单方法是将其归零。 从临床的角度来看,这不是很好,但它可能会为我们带来提升,因为我们不会多次受到惩罚,因为我们可能会预测一个应该没有的值。

features = [
    'visit_month', 'month_offset',
]

train_copy = train.copy()
train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]] = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].fillna(0)

n_folds = 5
skf = KFold(n_splits=n_folds, random_state=2023, shuffle=True)
train_oof_preds = np.zeros((train.shape[0], 4))
smape_scores = []

for fold, (train_index, test_index) in enumerate(skf.split(train_copy, train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]])):
    print("-------> Fold {} <--------".format(fold + 1))
    x_train, x_valid = pd.DataFrame(train_copy.iloc[train_index]), pd.DataFrame(train_copy.iloc[test_index])
    y_train, y_valid = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[train_index], train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[test_index]
    
    x_train_features = pd.DataFrame(x_train[features])
    x_valid_features = pd.DataFrame(x_valid[features])

    model = CatBoostRegressor(
        eval_metric="MultiRMSE",
        loss_function="MultiRMSE",
        random_state=2023,
        num_boost_round=5000,
        od_type="Iter",
        od_wait=200,
        use_best_model=True,
        verbose=0,    
    )
    model.fit(
        x_train_features[features], 
        y_train,
        eval_set=[(x_valid_features[features], y_valid)],
        verbose=0,
        early_stopping_rounds=200,
        use_best_model=True,
    )
    oof_preds = model.predict(x_valid_features[features])
    oof_preds[:, 3] = 0
    train_oof_preds[test_index] = np.rint(oof_preds)

    reshaped_truth = y_valid.to_numpy().reshape(-1, 1)
    new_preds = np.rint(oof_preds)
    reshaped_preds = new_preds.reshape(-1, 1)

    local_smape = smape(reshaped_truth.flatten(), reshaped_preds.flatten())
    smape_scores.append(local_smape)
    print(": SMAPE = {}".format(local_smape))
    
smape_updrs40 = np.mean(smape_scores)
print("--> Overall results for out of fold predictions")
print(": SMAPE = {}".format(smape_updrs40))

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5))

data = pd.DataFrame({"Fold": [x + 1 for x in range(n_folds)], "SMAPE": smape_scores})
_ = sns.lineplot(x="Fold", y="SMAPE", data=data, ax=ax)
_ = ax.set_title("SMAPE per Fold", fontsize=15)
_ = ax.set_ylabel("SMAPE")
_ = ax.set_xlabel("Fold #")

在这里插入图片描述
正如我们所见,只需将 UPDRS 4 值设置为 0 即可提高我们的 SMAPE 分数。 这里显然有改进的空间,特别是如果我们可以利用信息做出更好的 UPDRS 4 预测。 对于未来的模型,我们将继续使用归零的 UPDRS 分数。
Supplemental Data
如果我们要继续忽略蛋白质和肽数据,那么我们可以添加额外的临床数据。 这可能会为我们带来提升,因为我们为回归器提供了更多信息以供使用。

supplemental_dict = {}


for index, row in supplemental_clinical_data.iterrows():
    patient_id = row["patient_id"]
    visit_month = row["visit_month"]
    if patient_id not in supplemental_dict:
        supplemental_dict[patient_id] = {}
    supplemental_dict[patient_id][visit_month] = {
           "updrs_1": row["updrs_1"],
           "updrs_2": row["updrs_2"],
           "updrs_3": row["updrs_3"],
           "updrs_4": row["updrs_4"],
    }
    
additional = supplemental_clinical_data.copy()
additional["month_offset"] = 0

for index, row in supplemental_clinical_data.iterrows():
    visit_id = row["visit_id"]
    patient_id = row["patient_id"]
    visit_month = row["visit_month"]
    month_offsets = [6, 12, 24]
    for month_offset in month_offsets:
        new_visit_month = visit_month + month_offset
        if new_visit_month in supplemental_dict[patient_id]:
            new_row = {
                "visit_id": visit_id,
                "visit_month": visit_month,
                "month_offset": month_offset,
                "patient_id": patient_id,
                "updrs_1": supplemental_dict[patient_id][new_visit_month]["updrs_1"],
                "updrs_2": supplemental_dict[patient_id][new_visit_month]["updrs_2"],
                "updrs_3": supplemental_dict[patient_id][new_visit_month]["updrs_3"],
                "updrs_4": supplemental_dict[patient_id][new_visit_month]["updrs_4"],
                "upd23b_clinical_state_on_medication": row["upd23b_clinical_state_on_medication"],
            }
            additional = additional.append(new_row, ignore_index=True)

additional[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]] = additional[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].fillna(0)

features = [
    'visit_month', 'month_offset',
]

train_copy = train.copy()
train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]] = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].fillna(0)

n_folds = 5
skf = KFold(n_splits=n_folds, random_state=2023, shuffle=True)
train_oof_preds = np.zeros((train.shape[0], 4))
smape_scores = []

chunk_size = int(len(additional) / n_folds)
additional_chunks = [additional[i:i+chunk_size] for i in range(0, len(additional), chunk_size)]

for fold, (train_index, test_index) in enumerate(skf.split(train_copy, train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]])):
    print("-------> Fold {} <--------".format(fold + 1))
    x_train, x_valid = pd.DataFrame(train_copy.iloc[train_index]), pd.DataFrame(train_copy.iloc[test_index])
    y_train, y_valid = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[train_index], train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[test_index]
    
    x_train_features = pd.DataFrame(x_train[features])
    x_train_features = pd.concat([x_train_features, additional_chunks[fold]]).reset_index(drop=True)
    y_train = y_train.append(additional_chunks[fold][["updrs_1", "updrs_2", "updrs_3", "updrs_4"]])
    
    x_valid_features = pd.DataFrame(x_valid[features])

    model = CatBoostRegressor(
        eval_metric="MultiRMSE",
        loss_function="MultiRMSE",
        random_state=2023,
        num_boost_round=5000,
        od_type="Iter",
        od_wait=200,
        use_best_model=True,
        verbose=0,    
    )
    model.fit(
        x_train_features[features], 
        y_train,
        eval_set=[(x_valid_features[features], y_valid)],
        verbose=0,
        early_stopping_rounds=200,
        use_best_model=True,
    )
    oof_preds = model.predict(x_valid_features[features])
    oof_preds[:, 3] = 0
    train_oof_preds[test_index] = np.rint(oof_preds)

    reshaped_truth = y_valid.to_numpy().reshape(-1, 1)
    new_preds = np.rint(oof_preds)
    reshaped_preds = new_preds.reshape(-1, 1)

    local_smape = smape(reshaped_truth.flatten(), reshaped_preds.flatten())
    smape_scores.append(local_smape)
    print(": SMAPE = {}".format(local_smape))
    
smape_additional = np.mean(smape_scores)
print("--> Overall results for out of fold predictions")
print(": SMAPE = {}".format(smape_additional))

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5))

data = pd.DataFrame({"Fold": [x + 1 for x in range(n_folds)], "SMAPE": smape_scores})
_ = sns.lineplot(x="Fold", y="SMAPE", data=data, ax=ax)
_ = ax.set_title("SMAPE per Fold", fontsize=15)
_ = ax.set_ylabel("SMAPE")
_ = ax.set_xlabel("Fold #")

在这里插入图片描述
与单独使用临床数据相比,我们看到了一些改进。 这可能会在隐藏测试数据不包含蛋白质信息的情况下帮助我们。
用药状态
虽然我们的测试数据中没有患者的药物治疗状态,但人们可能仍然对它感兴趣,因为我们看到药物治疗状态确实会随着时间的推移对 UPDRS 分数产生直接影响。 让我们看一下,如果我们有它,它是否有可能提供提升。

features = [
    'visit_month', 'month_offset', 'med_state',
]

train_copy = train.copy()
train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]] = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].fillna(0)

n_folds = 5
skf = KFold(n_splits=n_folds, random_state=2023, shuffle=True)
train_oof_preds = np.zeros((train.shape[0], 4))
smape_scores = []

train_copy["med_state"] = train_copy["upd23b_clinical_state_on_medication"]
train_copy["med_state"] = train_copy["med_state"].apply(lambda x: 0 if x == "Off" else 1 if x == "On" else 2)

for fold, (train_index, test_index) in enumerate(skf.split(train_copy, train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]])):
    print("-------> Fold {} <--------".format(fold + 1))
    x_train, x_valid = pd.DataFrame(train_copy.iloc[train_index]), pd.DataFrame(train_copy.iloc[test_index])
    y_train, y_valid = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[train_index], train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[test_index]
    
    x_train_features = pd.DataFrame(x_train[features])
    x_valid_features = pd.DataFrame(x_valid[features])

    model = CatBoostRegressor(
        eval_metric="MultiRMSE",
        loss_function="MultiRMSE",
        random_state=2023,
        num_boost_round=5000,
        od_type="Iter",
        od_wait=200,
        use_best_model=True,
        verbose=0,    
    )
    model.fit(
        x_train_features[features], 
        y_train,
        eval_set=[(x_valid_features[features], y_valid)],
        verbose=0,
        early_stopping_rounds=200,
        use_best_model=True,
    )
    oof_preds = model.predict(x_valid_features[features])
    oof_preds[:, 3] = 0
    train_oof_preds[test_index] = np.rint(oof_preds)

    reshaped_truth = y_valid.to_numpy().reshape(-1, 1)
    new_preds = np.rint(oof_preds)
    reshaped_preds = new_preds.reshape(-1, 1)

    local_smape = smape(reshaped_truth.flatten(), reshaped_preds.flatten())
    smape_scores.append(local_smape)
    print(": SMAPE = {}".format(local_smape))
    
smape_med = np.mean(smape_scores)
print("--> Overall results for out of fold predictions")
print(": SMAPE = {}".format(smape_med))

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5))

data = pd.DataFrame({"Fold": [x + 1 for x in range(n_folds)], "SMAPE": smape_scores})
_ = sns.lineplot(x="Fold", y="SMAPE", data=data, ax=ax)
_ = ax.set_title("SMAPE per Fold", fontsize=15)
_ = ax.set_ylabel("SMAPE")
_ = ax.set_xlabel("Fold #")

在这里插入图片描述
Protein Data
到目前为止,我们还没有研究过蛋白质或肽的信息。 让我们看看当我们添加时会发生什么。

from sklearn.model_selection import GroupKFold

features = [
    "visit_month", "month_offset", "O00391", "O00533", "O00584", "O14498", "O14773", "O14791", "O15240", 
    "O15394", "O43505", "O60888", "O75144", "O75326", "O94919", "P00441", "P00450", "P00734", "P00736", 
    "P00738", "P00746", "P00747", "P00748", "P00751", "P01008", "P01009", "P01011", "P01019", "P01023", 
    "P01024", "P01031", "P01033", "P01034", "P01042", "P01344", "P01591", "P01608", "P01621", "P01717", 
    "P01780", "P01833", "P01834", "P01857", "P01859", "P01860", "P01861", "P01876", "P01877", "P02452", 
    "P02647", "P02649", "P02652", "P02655", "P02656", "P02671", "P02675", "P02679", "P02747", "P02748",
    "P02749", "P02750", "P02751", "P02753", "P02760", "P02763", "P02765", "P02766", "P02768", "P02774",
    "P02787", "P02790", "P04004", "P04075", "P04156", "P04180", "P04196", "P04207", "P04211", "P04216", 
    "P04217", "P04275", "P04406", "P04433", "P05060", "P05067", "P05090", "P05155", "P05156", "P05408", 
    "P05452", "P05546", "P06310", "P06396", "P06454", "P06681", "P06727", "P07195", "P07225", "P07333", 
    "P07339", "P07602", "P07711", "P07858", "P07998", "P08123", "P08133", "P08253", "P08294", "P08493", 
    "P08571", "P08603", "P08637", "P08697", "P09104", "P09486", "P09871", "P10451", "P10643", "P10645", 
    "P10909", "P11142", "P11277", "P12109", "P13473", "P13521", "P13591", "P13611", "P13671", "P13987", 
    "P14174", "P14314", "P14618", "P16035", "P16070", "P16152", "P16870", "P17174", "P17936", "P18065", 
    "P19021", "P19652", "P19823", "P19827", "P20774", "P20933", "P23083", "P23142", "P24592", "P25311", 
    "P27169", "P30086", "P31997", "P35542", "P36222", "P36955", "P36980", "P39060", "P40925", "P41222", 
    "P43121", "P43251", "P43652", "P49588", "P49908", "P51884", "P54289", "P55290", "P61278", "P61626", 
    "P61769", "P61916", "P80748", "P98160", "Q02818", "Q06481", "Q08380", "Q12805", "Q12841", "Q12907", 
    "Q13283", "Q13332", "Q13451", "Q13740", "Q14118", "Q14508", "Q14515", "Q14624", "Q15904", "Q16270",
    "Q16610", "Q562R1", "Q6UX71", "Q6UXB8", "Q6UXD5", "Q7Z3B1", "Q7Z5P9", "Q8IWV7", "Q8N2S1", "Q8NBJ4",
    "Q8NE71", "Q92520", "Q92823", "Q92876", "Q96BZ4", "Q96KN2", "Q96PD5", "Q96S96", "Q99435", "Q99674", 
    "Q99832", "Q99969", "Q9BY67", "Q9HDC9", "Q9NQ79", "Q9NYU2", "Q9UBR2", "Q9UBX5", "Q9UHG2", "Q9UNU6", 
    "Q9Y646", "Q9Y6R7", "P01594", "P02792", "P32754", "P60174", "Q13449", "Q99683", "Q99829", "Q9UKV8"
]

print(len(features))

train_copy = train.copy()
train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]] = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].fillna(0)

proteins = train_protiens["UniProt"].unique()

protein_visit_dict = {}
for protein in proteins:
    if protein not in protein_visit_dict:
        protein_visit_dict[protein] = {}
    for index, row in train_protiens[(train_protiens["UniProt"] == protein)].iterrows():
        visit_id = row["visit_id"]
        protein_visit_dict[protein][visit_id] = row["NPX"]
        
for protein in proteins:
    train_copy[protein] = train_copy["visit_id"].apply(
           lambda visit_id: protein_visit_dict[protein][visit_id] if visit_id in protein_visit_dict[protein] else 0
    )

n_folds = 5
skf = GroupKFold(n_splits=n_folds)
train_oof_preds = np.zeros((train.shape[0], 4))
smape_scores = []

for fold, (train_index, test_index) in enumerate(skf.split(train_copy, train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]], groups=train_copy["patient_id"])):
    print("-------> Fold {} <--------".format(fold + 1))
    x_train, x_valid = pd.DataFrame(train_copy.iloc[train_index]), pd.DataFrame(train_copy.iloc[test_index])
    y_train, y_valid = train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[train_index], train_copy[["updrs_1", "updrs_2", "updrs_3", "updrs_4"]].iloc[test_index]
    
    x_train_features = pd.DataFrame(x_train[features])
    x_valid_features = pd.DataFrame(x_valid[features])

    model = CatBoostRegressor(
        eval_metric="MultiRMSE",
        loss_function="MultiRMSE",
        random_state=2023,
        num_boost_round=5000,
        od_type="Iter",
        od_wait=200,
        use_best_model=True,
        verbose=0,    
    )
    model.fit(
        x_train_features[features], 
        y_train,
        eval_set=[(x_valid_features[features], y_valid)],
        verbose=0,
        early_stopping_rounds=200,
        use_best_model=True,
    )
    oof_preds = model.predict(x_valid_features[features])
    oof_preds[:, 3] = 0
    train_oof_preds[test_index] = np.rint(oof_preds)

    reshaped_truth = y_valid.to_numpy().reshape(-1, 1)
    new_preds = np.rint(oof_preds)
    reshaped_preds = new_preds.reshape(-1, 1)

    local_smape = smape(reshaped_truth.flatten(), reshaped_preds.flatten())
    smape_scores.append(local_smape)
    print(": SMAPE = {}".format(local_smape))
    
smape_protein = np.mean(smape_scores)
print("--> Overall results for out of fold predictions")
print(": SMAPE = {}".format(smape_protein))

fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(5, 5))

data = pd.DataFrame({"Fold": [x + 1 for x in range(n_folds)], "SMAPE": smape_scores})
_ = sns.lineplot(x="Fold", y="SMAPE", data=data, ax=ax)
_ = ax.set_title("SMAPE per Fold", fontsize=15)
_ = ax.set_ylabel("SMAPE")
_ = ax.set_xlabel("Fold #")

在这里插入图片描述
模型比较
我们可以比较每个模型的性能,看看哪个模型的性能最好。 红色虚线表示我们希望超越的基准性能。

bar, ax = plt.subplots(figsize=(20, 10))
ax = sns.barplot(
    x=["Baseline", "Constant UPDRS 4", "Supplemental Data", "Medication State", "Protein Data"],
    y=[
        smape_baseline,
        smape_updrs40,
        smape_additional,
        smape_med,
        smape_protein,
    ]
)
_ = ax.axhline(y=69.51, color='r', linestyle='--')
_ = ax.set_title("SMAPE Score (Lower is Better)", fontsize=15)
_ = ax.set_xlabel("")
_ = ax.set_ylabel("SMAPE")
_ = ax.set_ylim([61, 97])
for p in ax.patches:
    height = p.get_height()
    ax.text(x=p.get_x()+(p.get_width()/2), y=height, s="{:.4f}".format(height), ha="center")

在这里插入图片描述

5.结论

根据我们的观察,有几个结论:

  • 数据集的大小意味着内存压力可能不会太大。
  • 临床数据中共有 248 例患者,补充数据中有 771 例患者。
  • 就缺失信息而言:
    • 蛋白质或肽数据中没有空条目。
    • 临床数据和补充数据中有空条目。
      • 我们不能假设空值表示 UPDRS 评估部件等功能的 0 值,因为 0 值表示“正常”响应。
      • 患者服药状态下的空值不能假定为“开”或“关”。
  • 重复数据很少出现,如果不明确过滤掉,不太可能影响机器学习模型。
  • 与补充数据相比(0 - 108 个月与 0 - 36 个月),临床数据在“visit_month”中的范围似乎更大。
    • 一般来说,临床数据和补充数据在数据分布方面有很大不同,正如统计和对抗性验证所证实的那样。
  • 参考 Shi 等人 (2015) 的研究:
    • 获得的蛋白质和肽样品不会出现在作为疾病进展或严重程度的明确指标所需的组合中。
    • 我们的相关性分析表明,蛋白质“P07333”与“updrs_3”呈弱负相关,肽“GLVSWGNIPC(UniMod_4)GSK”与“updrs_1”呈弱负相关。

6.引用

  • Goetz, C. G., Tilley, B. C., Shaftman, S. R., Stebbins, G. T., Fahn, S., Martinez-Martin, P., Poewe, W., Sampaio, C., Stern, M. B., Dodel, R., Dubois, B., Holloway, R., Jankovic, J., Kulisevsky, J., Lang, A. E., Lees, A., Leurgans, S., LeWitt, P. A., Nyenhuis, D., Olanow, C. W., Rascol, O., Schrag, A., Teresi, J. A., van Hilten, J. J., and LaPelle, N. (2008). Movement Disorder Society-Sponsored Revision of the Unified Parkinson’s Disease Rating Scale (MDS-UPDRS): Scale Presentation and Clinimetric Testing Results. Movement Disorders, 23(15), 2129–2170. DOI: 10.1002/mds.22340
  • Holden, S.K., Finseth, T., Sillau, S.H. and Berman, B.D. (2018). Progression of MDS-UPDRS Scores Over Five Years in De Novo Parkinson Disease from the Parkinson’s Progression Markers Initiative Cohort. Movement Disorders Clinical Practice, 5, 47-53. DOI: 10.1002/mdc3.12553
  • Shi, M., Movius, J., Dator, R. P., Aro, P., Zhao, Y., Pan, C., Lin, X., Bammler, T. K., Stewart, T., Zabetian, C. P., Peskind, E. R., Hu, S. F., Quinn, J. F., Galasko, D., & Zhang, J. (2015). Cerebrospinal Fluid Peptides as Potential Parkinson Disease Biomarkers: A Staged Pipeline for Discovery and Validation*. Molecular & Cellular Proteomics, 14(3), 544–555. DOI: 10.1074/mcp.m114.040576

笔记本地址

笔记本地址-github

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/478741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯算法竞赛系列第四章——二分算法

欢迎回到&#xff1a;遇见蓝桥遇见你&#xff0c;不负代码不负卿&#xff01; 目录 引入&#xff1a;二分查找 题目描述 题解 代码执行 复杂度分析 例题一&#xff1a;搜索插入位置 题目描述 题解 代码执行 复杂度分析 例题二&#xff1a;寻找峰值 题目描述 题解 …

【五一创作】python 基础系列篇:八、熟练掌握推导式

python 基础系列篇&#xff1a;八、熟练掌握推导式 推导式特殊的元组推导式 推导式机制玩转推导式小结 推导式 在python提供的各种语法糖中&#xff0c;老顾最青睐的就是这个推导式&#xff0c;他大大减少了代码的书写量。 比如一个正常的&#xff0c;生成长度为5的列表&…

红黑树的概念与实现

目录 ​一、红黑树的概念 1.什么是红黑树 2.红黑树满足的性质 3.红黑树存在的意义 二、红黑树的实现 1.类的构建 2.插入函数 &#xff08;1&#xff09;插入一个节点 &#xff08;2&#xff09;调整节点 &#xff08;3&#xff09;旋转 三、红黑树的检验 一、红黑树…

okio篇2-RealBufferedSource

上一篇讲过&#xff0c;okio只有两个概念&#xff0c;source和sink。source对应InputStream&#xff0c;即负责将数据读出&#xff0c;是一个输出方&#xff08;所以只有source.read方法&#xff09;。sink对应outputStream&#xff0c;负责获取数据写入&#xff0c;是一个写入…

RT-Thread Nano在keil Simulator中的仿真

目的&#xff1a;使用STM32CubeMX生成包含RT-Thread Nano内核和FinSH控制台的keil工程&#xff0c;在没有硬件开发板的情况下&#xff0c;通过keil Simulator来运行系统&#xff0c;并通过SHELL来与系统进行交互。 一、使用STM32CubeMX生成RT-Thread Nano工程 官方文档已经说…

C++标准库 -- 动态内存 (Primer C++ 第五版 · 阅读笔记)

C标准库 --动态内存 (Primer C 第五版 阅读笔记&#xff09; 第12章 动态内存------(持续更新)12.1、动态内存与智能指针12.1.1、shared_ptr类12.1.2、直接管理内存12.1.3、shared_ptr和new结合使用12.1.4、智能指针和异常12.1.5、unique_ptr12.1.6、weak_ptr 12.2、动态数组1…

网络通信之网络层与数据链路层

文章目录 讲在前面网络层网络层概述IP协议格式网段划分公有IP、私有IP、特殊IP理解路由 数据链路层MAC地址以及MAC帧&#xff08;以太网帧&#xff09;MTU协议MTU对IP和TCP协议的影响ARP协议及其作用 涉及到的相关协议DNS协议&#xff08;应用层&#xff09;NAT与NAPT协议 总结…

BEV (0)---DETR

1 DETR 1.1 DETR处理流程 1.1.1 将图像输入给Backbone获得图像特征与位置编码 ①. 对给定的输入图像通过resnet进行特征提取&#xff0c;最终得到特征图C5∈RBx2048xhxw,其中h、w为输入图像尺寸得1/32。随后再用一层11卷积压缩一下通道&#xff0c;得到特征图P5∈RBx256xhxw。…

jvm调优策略

jvm调优主要是内存管理方面的调优&#xff0c;包括各个代的大小&#xff0c;GC策略等。 代大小调优 JVM 中最大堆大小有三方面限制&#xff1a;相关操作系统的数据模型&#xff08;32-bt还是64-bit&#xff09;限制&#xff1b;系统的可用虚拟内存限制&#xff1b;系统的可用物…

数据结构学习记录——什么是堆(优先队列、堆的概念、最大堆最小堆、优先队列的完全二叉树表示、堆的特性、堆的抽象数据类型描述)

目录 优先队列 若采用数组或链表实现优先队列 数组 链表 有序数组 有序链表 总结 若采用二叉搜索树来实现优先队列 最大堆 堆的概念 优先队列的完全二叉树表示 堆的两个特性 结构性 有序性 【例】最大堆和最小堆 【例】不是堆 堆的抽象数据类型描述 优先队列…

安排超市 -- BFS分割搜索

4.安排超市 给定一个n*n的地图。地图是上下左右四联通的&#xff0c;不能斜向行走&#xff1a; *代表障碍&#xff0c;不可通行。 .代表路&#xff0c;可以通行。 #代表房子。房子也是可以通行的。 小红现在需要在一些地方安排一些超市&#xff08;不能安排在障碍物上&#xf…

山东专升本计算机第七章-计算机网络基础

计算机网络基础 计算机网络系统 考点 6 计算机网络硬件 主体设备 • 称为主机 • 一般可分为中心站&#xff08;又称服务器&#xff09;和工作站&#xff08;客户机&#xff09; 连接设备 • 网卡 • 工作在数据链路层 • 网卡又称网络适配器&#xff0c;是连接主机和网…

【C++初阶】引用

一.概念 引用就是取别名&#xff0c;在语法上它不会开空间&#xff0c;而是和它引用的变量共用同一块空间。对引用的操作也就是对原来变量的操作。就像现实生活中给人取外号一样&#xff0c;不管是喊外号还是本名&#xff0c;指的都是那个人。 二.引用特性 1.引用类型必须和引用…

Java8 新特性讲解

一、Lambda表达式 Lambda 是一个匿名函数&#xff0c;我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格&#xff0c;使Java的语言表达能力得到了提升。 二、函数式接口 &#…

【网课平台】Day15.Devops:持续集成与持续交付

文章目录 一、Devops1、什么是Devops2、什么是CI/CD3、Devops方案参考 二、人工部署1、项目打jar包2、生成镜像、创建容器 三、自动化部署1、代码提交到git2、修改pom.xml文件3、前端部署 一、Devops 1、什么是Devops 一个软件的生命周期包括&#xff1a;需求分析阶、设计、开…

SpringCloud:ElasticSearch之集群

单机的elasticsearch做数据存储&#xff0c;必然面临两个问题&#xff1a;海量数据存储问题、单点故障问题。 海量数据存储问题&#xff1a;将索引库从逻辑上拆分为N个分片&#xff08;shard&#xff09;&#xff0c;存储到多个节点单点故障问题&#xff1a;将分片数据在不同节…

【原创】运维的终点是开发~chatGPT告诉你真相

文章目录 软件技术岗位鄙视链&#xff0c;你在哪层呢&#xff1f;让chatGPT告诉你运维工作好&#xff0c;还是开发工作好问它几个问题1. 一个三年运维成长的案例和薪资2. 一个三年开发成长的案例和薪资3. 一个五年运维成长的案例和薪资4. 一个五年开发成长的案例和薪资5. 一个十…

云分析迁移:顺应需求

云提供了对新分析功能、工具和生态系统的访问&#xff0c;可以快速利用这些功能、工具和生态系统来测试、试点和推出新产品。然而&#xff0c;尽管迫在眉睫&#xff0c;但企业在将分析迁移到云时仍感到担忧。组织正在寻找能够帮助他们分配资源和集成业务流程的服务提供商&#…

Linux 服务器上安装和使用 Redis,只需这 4 步!

一、使用 yum 安装 Redis 使用以下命令&#xff0c;直接将 redis 安装到 linux 服务器&#xff1a; yum -y install redis 二、配置远程连接 a&#xff09;首先第一步&#xff0c;将 redis 配置文件下载到本地&#xff08;如果你熟悉 vim 操作&#xff0c;直接用 vim 编辑即可…

论文阅读《PIDNet: A Real-time Semantic Segmentation Network Inspired by PID》

论文地址&#xff1a;https://arxiv.org/pdf/2206.02066.pdf 源码地址&#xff1a;https://github.com/XuJiacong/PIDNet 概述 针对双分支模型在语义分割任务上直接融合高分辨率的细节信息与低频的上下文信息过程中细节特征会被上下文信息掩盖的问题&#xff0c;提出了一种新的…