Pregunta problema

En el ámbito del análisis de datos y el aprendizaje automático, uno de los desafíos más relevantes es determinar qué algoritmo ofrece la mayor precisión en problemas de regresión aplicados a series de tiempo, considerando el tiempo de entrenamiento como un factor crítico. La eficacia de un modelo no solo se mide por su capacidad para hacer predicciones precisas, sino también por el tiempo que requiere para entrenarse adecuadamente.

En este contexto, surge la pregunta: ¿Qué algoritmo logra la mayor precisión relativa al tiempo de entrenamiento en problemas de regresión sobre datos de series de tiempo, y cómo varía esta eficiencia entre diferentes frameworks, como PyTorch, TensorFlow, Keras y Scikit-learn?

Con el objetivo de abordar esta cuestión, llevaremos a cabo un análisis comparativo entre los frameworks mencionados, enfocados en identificar cuál de ellos permite un entrenamiento más eficiente sin comprometer la eficacia del modelo. Para establecer un criterio de evaluación, consideraremos como umbral de eficacia aquellos modelos que logren alcanzar valores superiores a 0.6 en su desempeño. De este modo, buscamos no solo optimizar el tiempo de entrenamiento, sino también garantizar resultados satisfactorios en la calidad de las predicciones.

Contexto del EDA y su base de datos

Este conjunto de datos está diseñado para abordar problemas relacionados con Inteligencia Artificial (IA), donde se analizan algoritmos, frameworks, tipos de problemas y tipos de datos. Los valores numéricos representan métricas de desempeño, como la precisión o el tiempo de entrenamiento de los modelos. Diccionario de variables:

1.Algorithm (categórica): Tipo de algoritmo de IA utilizado(‘Neural Network’, ‘Random Forest’, ‘SVM’, ‘K-Means’).

2.Framework (categórica): Framework o biblioteca utilizada para la implementación del modelo de IA(‘TensorFlow’, ‘PyTorch’, ‘Keras’, ‘Scikit-learn’).

3.Problem_Type (categórica): Tipo de problema abordado por el modelo.(‘Classification’, ‘Regression’, ‘Clustering’).

4.Dataset_Type (categórica): Tipo de datos utilizados en el entrenamiento del modelo(‘Image’, ‘Text’, ‘Tabular’, ‘Time Series’.).

5.Accuracy (numérica, continua): Precisión del modelo en el conjunto de prueba (entre 0 y 1).

6.Precision (numérica, continua): Precisión del modelo (valor entre 0 y 1).

7.Recall (numérica, continua): Sensibilidad o capacidad del modelo para identificar correctamente los positivos (entre 0 y 1).

8.F1_Score (numérica, continua): Medida armónica entre precisión y recall (entre 0 y 1).

9.Training_Time (numérica, continua): Tiempo de entrenamiento del modelo en horas.

10.Date (fecha): Fecha en la que se realizó la evaluación del modelo, cubriendo el último año.

Para que quede más claro, expresemos todas las variables mediante una tabla con la función kable:

# Crear un dataframe con la información de las variables
variables_df <- data.frame(
  Variable = c("Algorithm", "Framework", "Problem_Type", "Dataset_Type", 
               "Accuracy", "Precision", "Recall", "F1_Score", 
               "Training_Time", "Date"),
  Naturaleza = c("Cualitativa/Categórica", "Cualitativa/Categórica", "Cualitativa/Categórica", "Cualitativa/Categórica", 
                 "Cuantitativa/Numérica", "Cuantitativa/Numérica", 
                 "Cuantitativa/Numérica", "Cuantitativa/Numérica", 
                 "Cuantitativa/Numérica", "Fecha"),
  Nivel_de_Medición = c("Nominal", "Nominal", "Nominal", "Nominal", 
                        "Continua", "Continua", "Continua", "Continua", 
                        "Continua", "Fecha"),
  Criterio_de_Clasificación = c("Tipo de algoritmo de IA", "Framework o biblioteca utilizada", 
                                "Tipo de problema", "Tipo de datos", 
                                "Precisión del modelo (0 a 1)", 
                                "Precisión del modelo (0 a 1)", 
                                "Capacidad de identificar positivos (0 a 1)", 
                                "Medida armónica (0 a 1)", 
                                "Tiempo en horas", "Último año")
)
# Quitar los guiones bajos de los nombres de las columnas si fuera necesario
names(variables_df) <- gsub("_", " ", names(variables_df))
# Mostrar la tabla con kable y aplicar estilos
kable(variables_df, caption = "Tabla 1: Clasificación de Variables", align = "c") %>%  # Alinear columnas al centro
  kable_styling(full_width = TRUE, bootstrap_options = c("striped", "hover", "condensed", "responsive"), font_size = 15, fixed_thead = TRUE) %>%
  column_spec(1, bold = TRUE, width = "10em") %>%   # Resaltar la columna de Variable
  column_spec(2, width = "12em") %>%                # Ajustar ancho de Naturaleza
  column_spec(3, width = "12em") %>%                # Ajustar ancho de Nivel de Medición
  column_spec(4, width = "22em") %>%                # Ajustar ancho de Criterio de Clasificación
  row_spec(0, bold = TRUE, background = "#ccc", color = "black")   # Resaltar la primera fila (encabezado)
