如何使用python sklearn 中的LeaveOneGroupOu?特例:leave one pair out的实现多重比较校正?

引言

一般我们会在研究中验证某个特征的有效性,会使用该特征对病例个样本进行二分类,通常回选择支持向量机(SVM),这里的目的并非一定要得到很高的正确率,更多地只是想说明该特征可以区分case组和control组。

留一法 or 留一对法?

因为样本量的原因,一般是使用留一法进行多重比较校正,但是有时候也会被要求使用留一对的方法,简单的就是每一次从case中拿出一例,然后从control中拿出一例,使用剩余的(m+n)- 2的样本进行训练,对取出的样本(一个来自case一个来自control)进行测试。

但是你可能会想case组有m个样本,control组有n个样本,两组人的数量不一样(m≠n)要怎么处理呢?
  1. 首先k=m<n?m:n(选取二者中较小的一个数)
  2. 一共进行k次随机取样本,训练模型,分类,得到分类正确率
  3. 然后重复步骤(1,2)N次,就会得到多个准确率和多条ROC曲线。
  4. 你可能会问为什么要重复这多(N)次?因为这m和n不相等,进行k次抽样,肯定会导致有一些样本(样本量大的那一组有样本无法被抽中成为测试集),因此通过重复N次来较少这个因素。
具体如何实现留一对?leave one pair out?

在我以前的博客中已经介绍了如何使用留一法的SVM,具体见留一法SVM,可以借助sklearn.model_selection中的LeaveOneGroupOut实现,该函数可以根据组别(可以理解为每一个样本有一个所属组的编号)将数据分成与组别相同的组数,比如我有20个样本,这20个样本根据性别可以分为2组。在后续的多重比较校正中每一次取出其中的一组作为测试集,剩余组作为训练集即可。leave one pair out 是上述过程的一个特例(因为在对两组人进行分类时,可能会有需求每次将类别1和类别2同时拿出来一个作为测试集),因此接祖leaveOneGroupOut,我们只需要对两组人进行分别编号,然后编号对应时作为一组,具体实现过程如下:

# 该方法是每次排除一个组,怎样才能每次排除一对?那就是正常组和病人组对应配对?
pair_data1 = [i for i in range(np.size(data1_label,0))]
pair_data2 = [i for i in range(np.size(data2_label,0))]
# 随机打乱分组
random.shuffle(pair_data1)
random.shuffle(pair_data2)
pair = pair_data1+pair_data2
loo = LeaveOneGroupOut()
loo.get_n_splits(dataset,groups=pair)
...
for train_index, test_index in loo.split(dataset,groups=pair):
    X_train, X_test = dataset[train_index], dataset[test_index]
    Y_train, Y_test = np.array(datalabels)[train_index], np.array(datalabels)[test_index]
    ...

上述代码说明
  1. 首先将data1进行编号0:M-1,然后对N进行编号0:N-1,数字为组别
  2. 之后对组别进行打乱,打乱的组别(后续说明为什么打乱)
  3. 然后将分组编号合并(前面为data1的组别,后面为data2的组别)
  4. 然后将样本特征及其对应的分组编号作为参数输入到LeaveOneGroupOut的get_n_splits中,其实上述过程会将数据分成max(M,N)份,如果M与N的大小不一样的话,整个过程会出现leave one out 的情况(后续会对此进行处理)
特殊处理说明
  1. 因为编号是由小到大,因此当编号小于min(M,N)时程序都能正常的leave one pair out,当编号大于等于min(M,N)时只能得到一个一个样本,此时在代码中加入判断条件跳出循环,就可以了。代码如下:
print(test_index)
    if np.size(X_test,0)==1:
        print('条件不满足,跳出循环!')
        break
  1. 为什么需要随机打乱?因为两组人的数量可能不同,分组的方式也不唯一,因此需要随机打乱来排除这种认为分组方式对结果的影响,因此每次代码的运行分组的方式都是不一样的,可以通过多运行几次来减小分组带来的误差,同时可以借此判断结果是否稳定。
完整实现代码

"""
Created on Wed Sep  9 10:06:14 2020

@author: Administer
"""

import numpy as np
from sklearn.model_selection import LeaveOneOut
from sklearn.model_selection import LeaveOneGroupOut
import scipy.io as scio
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
import random

###################功能性函数###################################################
# 将一个任意嵌套的列表整理为一个列表
def flatten(nested):
    try:
        for sublist in nested:
            for element in flatten(sublist):
                yield element
    except TypeError:
        yield nested
    

# 读取一个txt文件中的数字并将其转换为一个列表
def read_txt2number(path):
    significant_index = []
    with open(path) as f:
            for line in f.readlines():
                line = line.strip('\n')      #去掉列表中每一个元素的换行符
                significant_index.append(line)
    number_list = [int(i) for i in significant_index]   # 将字符类型转换为数字
    return number_list  

# 获取一个预测结果的TP TN FP FN
def get_TpTN_FpFn(list1,list2):
    # list1 为真实的label list2 为预测的label
    reallabel_list=list(flatten(list1))
    predictlabel_list=list(flatten(list2))
    TP_count = 0
    TN_count = 0
    FP_count = 0
    FN_count = 0
    for i in range(len(reallabel_list)):
        if reallabel_list[i] == 1 and predictlabel_list[i] ==1:
            TP_count += 1
        if reallabel_list[i] == -1 and predictlabel_list[i] ==-1:
            TN_count += 1
        if reallabel_list[i] == -1 and predictlabel_list[i] == 1:
            FP_count +=1
        if reallabel_list[i] == 1 and predictlabel_list[i] == -1:
            FN_count += 1
    return TP_count, TN_count, FP_count, FN_count
