#
#
# Calculate the volume of a sphere in n dimensions for SPEA2 fitness calculation.
#
#
.volSphere <- function(dimensions){
vol = 1
if(dimensions %% 2 == 0){
d2 <- dimensions / 2
for ( i in seq_len(d2))
vol <- vol * i
vol <- (pi^d2) / vol
} else {
v <- (dimensions-1)/2+1
for ( i in v:dimensions)
vol <- vol * i;
vol = 2^dimensions * pi^(v - 1) * vol
}
vol
}
#' @title Mutation operator for MESDIF
#' @description The biased mutation operator for MESDIF
#' @param chromosome The chromosome to mute.
#' @param variable The variable chosen to mutate.
#' @param maxVariableValues The maximum possible value for each variable on CAN representation or the beginning of each
#' variable for DNF representation
#' @param DNF_Rule A logical indicating if we are processing a DNF Rule.
#' @noRd
.mutateMESDIF <- function(chromosome, variable, maxVariableValues, DNF_Rule){
mutation_type <- sample(x = 1:2, size = 1) #Type 1 -> Eliminate the variable, Type 2 -> change the value for a random one
if(! DNF_Rule){ #CAN RULES
if(mutation_type == 1L){
#If type == 1, we eliminate the variable (eliminate is put the max value)
chromosome[variable] <- maxVariableValues[variable]
} else { #Assign a random value (elimination value NOT INCLUDED)
value <- sample(x = 0:(maxVariableValues[variable] - 1), size = 1)
chromosome[variable] <- value
}
} else { #DNF RULE
#For a DNF rule, first, get the range of values for the variable, because we change all his values
variable <- variable + 1
range <- (maxVariableValues[variable - 1] + 1):maxVariableValues[variable]
if(mutation_type == 1){ #Erase the variable (put all values of the range 0 or 1)
chromosome[range] <- 0
} else {
#Asign a random value (0 or 1) to each value of the range
#Note that this could erase the variable.
chromosome[range] <- sample(x = 0:1 , size = length(range), replace = TRUE)
}
}
chromosome # Return
}
#
#
#' @title Truncation operator for the elite population in MESDIF
#' @description This is called when the number of non-dominated individuals are greater than elite population size.
#' @param NonDominatedPop A matrix with non.dominated individuals
#' @param elitePopSize The size of the elite population
#' @param FitnessND A matrix with the fitness values of the non-dominated indiciduals.
#' @noRd
.truncOperator <- function(NonDominatedPop, elitePopSize, FitnessND ){
#Calculate distance between individuals
distance <- as.matrix( dist(x = FitnessND, method = "euclidean") ) ^2
#Distance between themselves eliminated.
diag(distance) <- Inf
#Order the distance matrix
sortedIndex <- apply(X = distance, MARGIN = 1,FUN = order)
individuals <- NROW(NonDominatedPop)
# True values indicates the elimination of the indivual
dontKeep <- logical(individuals)
#Iterative removal of individuals until the size of the elite population is reached
while(individuals > elitePopSize){
#Find the minimal distance among individuals
minimum <- which(distance == min(distance), arr.ind = TRUE,useNames = FALSE)
if(NROW(minimum) == 1){
#Remove the individual directly
dontKeep[minimum[,2]] <- T
distance[minimum[,2], ] <- Inf
distance[,minimum[,2] ] <- Inf
} else {
row <- minimum[1,1]
column <- minimum[1,2]
#We found the two closest individuals, now we have to erase one of them. This will be who have the minimum distance between his k-th closest neighbour
pos <- 1
while(distance[sortedIndex[pos,row], row]== distance[sortedIndex[pos,column], column] & pos < NROW(distance)){
pos <- pos + 1
}
#Erase the closest individual
if(distance[sortedIndex[pos,row],row] < distance[sortedIndex[pos,column],column]){
dontKeep[row] <- T
distance[row, ] <- Inf
distance[,row ] <- Inf
#The position in sortedIndex is now the last.
sortedIndex <- apply(sortedIndex, MARGIN = 2, function(x, value, individuals){
x[which(x == value):(individuals - 1)] <- x[which(x == value):(individuals - 1) + 1];
x[individuals] <- value;
x
}, row, individuals)
} else {
dontKeep[column] <- T
distance[, column] <- Inf
distance[column, ] <- Inf
sortedIndex <- apply(sortedIndex, MARGIN = 2, function(x, value, individuals){
x[which(x == value):(individuals - 1)] <- x[which(x == value):(individuals - 1) + 1];
x[individuals] <- value;
x
}, column, individuals)
}
}
individuals <- individuals - 1
}
list(poblation = NonDominatedPop[which(! dontKeep), , drop = F], individuals = which(! dontKeep) )
}
#'
#' @title Multiobjective Evolutionary Subgroup DIscovery Fuzzy rules (MESDIF) Algorithm
#' @description Performs a subgroup discovery task executing the MESDIF algorithm.
#'
#' @param paramFile The path of the parameters file. \code{NULL} If you want to use training and test \code{SDEFSR_Dataset} variables
#' @param training A \code{SDEFSR_Dataset} class variable with training data.
#' @param test A \code{SDEFSR_Dataset} class variable with test data. \code{NULL} if you only want to use training data.
#' @param output character vector with the paths where store information file, rules file and quality measures file, respectively.
#' @param seed An integer to set the seed used for generate random numbers.
#' @param nLabels Number of linguistic labels that represents numerical variables.
#' @param nEval An integer for set the maximum number of evaluations in the evolutive process. Large values of this parameter increments the computing time.
#' @param popLength An integer to set the number of individuals in the population.
#' @param eliteLength An integer to set the number of individuals in the elite population.
#' @param crossProb Sets the crossover probability. A number in [0,1].
#' @param mutProb Sets the mutation probability. A number in [0,1].
#' @param RulesRep Representation used in the rules. "can" for canonical rules, "dnf" for DNF rules.
#' @param Obj1 Sets the Objective number 1. See \code{Objective values} for more information about the possible values.
#' @param Obj2 Sets the Objective number 2. See \code{Objective values} for more information about the possible values.
#' @param Obj3 Sets the Objective number 3. See \code{Objective values} for more information about the possible values.
#' @param Obj4 Sets the Objective number 4. See \code{Objective values} for more information about the possible values.
#' @param targetVariable The name or index position of the target variable (or class). It must be a categorical one.
#' @param targetClass A string specifing the value of the target variable. \code{null} for search for all possible values.
#'
#'
#' @details This function sets as target variable the last one that appear in \code{SDEFSR_Dataset} object. If you want
#' to change the target variable, you can set the \code{targetVariable} to change this target variable.
#' The target variable MUST be categorical, if it is not, throws an error. Also, the default behaviour is to find
#' rules for all possible values of the target varaible. \code{targetClass} sets a value of the target variable where the
#' algorithm only finds rules about this value.
#'
#' If you specify in \code{paramFile} something distinct to \code{NULL} the rest of the parameters are
#' ignored and the algorithm tries to read the file specified. See "Parameters file structure" below
#' if you want to use a parameters file.
#'
#' @section How does this algorithm work?:
#' This algorithm performs a multi-objective genetic algorithm based on elitism (following the SPEA2 approach). The elite population has
#' a fixed size and it is filled by non-dominated individuals.
#'
#' An individual is non-dominated when \code{(! all(ObjI1 <= ObjI2) & any(ObjI1 < ObjI2))} where ObjI1
#' is the objective value for our individual and ObjI2 is the objetive value for another individual.
#' The number of dominated individuals by each one determine, in addition with a niches technique that considers
#' the proximity among values of the objectives a fitness value for the selection.
#'
#' The number of non-dominated individuals might be greater or less than elite population size and in those cases
#' MESDIF implements a truncation operator and a fill operator respectively. Then, genetic operators are
#' applied.
#'
#' At the final of the evolutive process it returns the rules stored in elite population. Therefore, the number of rules is fixed with the \code{eliteLength} parameter.
#'
#' @section Parameters file structure:
#' The \code{paramFile} argument points to a file which has the necesary parameters for MESDIF works.
#' This file \strong{must} have, at least, those parameters (separated by a carriage return):
#' \itemize{
#' \item \code{algorithm} Specify the algorithm to execute. In this case. "MESDIF"
#' \item \code{inputData} Specify two paths of KEEL files for training and test. In case of specify only the name of the file, the path will be the working directory.
#' \item \code{seed} Sets the seed for the random number generator
#' \item \code{nLabels} Sets the number of fuzzy labels to create when reading the files
#' \item \code{nEval} Set the maximun number of \strong{evaluations of rules} for stop the genetic process
#' \item \code{popLength} Sets number of individuals of the main population
#' \item \code{eliteLength} Sets number of individuals of the elite population. Must be less than \code{popLength}
#' \item \code{crossProb} Crossover probability of the genetic algorithm. Value in [0,1]
#' \item \code{mutProb} Mutation probability of the genetic algorithm. Value in [0,1]
#' \item \code{Obj1} Sets the objective number 1.
#' \item \code{Obj2} Sets the objective number 2.
#' \item \code{Obj3} Sets the objective number 3.
#' \item \code{Obj4} Sets the objective number 4.
#' \item \code{RulesRep} Representation of each chromosome of the population. "can" for canonical representation. "dnf" for DNF representation.
#' \item \code{targetVariable} The name or index position of the target variable (or class). It must be a categorical one.
#' \item \code{targetClass} Value of the target variable to search for subgroups. The target variable \strong{is always the last variable.} Use \code{null} to search for every value of the target variable
#' }
#'
#' An example of parameter file could be:
#' \preformatted{
#' algorithm = MESDIF
#' inputData = "irisd-10-1tra.dat" "irisd-10-1tst.dat"
#' outputData = "irisD-10-1-INFO.txt" "irisD-10-1-Rules.txt" "irisD-10-1-TestMeasures.txt"
#' seed = 0
#' nLabels = 3
#' nEval = 500
#' popLength = 100
#' eliteLength = 3
#' crossProb = 0.6
#' mutProb = 0.01
#' RulesRep = can
#' Obj1 = comp
#' Obj2 = unus
#' Obj3 = null
#' Obj4 = null
#' targetClass = Iris-setosa }
#'
#' @section Objective values:
#' You can use the following quality measures in the ObjX value of the parameter file using this values:
#' \itemize{
#' \item Unusualness -> \code{unus}
#' \item Crisp Support -> \code{csup}
#' \item Crisp Confidence -> \code{ccnf}
#' \item Fuzzy Support -> \code{fsup}
#' \item Fuzzy Confidence -> \code{fcnf}
#' \item Coverage -> \code{cove}
#' \item Significance -> \code{sign}
#' }
#'
#' If you dont want to use a objective value you must specify \code{null}
#'
#'
#' @return The algorithm shows in the console the following results:
#' \enumerate{
#' \item The parameters used in the algorithm
#' \item The rules generated.
#' \item The quality measures for test of every rule and the global results. This globals results shows the number of
#' rules generated and means results for each quality measure.
#' }
#'
#' Also, the algorithms save those results in the files specified in the \code{output} parameter of the algorithm or
#' in the \code{outputData} parameter in the parameters file.
#'
#' Additionally a \code{SDEFSR_Rules} object is returned with this information.
#'
#'
#' @references
#' \itemize{
#' \item Berlanga, F., Del Jesus, M., Gonzalez, P., Herrera, F., & Mesonero, M. (2006). Multiobjective Evolutionary Induction of Subgroup Discovery Fuzzy Rules: A Case Study in Marketing.
#' \item Zitzler, E., Laumanns, M., & Thiele, L. (2001). SPEA2: Improving the Strength Pareto Evolutionary Algorithm.
#' }
#'
#' @examples
#' MESDIF( paramFile = NULL,
#' training = habermanTra,
#' test = habermanTst,
#' output = c("optionsFile.txt", "rulesFile.txt", "testQM.txt"),
#' seed = 0,
#' nLabels = 3,
#' nEval = 300,
#' popLength = 100,
#' eliteLength = 3,
#' crossProb = 0.6,
#' mutProb = 0.01,
#' RulesRep = "can",
#' Obj1 = "CSUP",
#' Obj2 = "CCNF",
#' Obj3 = "null",
#' Obj4 = "null",
#' targetClass = "positive"
#' )
#'
#' \dontrun{
#' Execution for all classes, see 'targetClass' parameter
#' MESDIF( paramFile = NULL,
#' training = habermanTra,
#' test = habermanTst,
#' output = c("optionsFile.txt", "rulesFile.txt", "testQM.txt"),
#' seed = 0,
#' nLabels = 3,
#' nEval = 300,
#' popLength = 100,
#' eliteLength = 3,
#' crossProb = 0.6,
#' mutProb = 0.01,
#' RulesRep = "can",
#' Obj1 = "CSUP",
#' Obj2 = "CCNF",
#' Obj3 = "null",
#' Obj4 = "null",
#' targetClass = "null"
#' )
#' }
#'
#' @export
MESDIF <- function(paramFile = NULL,
training = NULL,
test = NULL,
output = c("optionsFile.txt", "rulesFile.txt", "testQM.txt"),
seed = 0,
nLabels = 3,
nEval = 10000,
popLength = 100,
eliteLength = 3,
crossProb = 0.6,
mutProb = 0.01,
RulesRep = "can",
Obj1 = "CSUP",
Obj2 = "CCNF",
Obj3 = "null",
Obj4 = "null",
targetVariable = NA,
targetClass = "null"
)
{
if(is.null(paramFile)){
#Generate our "parameters file"
if(is.null(training))
stop("Not provided a 'test' or 'training' file and neither a parameter file. Aborting...")
if(is.null(test))
test <- training #To execute only one dataset
if(class(training) != "SDEFSR_Dataset" | class(test) != "SDEFSR_Dataset")
stop("'training' or 'test' parameters is not a SDEFSR_Dataset class")
if(training[[1]] != test[[1]] )
stop("datasets ('training' and 'test') does not have the same relation name.")
if(length(output) != 3 )
stop("You must specify three files to save the results.")
parameters <- list(seed = seed,
algorithm = "MESDIF",
outputData = output,
nEval = nEval,
popLength = popLength,
elitePop = eliteLength,
nLabels = nLabels,
mutProb = mutProb,
crossProb = crossProb,
RulesRep = RulesRep,
Obj1 = Obj1,
Obj2 = Obj2,
Obj3 = Obj3,
Obj4 = Obj4,
targetClass = targetClass,
targetVariable = if(is.na(targetVariable)) training$attributeNames[length(training$attributeNames)] else targetVariable)
} else {
# Parameters --------------------------
parameters <- .read.parametersFile2(file = paramFile) # parameters of the algorithm
if(parameters$algorithm != "MESDIF")
stop(paste("The algorithm specificied (", parameters$algorithm, ") in parameters file is not \"MESDIF\". Check parameters file. Aborting program..."))
training <- read.dataset(file = parameters$inputData[1]) # training data
if(is.na(parameters$inputData[2])){
test <- training
} else {
test <- read.dataset(file = parameters$inputData[2]) # test data
}
}
if(is.na(parameters$targetVariable))
parameters$targetVariable <- training$attributeNames[length(training$attributeNames)]
#Change target variable if it is neccesary
training <- changeTargetVariable(training, parameters$targetVariable)
test <- changeTargetVariable(test, parameters$targetVariable)
#Check if the last variable is categorical.
if(training$attributeTypes[length(training$attributeTypes)] != 'c' | test$attributeTypes[length(test$attributeTypes)] != 'c')
stop("Target variable is not categorical.")
#Set the number of fuzzy labels
training <- modifyFuzzyCrispIntervals(training, parameters$nLabels)
training$sets <- .giveMeSets(data_types = training$attributeTypes, max = training$max, n_labels = parameters$nLabels)
test <- modifyFuzzyCrispIntervals(test, parameters$nLabels)
test$sets <- .giveMeSets(data_types = test$attributeTypes, max = test$max, n_labels = parameters$nLabels)
#Set Covered
#training$covered <- logical(training$Ns)
test$covered <- logical(test$Ns)
#Remove files
file.remove(parameters$outputData[which(file.exists(parameters$outputData))])
# Set the representation of rules (CAN or DNF)
if(tolower(parameters$RulesRep) == "can"){
DNF = FALSE
} else {
DNF = TRUE
vars <- Reduce(f = '+', x = training[["sets"]], accumulate = TRUE)
vars <- vars[length(vars)]
}
# Show information of the parameters to the user
.show_parameters(params = parameters, train = training, test = test)
counter <- 0
#Get the quality measures uses as objectives
Objectives <- .parseObjectives(parameters = parameters, "MESDIF", DNF)
if(all(is.na(Objectives[1:3]))) stop("No objective values selected. You must select, at least, one objective value. Aborting...")
#Get which variable are categorical and numerical to improve the eficiency
cate <- training[["attributeTypes"]][- length(training[["attributeTypes"]])] == 'c'
num <- training[["attributeTypes"]][- length(training[["attributeTypes"]])] == 'r' | training[["attributeTypes"]][- length(training[["attributeTypes"]])] == 'e'
#---------------------------------------------------
#----- EXECUTION OF THE ALGORITHM -------------------
if(parameters$targetClass != "null"){ # Execution for one value of the target class
cat("\n", "\n", "Searching rules for only one value of the target class...", "\n", "\n", file ="", fill = TRUE)
#Execution of the algorithm
rules <- .findRule(parameters$targetClass, "MESDIF", training, parameters, DNF, cate, num, Objectives)
if(! DNF)
rules <- matrix(unlist(rules), ncol = training[["nVars"]] + 1 , byrow = TRUE)
else
rules <- matrix(unlist(rules), ncol = vars + 1 , byrow = TRUE)
} else { #Execution for all classes
cat("\n", "\n", "Searching rules for all values of the target class...", "\n", "\n", file ="", fill = TRUE)
#If we are on Windowns, we cant use mclapply because it use FORK() for parallelism
if(Sys.info()[1] == "Windows")
rules <- lapply(X = training$class_names, FUN = .findRule, "MESDIF",training, parameters, DNF, cate, num, Objectives)
else
rules <- parallel::mclapply(X = training$class_names, FUN = .findRule, "MESDIF",training, parameters, DNF, cate, num, Objectives , mc.cores = parallel::detectCores() - 1)
if(! DNF)
rules <- matrix(unlist(rules), ncol = training[["nVars"]] + 1 , byrow = TRUE)
else
rules <- matrix(unlist(rules), ncol = vars + 1 , byrow = TRUE)
}
#Print rules to the user on console and save into a file
for(i in seq_len(NROW(rules))){
cat("GENERATED RULE", i, file = "", sep = " ",fill = TRUE)
cat("GENERATED RULE", i, file = parameters$outputData[2], sep = " ",fill = TRUE, append = TRUE)
#Print the rule into a human-readable format
.print.rule(rule = as.numeric( rules[i, - NCOL(rules)] ), max = training$sets, names = training$attributeNames, consecuent = rules[i, NCOL(rules)], types = training$attributeTypes,fuzzySets = training$fuzzySets, categoricalValues = training$categoricalValues, DNF, rulesFile = parameters$outputData[2])
cat("\n","\n", file = "", sep = "",fill = TRUE)
cat("\n", file = parameters$outputData[2], sep = "",fill = TRUE, append = TRUE)
}
#---------------------------------------------------
cat("\n", "\n", "Testing rules...", "\n", "\n", file = "", sep = " ", fill = TRUE)
#-------- Rule Testing --------------------
#
# Store accumulated values to show the 'Global' result as a mean of results
sumNvars <- 0
sumCov <- 0
sumFsup <- 0
sumCsup <- 0
sumCconf <- 0
sumFconf <- 0
sumUnus <- 0
sumSign <- 0
sumAccu <- 0
sumTpr <- 0
sumFpr <- 0
n_rules <- NROW(rules)
rulesToReturn <- vector(mode = "list", length = n_rules)
for(i in seq_len(n_rules)){
#Take the test values of each rule
val <- .proveRule(rule = rules[i, - NCOL(rules)], testSet = test, targetClass = rules[i, NCOL(rules)], numRule = i, parameters = parameters, Objectives = Objectives, Weights = c(0.7,0.3,0), cate = cate, num = num, DNF = DNF)
test[["covered"]] <- val[["covered"]]
sumNvars <- sumNvars + val[["nVars"]]
sumCov <- sumCov + val[["coverage"]]
sumFsup <- sumFsup + val[["fsupport"]]
sumCconf <- sumCconf + val[["cconfidence"]]
sumFconf <- sumFconf + val[["fconfidence"]]
sumUnus <- sumUnus + val[["unusualness"]]
sumSign <- sumSign + val[["significance"]]
sumAccu <- sumAccu + val[["accuracy"]]
sumTpr <- sumTpr + val[["tpr"]]
sumFpr <- sumFpr + val[["fpr"]]
#Remove the name of the vector for significance
names(val[["significance"]]) <- NULL
#Add values to the rulesToReturn Object
rulesToReturn[[i]] <- list( rule = createHumanReadableRule(rules[i,], training, DNF),
qualityMeasures = list(nVars = val[["nVars"]],
Coverage = val[["coverage"]],
Unusualness = val[["unusualness"]],
Significance = val[["significance"]],
FuzzySupport = val[["fsupport"]],
Support = val[["csupport"]],
FuzzyConfidence = val[["fconfidence"]],
Confidence = val[["cconfidence"]],
TPr = val[["tpr"]],
FPr = val[["fpr"]]))
}
#Global quality measures as a mean of individual values
cat("Global:", file ="", fill = TRUE)
cat(paste("\t - N_rules:", NROW(rules), sep = " "),
paste("\t - N_vars:", round(sumNvars / n_rules, 6), sep = " "),
paste("\t - Coverage:", round(sumCov / n_rules, 6), sep = " "),
paste("\t - Significance:", round(sumSign / n_rules, 6), sep = " "),
paste("\t - Unusualness:", round(sumUnus / n_rules, 6), sep = " "),
paste("\t - Accuracy:", round(sumAccu / n_rules, 6), sep = " "),
paste("\t - CSupport:", round(sum(test[["covered"]] / test[["Ns"]]), 6), sep = " "),
paste("\t - FSupport:", round(sumFsup / n_rules, 6), sep = " "),
paste("\t - FConfidence:", round(sumFconf / n_rules, 6), sep = " "),
paste("\t - CConfidence:", round(sumCconf / n_rules, 6), sep = " "),
paste("\t - True Positive Rate:", round(sumTpr / n_rules, 6), sep = " "),
paste("\t - False Positive Rate:", round(sumFpr / n_rules, 6), sep = " "),
file = "", sep = "\n"
)
#Save in testMeasures File the global results
cat( "Global:",
paste("\t - N_rules:", nrow(rules), sep = " "),
paste("\t - N_vars:", round(sumNvars / n_rules, 6), sep = " "),
paste("\t - Coverage:", round(sumCov / n_rules, 6), sep = " "),
paste("\t - Significance:", round(sumSign / n_rules, 6), sep = " "),
paste("\t - Unusualness:", round(sumUnus / n_rules, 6), sep = " "),
paste("\t - Accuracy:", round(sumAccu / n_rules, 6), sep = " "),
paste("\t - CSupport:", round(sum(test[["covered"]] / test[["Ns"]]), 6), sep = " "),
paste("\t - FSupport:", round(sumFsup / n_rules, 6), sep = " "),
paste("\t - FConfidence:", round(sumFconf / n_rules, 6), sep = " "),
paste("\t - CConfidence:", round(sumCconf / n_rules, 6), sep = " "),
paste("\t - True Positive Rate:", round(sumTpr / n_rules, 6), sep = " "),
paste("\t - False Positive Rate:", round(sumFpr / n_rules, 6), sep = " "),
file = parameters$outputData[3], sep = "\n", append = TRUE
)
#---------------------------------------------------
class(rulesToReturn) <- "SDEFSR_Rules"
rulesToReturn # Return
}
#
#
# This function calls the executeGA method which launch a genetic algorithm of an SDEFSR algorithm to find rules
#
#
.findRule <- function(targetClass, algorithm, training, parameters, DNF, cate, num, Objectives, porcCob = 0.5, strictDominance = TRUE, reInit = TRUE, minCnf = 0.6){
#Check if target class is valid
if(! any(training$class_names == targetClass)) stop("Invalid target class value provided.")
#cat(" ? Target value:", targetClass ,"\n", file = "", sep = " ", fill = TRUE)
# Sets the examples to cover (for SDIGA)
to_cover = training$examplesPerClass[[targetClass]]
#Launch the genetic algorithm
rule <- .executeGA(algorithm = algorithm, dataset = training, targetClass = targetClass, n_vars = training$nVars, to_cover = to_cover, nLabels = parameters$nLabels, N_evals = parameters$nEval, tam_pob = parameters$popLength, p_cross = parameters$crossProb, p_mut = parameters$mutProb, seed = parameters$seed, Objectives = Objectives, Weights = c(0.7,0.3,0), DNFRules = DNF, cate = cate, num = num, elitism = parameters[["elitePop"]], porcCob = porcCob, strictDominance = strictDominance, reInit = reInit, minCnf = minCnf)
#For each finded rule
rules <- vector(mode = "list", length = NROW(rule))
if(length(rule > 0)){
rule <- cbind(rule, targetClass) #Add at the end the target class
for(i in seq_len(length(rules))){
#Add it to the list
rules[[i]] <- rule[i,]
}
}
rules #Return
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.