Tabla 1: Clasificación de Variables
Variable Naturaleza Nivel de Medición Criterio de Clasificación
Algorithm Cualitativa/Categórica Nominal Tipo de algoritmo de IA
Framework Cualitativa/Categórica Nominal Framework o biblioteca utilizada
Problem_Type Cualitativa/Categórica Nominal Tipo de problema
Dataset_Type Cualitativa/Categórica Nominal Tipo de datos
Accuracy Cuantitativa/Numérica Continua Precisión del modelo (0 a 1)
Precision Cuantitativa/Numérica Continua Precisión del modelo (0 a 1)
Recall Cuantitativa/Numérica Continua Capacidad de identificar positivos (0 a 1)
F1_Score Cuantitativa/Numérica Continua Medida armónica (0 a 1)
Training_Time Cuantitativa/Numérica Continua Tiempo en horas
Date Fecha Fecha Último año

Resumen de la base de datos

Primero, iniciaremos haciendo un resumen de la base de datos con la función summary:

summary(Base)
##   Algorithm          Framework         Problem_Type       Dataset_Type      
##  Length:560         Length:560         Length:560         Length:560        
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##                                                                             
##     Accuracy        Precision          Recall          F1_Score     
##  Min.   :0.5038   Min.   :0.4019   Min.   :0.3001   Min.   :0.4000  
##  1st Qu.:0.6236   1st Qu.:0.5632   1st Qu.:0.4819   1st Qu.:0.5515  
##  Median :0.7578   Median :0.7195   Median :0.6493   Median :0.7086  
##  Mean   :0.8779   Mean   :0.8129   Mean   :0.7486   Mean   :0.8122  
##  3rd Qu.:0.8824   3rd Qu.:0.8596   3rd Qu.:0.8404   3rd Qu.:0.8438  
##  Max.   :9.7181   Max.   :9.7320   Max.   :9.3662   Max.   :9.3740  
##  NA's   :39       NA's   :19       NA's   :20       NA's   :20      
##  Training_Time          Date                       
##  Min.   : 0.1032   Min.   :2023-03-08 11:26:21.07  
##  1st Qu.: 1.2441   1st Qu.:2023-07-26 05:26:21.07  
##  Median : 2.4347   Median :2023-12-12 23:26:21.07  
##  Mean   : 2.9910   Mean   :2023-12-12 23:26:21.07  
##  3rd Qu.: 3.8131   3rd Qu.:2024-04-30 17:26:21.07  
##  Max.   :46.9856   Max.   :2024-09-17 11:26:21.07  
##  NA's   :20

Características generales de la base de datos.

La dimension de la base de datos es:

dim(Base)
## [1] 560  10

Es decir, se compone de 560 filas y 10 columnas.

Filtros de limpieza

Tratamiento de NA

Para comenzar, eliminaremos las entradas de la base de datos que contengan datos faltantes. Esto garantizará que solo se consideren registros completos y relevantes para el análisis, evitando cualquier sesgo o distorsión en los resultados debido a la falta de información clave.

Veamos la cantidad de NA mediante un gráfico:

missmap(Base, col = c("red", "blue"), legend = TRUE)

El gráfico nos indica que existe un 2% de información faltante. El primer indicio que nos permite aplicar la técnica de eliminación, es que la muestra o el porcentaje de valores valtantes no es mayor al 5%, por lo que, no se perdera mucha información; el segundo indicio, la base de datos fue generada aleatoriamente, lo que hace que los datos sean del tipo MCAR, por lo tanto, la eliminación no nos llevará a resultados sesgados. Analizemos cuantos NA hay:

# Calcular el número de NAs
na_counts <- sapply(Base[, c("Algorithm", "Framework", "Precision", "Training_Time", 
                             "Problem_Type", "Dataset_Type", "Accuracy", 
                             "Recall", "F1_Score", "Date")], 
                    function(x) sum(is.na(x)))

