(1)准备数据过程中,遇到了缺失值的问题。以往都是自己手动写代码,用缺失值样本所在类别的均值或者众数替换掉,结果今天发现,DMwR2包就有处理缺失值的函数,而且思想一致【大哭】
先奉上代码:
- install.packages("DMwR2");
- library(DMwR2) ;
- knnImputation(YourDataFrame)
(2)准备用SMOTE函数时,发现DMwR包在老早之前,就从R语言 CRAN中移除了。多方搜索,找到了DMwR包的网页下载路劲:https://cran.r-project.org/src/contrib/Archive/DMwR/?C=D;O=A。直接下载最新版,下载后将Zip文件存放至R语言目标文件夹的library中(主要是方便以后别人需要,我可以很快找到这个东西在哪里)。
比如,我的存放路径是:D:SoftwareR-4.1.1library
那么,现在回到R语言编程界面,输入代码:install.packages("D:/Software/R-4.1.1/library/DMwR_0.4.1.tar.gz", repos=NULL, type="source"),执行。【在这里我犯了一个小错,输入路径时候,把压缩包的后缀'.gz'落下了,由此走了好多弯路,解决不了,还下载R语言的2个老版本企图解决问题,不过还好问题没解决,我又发现了这个小错误。】
结果报错:“ERROR: dependency 'abind' is not available for package 'DMwR'”。于是,再手动install.packages("abind"),安装成功后,再次执行install.packages("D:/Software/R-4.1.1/library/DMwR_0.4.1.tar.gz", repos=NULL, type="source")。到这里,我的DMwR就成功了。
如果上面这个ERROR后还跟了其他的包也缺失的话,就多单独安装一下。
(3)SMOTE函数的运用
完成上述的操作,library(DMwR)即可调用SMOTE函数。
SMOTE函数如下:SMOTE(form, data, perc.over = 200, k = 5, perc.under = 200, learner = NULL, …)。
1) form是公式,直接Y~.即可(这个Y就是你因变量的名称)。值得注意的是,这里的Y,如果是数据集中是数值型的话,一定要转换成factor类型(DataFrame[,"Y"] <- factor(DataFrame[,"Y"]) 即可);
2)data,我们的数据集,包含了因变量和很多自变量的表;
3)perc.over:指定从少数样本中采样的比例;
4)perc.under:对多数样本下采样,有多少比例的样本被选入新的数据集中;
5)k:跟K近邻的思想一致,理解为通过周围都少个邻居决定生成数据吧;
6)重点!!!!怎么确定perc.over和perc.under的输入数值呢?
这里,针对我们的数据集做几个假设:
- 数据集中少数样本个数:N=30
- 数据集中少数样本个数:M=100
- 想要生成的少数样本数量TarN;多数样本TarM;| TarN=60;TarM=0
- perc.over:X; perc.under:Y
- 现在,我想生成90个少数类样本,多数类不变。怎么确定我的X和Y呢?
且看公式:
X =(TarN)*100/N = 200
Y =(M+TarM)*100*100/(X*N) = 166.6667(保留3-4位小数就可以比较精准了)
这时候,再看我们的数据集,就有N+TarN+M+TarM条数据了。这篇博客的计算公式主要参考如下链接: https://blog.csdn.net/qq_34139222/article/details/57461398,但是在实际验证的过程中,发现该博客在perc.over确定的时候,跟实际情况对不上。所以做了修改。经验证,上述X,Y的最终计算结果应该是木有问题的了。
最后,用SMOTE处理了数据的时候,记得一定要在原始数据集中分出一批数据,不参与Smote的扩增处理,保证一批干净的数据用于后续模型的验证。但是多次碰到一个问题,就是用SMOTE处理了数据后,在内部验证的结果都非常好,但一用前面说的干净数据集验证时,结果特别差。以前没有仔细找原因,今天特地看了一下自己的生成数据,发现一个问题:我的数据集中有很多类别型变量,但是用smote算法后,它由于是靠距离决定的,所以模拟数据的结果,所有的变量都是数值型变量。因此,生成了模拟数据后,还需要再做一步的工作是,对类别型变量做一个round(X,0)的处理,不要小数点,不要变成数值型数据。
做了这一步改进之后,结果依然很差。再后,找到原因了。我在生成虚拟数据集的时候,少数类在原本数据集的基础上,增加了我想增加的数量,原本的少数类数据不变。但是,多数类样本的数值,几乎全都变了。但是这个问题,我不知道Smote的哪个参数能解决。所以,就写了段代码,自动用原来的多数类样本,替换掉虚拟集的多数类样本信息。代码如下,希望你们能用到。
setwd("E:\Radiomics Program\1-ICCProgram")
# 读入数据
Clincial <- read.csv("ClinicalForR.csv",encoding = "UTF-8", stringsAsFactors = FALSE,na.strings="")
# 加载需要包
library(magrittr)
library(DMwR)
# 查看两类数据的样本分布。我的第一列就是因变量
PositiveRows <- which(Clinical[,1]==1)
NegativeRows <- which(Clinical[,1]==0)
# 在两组中,分别随机选择部分数据出来,不参与Smote过程,做外部验证
set.seed(1000)
IndepTestRowsPosi <- sample(PositiveRows,18,replace=F)
IndepTestRowsNega <- sample(NegativeRows ,15,replace=F)
# 自己写了一个SMOTEData的函数,除了满足smote之外,还做一些其他的处理。
SMOTEData <- function(Data,PositiveRows,NegativeRows ,SmoteRowsMinus,SmoteRowsMijnos,Name,OverTimes){
# 只选择外部验证数据集之外的数据,参与SMOTE过程
TmpData <- Data[c(setdiff(PositiveRows,SmoteRowsMinus),setdiff(NegativeRows ,SmoteRowsMijnos )),]
# 因变量一定要转换成因子型
TmpData[,1] <- factor(TmpData[,1])
str(TmpData)
# 对一列做统计,后面需要用到这个统计信息
Table <- TmpData[,1] %>% table() %>% as.data.frame();Table
# SMOTE算法 扩充OverTimes倍阳性患者。计算好我们的过采样和降采样所需要的数据,具体计算过程看博客正文。
MinusN <- Table[which.min(Table[,2]),2]# %>% as.character() %>% as.numeric()
MijorN <- Table[which.max(Table[,2]),2]
X <- MinusN*OverTimes*100/MinusN
Y <- MijorN*100*100/(X*MinusN )
newData <- SMOTE(Label~ ., TmpData, perc.over = X,perc.under=Y)
table(newData[,1])#;str(newData)
# 生成数据集中,类别型数据也被生成了数值型数据,所以做一个简单的处理。就是把 不同数值低于5个列找出来,认为他是类别型变量,然后取整数即可。
TrainData <- newData
# 确定分类数据,分类数据,不能是小数表示
TmpData2 <- TrainData[1:30,]
for(i in 2:ncol(TrainData)){
UniqueLen <- TmpData2[,i] %>% unique() %>% length()
if(UniqueLen <= 5){
TrainData[,i] <- round(TrainData[,i],0)
}
else{
next()
}
}
# 多数类并没有要继续生成虚拟数据,但是在smote函数运行过程总,多数类样本的指标信息被修改了。替换
Rep <- Table[which.max(Table[,2]),1] %>% as.character() %>% as.numeric()
TrainData[which(TrainData[,1]==Rep),] <- TmpData[which(TmpData[,1]==Rep),]
TestData <- Data[-c(setdiff(PositiveRows,SmoteRowsMinus),setdiff(NegativeRows ,SmoteRowsMijnos )),]
# 将我们生成好的训练集数据和独立测试数据保存包设定工作目录下面
write.csv(TrainData,paste0(getwd(),"/Smote_train_",Name,".csv"),row.names=F)
write.csv(TestData ,paste0(getwd(),"/Smote_independent_test_",Name,".csv"),row.names=F)
}
SMOTEData(Clinical,PositiveRows ,NegativeRows ,IndepTestRowsPosi ,IndepTestRowsNega,"Clinical",2)
最后,哪两批数据来测试,亲测有效。没有训练和内部验证结果特别好,外部验证结果AUC不到0.55的这种极端情况了~