Discrimination model (PLS-DA)
2021-04-08
Discrimination analysis
Examples: Using PLS-DA for geographical origin of oils
Dataset: Mid-infrared spectroscopy of Extra Virgin Olive Oils for Geographical origin discrimination analysis (Quadram open dataset,2003).
Data preprocess
## Load date
setwd("C:/blog/Dataset")
oil <- read.csv('FTIR_Spectra_olive_oils.csv',header = F,
stringsAsFactors = T)
print(oil[1:10,1:5])
## V1 V2 V3 V4 V5
## 1 Sample Number: 1 1 2 2
## 2 Group Code: 1 1 1 1
## 3 Wavenumbers Greece Greece Greece Greece
## 4 798.892 0.127523009 0.126498181 0.130411785 0.130022227
## 5 800.8215 0.127949615 0.127130974 0.130675401 0.130406662
## 6 802.751 0.129282219 0.128510777 0.13201661 0.132018029
## 7 804.6805 0.131174169 0.13033991 0.133824061 0.134007275
## 8 806.61 0.133590328 0.132527221 0.136095296 0.136270568
## 9 808.5395 0.136425525 0.135308508 0.138943757 0.13887477
## 10 810.469 0.139357827 0.13835292 0.141722779 0.141481132
## data transpose
oil_transpose <- as.data.frame(t(oil))
print(oil_transpose[1:10,1:5])
## V1 V2 V3 V4 V5
## V1 Sample Number: Group Code: Wavenumbers 798.892 800.8215
## V2 1 1 Greece 0.127523009 0.127949615
## V3 1 1 Greece 0.126498181 0.127130974
## V4 2 1 Greece 0.130411785 0.130675401
## V5 2 1 Greece 0.130022227 0.130406662
## V6 3 1 Greece 0.128601989 0.128789565
## V7 3 1 Greece 0.128217254 0.128282253
## V8 4 1 Greece 0.126174933 0.126732773
## V9 4 1 Greece 0.126466053 0.126915413
## V10 5 1 Greece 0.127060105 0.127551128
## rename some col names to make data analysis easier
colnames(oil_transpose)=oil_transpose[1,]
oil_transpose <- oil_transpose[-1,]
library(dplyr)
oil_transpose <- rename(oil_transpose,c("Group" ="Group Code:",
"Number" ="Sample Number:",
"Countries" ="Wavenumbers"))
## Get transposed dataset
oil.data <- oil_transpose
print(oil.data[1:10,1:5])
## Number Group Countries 798.892 800.8215
## V2 1 1 Greece 0.127523009 0.127949615
## V3 1 1 Greece 0.126498181 0.127130974
## V4 2 1 Greece 0.130411785 0.130675401
## V5 2 1 Greece 0.130022227 0.130406662
## V6 3 1 Greece 0.128601989 0.128789565
## V7 3 1 Greece 0.128217254 0.128282253
## V8 4 1 Greece 0.126174933 0.126732773
## V9 4 1 Greece 0.126466053 0.126915413
## V10 5 1 Greece 0.127060105 0.127551128
## V11 5 1 Greece 0.126812707 0.127460743
df_raw <- oil.data[,4:573]
df <- as.data.frame(apply(df_raw, 2, as.numeric))
str(df)
## 'data.frame': 120 obs. of 570 variables:
## $ 798.892 : num 0.128 0.126 0.13 0.13 0.129 ...
## $ 800.8215 : num 0.128 0.127 0.131 0.13 0.129 ...
## $ 802.751 : num 0.129 0.129 0.132 0.132 0.13 ...
## $ 804.6805 : num 0.131 0.13 0.134 0.134 0.132 ...
## $ 806.61 : num 0.134 0.133 0.136 0.136 0.134 ...
## $ 808.5395 : num 0.136 0.135 0.139 0.139 0.137 ...
## $ 810.469 : num 0.139 0.138 0.142 0.141 0.14 ...
## $ 812.3985 : num 0.142 0.141 0.144 0.144 0.142 ...
## $ 814.328 : num 0.145 0.144 0.146 0.147 0.145 ...
## $ 816.2575 : num 0.147 0.146 0.148 0.148 0.147 ...
## $ 818.187 : num 0.148 0.147 0.15 0.15 0.149 ...
## $ 820.1165 : num 0.15 0.149 0.152 0.152 0.15 ...
## $ 822.046 : num 0.152 0.151 0.154 0.154 0.153 ...
## $ 823.9755 : num 0.155 0.154 0.157 0.157 0.155 ...
## $ 825.905 : num 0.158 0.157 0.159 0.159 0.158 ...
## $ 827.8345 : num 0.161 0.16 0.163 0.162 0.161 ...
## $ 829.764 : num 0.164 0.163 0.166 0.166 0.164 ...
## $ 831.6935 : num 0.167 0.166 0.169 0.169 0.167 ...
## $ 833.623 : num 0.171 0.169 0.172 0.172 0.17 ...
## $ 835.5525 : num 0.174 0.173 0.176 0.176 0.174 ...
## $ 837.482 : num 0.178 0.177 0.179 0.179 0.177 ...
## $ 839.4115 : num 0.182 0.181 0.184 0.184 0.182 ...
## $ 841.341 : num 0.187 0.186 0.189 0.189 0.186 ...
## $ 843.2705 : num 0.192 0.191 0.194 0.194 0.192 ...
## $ 845.2 : num 0.197 0.196 0.199 0.198 0.197 ...
## $ 847.1295 : num 0.201 0.201 0.203 0.203 0.201 ...
## $ 849.059 : num 0.205 0.204 0.207 0.207 0.205 ...
## $ 850.9885 : num 0.208 0.207 0.209 0.209 0.208 ...
## $ 852.918 : num 0.21 0.209 0.211 0.211 0.209 ...
## $ 854.8475 : num 0.21 0.21 0.212 0.212 0.21 ...
## $ 856.777 : num 0.21 0.209 0.212 0.211 0.21 ...
## $ 858.7065 : num 0.209 0.208 0.211 0.21 0.209 ...
## $ 860.636 : num 0.209 0.207 0.21 0.21 0.209 ...
## $ 862.5655 : num 0.209 0.208 0.209 0.209 0.208 ...
## $ 864.495 : num 0.209 0.209 0.21 0.21 0.209 ...
## $ 866.4245 : num 0.21 0.21 0.211 0.211 0.21 ...
## $ 868.354 : num 0.211 0.21 0.211 0.212 0.211 ...
## $ 870.2835 : num 0.211 0.21 0.212 0.212 0.211 ...
## $ 872.213 : num 0.211 0.21 0.211 0.212 0.211 ...
## $ 874.1425 : num 0.209 0.209 0.21 0.21 0.21 ...
## $ 876.072 : num 0.207 0.206 0.208 0.209 0.207 ...
## $ 878.0015 : num 0.205 0.204 0.206 0.206 0.205 ...
## $ 879.931 : num 0.202 0.202 0.203 0.203 0.202 ...
## $ 881.8605 : num 0.201 0.2 0.202 0.202 0.201 ...
## $ 883.79 : num 0.2 0.199 0.201 0.201 0.2 ...
## $ 885.7195 : num 0.2 0.199 0.201 0.201 0.2 ...
## $ 887.649 : num 0.2 0.2 0.201 0.201 0.201 ...
## $ 889.5785 : num 0.201 0.2 0.202 0.202 0.201 ...
## $ 891.508 : num 0.2 0.199 0.201 0.201 0.2 ...
## $ 893.4375 : num 0.199 0.198 0.2 0.199 0.199 ...
## $ 895.367 : num 0.197 0.196 0.198 0.198 0.197 ...
## $ 897.2965 : num 0.195 0.194 0.197 0.197 0.195 ...
## $ 899.226 : num 0.195 0.194 0.196 0.196 0.194 ...
## $ 901.1555 : num 0.195 0.193 0.196 0.195 0.194 ...
## $ 903.085 : num 0.195 0.194 0.196 0.196 0.194 ...
## $ 905.0145 : num 0.195 0.194 0.196 0.196 0.195 ...
## $ 906.944 : num 0.195 0.193 0.196 0.195 0.195 ...
## $ 908.8735 : num 0.194 0.193 0.195 0.195 0.194 ...
## $ 910.803 : num 0.193 0.192 0.194 0.194 0.193 ...
## $ 912.7325 : num 0.192 0.19 0.193 0.193 0.192 ...
## $ 914.662 : num 0.19 0.189 0.192 0.192 0.191 ...
## $ 916.5915 : num 0.189 0.188 0.191 0.191 0.189 ...
## $ 918.521 : num 0.188 0.186 0.189 0.189 0.188 ...
## $ 920.4505 : num 0.186 0.185 0.188 0.188 0.186 ...
## $ 922.38 : num 0.185 0.183 0.187 0.187 0.185 ...
## $ 924.3095 : num 0.184 0.183 0.186 0.186 0.184 ...
## $ 926.239 : num 0.184 0.182 0.186 0.186 0.184 ...
## $ 928.1685 : num 0.185 0.183 0.187 0.187 0.185 ...
## $ 930.098 : num 0.186 0.185 0.189 0.189 0.187 ...
## $ 932.0275 : num 0.19 0.189 0.192 0.192 0.191 ...
## $ 933.957 : num 0.195 0.194 0.197 0.197 0.195 ...
## $ 935.8865 : num 0.2 0.199 0.202 0.202 0.201 ...
## $ 937.816 : num 0.206 0.204 0.208 0.208 0.206 ...
## $ 939.7455 : num 0.211 0.21 0.213 0.213 0.212 ...
## $ 941.675 : num 0.217 0.216 0.219 0.219 0.217 ...
## $ 943.6045 : num 0.223 0.221 0.225 0.225 0.223 ...
## $ 945.534 : num 0.229 0.227 0.231 0.231 0.229 ...
## $ 947.4645 : num 0.234 0.233 0.236 0.237 0.235 ...
## $ 949.394 : num 0.24 0.238 0.242 0.242 0.24 ...
## $ 951.3235 : num 0.245 0.244 0.247 0.247 0.246 ...
## $ 953.253 : num 0.251 0.25 0.253 0.253 0.251 ...
## $ 955.1825 : num 0.257 0.256 0.259 0.259 0.257 ...
## $ 957.112 : num 0.263 0.262 0.264 0.265 0.263 ...
## $ 959.0415 : num 0.268 0.267 0.27 0.27 0.268 ...
## $ 960.971 : num 0.273 0.271 0.274 0.275 0.273 ...
## $ 962.9005 : num 0.276 0.275 0.278 0.278 0.276 ...
## $ 964.83 : num 0.278 0.276 0.28 0.28 0.277 ...
## $ 966.7595 : num 0.278 0.277 0.281 0.281 0.278 ...
## $ 968.689 : num 0.277 0.277 0.28 0.281 0.278 ...
## $ 970.6185 : num 0.276 0.275 0.278 0.279 0.276 ...
## $ 972.548 : num 0.274 0.273 0.276 0.276 0.274 ...
## $ 974.4775 : num 0.272 0.271 0.273 0.274 0.271 ...
## $ 976.407 : num 0.271 0.269 0.272 0.273 0.27 ...
## $ 978.3365 : num 0.271 0.269 0.272 0.273 0.271 ...
## $ 980.266 : num 0.273 0.271 0.273 0.274 0.272 ...
## $ 982.1955 : num 0.275 0.273 0.276 0.276 0.274 ...
## $ 984.125 : num 0.276 0.275 0.277 0.277 0.276 ...
## $ 986.0545 : num 0.277 0.276 0.278 0.278 0.277 ...
## $ 987.984 : num 0.278 0.277 0.279 0.279 0.278 ...
## [list output truncated]
print(df[1:10,1:5])
## 798.892 800.8215 802.751 804.6805 806.61
## 1 0.1275230 0.1279496 0.1292822 0.1311742 0.1335903
## 2 0.1264982 0.1271310 0.1285108 0.1303399 0.1325272
## 3 0.1304118 0.1306754 0.1320166 0.1338241 0.1360953
## 4 0.1300222 0.1304067 0.1320180 0.1340073 0.1362706
## 5 0.1286020 0.1287896 0.1300223 0.1320119 0.1344266
## 6 0.1282173 0.1282823 0.1296366 0.1317986 0.1340615
## 7 0.1261749 0.1267328 0.1282438 0.1298927 0.1317546
## 8 0.1264661 0.1269154 0.1282541 0.1299583 0.1320672
## 9 0.1270601 0.1275511 0.1289000 0.1306090 0.1329558
## 10 0.1268127 0.1274607 0.1287653 0.1306390 0.1331313
## SNV transformation
library(prospectr)
df_snv <- standardNormalVariate(df)
oil.snv <- cbind(oil.data[,1:3], df)
To have a pls-da plot
library(mixOmics)
## Data set
oil_data <- oil.snv[,4:573]
X <- oil_data
Country <- oil.snv[,3]
Y <- as.factor(Country)
summary(Y)
## Greece Italy Portugal Spain
## 20 34 16 50
MyResult.splsda <- mixOmics::splsda(X,Y, keepX = c(10,15))
plotIndiv(MyResult.splsda, ind.names = FALSE, legend=TRUE,
ellipse = TRUE, star = TRUE, title = 'sPLS-DA on geographical origin of oil',
X.label = 'PLS-DA 1 (44%)', Y.label = 'PLS-DA 2 (20%)')
auc.plsda <- auroc(MyResult.splsda)
## $Comp1
## AUC p-value
## Greece vs Other(s) 0.6505 3.404e-02
## Italy vs Other(s) 0.9337 1.528e-13
## Portugal vs Other(s) 0.5343 6.599e-01
## Spain vs Other(s) 0.9646 0.000e+00
##
## $Comp2
## AUC p-value
## Greece vs Other(s) 0.8570 4.960e-07
## Italy vs Other(s) 0.9867 2.220e-16
## Portugal vs Other(s) 0.9633 2.646e-09
## Spain vs Other(s) 0.9891 0.000e+00
Variable selection outputs
The contribution of selected variables for PLS-DA model:
MyResult.splsda2 <- mixOmics::splsda(X,Y, ncomp=3, keepX=c(15,10,5))
plotLoadings(MyResult.splsda2, contrib = 'max', method = 'mean')
Tuning parameters and numerical outputs
MyResult.plsda2 <- mixOmics::splsda(X,Y, ncomp=10)
set.seed(30) # for reproducbility in this vignette, otherwise increase nrepeat
MyPerf.plsda <- perf(MyResult.plsda2, validation = "Mfold", folds = 3,
progressBar = FALSE, nrepeat = 10) # we suggest nrepeat = 50
plot(MyPerf.plsda, col = color.mixo(5:7), sd = TRUE, legend.position = "horizontal")
list.keepX <- c(5:10, seq(20, 100, 10))
list.keepX
## [1] 5 6 7 8 9 10 20 30 40 50 60 70 80 90 100
set.seed(123)
tune.splsda.oil <- tune.splsda(X, Y, ncomp = 10,
validation = 'Mfold',
folds = 3, dist = 'max.dist', progressBar = FALSE,
measure = "BER", test.keepX = list.keepX,
nrepeat = 50)
error <- tune.splsda.oil$error.rate
ncomp <- tune.splsda.oil$choice.ncomp$ncomp
ncomp
## [1] 7
select.keepX <- tune.splsda.oil$choice.keepX[1:ncomp]
select.keepX
## comp1 comp2 comp3 comp4 comp5 comp6 comp7
## 7 5 50 10 5 80 40
The final pls-da model using tuning setting
MyResult.splsda.final <- mixOmics::splsda(X, Y, ncomp = ncomp, keepX = select.keepX)
plotIndiv(MyResult.splsda.final, ind.names = FALSE, legend=TRUE,
ellipse = TRUE, star = TRUE, title = 'sPLS-DA on geographical origin of oil',
X.label = 'PLS-DA 1 (30%)', Y.label = 'PLS-DA 2 (16%)')
Discrimination accuracy based on traing and testing data
library(caret)
oildata <- cbind(Country, oil_data)
set.seed(100)
# Step 1: Get row numbers for the training data
trainRowNumbers <- createDataPartition(oildata$Country, p=0.7, list=FALSE)
# Step 2: Create the training dataset
trainData <- oildata[trainRowNumbers,]
# Step 3: Create the test dataset
testData <- oildata[-trainRowNumbers,]
plsda_model <- caret::plsda(trainData[,2:571],factor(trainData$Country), ncomp = 6, probMethod = "Bayes")
C1 <- confusionMatrix(predict(plsda_model, trainData[,2:571]),as.factor(trainData$Country))
C2 <- confusionMatrix(predict(plsda_model, testData[,2:571]),as.factor(testData$Country))
The Confusion Matrix for geographical origin using training data
C1
## Confusion Matrix and Statistics
##
## Reference
## Prediction Greece Italy Portugal Spain
## Greece 14 0 0 0
## Italy 0 24 0 3
## Portugal 0 0 12 0
## Spain 0 0 0 32
##
## Overall Statistics
##
## Accuracy : 0.9647
## 95% CI : (0.9003, 0.9927)
## No Information Rate : 0.4118
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9502
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: Greece Class: Italy Class: Portugal Class: Spain
## Sensitivity 1.0000 1.0000 1.0000 0.9143
## Specificity 1.0000 0.9508 1.0000 1.0000
## Pos Pred Value 1.0000 0.8889 1.0000 1.0000
## Neg Pred Value 1.0000 1.0000 1.0000 0.9434
## Prevalence 0.1647 0.2824 0.1412 0.4118
## Detection Rate 0.1647 0.2824 0.1412 0.3765
## Detection Prevalence 0.1647 0.3176 0.1412 0.3765
## Balanced Accuracy 1.0000 0.9754 1.0000 0.9571
The Confusion Matrix for geographical origin using testing data
C2
## Confusion Matrix and Statistics
##
## Reference
## Prediction Greece Italy Portugal Spain
## Greece 5 0 0 1
## Italy 0 10 0 0
## Portugal 0 0 4 1
## Spain 1 0 0 13
##
## Overall Statistics
##
## Accuracy : 0.9143
## 95% CI : (0.7694, 0.982)
## No Information Rate : 0.4286
## P-Value [Acc > NIR] : 2.195e-09
##
## Kappa : 0.8778
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: Greece Class: Italy Class: Portugal Class: Spain
## Sensitivity 0.8333 1.0000 1.0000 0.8667
## Specificity 0.9655 1.0000 0.9677 0.9500
## Pos Pred Value 0.8333 1.0000 0.8000 0.9286
## Neg Pred Value 0.9655 1.0000 1.0000 0.9048
## Prevalence 0.1714 0.2857 0.1143 0.4286
## Detection Rate 0.1429 0.2857 0.1143 0.3714
## Detection Prevalence 0.1714 0.2857 0.1429 0.4000
## Balanced Accuracy 0.8994 1.0000 0.9839 0.9083
The overall accuracy for training set is 96% and for testing set is 91%.