# Usar una única llamada a cat() para mostrar el texto y resultados en un solo bloque
cat("Cantidad de NA o entradas vacías por columna:\n\n", 
    paste(names(na_counts), na_counts, sep = ": ", collapse = "\n"))
## Cantidad de NA o entradas vacías por columna:
## 
##  Algorithm: 0
## Framework: 0
## Precision: 19
## Training_Time: 20
## Problem_Type: 0
## Dataset_Type: 0
## Accuracy: 39
## Recall: 20
## F1_Score: 20
## Date: 0
# Crear un dataframe con la información de las variables
variables_df <- data.frame(
  Variable = c("Algorithm", "Framework", "Problem_Type", "Dataset_Type", 
               "Accuracy", "Precision", "Recall", "F1_Score", 
               "Training_Time", "Date"),
  Cantidad = c("0", "0", "0", "0", 
                 "39", "19", 
                 "20", "20", 
                 "20", "0"),
  Porcentaje = c("0", "0", "0", "0", 
                 round(((39 * 100) / 560), 3), round(((19 * 100) / 560), 3), 
                 round(((20 * 100) / 560), 3), round(((20 * 100) / 560), 3), 
                 round(((20 * 100) / 560), 3), "0")
)

# Quitar los guiones bajos de los nombres de las columnas si fuera necesario
names(variables_df) <- gsub("_", " ", names(variables_df))

# Mostrar la tabla con kable y aplicar estilos
kable(variables_df, caption = "Tabla 2: Cantidad de NA por Variable", align = "c") %>%  # Alinear columnas al centro
  kable_styling(full_width = TRUE, bootstrap_options = c("striped", "hover", "condensed", "responsive"), font_size = 15, fixed_thead = TRUE) %>%
  column_spec(1, bold = TRUE, width = "10em") %>%   # Resaltar la columna de Variable
  column_spec(2, width = "10em") %>%                # Ajustar ancho de Naturaleza
  column_spec(3, width = "10em") %>%                # Ajustar ancho de Nivel de Medición
  row_spec(0, bold = TRUE, background = "#ccc", color = "black")   # Resaltar la primera fila (encabezado)
Tabla 2: Cantidad de NA por Variable
Variable Cantidad Porcentaje
Algorithm 0 0
Framework 0 0
Problem_Type 0 0
Dataset_Type 0 0
Accuracy 39 6.964
Precision 19 3.393
Recall 20 3.571
F1_Score 20 3.571
Training_Time 20 3.571
Date 0 0

La variable porcentaje es basada en el total de filas que hay, en otras palabras, representa la cantidad de NA, por ejemplo, los 39 datos faltantes representa cierto porcentaje del 560.

Sabiendo lo anterior, apliquemos la función na.omit para eliminar los NA de la base de datos.

Base_omit <- na.omit(Base)

head(Base_omit)
## # A tibble: 6 × 10
##   Algorithm      Framework   Problem_Type Dataset_Type Accuracy Precision Recall
##   <chr>          <chr>       <chr>        <chr>           <dbl>     <dbl>  <dbl>
## 1 Neural Network Keras       Clustering   Image           0.885     0.595  0.969
## 2 SVM            Keras       Clustering   Text            0.842     0.842  0.875
## 3 SVM            Scikit-lea… Regression   Tabular         0.723     0.686  0.301
## 4 K-Means        PyTorch     Regression   Image           0.637     0.626  7.45 
## 5 Neural Network Scikit-lea… Regression   Image           0.713     0.676  0.480
## 6 SVM            PyTorch     Regression   Image           0.897     9.73   0.781
## # ℹ 3 more variables: F1_Score <dbl>, Training_Time <dbl>, Date <dttm>

A continuación, verifiquemos la distribución de cada variable númerica para ver si hay un cambio o no.

# Cargar las librerías necesarias
library(ggplot2)
library(gridExtra)

