---
title: "Tutorial sobre Tareas de Clasificación No Balanceada"
author:
- Alberto Fernández Hilario
- alberto@decsai.ugr.es
- Universidad de Granada - DaSCI
date: "`r format(Sys.time(), '%d %B %Y')`"
output:
  pdf_document:
    toc: yes
  html_document:
    df_print: paged
    toc: yes
    toc_float: true
  html_notebook:
    toc: yes
subtitle: Seminario Permanente de Formación en Inteligencia Artificial Aplicada a
  la Defensa
always_allow_html: yes
---

Este es un Cuaderno o NoteBook de R que incluye una tutorial práctico sobre "*Clasificación No Balanceada*" dentro del "*Seminario Permanente de Formación en Inteligencia Artificial Aplicada a la Defensa*" conjunto entre el Mando de Adiestramiento y Doctrina y la Universidad de Granada. 

Existen bloques de texto (como este) y bloques de código (con instrucciones en R). Cuando se ejecuta el código dentro del cuaderno, los resultados aparecen debajo del mismo. 

# Introducción
Cualquier conjunto de datos con una distribución de clases desigual está técnicamente desequilibrado. Sin embargo, se dice que un conjunto de datos está desequilibrado cuando hay una desproporción significativa, o en algunos casos extrema, entre el número de ejemplos de cada clase del problema. En otras palabras, el desequilibrio de clases se produce cuando el número de ejemplos que representan una clase es mucho menor que los de las otras clases. Por lo tanto, una o más clases pueden estar subrepresentadas en el conjunto de datos. Una definición tan simple ha atraído mucha atención de los investigadores y profesionales debido al número de aplicaciones en el mundo real en las que los datos en bruto recopilados cumplen esta definición.

En este tutorial paso a paso, se pretende que usted:

1. Obtenga los paquetes más útiles para abordar la clasificación desequilibrada con el aprendizaje automático en R.
2. Cargue un conjunto de datos y entienda su estructura usando resúmenes estadísticos y visualización de datos.
3. Cree varios modelos de aprendizaje automático en sinergia con técnicas de preprocesamiento para datos desequilibrados, elegir los mejores y crear confianza en que el rendimiento predictivo es fiable.

Tal como se indicó anteriormente, para facilitar esta sesión "práctica", el documento contiene trozos de código fuente de R que pueden ser ejecutados directamente. Intente ejecutar este primer trozo haciendo clic en el botón *Run* dentro del trozo o colocando el cursor dentro de él y pulsando *Cmd+Shift+Enter* (*Ctrl+Shift+Enter* en Windows).
 

```{r}
print("Welcome to your first R NoteBook")
```

Si quiere añadir un nuevo trozo de código, sólo tiene que hacer clic en el botón *Insertar trozo* (*Insert Chunk*) de la barra de herramientas o pulsando *Cmd+Opción+I* (o *Ctrl+Alt+I* en Windows). De esta manera, puede añadir su propio código si así lo necesita. 

Cuando guarde el cuaderno, se guardará un archivo HTML con el código y la salida junto a él (haz clic en el botón *Previsualizar* (*preview*) o pulsando *Cmd+Mayús+K* para obtener una vista previa del archivo HTML). En realidad, esta es una buena manera de compilar todas las tareas desarrolladas durante el tutorial.

Aquí hay un resumen de lo que se pretende cubrir en este *Tutorial sobre Clasificación No Balancedad*:

1. Instalación de la plataforma R.
2. Cargando el conjunto de datos.
3. Resumiendo el conjunto de datos.
4. Visualizando el conjunto de datos.
5. Evaluar algunos algoritmos.
6. Comparación entre diferentes soluciones.

Tómese su tiempo. Trabaje en cada paso.

# Instalar los paquetes requeridos
Instale los paquetes que vamos a usar hoy. Los paquetes son complementos o bibliotecas de terceros que se pueden usar en R.