##################数据的读取与整理了#############################################
   
path2 = r'C:\Users\Administer\Desktop\GTCS_HC\20201013data_and_result\6_gene expression class_SVM\SVM class\dataset'

MSN_HC = scio.loadmat(path2 + '\\average_MSr_dataset1_HC.mat')
train_MSN_HC = MSN_HC['result']
MSN_GTCS = scio.loadmat(path2 + '\\average_MSr_dataset1_GTCS.mat')
train_MSN_GTCS = MSN_GTCS['result']

train_data1 = train_MSN_HC;
train_data2 = train_MSN_GTCS;
dataset =(np.hstack((train_data1,train_data2))).T

data1_label = list(-1 for i in range(np.size(train_data1,1)));
data2_label = list(1 for i in range(np.size(train_data2,1)))
datalabels = data1_label + data2_label


#####################################LOOVC#####################################
# loo = LeaveOneOut()
# loo.get_n_splits(dataset)

# 该方法是每次排除一个组,怎样才能每次排除一对?那就是正常组和病人组对应配对?
pair_data1 = [i for i in range(np.size(data1_label,0))]
pair_data2 = [i for i in range(np.size(data2_label,0))]
# 随机打乱分组
random.shuffle(pair_data1)
random.shuffle(pair_data2)
pair = pair_data1+pair_data2
loo = LeaveOneGroupOut()
loo.get_n_splits(dataset,groups=pair)


predictlabel_list = []
reallabel_list = []
coef_weight_list = []
Y_score_list = []
sum_of_coef = np.zeros((1,np.size(dataset,1)))
#clf = make_pipeline(StandardScaler(), SVC(C=0.9, kernel='linear', gamma='auto')) 
#clf = SVC(C=0.9, kernel='linear', gamma='auto')
#clf = svm.LinearSVC()
scaler = StandardScaler()
dataset = scaler.fit_transform(dataset)
clf = SVC(C=1, kernel='linear', gamma='auto')
count_right_label = 0
count = 0                     # 循环次数
# 用留一法进行验证

for train_index, test_index in loo.split(dataset,groups=pair):
    X_train, X_test = dataset[train_index], dataset[test_index]
    Y_train, Y_test = np.array(datalabels)[train_index], np.array(datalabels)[test_index]
    print(test_index)
    if np.size(X_test,0)==1:
        print('条件不满足,跳出循环!')
        break
    clf.fit(X_train,Y_train)
    Y_score_temp = clf.decision_function([X_test[0,:]])            # 得到的结果为该类到超平面的距离
    temp1 = clf.coef_
    Y_score_temp1 = clf.decision_function([X_test[1,:]])            # 得到的结果为该类到超平面的距离
    temp2 = clf.coef_
    sum_of_coef += np.abs(temp1)
    sum_of_coef += np.abs(temp2)
    coef_weight_list.append(temp1)
    coef_weight_list.append(temp2)
    predictlabel_list.append(clf.predict([X_test[0,:]]))
    predictlabel_list.append(clf.predict([X_test[1,:]]))
    
    reallabel_list.append(Y_test[0])
    reallabel_list.append(Y_test[1])
    
    Y_score_list.append(Y_score_temp)
    Y_score_list.append(Y_score_temp1)
    if Y_test[0] == clf.predict([X_test[0,:]]):
       count_right_label +=1 
    if Y_test[1] == clf.predict([X_test[1,:]]):
       count_right_label +=1 
    count += 1
    print('第{}次循环'.format(count))
accurancy = count_right_label/len(reallabel_list)
print('******循环结束!************')
print('准确率为:%.2f%%' %(accurancy*100))
print('******运行结束!************')

average_weight = list(flatten(sum_of_coef/2*count)) 

TP_count, TN_count, FP_count, FN_count = get_TpTN_FpFn(reallabel_list,predictlabel_list)
F1_score = (2 * TP_count)/(2*TP_count + FP_count +FN_count)
F2_score = (2 * TN_count)/(2*TN_count + FP_count +FN_count)
ACC = (TP_count + TN_count)/(TP_count + FN_count + TN_count + FP_count)
SEN = TP_count/(TP_count + FN_count)
SPE = TN_count / (TN_count + FP_count)

print('F1_SCORE为:%.2f%%' %(F1_score*100))  
print('F2_SCORE为:%.2f%%' %(F2_score*100))
print('ACC(准确率)为:%.2f%%' %(ACC*100))  
print('SEN(敏感度)为:%.2f%%' %(SEN*100))
print('SPE(特异性)为:%.2f%%' %(SPE*100))  

#print('ROI权重为:{}'.format(average_weight))

import matplotlib.pyplot as plt



# ROC AUC

from sklearn.metrics import roc_curve, auc
# 为每个类别计算ROC曲线和AUC
real = np.array(list(flatten(reallabel_list)))
predict = np.array(list(flatten(predictlabel_list)))
roc_auc = dict()
fpr, tpr, threshold = roc_curve(real,Y_score_list)
roc_auc = auc(fpr, tpr)


plt.figure()
lw = 2
plt.plot(fpr, tpr, color='darkorange',
         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
order=10
# plt.savefig('./loocv_ROC_roc'+str(order)+'.png',dpi=400) # 以400大批保存图片
plt.show()
# scio.savemat('fpr_fdr'+str(order)+'.mat',{'fpr':fpr,'tpr':tpr} )

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张小李的风

谢谢老板!!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值