# Crear una función para generar los histogramas
crear_histogramas <- function(data_original, data_sin_na, variable, nombre_variable) {
  # Histograma de datos originales
  ggp1 <- ggplot(data_original, aes_string(x = variable)) +
    geom_histogram(aes(y = ..density..), bins = 30, fill = "#FFB3B3", color = "#FF6666", alpha = 0.8) +
    geom_density(color = "#FF3333", size = 1) +
    ggtitle(paste("Distribución original de", nombre_variable)) +
    xlab(nombre_variable) + ylab("Densidad") +
    theme_light() +
    theme(plot.title = element_text(size = 14, hjust = 0.5, margin = margin(b = 10))) +
    xlim(0, 2)  # Ajusta este límite según tus datos
  
  # Histograma de datos sin NA
  ggp2 <- ggplot(data_sin_na, aes_string(x = variable)) +
    geom_histogram(aes(y = ..density..), bins = 30, fill = "#B3D9FF", color = "#66B2FF", alpha = 0.8) +
    geom_density(color = "#3399FF", size = 1) +
    ggtitle(paste("Distribución sin NA de", nombre_variable)) +
    xlab(nombre_variable) + ylab("Densidad") +
    theme_light() +
    theme(plot.title = element_text(size = 14, hjust = 0.5, margin = margin(b = 10))) +
    xlim(0, 2)  # Ajusta este límite según tus datos
  
  # Mostrar ambos gráficos uno al lado del otro
  grid.arrange(ggp1, ggp2, ncol = 2)
}

# Eliminar los NA una vez y guardar en una variable
Base_omit <- na.omit(Base)

# Lista de variables cuantitativas y sus nombres legibles
variables <- list(
  Accuracy = "Accuracy",
  Precision = "Precision",
  Recall = "Recall",
  F1_Score = "F1 Score",
  Training_Time = "Training Time"
)

# Iterar sobre las variables y generar los histogramas
for (var in names(variables)) {
  crear_histogramas(Base, Base_omit, var, variables[[var]])
  cat("\n\n\n\n\n\n\n\n")
}

Conclusión

A partir de los histogramas, se puede observar que las distribuciones de las variables Accuracy, Precision, Recall, F1 Score, Training Time permacenen prácticamente inalteradas antes y después de la eliminación de los valores faltantes (NA). Lo que nos indica que la eliminación de los datos no afectó significativamente la estructura de los datos, por ende, nos permite trabajar con una base de datos más limpia y sin sesgos ni problemas derivados de los valores faltantes. Con la base de datos sin NA, podemos avanzar hacia los siguientes pasos de este análisis con mayor confianza en la viabilidad de los datos.

missmap(Base_omit, col = c("red", "blue"), legend = TRUE)

Después de la eliminación ya no queda ningún valor faltante en la base de datos.

Tratamiento de Datos Atípicos

Para comenzar, imputaremos según sea necesario las entradas de la base de datos que contengan datos atípicos. Esto garantizará que solo se consideren registros completos y relevantes para el análisis, evitando cualquier sesgo o distorsión en los resultados.

Histogramas y Caja de Bigotes

Comenzaremos graficando los histogramas y caja de bigotes para verificar la presencia de datos atípicos:

# Librería necesaria
library(ggplot2)
library(patchwork)

# Crear gráficos
graf1 <- ggplot(data.frame(value = Base_omit$Accuracy), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$Accuracy), sd = sd(Base_omit$Accuracy)), color = "red", size = 1) +
  ggtitle("Histograma Acurracy") +
  xlab("Acurracy") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))

box1 <- ggplot(data.frame(value = Base_omit$Accuracy), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de Accuracy") +
  ylab("Accuracy") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))

# Repetimos para Precision, Recall, F1_Score y Training_Time (mismo formato)
graf2 <- ggplot(data.frame(value = Base_omit$Precision), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$Precision), sd = sd(Base_omit$Precision)), color = "red", size = 1) +
  ggtitle("Histograma Precision") +
  xlab("Precision") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))

box2 <- ggplot(data.frame(value = Base_omit$Precision), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de Precision") +
  ylab("Precision") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))

graf3 <- ggplot(data.frame(value = Base_omit$Recall), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$Recall), sd = sd(Base_omit$Recall)), color = "red", size = 1) +
  ggtitle("Histograma Recall") +
  xlab("Recall") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))

box3 <- ggplot(data.frame(value = Base_omit$Recall), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de Recall") +
  ylab("Recall") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))

graf4 <- ggplot(data.frame(value = Base_omit$F1_Score), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$F1_Score), sd = sd(Base_omit$F1_Score)), color = "red", size = 1) +
  ggtitle("Histograma F1 Score") +
  xlab("F1 Score") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))

box4 <- ggplot(data.frame(value = Base_omit$F1_Score), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de F1 Score") +
  ylab("F1 Score") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))

graf5 <- ggplot(data.frame(value = Base_omit$Training_Time), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$Training_Time), sd = sd(Base_omit$Training_Time)), color = "red", size = 1) +
  ggtitle("Histograma Training Time") +
  xlab("Training Time") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))

box5 <- ggplot(data.frame(value = Base_omit$Training_Time), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de Training Time") +
  ylab("Training Time") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))


(graf1 + box1)