```{r eval=FALSE, include=FALSE}
install.packages(c("caret","dplyr","pROC","tidyr","imbalance"),repos = "http://cran.r-project.org")

```

**NOTA:** Puede que necesite otros paquetes, pero Caret debería preguntarnos si queremos cargarlos. Si tiene problemas con los paquetes, puede instalar los paquetes de caret y todos los paquetes que pueda necesitar escribiendo lo siguiente (elimine el carácter de comentario \#):

```{r}
#install.packages("caret", dependencies=c("Depends", "Suggests"),repos = "http://cran.r-project.org")
```

Ahora, cargue los paquetes que va a usar en este tutorial, los paquetes de `caret` e `imbalance`, entre otros.

```{r}
library(caret)
library(imbalance)
library(dplyr)
library(pROC)
library(tidyr)
library(rlist)
library(Rtsne)
```

Como quizá ya sabe, el paquete `caret` proporciona una interfaz consistente en cientos de algoritmos de Machine Learning y ofrece métodos útiles y convenientes para la visualización de datos, el remuestreo de datos, la puesta a punto de modelos y la comparación de modelos, entre otras características. Es una herramienta imprescindible para los proyectos de aprendizaje automático en R.

Una de sus ventajas para el uso del `caret` es que integra directamente el uso de técnicas de preprocesamiento al establecer el método de control (se describirámás adelante) para la etapa de entrenamiento.

Para más información sobre el paquete R caret, ver la [página web del paquete caret](http://topepo.github.io/caret/index.html).

# Paso uno. Obtener los datos

Para establecer un escenario controlado, utilizaremos dos datos artificiales diferentes, a saber, los datos de "círculo" y "subclúster", que pueden encontrarse en diferentes estudios sobre clasificación desequilibrada. Se trata de problemas binarios, tanto en términos de atributos como de clases. 

Además, podemos cargar algunos datos del paquete de `imbalance`, como el del glass0 (véase [Wikipedia](https://www.kaggle.com/uciml/glass)). En este caso, podemos observar el comportamiento de los diferentes métodos en un caso de estudio real. 

Esto es lo que vamos a hacer en este paso:

0. Cargar los datos del glass0 de la manera más fácil (con el paquete `imbalance`). Esto es un adelante de la segunda parte del tutorial exclusiva de la citada biblioteca.
1. Cargar los datos de *círculo* desde CSV.
2. Separar los datos en un conjunto de datos de entrenamiento y un conjunto de datos de validación.

Para los datos de "*subcluster*", siga el mismo procedimiento que en el caso de "*círculo*".

## Cargar datos de la manera más fácil
Afortunadamente, el paquete de desequilibrio nos proporciona el conjunto de datos de *Glass0*, entre otros. Cargue el conjunto de datos de la siguiente manera:

```{r}
# adjunto el dataset glass0 al entorno
data(glass0)
# renombre el dataset por comodidad
dataset <- glass0
```

Ahora tiene el conjunto de datos *glass0* cargado en R y accesible a través de la variable de `dataset`.

Una ventaja de utilizar un nombre de variable genérico, como en este caso, es porque resulta útil si se desea copiar y pegar código entre proyectos y el conjunto de datos siempre tiene el mismo nombre.

## Cargar desde CSV
En caso que haya descargado el conjunto de datos previamente, puede que quieras cargar los datos directamente desde un archivo CSV. También lo podemos hacer desde un enlace web.

1. Descargue el conjunto de datos de los [círculos](https://www.dropbox.com/s/06k36xri8c1bepf/circle.csv?dl=0) (y [subcluster](https://www.dropbox.com/s/2xqzsr3mm1we4ks/subclus.csv?dl=0)) desde los enlaces.
2. Guarde el archivo como `circle.csv` (y `subclus`) en su directorio de proyecto.
3. Cargue el conjunto de datos del archivo CSV de la siguiente manera:

```{r eval=FALSE, include=FALSE}
# cargar el fichero CSV desde local o directorio Web
dataset <- read.csv("circle.csv",header = TRUE)
# poner el nomber de los atributos en los datos (deben conocerse a priori o estar en el fichero)
colnames(dataset) <- c("Att1", "Att2", "Class")
dataset$Class <- relevel(dataset$Class,"positive") #para asegurar que aparece como la primera clase
```

Otra alternativa hubiese sido leer el CSV directamente desde la dirección Web (URL) del siguiente modo:

```{r}
# cargar el fichero CSV desde local o directorio Web
dataset <- read.csv("https://www.dropbox.com/s/06k36xri8c1bepf/circle.csv?raw=1",header = TRUE)
# poner el nomber de los atributos en los datos (deben conocerse a priori o estar en el fichero)
colnames(dataset) <- c("Att1", "Att2", "Class")
#para asegurar que aparece como la primera clase
dataset$Class <- factor(dataset$Class,levels=c("positive","negative")) 
```

Ahora tiene los datos del *círculo* cargados en R y accesibles a través de la variable `dataset`.

# Paso dos. Conocer los datos: Resumir el conjunto de datos
Ahora es el momento de echar un vistazo a los datos. Esta etapa se ha realizado parcialmente en otras sesiones prácticas del Curso. En cualquier caso, siempre es positivo dar un repaso a esta tarea tan importante dentro del ciclo de Ciencia de Datos.

En concreto, en este paso vamos a echar un vistazo a los datos de diferentes maneras:

1. Dimensiones del conjunto de datos.
2. Tipos de atributos.
3. Echar un vistazo a los datos en sí.
4. Niveles del atributo de clase.
5. Desglose de las instancias de cada clase.
6. Resumen estadístico de todos los atributos.

No se preocupe, cada inspección sobre los datos es un breve comando o instrucción. Se mostrarán por tanto comandos útiles que puede usar una y otra vez en futuros proyectos.

## Dimensiones del conjunto de datos

Podemos hacernos una idea rápida de cuántas instancias (filas) y cuántos atributos (columnas) contienen los datos con la función `dim`.

```{r}
# dimensions of dataset
dim(dataset)
```

Debería ver 2390 instancias y 3 atributos en el caso de los datos del "*círculo*".

## Tipos de Atributos
Es una buena idea tener una idea de los tipos de los atributos. Podrían ser dobles (reales), enteros, cadenas, factores y otros tipos.

Conocer los tipos es importante ya que le dará una idea de cómo resumir mejor los datos que tiene y los tipos de transformaciones que podría necesitar para preparar los datos antes de modelarlos.

En este ejemplo, debería ver que todos los datos de entrada son dobles y que el valor de la clase es un factor.

```{r}
# Chequear la estructura y tipo de cada atributo
str(dataset)
```

## Vistazo rápido a los Datos
También es siempre una buena idea observar sus datos. Debería ver las primeras 5 filas de los datos de la siguiente manera:

```{r}
# Observar las 5 primeras filas de datos
head(dataset)
```

## Niveles de la clase
La variable de clase es un factor. Un factor es una clase que tiene múltiples etiquetas de clase o niveles. Veamos los niveles. Fíjese en cómo podemos referirnos a un atributo por su nombre como una propiedad del conjunto de datos. En los resultados podemos ver que la clase tiene 2 etiquetas diferentes:

```{r}
# listas los niveles para la clase
levels(dataset$Class)
```

Este es un problema de clasificación binaria.

## Resumen estadístico
Ahora, finalmente, podemos echar un vistazo a un resumen de cada atributo.

Esto incluye la media, los valores mínimo y máximo, así como algunos percentiles (25, 50 o media y 75, e.g., los valores en estos puntos si ordenamos todos los valores de un atributo). Podemos ver aquí la distribución desigual entre las clases: 2335 vs. 55 (datos del *círculo*). Esto se confirma calculando el Ratio de Desequilibrio (IR).

```{r}
# resumir la distribución de los atributos
summary(dataset)

imbalanceRatio(dataset)
```

# Visualizar el conjunto de datos
Ahora tenemos una idea básica sobre los datos. Necesitamos ampliarla con algunas visualizaciones.

Vamos a ver dos tipos de gráficos:

1. Gráficos univariados para entender mejor cada atributo.
2. Gráficas multivariadas para entender mejor las relaciones entre los atributos.

## Gráficas univariadas
Empezamos con algunos gráficos univariados, es decir, gráficos de cada variable individual.

Es útil para la visualización tener una forma de referirse sólo a los atributos de entrada y sólo a los atributos de salida. Configurémoslo y llamemos a los atributos de entrada x y al de salida (o clase) y.

```{r}
# Dividir entre variables de entrada y salida
x <- dataset[,1:2]
y <- dataset[,3]
```

Dado que las variables de entrada son numéricas, podemos crear gráficos de caja y bigote (*boxplots*) de cada una. Esto nos da una idea mucho más clara de la distribución de los atributos de entrada:

```{r}
# boxplot para cada atributo en una imagen
par(mfrow=c(1,2))
  for(i in 1:2) {
  boxplot(x[,i], main=names(dataset)[i])
}

```

También podemos crear un *barplot* diagrama de barras (mejor un gráfico de queso) de la variable de la clase para obtener una representación gráfica de la distribución de la clase. Esto confirma lo que aprendimos en la última sección, que las instancias están distribuidas de manera desigual entre las dos clases.

```{r}
# simple barplot para ver las clases
# plot(y)

# Un diagrama de queso es más apropiado
n_classes <- c(sum(y=="positive"),sum(y=="negative"))
pct <- round(n_classes/sum(n_classes)*100,digits=2)

lbls <- levels(dataset$Class)
lbls <- paste(lbls, pct) # add percents to labels
lbls <- paste(lbls,"%",sep="") # ad % to labels

pie(n_classes,labels = lbls, main="Class distribution")
```

## Gráficos multivariados
Ahora podemos ver las interacciones entre las variables.

Primero veamos los diagramas de dispersión de todos los pares de atributos y coloreemos los puntos por clase. Además, como los gráficos de dispersión muestran que los puntos de cada clase están generalmente separados, podemos dibujar elipses alrededor de ellos. Nuestro objetivo es ver las relaciones entre los atributos de entrada (tendencias) y entre los atributos y los valores de las clases (elipses). En este caso los datos son tan simples que no hay una conclusión clara.

```{r}
# scatterplot matrix
featurePlot(x=x, y=y, plot="ellipse")
```

También podemos mirar los gráficos de cajas y bigotes de cada variable de entrada de nuevo, pero esta vez divididos en gráficos separados para cada clase. Esto puede ayudar a determinar las separaciones lineales obvias entre las clases.

```{r}
# boxplots para cada atributo
featurePlot(x=x, y=y, plot="box")
```

Las gráficas de dispersión pueden dar una gran idea de lo que se está tratando: puede ser interesante ver cuánto afecta una variable a otra. En otras palabras, se pretende ver si hay alguna correlación entre dos variables. Puede hacer gráficos de dispersión con el paquete [ggvis](http://www.rdocumentation.org/packages/ggvis), por ejemplo.

```{r}
# Load in `ggvis`
library(ggvis)

# Dataset scatter plot
dataset %>% ggvis(~Att1, ~Att2, fill = ~Class) %>% layer_points()
```

# Evaluar algunos algoritmos
Ahora es el momento de crear algunos modelos de los datos y estimar su capacidad de predicción sobre datos no vistos.

Esto es lo que vamos a cubrir en este paso:

1. Preparar el conjunto de prueba o test a partir de la validación *hold-out*. No es la mejor práctica, pero nos servirá para propósitos de didácticos.
2. Aplicar diferentes enfoques de preprocesamiento a los datos de entrenamiento. El test debe mantenerse sin cambios.
3. Construir un modelo kNN para predecir la clase a partir de cada uno de los datos de entrenamiento.
4. Comparar y seleccionar la mejor metodología.

## Validación de modelos

Como ya se ha comentado, nos centraremos en una partición tipo *hold-out*. De esta manera, tendremos sólo un entrenamiento y una partición de prueba, lo que puede causar un sesgo en nuestras conclusiones. Sin embargo, se muestra cómo proceder con un procedimiento adecuado de validación cruzada. 

Debemos reajustar la semilla de números aleatorios antes de cada ejecución para asegurarnos de que la evaluación de cada algoritmo se realiza utilizando exactamente las mismas divisiones de datos. Esto asegura que los resultados sean directamente comparables.

```{r}
set.seed(42) #Para asegurar la misma salida (en el mismo equipo)

#Una forma fácil de crear "particiones de datos":
trainIndex <- createDataPartition(dataset$Class, p = .75,list = FALSE, times = 1)

trainData <- dataset[ trainIndex,]
testData  <- dataset[-trainIndex,]

#Chequear el IR para asegurar una partición estratificada
imbalanceRatio(trainData)
imbalanceRatio(testData)

#Ad hoc FCV
#testIndices <- createFolds(dataset$Class, k=5)
#First partition
#dataTrain <- dataset[-testIndices[[1]],]
#dataTest  <- dataset[testIndices[[1]],]
```

## Construir modelos
No sabemos qué algoritmos serían buenos en este problema o qué configuraciones usar. 

Evaluemos 3 metodologías diferentes con kNN:

1. Conjunto de datos originales (datos en bruto)
2. Técnicas de muestreo triviales (sobre y submuestreo aleatorio).
3. Sobremuestreo SMOTE.

En primer lugar, necesitamos crear dos funciones auxiliares para las etapas de aprendizaje y predicción. Por favor, tenga en cuenta que estamos optimizando el parámetro k de kNN a través de un procedimiento "*grid search*". Si eliminamos esta parte, también podemos construir una función general para cualquier posible algoritmo de clasificación.

1) Aprendizaje

```{r}
# a) Learning function
learn_model <-function(dataset, classifier, hyperp, ctrl, message){
  model.fit <- train(Class ~ ., data = dataset, method = classifier, trControl = ctrl, preProcess = 
                       c("center","scale"), metric="ROC",tuneGrid = hyperp)
  model.pred <- predict(model.fit,newdata = dataset)
  #Get the confusion matrix to see accuracy value and other parameter values
  model.cm <- confusionMatrix(model.pred, dataset$Class,positive = "positive")
  model.probs <- predict(model.fit,newdata = dataset, type="prob")
  model.roc <- roc(dataset$Class,model.probs[,"positive"],color="green")
  return(model.fit)
}
```

2) Predicción:

```{r}
# b) Estimation function
test_model <-function(dataset, model.fit,message){
  model.pred <- predict(model.fit,newdata = dataset)
  #Get the confusion matrix to see accuracy value and other parameter values
  model.cm <- confusionMatrix(model.pred, dataset$Class,positive = "positive")
  print(model.cm)
  model.probs <- predict(model.fit,newdata = dataset, type="prob")
  model.roc <- roc(dataset$Class,model.probs[,"positive"])
  #print(knn.roc)
  plot(model.roc, type="S", print.thres= 0.5,main=c("ROC Test",message),col="blue")
  #print(paste0("AUC Test ",message,auc(model.roc)))
  return(model.cm)
}
```

## Aprender y almacenar diferentes modelos de los datos

Primero, comprobamos la obtención de los resultados de los datos originales. Por favor, recuerde que una validación interna de CV se utiliza para establecer los mejores parámetros para el modelo de kNN (véase más arriba). Esto se indica en la llamada de `trainControl`.

```{r}
#Execute model ("raw" data)
ctrl <- trainControl(method="repeatedcv",number=5,repeats = 3, classProbs=TRUE,summaryFunction = twoClassSummary)
#ctrl <- trainControl(method="none",classProbs=TRUE,summaryFunction = twoClassSummary)
classifier = "knn"
hyperp = data.frame(k = c(1,3,5,7,9,11))
#hyperp = data.frame(k = 3)

#classifier = "rpart"
#hyperp = data.frame(cp = 0.05)
model.raw <- learn_model(trainData,classifier,hyperp,ctrl,"RAW ")
#Podemos decidir representar los resultados del Grid search de los hiperparámetros del modelo
if (dim(hyperp)[1] > 1){
  plot(model.raw,main="Grid Search RAW")
}
print(model.raw)
cm.raw <- test_model(testData,model.raw,"RAW ")
```

Ahora, incluimos la etapa de preprocesamiento en el método de control de `caret` y obtenemos nuevos modelos. Primero con una simple técnica de submuestreo. El uso de *RUS* debe obtener un conjunto de entrenamiento perfectamente equilibrado eliminando instancias de la clase mayoritaria de forma aleatoria. 


```{r}
#Execute model ("preprocessed" data)
#Undersampling
ctrl <- trainControl(method="repeatedcv",number=5,repeats = 3,classProbs=TRUE,summaryFunction = twoClassSummary,sampling = "down")

model.us <- learn_model(trainData,classifier,hyperp,ctrl,"US ")
cm.us <- test_model(testData,model.us,"US ")
```

Ahora debemos comprobar el comportamiento del enfoque de sobremuestreo aleatorio. El uso del *ROS* debe obtener un conjunto de entrenamiento perfectamente equilibrado al replicar instancias de la clase minoritaria de forma aleatoria.

```{r}
#Oversampling
ctrl <- trainControl(method="repeatedcv",number=5,repeats = 3,
                     classProbs=TRUE,summaryFunction = twoClassSummary,sampling = "up")
model.os <- learn_model(trainData,classifier,hyperp,ctrl,"OS ")
cm.os <- test_model(testData,model.os,"OS ")
```

Finalmente, chequeamos la solución del estado del arte conocida como SMOTE. La aplicación de SMOTE debería obtener un conjunto de entrenamiento perfectamente equilibrado, creando nuevas instancias de la clase minoritaria

```{r}
#SMOTE
ctrl <- trainControl(method="repeatedcv",number=5,repeats = 3, classProbs=TRUE,summaryFunction = twoClassSummary,sampling = "smote")
model.smt <- learn_model(trainData,classifier,hyperp,ctrl,"SMT ")
cm.smt <- test_model(testData,model.smt,"SMT ")
```

## Selecciona el mejor modelo
Ahora tenemos 4 modelos aprendidos del mismo conjunto de datos pero con diferentes opciones de preprocesamiento. Cada modelo comprende una estimación de precisión diferente, y por lo tanto necesitamos comparar los modelos entre sí y seleccionar el más "exacto" en términos de métricas de rendimiento desequilibrado, por supuesto.

Podemos informar sobre el rendimiento de cada modelo creando primero una lista de los modelos creados y utilizando la función de resumen:

```{r}
# resumen del acierto de los modelos

models <- list(raw = model.raw,us = model.us,os = model.os,smt = model.smt)
results <- resamples(models)
summary(results)
```

También podemos crear un gráfico de los resultados de la evaluación del modelo y comparar la dispersión y el rendimiento medio de cada modelo. En el caso de los datos de *círculos*, todos los métodos de preprocesamiento son igualmente buenos en términos de sensibilidad (reconocimiento de clase positiva), pero el sobremuestreo es un poco mejor para la especificidad (reconocimiento de clase negativa).

El procedimiento ideal es obtener una población de medidas de rendimiento para cada algoritmo, porque cada algoritmo se evalúa varias veces (validación cruzada de *k folds*). 

```{r}
# comparar el acierto de los distintos modelos
bwplot(results)
#dotplot(results)
```

Finalmente podemos hacer otro gráfico diferente para comparar métricas adicionales para la clasificación no balanceada, como la precisión, el recall y la F1. 

```{r}
#LLevar a cabo una comparación sobre todas las métricas de imbalanced
comparison <- data.frame(model = names(models),
                         Sensitivity = rep(NA, length(models)),
                         Specificity = rep(NA, length(models)),
                         Precision = rep(NA, length(models)),
                         Recall = rep(NA, length(models)),
                         F1 = rep(NA, length(models)))

for (name in names(models)) {
  cm_model <- get(paste0("cm.", name))
  
  comparison[comparison$model == name, ] <- filter(comparison, model == name) %>%
    mutate(Sensitivity = cm_model$byClass["Sensitivity"],
           Specificity = cm_model$byClass["Specificity"],
           Precision = cm_model$byClass["Precision"],
           Recall = cm_model$byClass["Recall"],
           F1 = cm_model$byClass["F1"])
}

comparison %>%
  gather(x, y, Sensitivity:F1) %>%
  ggplot(aes(x = x, y = y, color = model)) +
  geom_jitter(width = 0.2, alpha = 0.5, size = 3)

```

# Algoritmos avanzados en clasificación no balanceada: Utilizando el paquete "imbalance"

Como comentamos inicialmente, existe un paquete CRAN denominado `imbalance` que implementa algunas de las técnicas de preprocesamiento de datos más conocidas para la clasificación con clases no balanceadas. Debemos mirar más de cerca la documentación tanto en la [página principal del paquete de desequilibrio](https://github.com/ncordon/imbalance), como en la función de ayuda

```{r}
help("imbalance") #ver la documentación en la esquina inferior derecha
```

Utilizando la biblioteca `imbalance` podemos considerar la aplicación de técnicas avanzadas basadas en SMOTE. Para ello, debemos centrarnos en la función de `oversample` ("sobremuestreo"):

```{r}
help("oversample") #ver la documentación en la esquina inferior derecha
```

En primer lugar, se actualiza la función de comparación para hacerlo todo más compacto:

```{r}
#Funcion para comparar la salida de las distintas técnicas de Oversampling

perform.comparison.smote <- function(dataset, imb.ratio, methods){
  ## Train index generation
  trainIndex <- createDataPartition(dataset$Class, p = .75,
  list = FALSE, times = 1)
  ## train-test separation
  trainData <- dataset[trainIndex,]
  testData <- dataset[-trainIndex,]
  ## Basic model training (no grid search)
  ctrl <- trainControl(method="none",classProbs=TRUE, summaryFunction = twoClassSummary)
  ## Change for another classifier (or include as parameter)
  classifier = "rpart"
  hyperp = data.frame(cp = 0.05)
  basic.model <- learn_model(trainData, classifier,hyperp,ctrl, "RAW")
  basic.model <- test_model(testData, basic.model, "RAW")
  
  ## Model training with each data generation policy
  cm.models <- sapply(methods, function (x) {
    aug.trainData <- oversample(trainData, ratio=imb.ratio, method=x, )
    ctrl <- trainControl(method="none", classProbs=TRUE, summaryFunction = twoClassSummary)
    model <- learn_model(aug.trainData, classifier,hyperp, ctrl, x)
    test_model(testData, model, x)
    }, simplify = F)
    
  cm.models <- list.prepend(cm.models, RAW=basic.model)
  
  ## Metrics gathering
  comparison <- lapply(cm.models, function(x){
      x$byClass[c("Balanced Accuracy", "F1", "Precision","Recall", "Specificity")]
  })
  
  ## Transformation into dataframe
  comparison <- as.data.frame(do.call(rbind, comparison))
  comparison$model <- rownames(comparison)
  comparison
}

```

A continuación, cargamos alguno de los conjuntos de datos proporcionados por el paquete (banana, o glass0, por ejemplo). Seleccionamos varias técnicas o variantes SMOTE diferentes, para aplicarlas sobre los problemas anteriores. 

```{r}
# Load the data
dataset <- banana
dataset <- unique(dataset)
dataset[,-length(dataset)] <- sapply(dataset[,-length(dataset)], as.numeric)
repr.data <- dataset
colnames(repr.data) <- c("x", "y", "Class")
ggplot(repr.data) + geom_point(aes(x=x, y=y, color=Class))

# Apply preprocessing with oversample function
imb.ratio <- 0.65
methods <- c("SMOTE", "BLSMOTE", "DBSMOTE", "MWMOTE")
comparison <- perform.comparison.smote(dataset, imb.ratio, methods)

# Check results with kNN, DT or any other classifier (included in previous chunk)
comparison$model <- factor(comparison$model, levels=comparison$model)
comparison %>%
gather(x, y, "Balanced Accuracy":"Specificity") %>%
ggplot(aes(x = x, y = y, color = model)) +
geom_jitter(width = 0.2, alpha = 0.5, size = 3)
```

Ahora, haga una comparación gráfica entre el conjunto de datos original y el preprocesado (sólo para SMOTE, por ejemplo). Recordemos que, siendo un gráfico 2D, sólo debe seleccionar dos de las columnas de entrada. 

```{r}
# Visualize the data distribution between original and preprocess data.

representation <- function (ov_technique, data) {
    aug.data <- oversample(data, ratio=imb.ratio, method=ov_technique, )
    repr.data <- aug.data
    colnames(repr.data) <- c("x1", "x2", "Class")
    p <- ggplot(repr.data) + geom_point(aes(x=x1, y=x2, color=Class))
    p + ggtitle(paste0("2D representation for ",ov_technique)) # for the main title)
    }

sapply(methods, representation, data=dataset, simplify = F)


```

Alternativamente, puede aplicar el método `tsne` antes del gráfico para extraer dos únicas características.

```{r}
library(Rtsne)
dataset <- ecoli1
#Dos variables que con un valor único son eliminadas
dataset <- subset(dataset, select=-c(Lip, Chg))
#Chequear posibles muestras respetidas
dataset <- unique(dataset)
#Por si hubiese algún valor numérico erróneamente cargado
dataset[,-length(dataset)] <- sapply(dataset[,-length(dataset)], as.numeric)

#No debería haber problemas puesto que hemos utilizado "unique"
tsne.suitable.ind <- !duplicated(dataset[,-length(dataset)])
data.2d <- as.data.frame(Rtsne(dataset[tsne.suitable.ind,-length(dataset)])$Y)

colnames(data.2d) <- c("x1", "x2")
#La clase no estaba inicialmente incluida
data.2d$Class <- dataset$Class[tsne.suitable.ind]

ggplot(data.2d) + geom_point(aes(x=x1, y=x2, color=Class)) + labs(title="Raw data with TSNE")

sapply(methods, representation, data=data.2d, simplify = F)

```

Por último, es posible utilizar la propia función `plotComparison` incluida en el paquete para observar las diferentes entre el conjunto de datos original y el preprocesado, siempre realizando una representación en dos dimensiones realizando todas las combinaciones de atributos del conjunto de datos.

```{R}

#dataset corresponde con "ecoli1"
aug.data <- oversample(dataset, ratio=0.75, method="SMOTE")
plotComparison(dataset, aug.data, attrs = names(dataset)[1:3], cols = 2, classAttr = "Class")

```

# Comentarios finales
Espero que hayan disfrutado este tutorial sobre la Clasificación No Balanceada en Aprendizaje Automático. Si necesita más detalles sobre cómo realizar cualquier tipo de tarea, por favor pregúnteme por correo electrónico en la siguiente dirección: alberto@decsai.ugr.es

