blob: 7042ad6e6e1d91236637b344342a0c3ae3291253 [file] [log] [blame]
PeterFankhauserIDSa18d5352021-04-18 10:38:27 +02001library(caret)
2library(tidyverse)
3library(DMwR)
4library(randomForest)
5library(FSelector)
6library(MLmetrics)
7# library(randomForestExplainer)
8# may need to: options(expressions = 5e5) to avoid stackoverflow for installing package
9
10set.seed(42)
11
12# Test
13
14ngramfile2<-"gold03_anno_ml_synfeat_nstopw" # 2nd dataset
15ngramfile1 <-"goldstandard01_anno_ml_synfeat_nstop1" # 1st dataset
16
17setwd(dirname(rstudioapi::getSourceEditorContext()$path))
18stopwords <- readLines(con = "../data/stopwords.txt",encoding="UTF-8")
19oringramme1 <- read.csv(paste("../data/",ngramfile1,".csv",sep=""), header = TRUE, sep = "\t", dec=".", quote="", encoding="UTF-8",stringsAsFactors=FALSE)
20oringramme2 <- read.csv(paste("../data/",ngramfile2,".csv",sep=""), header = TRUE, sep = "\t", dec=".", quote="", encoding="UTF-8",stringsAsFactors=FALSE)
21
22
23syfeaturenames <- read.csv("../data/syfeatures.tsv", header = TRUE, sep = "\t", dec=".", quote="", encoding="UTF-8",stringsAsFactors=FALSE)
24
25featurenames <- read.csv("../data/features.tsv", header = TRUE, sep = "\t", dec=".", quote="", encoding="UTF-8",stringsAsFactors=FALSE)
26
27deleteStopwords = function(wl, stopwords = NULL) {
28 wl[!(wl %in% stopwords)]
29}
30
31oringramme1 <- oringramme1 %>%
32 mutate(CO_IDIOM = ifelse(is.na(CO_IDIOM),0,CO_IDIOM)) %>% # treat NAs as 0
33 filter(CO_IDIOM < 2) # just two classes: 0 no idiom, 1 idiom
34
35oringramme2 <- oringramme2 %>%
36 mutate(CO_IDIOM = ifelse(is.na(CO_IDIOM),0,CO_IDIOM)) %>% # treat NAs as 0
37 filter(CO_IDIOM < 2) # just two classes: 0 no idiom, 1 idiom
38
39# Reduce number of classes, treat null values, add NSTOPW, change names for SY features and rename all features
40# new featurenames.
41
42ngramme1 <- oringramme1 %>%
43 add_column(NSTOPW = sapply(oringramme1$tokens,function(x) length(deleteStopwords(tolower(unlist(strsplit(x," "))),stopwords)))) %>%
44 add_column(nstokens = sapply(oringramme1$tokens, function(x) paste(deleteStopwords(tolower(unlist(strsplit(x," "))),stopwords),collapse=" "))) %>%
45 # select(-matches("CO_TOKEN.*"), -tokens) %>%
46 add_column(CO_GRAM = sapply(oringramme1$tokens, function(x) length(unlist(strsplit(x," ")))))%>%
47 select(-matches("CO_TOKEN.*")) %>% # keep tokens for interpretability
48 mutate(across(matches(".rank.*"), ~ replace_na(.x, 1000))) %>%
49 mutate(across(c("dice", "lfmd", "llr", "ld", "pmi"), ~ replace_na(.x, min(.x) - 1))) %>%
50 rename_at(syfeaturenames$innames, ~ syfeaturenames[syfeaturenames$innames==.x,]$synames ) %>%
51 rename_at(featurenames$oldnames, ~ featurenames[featurenames$oldnames==.x,]$newnames ) %>%
52 mutate(across(everything(), ~ replace_na(.x, 0))) %>%
53 mutate(CO_IDIOM = as.factor(if_else(CO_IDIOM == 1, "idiom", "no_idiom"))) # just two classes: 0 no idiom, 1 idiom
54
55ngramme2 <- oringramme2 %>%
56 add_column(NSTOPW = sapply(oringramme2$tokens,function(x) length(deleteStopwords(tolower(unlist(strsplit(x," "))),stopwords)))) %>%
57 add_column(nstokens = sapply(oringramme2$tokens, function(x) paste(deleteStopwords(tolower(unlist(strsplit(x," "))),stopwords),collapse=" "))) %>%
58 # select(-matches("CO_TOKEN.*"), -tokens) %>%
59 select(-matches("CO_TOKEN.*")) %>% # keep tokens for interpretability
60 mutate(across(matches(".rank.*"), ~ replace_na(.x, 1000))) %>%
61 mutate(across(c("dice", "lfmd", "llr", "ld", "pmi"), ~ replace_na(.x, min(.x) - 1))) %>%
62 rename_at(syfeaturenames$innames, ~ syfeaturenames[syfeaturenames$innames==.x,]$synames ) %>%
63 rename_at(featurenames$oldnames, ~ featurenames[featurenames$oldnames==.x,]$newnames ) %>%
64 mutate(across(everything(), ~ replace_na(.x, 0))) %>%
65 mutate(CO_IDIOM = as.factor(if_else(CO_IDIOM == 1, "idiom", "no_idiom"))) # just two classes: 0 no idiom, 1 idiom
66
67# combine
68
69ngramme1<-subset(ngramme1,select=-c(CO_SONGS))
70ngramme <- rbind(ngramme1,ngramme2[colnames(ngramme1)])
71# ngramme<-ngramme1
72
73ngramme<-ngramme1%>% distinct(nstokens,.keep_all=T)
74ngramme1<-ngramme1%>% distinct(nstokens,.keep_all=T)
75
76# Optional
77write.table(ngramme,file=paste("../data/","combined_noduplicates.tsv",sep=""), sep = "\t", quote=F)
78
79write.table(ngramme1,file=paste("../data/","dataset1_noduplicates.tsv",sep=""), sep = "\t", quote=F)
80
81
82
83# featuresets
84
85o_vars <- c("O_C2_N", "O_C2_SGT", "O_DEREKO", "O_GRAM", "O_NSTOPW")
86o_vars_1 <- c("O_DEREKO", "O_GRAM", "O_NSTOPW")
87o_vars_2 <- c("O_C2_N", "O_C2_SGT")
88co_vars <- c("CO_VEC","CO_VEC_LEX")
89sy_c1_vars <- c("SY_C1_C_L","SY_C1_C_R","SY_C1_DICE","SY_C1_LD","SY_C1_LDAF","SY_C1_LL","SY_C1_MI","SY_C1_MI_L","SY_C1_MI_R","SY_C1_MI2","SY_C1_MI3","SY_C1_NMI")
90sy_c1_vars_1 <- c("SY_C1_LD","SY_C1_LDAF","SY_C1_LL","SY_C1_MI","SY_C1_MI3")
91# sy_c1_vars_1 <- c("SY_C1_LD","SY_C1_LL","SY_C1_MI","SY_C1_MI3")
92sy_c2_vars <- c("SY_C2_EXP","SY_C2_G","SY_C2_K","SY_C2_LD","SY_C2_LL","SY_C2_LMI","SY_C2_MI","SY_C2_MI3","SY_C2_T","SY_C2_Z")
93sy_c2_vars_1 <- c("SY_C2_G","SY_C2_LD","SY_C2_LL","SY_C2_MI","SY_C2_MI3")
94sy_w_vars <- c("SY_W_AVG","SY_W_CON","SY_W_MAX","SY_W_NSUM","SY_W_NSUM_AF")
95# sy_w_vars_1 <- c("SY_W_AVG","SY_W_CON","SY_W_MAX","SY_W_NSUM")
96sy_r_vars <- c("SY_C1_R","SY_W_R1","SY_W_R2","SY_R_D")
97
98all_vars <- c(o_vars,co_vars,sy_c1_vars,sy_c2_vars,sy_w_vars,sy_r_vars)
99all_vars_1 <- c(o_vars, co_vars, sy_c1_vars_1, sy_c2_vars_1, sy_w_vars, sy_r_vars)
100
101
102# formulae for training and testing rf
103
104all_fml <- as.formula(paste("CO_IDIOM ~ ", paste(all_vars, collapse= "+")))
105all_fml_1 <- as.formula(paste("CO_IDIOM ~ ", paste(all_vars_1, collapse= "+")))
106
107sy_c1_fml_1 <- as.formula(paste("CO_IDIOM ~ ", paste(sy_c1_vars_1, collapse= "+")))
108sy_c2_fml_1 <- as.formula(paste("CO_IDIOM ~ ", paste(sy_c2_vars_1, collapse= "+")))
109sy_w_fml <- as.formula(paste("CO_IDIOM ~ ", paste(sy_c2_vars_1, collapse= "+")))
110sy_r_fml <- as.formula(paste("CO_IDIOM ~ ", paste(sy_r_vars, collapse= "+")))
111co_fml <- as.formula(paste("CO_IDIOM ~ ", paste(co_vars, collapse= "+")))
112o_fml_1 <- as.formula(paste("CO_IDIOM ~ ", paste(o_vars_1, collapse= "+")))
113o_fml <- as.formula(paste("CO_IDIOM ~ ", paste(o_vars, collapse= "+")))
114o_fml_2 <- as.formula(paste("CO_IDIOM ~ ", paste(o_vars_2, collapse= "+")))
115
116# Simple train/test split
117
118set.seed(111)
119trainRows <- sample(nrow(ngramme), nrow(ngramme)*0.8, replace = FALSE)
120train <- ngramme[trainRows,]
121test <- ngramme[setdiff(1:nrow(ngramme),trainRows),]
122
123cat("Random Forest\n")
124
125rf_classifier = randomForest(all_fml, train, importance=TRUE)
126
127# only SY features
128# rf_classifier = randomForest(fmlasy, train, importance=TRUE)
129
130prediction_for_table <- predict(rf_classifier, test %>% select(-CO_IDIOM))
131
132res <- confusionMatrix(prediction_for_table, test$CO_IDIOM, positive= "idiom",mode="everything")
133print(res)
134collected_results <- bind_cols("rf" = res$byClass)
135
136# Sensitivity is recall of class 1
137# Pos Pred Value is precision
138varImpPlot(rf_classifier)
139
140cat("Random Forest with cutoff\n")
141prediction_for_table <- predict(rf_classifier,test %>% select(-CO_IDIOM), cutoff = c(0.3, 0.7))
142res <- confusionMatrix(prediction_for_table,test$CO_IDIOM, positive = "idiom",mode="everything")
143collected_results <- bind_cols(collected_results, "rf with cutoff" = res$byClass)
144print(res)
145
146cat("With SMOTE resampled training data\n")
147smoted.data <- SMOTE(all_fml, subset(train, select = c("CO_IDIOM", all_vars)), perc.over = 1200, perc.under = 100)
148rf_classifier = randomForest(all_fml, smoted.data, importance=TRUE)
149prediction_for_table <- predict(rf_classifier,test %>% select(-CO_IDIOM))
150res <- confusionMatrix(prediction_for_table,test$CO_IDIOM, positive = "idiom",mode="everything")
151collected_results <- bind_cols(collected_results, "rf with SMOTE" = res$byClass)
152print(res)
153
154cat("With SMOTE and cutoff\n")
155prediction_for_table <- predict(rf_classifier,test %>% select(-CO_IDIOM), cutoff = c(0.3, 0.7))
156res <- confusionMatrix(prediction_for_table,test$CO_IDIOM, positive = "idiom",mode="everything")
157collected_results <- bind_cols(collected_results, "rf with SMOTE and cutoff" = res$byClass)
158print(res)
159
160collected_results <- collected_results %>%
161 round(3) %>%
162 add_column(measure = names(res$byClass)) %>%
163 column_to_rownames("measure")
164
165View(collected_results)
166
167# Analysing tradeoff between Fscore, Recall, Precision for various cutoffs
168# full range from precision almost 100% to recall almost 100%
169rf_classifier = randomForest(all_fml, train, importance=TRUE)
170cvalues<-tibble()
171for (c in c(seq(from=0.4,to=0.99,by=0.025),0.999)) {
172 prediction_for_table <- predict(rf_classifier, test %>% select(-CO_IDIOM), cutoff = c(1-c, c))
173 conf<-confusionMatrix(prediction_for_table, test$CO_IDIOM, positive = "idiom")
174 cvalues <-bind_rows(cvalues, c(cutoff=c, conf$byClass))
175}
176cvalues %>%
177 select(c("cutoff", "Recall", "Precision", "F1", "Specificity", "Balanced Accuracy")) %>%
178 pivot_longer(!cutoff, names_to=c("measure")) %>%
179 ggplot(aes(cutoff, value, colour=measure)) + geom_line(size=1) +
180 scale_x_continuous(breaks = scales::pretty_breaks(n = 10)) +
181 scale_y_continuous(breaks = scales::pretty_breaks(n = 10))
182
183# cross validation
184
185featuresets<-data.frame(read.csv("../data/featuresets.tsv", header = FALSE, sep = "\t", dec=".", quote="", encoding="UTF-8",stringsAsFactors=FALSE))
186
187collected_results<-tibble()
188for (i in c(1:nrow(featuresets))) {
189 set.seed(325)
190 train_control <- trainControl(method="repeatedcv", number=5, repeats=5, verboseIter=T,classProbs = TRUE,
191 savePredictions = "final")
192 vars<-unlist(strsplit(featuresets[i,2],","))
193 fml <- as.formula(paste("CO_IDIOM ~ ", paste(vars, collapse= "+")))
194 rf <- train(fml,
195 data=ngramme1,
196 method='rf',
197 tuneGrid = data.frame(.mtry = floor(sqrt(length(vars)))),
198 cutoff=c(0.3,0.7),
199 trControl=train_control)
200 res<-confusionMatrix(rf$pred$pred, rf$pred$obs,mode="everything")
201 collected_results <-bind_rows(collected_results, c(features=featuresets[i,1], res$byClass))
202}
203
204collected_results1<-collected_results[c(2:16),c(1,6,7,8,12)]
205collected_results1<-collected_results1%>%
206 mutate(across(everything(), ~ replace_na(.x, 0))) %>%
207 mutate(across(c(2:5), ~ round(as.numeric(.x),digits=3)))
208
209# Analysing tradeoff between Fscore, Recall, Precision for various cutoffs
210# full range from precision almost 100% to recall almost 100%
211
212
213vars<-unlist(strsplit(featuresets[2,2],","))
214fml <- as.formula(paste("CO_IDIOM ~ ", paste(vars, collapse= "+")))
215cvalues<-tibble()
216for (c in c(seq(from=0.4,to=0.99,by=0.025),0.999)) {
217 set.seed(325)
218 train_control <- trainControl(method="repeatedcv", number=5, repeats=5, verboseIter=T,classProbs = TRUE,
219 savePredictions = "final")
220 rf <- train(fml,
221 data=ngramme1,
222 method='rf',
223 tuneGrid = data.frame(.mtry = floor(sqrt(length(vars)))),
224 cutoff=c(1-c,c),
225 trControl=train_control)
226 res<-confusionMatrix(rf$pred$pred, rf$pred$obs,mode="everything")
227 cvalues <-bind_rows(cvalues, c(cutoff=c, res$byClass))
228}
229cvalues %>%
230 select(c("cutoff", "Recall", "Precision", "F1", "Specificity", "Balanced Accuracy")) %>%
231 pivot_longer(!cutoff, names_to=c("measure")) %>%
232 ggplot(aes(cutoff, value, colour=measure)) + geom_line(size=1) +
233 scale_x_continuous(breaks = scales::pretty_breaks(n = 10)) +
234 scale_y_continuous(breaks = scales::pretty_breaks(n = 10))
235
236cvaluesall<-cvalues
237
238
239# Using estimates by random forest on entire dataset
240
241library(randomForest)
242rf_classifier_full = randomForest(all_fml_1, data=ngramme1, importance=TRUE, cutoff=c(0.3,0.7))
243rf_classifier_full
244# class.error is 1 - recall
245varImpPlot(rf_classifier_full)
246
247# Feature ranking
248
249# rf features as table
250
251# correlated features seem to split their rankings
252
253rfranks<-importance(rf_classifier_full)[,3:4]
254
255# ttest
256
257idioms<-ngramme1 %>% filter(CO_IDIOM == "idiom")
258nonidioms<-ngramme1 %>% filter(CO_IDIOM != "idiom")
259
260m1<-mean(unlist(nonidioms["SY_C1_LD"]),na.rm=T)
261
262nonidioms1<-nonidioms%>%filter(SY_C1_LD > m1)
263
264mean(unlist(idioms["SY_C1_LD"]),na.rm=T)
265
266idioms1<-ngramme1 %>% filter(CO_IDIOM == "idiom")
267idioms2<-ngramme2 %>% filter(CO_IDIOM == "idiom")
268
269ttestPvalues<-sapply(all_vars_1,
270 function(sel) t.test(idioms[sel],nonidioms[sel])$p.value)
271
272t.test(idioms["SY_C1_LL"],nonidioms["SY_C1_LL"])
273
274ttestSignificance<-sapply(all_vars_1,
275 function(sel) {
276 p<-t.test(idioms[sel],nonidioms[sel])$p.value
277 if (p < 0.001) {
278 return("***")
279 }
280 if (p < 0.01) {
281 return("**")
282 }
283 if (p < 0.05) {
284 return ("*")
285 }
286 return(" ")
287 })
288
289
290ttestSignificance1<-sapply(all_vars_1,
291 function(sel) {
292 p<-t.test(idioms[sel],nonidioms1[sel])$p.value
293 if (p < 0.001) {
294 return("***")
295 }
296 if (p < 0.01) {
297 return("**")
298 }
299 if (p < 0.05) {
300 return ("*")
301 }
302 return(" ")
303 })
304
305
306# information gain
307# multiply by 1000 to avoid undersized bins
308# features are ranked individually no matter their correlation
309igain<-information.gain(all_fml_1, data=ngramme1%>%mutate_at(all_vars_1, ~ . * 1000),unit="log2")
310
311# difference between means (positive or negative?)
312
313diffMeans<-sapply(all_vars_1,function(sel) mean(unlist(idioms[sel]),na.rm=T)-mean(unlist(nonidioms[sel]),na.rm=T))
314
315diffMeansSign<-sapply(all_vars_1,function(sel) ifelse(mean(unlist(idioms[sel]),na.rm=T)-mean(unlist(nonidioms[sel]),na.rm=T)>0,"+","-"))
316
317diffMeansSign1<-sapply(all_vars_1,function(sel) ifelse(mean(unlist(idioms[sel]),na.rm=T)-mean(unlist(nonidioms1[sel]),na.rm=T)>0,"+","-"))
318
319
320featurenames[,c("newnames","explanation")]
321
322featureRanks<-cbind(rownames(rfranks),rfranks,igain,ttestPvalues,ttestSignificance,diffMeans,diffMeansSign,ttestSignificance1,diffMeansSign1)
323colnames(featureRanks)[1]<-"newnames"
324
325featureRanks<-merge(featureRanks,featurenames[,c("newnames","explanation")],by="newnames")
326
327
328
329#randomForestExplainer::explain_forest(rf_classifier )
330
331# averate estimates and feature ranks over 10 runs
332
333errrate<-0
334conf<-matrix(0,2,3)
335featureRanks<-matrix(0,4,length(vars))
336for (i in 1:10) {
337 rfc =randomForest(fmla, data=ngramme, importance=TRUE)
338 #rfc =randomForest(fmla, data=ngramme, importance=TRUE, cutoff=c(0.2, 0.8))
339 errrate<-errrate+rfc$err.rate[100,1]
340 conf<-conf+rfc$confusion
341 featureRanks<-featureRanks+
342 cbind(importance(rfc)[,3:4],
343 information.gain(fmla, data=ngramme%>%mutate_at(vars, ~ . * 1000),unit="log2"),
344 sapply(vars,
345 function(sel) t.test(idioms[sel],nonidioms[sel])$p.value))
346 print(errrate/i)
347 conf1<-round(
348 rbind(
349 cbind(conf[,1:2]/i,(1-conf[,3]/i)*100),
350 c(100*diag(conf[,1:2])/colSums(conf[,1:2]),NA),
351 c(rowSums(conf[,1:2]/i),NA)),digits=2)
352 colnames(conf1)<-c("1","0","rec")
353 rownames(conf1)<-c("1","0","prec","sum")
354 print(conf1)
355}
356featureRanks<-featureRanks/10
357colnames(featureRanks)<-c("MeanDecreaseAccuracy","MeanDecreaseGini","InformationGain","Ttest")
358
359
360
361