Se pueden ver a simple vista 6 valores atípicos por encima del tercer cuartil. Estos outliers podrían influir de manera significativa en la media y otros estadísticos. Además, la distribución de Accuracy presenta un sesgo hacia la izquierda, con la mayoría de los valores concentrados cerca de 0.

(graf2 + box2)

Se observan varios valores atípicos por encima del tercer cuartil (aproximadamente 7) en el boxplot de Precision. Estos outliers podrían estar afectando la media y otros estadísticos importantes, lo que será considerado en los análisis posteriores. La distribución de Precision está claramente sesgada hacia la izquierda, con la mayoría de los valores concentrados cerca de 0.

(graf3 + box3)

A simple vista se observan 7 valores atípicos por encima del tercer cuartil. La distribución está visiblemente sesgada hacia la izquierda, con la mayoría de los valores concentrados cerca de 0.

(graf4 + box4)

Se observan 6 valores atípicos por encima del tercer cuartil. Al igual que los otros estos outliers podrían estar afectando la media y otros estadísticos importantes. La distribución está sesgada hacia la izquierda, con la mayoría de los valores concentrados cerca de 0.

(graf5 + box5)

Se ven 6 valores atípicos por encima del tercer cuartil. También presenta un sesgo hacia la izquierda y como los otros la mayoría de sus valores concentrados cerca de 0.

Pruebas

Gracias a los histogramas y al diagrama de caja de bigotes nos dimos cuenta de dos cosas: Ninguna de las variables númericas sigue una distribución normal, y existen a simple vista valores atípicos.Con todo esto, haremos una prueba para comprobar la presencia de estos valores. Descartamos la prueba de Grubbs; porque nuestros datos no siguen una distribución normal; la prueba de Dixon; ya que la muestra es mayor a 25. Por lo que, proseguiremos con la prueba de Rosner:

test <- rosnerTest(Base_omit$Accuracy, k = 10)

test$all.stats
##    i    Mean.i      SD.i     Value Obs.Num     R.i+1 lambda.i+1 Outlier
## 1  0 0.8457621 0.8216572 9.7180796      15 10.798076   3.833870    TRUE
## 2  1 0.8259135 0.7069241 8.2944274     196 10.564803   3.833271    TRUE
## 3  2 0.8091679 0.6125670 7.9008618     232 11.577010   3.832670    TRUE
## 4  3 0.7932315 0.5124044 7.1274667     110 12.361788   3.832068    TRUE
## 5  4 0.7789652 0.4151830 5.9788899     239 12.524415   3.831464    TRUE
## 6  5 0.7672273 0.3338475 5.2598564      77 13.457130   3.830859    TRUE
## 7  6 0.7570629 0.2565839 5.2005460     112 17.317859   3.830252    TRUE
## 8  7 0.7469870 0.1449455 0.9997069     200  1.743551   3.829643   FALSE
## 9  8 0.7464126 0.1446072 0.9983484     422  1.742208   3.829033   FALSE
## 10 9 0.7458388 0.1442696 0.9981670      99  1.749004   3.828422   FALSE

Basándonos en la prueba de Rosner, identificamos 7 valores atípicos en las observaciones: 15, 196, 232, 110, 239, 77 y 112. Estos valores, al estar por encima de 1 en una métrica que debe estar dentro del rango 0-1, no son correctos y afectan negativamente las medidas de tendencia central como la media.

test <- rosnerTest(Base_omit$Precision, k = 10)

test$all.stats
##    i    Mean.i      SD.i    Value Obs.Num    R.i+1 lambda.i+1 Outlier
## 1  0 0.8386718 0.9308086 9.732008       6  9.55442   3.833870    TRUE
## 2  1 0.8187762 0.8310328 9.674189     223 10.65591   3.833271    TRUE
## 3  2 0.7989210 0.7180191 8.932619     250 11.32797   3.832670    TRUE
## 4  3 0.7806431 0.6061150 7.044472     439 10.33439   3.832068    TRUE
## 5  4 0.7665353 0.5286183 6.207645     111 10.29308   3.831464    TRUE
## 6  5 0.7542529 0.4614512 5.760933     433 10.84986   3.830859    TRUE
## 7  6 0.7429256 0.3955383 5.432777     241 11.85688   3.830252    TRUE
## 8  7 0.7322910 0.3266570 4.145151     157 10.44784   3.829643    TRUE
## 9  8 0.7245345 0.2834703 4.075645     288 11.82174   3.829033    TRUE
## 10 9 0.7169010 0.2341822 4.055990      96 14.25851   3.828422    TRUE

Basándonos en la prueba de Rosner, identificamos 10 valores atípicos, lo cual difiere de los 7 valores atípicos previamente observados en el boxplot. Esta diferencia puede deberse a la mayor sensibilidad de la prueba de Rosner para detectar outliers. Los 10 valores identificados, al estar por encima de 1 en una métrica que debe estar entre 0 y 1, no son válidos.

test <- rosnerTest(Base_omit$Recall, k = 10)

test$all.stats
##    i    Mean.i      SD.i     Value Obs.Num     R.i+1 lambda.i+1 Outlier
## 1  0 0.7610971 0.8314054 9.3661823     114 10.350047   3.833870    TRUE
## 2  1 0.7418463 0.7255257 7.7377491     157  9.642529   3.833271    TRUE
## 3  2 0.7261605 0.6460189 7.4548096       4 10.415561   3.832670    TRUE
## 4  3 0.7110399 0.5622109 5.7659164     270  8.991068   3.832068    TRUE
## 5  4 0.6996551 0.5089064 5.7263733      88  9.877490   3.831464    TRUE
## 6  5 0.6883081 0.4497505 5.4998481     420 10.698244   3.830859    TRUE
## 7  6 0.6774222 0.3874519 5.4366692     308 12.283452   3.830252    TRUE
## 8  7 0.6666303 0.3144283 4.8590798     303 13.333562   3.829643    TRUE
## 9  8 0.6571020 0.2428199 3.4388274     221 11.455922   3.829033    TRUE
## 10 9 0.6507655 0.2034434 0.3000943     218  1.723680   3.828422   FALSE

Basándonos en la prueba de Rosner, identificamos 9 valores atípicos, lo cual difiere de los 7 valores atípicos previamente observados en el boxplot. Esta diferencia puede deberse a la mayor sensibilidad de la prueba de Rosner para detectar outliers. Los 9 valores identificados, al estar por encima de 1 en una métrica que debe estar entre 0 y 1, no son válidos.

test <- rosnerTest(Base_omit$F1_Score, k = 10)

test$all.stats
##    i    Mean.i      SD.i     Value Obs.Num     R.i+1 lambda.i+1 Outlier
## 1  0 0.8013885 0.8749189 9.3740487     296  9.798234   3.833870    TRUE
## 2  1 0.7822103 0.7759213 9.2953593     316 10.971665   3.833271    TRUE
## 3  2 0.7631225 0.6634602 8.1785788     281 11.176942   3.832670    TRUE
## 4  3 0.7464585 0.5630661 7.7476843     437 12.434110   3.832068    TRUE
## 5  4 0.7306900 0.4548205 5.4997417     230 10.485569   3.831464    TRUE
## 6  5 0.7199247 0.3946604 5.3206680     333 11.657473   3.830859    TRUE
## 7  6 0.7095157 0.3286397 5.1312436     160 13.454635   3.830252    TRUE
## 8  7 0.6994892 0.2524146 4.6320729     267 15.579855   3.829643    TRUE
## 9  8 0.6905515 0.1689673 0.9993356     418  1.827479   3.829033   FALSE
## 10 9 0.6898481 0.1685139 0.9985770     422  1.832068   3.828422   FALSE

Basándonos en la prueba de Rosner, identificamos 8 valores atípicos, lo cual difiere de los 6 valores atípicos previamente observados en el boxplot. Esta diferencia puede deberse a la mayor sensibilidad de la prueba de Rosner para detectar outliers. Los 8 valores identificados, al estar por encima de 1 en una métrica que debe estar entre 0 y 1, no son válidos.

test <- rosnerTest(Base_omit$Training_Time, k = 10)

test$all.stats
##    i   Mean.i     SD.i     Value Obs.Num     R.i+1 lambda.i+1 Outlier
## 1  0 3.059781 4.646419 46.985626     324  9.453699   3.833870    TRUE
## 2  1 2.961513 4.159537 46.838741     217 10.548585   3.833271    TRUE
## 3  2 2.863133 3.606191 44.586446     100 11.569913   3.832670    TRUE
## 4  3 2.769373 3.017332 44.357901     201 13.783214   3.832068    TRUE
## 5  4 2.675705 2.282925 28.294985     344 11.222129   3.831464    TRUE
## 6  5 2.617874 1.932676 20.933435     109  9.476788   3.830859    TRUE
## 7  6 2.576436 1.726646 20.251860     417 10.236856   3.830252    TRUE
## 8  7 2.536356 1.508782 13.791378     214  7.459672   3.829643    TRUE
## 9  8 2.510776 1.411524  4.997833     156  1.761966   3.829033   FALSE
## 10 9 2.505111 1.408117  4.986466      43  1.762179   3.828422   FALSE

Basándonos en la prueba de Rosner, identificamos 8 valores atípicos, lo cual difiere de los 6 valores atípicos previamente observados en el boxplot. Esta diferencia puede deberse a la mayor sensibilidad de la prueba de Rosner para detectar outliers. Los 8 valores identificados, al estar por encima de 2.5 (Mediana) en una métrica que esta alrededor de 2-4, los hace menos válidos.

Con las pruebas ya listas, vamos a imputar los datos con la técnica imputación con la Mediana, ya que los datos no siguen una distribución normal.

# Calcular la mediana de los valores que están en el rango permitido (0 a 1)
mediana_accuracy <- median(Base_omit$Accuracy[Base_omit$Accuracy <= 1], na.rm = TRUE)

# Reemplazar los valores atípicos (mayores a 1) por la mediana
Base_omit$Accuracy[Base_omit$Accuracy > 1] <- mediana_accuracy



boxp1 <- ggplot(data.frame(value = Base_omit$Accuracy), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de Accuracy") +
  ylab("Accuracy") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))


mediana_2 <- median(Base_omit$Precision[Base_omit$Precision <= 1], na.rm = TRUE)

# Reemplazar los valores atípicos (mayores a 1) por la mediana
Base_omit$Precision[Base_omit$Precision > 1] <- mediana_2



boxp2 <- ggplot(data.frame(value = Base_omit$Precision), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de Precision") +
  ylab("Precision") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))



# Calcular la mediana de los valores que están en el rango permitido (0 a 1)
mediana_3 <- median(Base_omit$Recall[Base_omit$Recall <= 1], na.rm = TRUE)

# Reemplazar los valores atípicos (mayores a 1) por la mediana
Base_omit$Recall[Base_omit$Recall > 1] <- mediana_3



boxp3 <- ggplot(data.frame(value = Base_omit$Recall), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de Recall") +
  ylab("Recall") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))


# Calcular la mediana de los valores que están en el rango permitido (0 a 1)
mediana_4 <- median(Base_omit$F1_Score[Base_omit$F1_Score <= 1], na.rm = TRUE)

# Reemplazar los valores atípicos (mayores a 1) por la mediana
Base_omit$F1_Score[Base_omit$F1_Score > 1] <- mediana_4



boxp4 <- ggplot(data.frame(value = Base_omit$F1_Score), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de F1 Score") +
  ylab("F1 Score") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))

Base_omit$Training_Time[Base_omit$Training_Time > 10] <- median(Base_omit$Training_Time, na.rm = TRUE)



boxp5 <- ggplot(data.frame(value = Base_omit$Precision), aes(x = "", y = value)) +
  geom_boxplot(fill = "#99CCFF", color = "black", outlier.colour = "red", outlier.size = 3, width = 0.4) + 
  ggtitle("Boxplot de Training Time") +
  ylab("Training Time") + 
  theme_minimal() +
  theme(plot.title = element_text(hjust = 0.5, size = 14))

Veamos los boxplot ahora:

(boxp1 | boxp2 | boxp3) / (boxp4 | boxp5)

grafi1 <- ggplot(data.frame(value = Base_omit$Accuracy), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$Accuracy), sd = sd(Base_omit$Accuracy)), color = "red", size = 1) +
  ggtitle("Histograma Acurracy") +
  xlab("Acurracy") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))


# Repetimos para Precision, Recall, F1_Score y Training_Time (mismo formato)
grafi2 <- ggplot(data.frame(value = Base_omit$Precision), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$Precision), sd = sd(Base_omit$Precision)), color = "red", size = 1) +
  ggtitle("Histograma Precision") +
  xlab("Precision") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))


grafi3 <- ggplot(data.frame(value = Base_omit$Recall), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$Recall), sd = sd(Base_omit$Recall)), color = "red", size = 1) +
  ggtitle("Histograma Recall") +
  xlab("Recall") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))


grafi4 <- ggplot(data.frame(value = Base_omit$F1_Score), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$F1_Score), sd = sd(Base_omit$F1_Score)), color = "red", size = 1) +
  ggtitle("Histograma F1 Score") +
  xlab("F1 Score") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))


grafi5 <- ggplot(data.frame(value = Base_omit$Training_Time), aes(x = value)) +
  geom_histogram(aes(y = ..density..), bins = 30, fill = "#99CCFF", color = "black", alpha = 0.7) +
  stat_function(fun = dnorm, args = list(mean = mean(Base_omit$Training_Time), sd = sd(Base_omit$Training_Time)), color = "red", size = 1) +
  ggtitle("Histograma Training Time") +
  xlab("Training Time") + ylab("Densidad") +
  theme_minimal() +
  theme(plot.title = element_text(size = 14, hjust = 0.5))

Observemos los histogramas ahora:

(grafi1 | grafi2 | grafi3) / (grafi4 | grafi5)

Conclusión

Tras la imputación de los valores atípicos detectados, los boxplots confirman que ya no hay outliers en la base de datos. Esto nos da más certeza y confianza en la calidad de los datos, lo cual es crucial para la precisión de los análisis y resúmenes estadísticos.

Distribución de combinaciones de algoritmos y frameworks

El objetivo de esta sección es entender cómo se distribuyen las combinaciones de diferentes algoritmos y frameworks dentro del dataset. Esta información es crucial para identificar posibles sesgos en el conjunto de datos y para asegurar una representación equilibrada de las combinaciones al analizar el rendimiento.

  • Primero, creamos una cuadrícula (expand.grid) que contiene todas las combinaciones posibles de algoritmos y frameworks.

  • Luego, contamos cuántas veces aparece cada combinación en el conjunto de datos limpio (Base_clean) y almacenamos estos recuentos en combination_counts.

  • Calculamos el porcentaje de cada combinación para facilitar la interpretación.

  • Finalmente, visualizamos la distribución con un gráfico de torta utilizando plot_ly.

combinations <- expand.grid(
  Algorithm = c("Neural Network", "Random Forest", "SVM", "K-Means"),
  Framework = c("TensorFlow", "PyTorch", "Keras", "Scikit-learn"),
  stringsAsFactors = FALSE
)

combination_counts <- data.frame(Algorithm = character(), Framework = character(), Count = integer(), stringsAsFactors = FALSE)

for (i in 1:nrow(combinations)) {
  algo <- combinations$Algorithm[i]
  framework <- combinations$Framework[i]
  count <- nrow(Base_omit[Base_omit$Algorithm == algo & Base_omit$Framework == framework, ])
  combination_counts <- rbind(combination_counts, data.frame(Algorithm = algo, Framework = framework, Count = count))
}

combination_counts <- combination_counts %>%
  mutate(Percentage = Count / sum(Count) * 100)

plot <- plot_ly(combination_counts, 
                labels = ~paste(Algorithm, "y", Framework),
                values = ~Percentage, 
                type = 'pie',
                textinfo = 'none',
                insidetextorientation = 'radial') %>%
  layout(title = "Distribución de combinaciones de Algoritmos y Frameworks", showlegend = TRUE)

plot

El gráfico de torta muestra visualmente cómo se distribuyen las combinaciones de algoritmos y frameworks. Esto ayuda a identificar rápidamente cuáles son las combinaciones más y menos frecuentes en el conjunto de datos.

  • Este análisis preliminar sugiere que es necesario observar no solo el rendimiento de los algoritmos y frameworks, sino también cómo se distribuyen estos en el conjunto de datos, para proporcionar un contexto adecuado a los resultados obtenidos.

Resumen Estadístico

Filtros de información

1: Algoritmo con mayor eficacia en su tiempo de entrenamiento.

El objetivo de esta sección es identificar cuál de los algoritmos presenta la mejor relación entre la precisión obtenida y el tiempo de entrenamiento requerido. Este análisis es fundamental, ya que una alta precisión con un tiempo de entrenamiento razonable es ideal en aplicaciones prácticas de inteligencia artificial.

  • En este análisis, calculamos la media del tiempo de entrenamiento para cada algoritmo en el conjunto de datos limpio. Esto nos permite evaluar cómo se comporta cada algoritmo en términos de eficiencia de entrenamiento.

  • El uso de un bucle for permite iterar sobre cada algoritmo y calcular su tiempo medio, guardando también el conteo de entradas que contribuyen a esa media.

##        Algorithm Mean_Time Entries
## 1            SVM  2.293516     105
## 2  Random Forest  2.427681      95
## 3        K-Means  2.606262     135
## 4 Neural Network  2.666324     113

El dataframe time_means_algorithms muestra el tiempo promedio que toma entrenar cada algoritmo.

  • Los algoritmos Neural Network y Random Forest tienen tiempos medios de entrenamiento relativamente bajos. Esto sugiere que estos algoritmos pueden ser entrenados de manera eficiente en términos de tiempo, lo cual es un factor crítico cuando se trabaja con grandes volúmenes de datos o en entornos con recursos computacionales limitados.