1 Pregunta 01: Análisis exploratorio de datos de wine.csv

El objetivo de esta sección es desarrollar un análisis exploratorio de la base de datos wine.csv, con el fin de revisar la estructura de los datos, los estadísticos descriptivos y el comportamiento gráfico de cada variable.

1.1 Carga de datos

# El archivo wine.csv debe estar en la misma carpeta que este RMarkdown.
# Si el archivo mantiene el nombre original descargado, también se reconoce automáticamente.

if(file.exists("wine.csv")){
  wine <- read.csv("wine.csv")
} else if(file.exists("winequality-red (2).csv")){
  wine <- read.csv("winequality-red (2).csv")
} else if(file.exists("winequality-red.csv")){
  wine <- read.csv("winequality-red.csv")
} else {
  stop("No se encontró el archivo wine.csv. Coloque el CSV en la misma carpeta que este RMarkdown.")
}

str(wine)
## 'data.frame':    1599 obs. of  12 variables:
##  $ fixed.acidity       : num  7.4 7.8 7.8 11.2 7.4 7.4 7.9 7.3 7.8 7.5 ...
##  $ volatile.acidity    : num  0.7 0.88 0.76 0.28 0.7 0.66 0.6 0.65 0.58 0.5 ...
##  $ citric.acid         : num  0 0 0.04 0.56 0 0 0.06 0 0.02 0.36 ...
##  $ residual.sugar      : num  1.9 2.6 2.3 1.9 1.9 1.8 1.6 1.2 2 6.1 ...
##  $ chlorides           : num  0.076 0.098 0.092 0.075 0.076 0.075 0.069 0.065 0.073 0.071 ...
##  $ free.sulfur.dioxide : num  11 25 15 17 11 13 15 15 9 17 ...
##  $ total.sulfur.dioxide: num  34 67 54 60 34 40 59 21 18 102 ...
##  $ density             : num  0.998 0.997 0.997 0.998 0.998 ...
##  $ pH                  : num  3.51 3.2 3.26 3.16 3.51 3.51 3.3 3.39 3.36 3.35 ...
##  $ sulphates           : num  0.56 0.68 0.65 0.58 0.56 0.56 0.46 0.47 0.57 0.8 ...
##  $ alcohol             : num  9.4 9.8 9.8 9.8 9.4 9.4 9.4 10 9.5 10.5 ...
##  $ quality             : int  5 5 5 6 5 5 5 7 7 5 ...
dim(wine)
## [1] 1599   12
head(wine)
##   fixed.acidity volatile.acidity citric.acid residual.sugar chlorides
## 1           7.4             0.70        0.00            1.9     0.076
## 2           7.8             0.88        0.00            2.6     0.098
## 3           7.8             0.76        0.04            2.3     0.092
## 4          11.2             0.28        0.56            1.9     0.075
## 5           7.4             0.70        0.00            1.9     0.076
## 6           7.4             0.66        0.00            1.8     0.075
##   free.sulfur.dioxide total.sulfur.dioxide density   pH sulphates alcohol
## 1                  11                   34  0.9978 3.51      0.56     9.4
## 2                  25                   67  0.9968 3.20      0.68     9.8
## 3                  15                   54  0.9970 3.26      0.65     9.8
## 4                  17                   60  0.9980 3.16      0.58     9.8
## 5                  11                   34  0.9978 3.51      0.56     9.4
## 6                  13                   40  0.9978 3.51      0.56     9.4
##   quality
## 1       5
## 2       5
## 3       5
## 4       6
## 5       5
## 6       5

1.2 Revisión de valores perdidos

valores_perdidos <- data.frame(
  Variable = names(wine),
  Valores_Perdidos = colSums(is.na(wine))
)

kable(valores_perdidos, caption = "Cantidad de valores perdidos por variable")
Cantidad de valores perdidos por variable
Variable Valores_Perdidos
fixed.acidity fixed.acidity 0
volatile.acidity volatile.acidity 0
citric.acid citric.acid 0
residual.sugar residual.sugar 0
chlorides chlorides 0
free.sulfur.dioxide free.sulfur.dioxide 0
total.sulfur.dioxide total.sulfur.dioxide 0
density density 0
pH pH 0
sulphates sulphates 0
alcohol alcohol 0
quality quality 0

1.3 Resumen descriptivo general

resumen <- data.frame(
  Variable = names(wine),
  Media = sapply(wine, mean, na.rm = TRUE),
  Mediana = sapply(wine, median, na.rm = TRUE),
  Desviacion_Estandar = sapply(wine, sd, na.rm = TRUE),
  Minimo = sapply(wine, min, na.rm = TRUE),
  Maximo = sapply(wine, max, na.rm = TRUE)
)

# Se redondean solo las columnas numéricas para evitar errores con la columna de texto "Variable".
resumen <- resumen %>%
  mutate(across(where(is.numeric), ~ round(.x, 4)))

kable(resumen, caption = "Resumen descriptivo de las variables")
Resumen descriptivo de las variables
Variable Media Mediana Desviacion_Estandar Minimo Maximo
fixed.acidity fixed.acidity 8.3196 7.9000 1.7411 4.6000 15.9000
volatile.acidity volatile.acidity 0.5278 0.5200 0.1791 0.1200 1.5800
citric.acid citric.acid 0.2710 0.2600 0.1948 0.0000 1.0000
residual.sugar residual.sugar 2.5388 2.2000 1.4099 0.9000 15.5000
chlorides chlorides 0.0875 0.0790 0.0471 0.0120 0.6110
free.sulfur.dioxide free.sulfur.dioxide 15.8749 14.0000 10.4602 1.0000 72.0000
total.sulfur.dioxide total.sulfur.dioxide 46.4678 38.0000 32.8953 6.0000 289.0000
density density 0.9967 0.9968 0.0019 0.9901 1.0037
pH pH 3.3111 3.3100 0.1544 2.7400 4.0100
sulphates sulphates 0.6581 0.6200 0.1695 0.3300 2.0000
alcohol alcohol 10.4230 10.2000 1.0657 8.4000 14.9000
quality quality 5.6360 6.0000 0.8076 3.0000 8.0000

1.4 Función para mostrar el comportamiento de cada variable

analizar_variable <- function(data, variable){

  x <- data[[variable]]

  cat("\nVariable:", variable, "\n")
  cat("Media:", round(mean(x, na.rm = TRUE), 4), "\n")
  cat("Mediana:", round(median(x, na.rm = TRUE), 4), "\n")
  cat("Desviación estándar:", round(sd(x, na.rm = TRUE), 4), "\n")
  cat("Mínimo:", round(min(x, na.rm = TRUE), 4), "\n")
  cat("Máximo:", round(max(x, na.rm = TRUE), 4), "\n")

  p1 <- ggplot(data, aes(x = .data[[variable]])) +
    geom_histogram(bins = 30, fill = "deeppink", color = "white") +
    labs(
      title = paste("Histograma de", variable),
      x = variable,
      y = "Frecuencia"
    ) +
    theme_minimal()

  p2 <- ggplot(data, aes(y = .data[[variable]])) +
    geom_boxplot(fill = "orange") +
    labs(
      title = paste("Boxplot de", variable),
      y = variable
    ) +
    theme_minimal()

  print(p1)
  print(p2)
}

1.5 Aplicación de la función a todas las variables

for(v in names(wine)){
  analizar_variable(wine, v)
}
## 
## Variable: fixed.acidity 
## Media: 8.3196 
## Mediana: 7.9 
## Desviación estándar: 1.7411 
## Mínimo: 4.6 
## Máximo: 15.9

## 
## Variable: volatile.acidity 
## Media: 0.5278 
## Mediana: 0.52 
## Desviación estándar: 0.1791 
## Mínimo: 0.12 
## Máximo: 1.58

## 
## Variable: citric.acid 
## Media: 0.271 
## Mediana: 0.26 
## Desviación estándar: 0.1948 
## Mínimo: 0 
## Máximo: 1

## 
## Variable: residual.sugar 
## Media: 2.5388 
## Mediana: 2.2 
## Desviación estándar: 1.4099 
## Mínimo: 0.9 
## Máximo: 15.5

## 
## Variable: chlorides 
## Media: 0.0875 
## Mediana: 0.079 
## Desviación estándar: 0.0471 
## Mínimo: 0.012 
## Máximo: 0.611

## 
## Variable: free.sulfur.dioxide 
## Media: 15.8749 
## Mediana: 14 
## Desviación estándar: 10.4602 
## Mínimo: 1 
## Máximo: 72

## 
## Variable: total.sulfur.dioxide 
## Media: 46.4678 
## Mediana: 38 
## Desviación estándar: 32.8953 
## Mínimo: 6 
## Máximo: 289

## 
## Variable: density 
## Media: 0.9967 
## Mediana: 0.9968 
## Desviación estándar: 0.0019 
## Mínimo: 0.9901 
## Máximo: 1.0037

## 
## Variable: pH 
## Media: 3.3111 
## Mediana: 3.31 
## Desviación estándar: 0.1544 
## Mínimo: 2.74 
## Máximo: 4.01

## 
## Variable: sulphates 
## Media: 0.6581 
## Mediana: 0.62 
## Desviación estándar: 0.1695 
## Mínimo: 0.33 
## Máximo: 2

## 
## Variable: alcohol 
## Media: 10.423 
## Mediana: 10.2 
## Desviación estándar: 1.0657 
## Mínimo: 8.4 
## Máximo: 14.9

## 
## Variable: quality 
## Media: 5.636 
## Mediana: 6 
## Desviación estándar: 0.8076 
## Mínimo: 3 
## Máximo: 8

1.6 Distribución de la variable quality

tabla_quality <- data.frame(table(wine$quality))
names(tabla_quality) <- c("Calidad", "Frecuencia")

kable(tabla_quality, caption = "Distribución de la calidad del vino")
Distribución de la calidad del vino
Calidad Frecuencia
3 10
4 53
5 681
6 638
7 199
8 18
ggplot(wine, aes(x = factor(quality))) +
  geom_bar(fill = "darkgreen") +
  labs(
    title = "Distribución de la calidad del vino",
    x = "Calidad",
    y = "Frecuencia"
  ) +
  theme_minimal()

1.7 Correlación de las variables con quality

correlaciones <- cor(wine)

cor_quality <- data.frame(
  Variable = names(correlaciones[, "quality"]),
  Correlacion_con_quality = as.numeric(correlaciones[, "quality"])
) %>%
  arrange(desc(Correlacion_con_quality)) %>%
  mutate(across(where(is.numeric), ~ round(.x, 4)))

kable(cor_quality, caption = "Correlación de cada variable con quality")
Correlación de cada variable con quality
Variable Correlacion_con_quality
quality 1.0000
alcohol 0.4762
sulphates 0.2514
citric.acid 0.2264
fixed.acidity 0.1241
residual.sugar 0.0137
free.sulfur.dioxide -0.0507
pH -0.0577
chlorides -0.1289
density -0.1749
total.sulfur.dioxide -0.1851
volatile.acidity -0.3906

1.8 Relación gráfica de cada variable con quality

wine %>%
  pivot_longer(cols = -quality, names_to = "Variable", values_to = "Valor") %>%
  ggplot(aes(x = Valor, y = quality)) +
  geom_point(alpha = 0.3) +
  facet_wrap(~ Variable, scales = "free") +
  labs(
    title = "Relación de las variables explicativas con la calidad del vino",
    x = "Valor de la variable",
    y = "Calidad"
  ) +
  theme_minimal()

1.9 Conclusión

El conjunto de datos contiene 1599 observaciones y 12 variables numéricas. No se identifican valores perdidos en la base de datos. La variable quality se concentra principalmente en valores medios, especialmente en las calificaciones 5 y 6.

A partir del análisis de correlaciones, se observa que la variable con mayor relación positiva con la calidad del vino es alcohol, mientras que volatile acidity presenta una relación negativa importante. También se identifican variables con posible presencia de valores atípicos, como residual sugar, chlorides, sulphates y total sulfur dioxide, lo cual puede observarse en los boxplots generados por la función de análisis exploratorio.

2 Pregunta 02: Comparación de propuestas de mejora

El objetivo de esta sección es comparar el modelo de mantenimiento actual con tres propuestas de mejora: árbol de clasificación, redes neuronales y métodos de regresión. Como el objetivo es reducir el tiempo de fallas, el método con menor promedio y diferencia estadísticamente significativa será considerado como la mejor alternativa.

2.1 Ingreso de datos

arbol <- c(23.81, 22.13, 22.64, 21.69, 23.58, 22.14, 18.73, 21.59,
           20.36, 20.53, 20.11, 20.34, 19.19, 22.92, 18.65, 20.60,
           19.83, 20.09, 19.43, 22.06, 21.15, 19.26, 18.08, 20.24,
           18.75, 20.69, 21.62, 23.69, 23.93, 23.19)

redn <- c(23.24, 20.08, 18.01, 23.28, 19.23, 21.22, 21.47, 20.60,
          21.11, 21.27, 21.03, 17.34, 22.80, 21.85, 17.85, 23.15,
          19.57, 19.56, 20.79, 18.04, 20.95, 21.83, 18.17, 22.66,
          18.29, 18.89, 19.49, 19.19, 26.47, 25.25)

regresion <- c(16.13, 17.84, 18.28, 15.61, 17.62, 16.12, 17.29, 16.13,
               16.64, 15.03, 18.16, 16.82, 17.44, 16.76, 17.26, 15.55,
               17.49, 18.42, 17.54, 17.13, 15.50, 16.80, 18.47, 18.42,
               18.43, 15.56, 16.03, 15.39, 15.12, 17.77)

actual <- c(17.09, 15.77, 18.45, 16.55, 22.23, 22.11, 18.26, 18.04,
            19.66, 19.76, 18.74, 19.02, 18.54, 16.70, 17.57, 19.89,
            19.06, 18.70, 19.39, 19.68, 19.20, 16.85, 19.91, 19.82,
            18.08, 19.38, 20.30, 21.60, 23.39, 19.33)

data2 <- data.frame(
  Replica = factor(1:30),
  Arbol_Clasificacion = arbol,
  Redes_Neuronales = redn,
  Metodos_Regresion = regresion,
  Sistema_Actual = actual
)

head(data2)
##   Replica Arbol_Clasificacion Redes_Neuronales Metodos_Regresion Sistema_Actual
## 1       1               23.81            23.24             16.13          17.09
## 2       2               22.13            20.08             17.84          15.77
## 3       3               22.64            18.01             18.28          18.45
## 4       4               21.69            23.28             15.61          16.55
## 5       5               23.58            19.23             17.62          22.23
## 6       6               22.14            21.22             16.12          22.11

2.2 Transformación de datos a formato largo

datos_long <- data2 %>%
  pivot_longer(
    cols = c(Arbol_Clasificacion, Redes_Neuronales, Metodos_Regresion, Sistema_Actual),
    names_to = "Metodo",
    values_to = "Tiempo"
  )

head(datos_long)
## # A tibble: 6 × 3
##   Replica Metodo              Tiempo
##   <fct>   <chr>                <dbl>
## 1 1       Arbol_Clasificacion   23.8
## 2 1       Redes_Neuronales      23.2
## 3 1       Metodos_Regresion     16.1
## 4 1       Sistema_Actual        17.1
## 5 2       Arbol_Clasificacion   22.1
## 6 2       Redes_Neuronales      20.1

2.3 Estadísticos descriptivos por método

resumen_metodos <- datos_long %>%
  group_by(Metodo) %>%
  summarise(
    Promedio = mean(Tiempo),
    Mediana = median(Tiempo),
    Desviacion = sd(Tiempo),
    Minimo = min(Tiempo),
    Maximo = max(Tiempo),
    .groups = "drop"
  ) %>%
  arrange(Promedio) %>%
  mutate(across(where(is.numeric), ~ round(.x, 4)))

kable(resumen_metodos, caption = "Estadísticos descriptivos por método")
Estadísticos descriptivos por método
Metodo Promedio Mediana Desviacion Minimo Maximo
Metodos_Regresion 16.8917 16.975 1.1033 15.03 18.47
Sistema_Actual 19.1023 19.130 1.7296 15.77 23.39
Redes_Neuronales 20.7560 20.870 2.2243 17.34 26.47
Arbol_Clasificacion 21.0340 20.645 1.7056 18.08 23.93

2.4 Gráfico comparativo

ggplot(datos_long, aes(x = Metodo, y = Tiempo)) +
  geom_boxplot(fill = "lightblue") +
  labs(
    title = "Comparación de tiempos de falla por método",
    x = "Método",
    y = "Tiempo de fallas"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 25, hjust = 1))

2.5 Planteamiento de hipótesis

Hipótesis nula:

H0: No existen diferencias significativas entre los tiempos promedio de fallas de los métodos evaluados.

Hipótesis alternativa:

H1: Al menos uno de los métodos presenta un tiempo promedio de fallas diferente.

Nivel de significancia:

α = 0.05

2.6 ANOVA con bloques

Como se tienen 30 réplicas para cada método, se considera la réplica como un bloque. El análisis permite comparar los métodos controlando el efecto de cada réplica.

modelo_anova <- aov(Tiempo ~ Metodo + Replica, data = datos_long)
summary(modelo_anova)
##             Df Sum Sq Mean Sq F value  Pr(>F)    
## Metodo       3  326.4  108.81  36.867 1.8e-15 ***
## Replica     29   93.1    3.21   1.088   0.371    
## Residuals   87  256.8    2.95                    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

2.7 Comparaciones múltiples

comparaciones <- pairwise.t.test(
  datos_long$Tiempo,
  datos_long$Metodo,
  paired = TRUE,
  p.adjust.method = "bonferroni"
)

comparaciones
## 
##  Pairwise comparisons using paired t tests 
## 
## data:  datos_long$Tiempo and datos_long$Metodo 
## 
##                   Arbol_Clasificacion Metodos_Regresion Redes_Neuronales
## Metodos_Regresion 1.0e-09             -                 -               
## Redes_Neuronales  1.0000              1.3e-07           -               
## Sistema_Actual    0.0001              6.4e-05           0.0130          
## 
## P value adjustment method: bonferroni

2.8 Prueba no paramétrica de Friedman

La prueba de Friedman se utiliza como complemento cuando se comparan varios tratamientos relacionados o bloqueados.

friedman.test(as.matrix(data2[, -1]))
## 
##  Friedman rank sum test
## 
## data:  as.matrix(data2[, -1])
## Friedman chi-squared = 53.28, df = 3, p-value = 1.598e-11

2.9 Comparación directa entre métodos de regresión y sistema actual

t.test(regresion, actual, paired = TRUE, alternative = "less")
## 
##  Paired t-test
## 
## data:  regresion and actual
## t = -5.3138, df = 29, p-value = 5.303e-06
## alternative hypothesis: true mean difference is less than 0
## 95 percent confidence interval:
##       -Inf -1.503788
## sample estimates:
## mean difference 
##       -2.210667

2.10 Conclusión

A partir de los estadísticos descriptivos, el método con menor tiempo promedio es Metodos_Regresion, con una media aproximada de 16.89. Este promedio es menor que el del sistema actual, cuya media es aproximadamente 19.10. Por otro lado, los métodos de árbol de clasificación y redes neuronales presentan promedios superiores al sistema actual, por lo que no representan una mejora en términos de reducción del tiempo de fallas.

El ANOVA con bloques permite evaluar si existen diferencias significativas entre los métodos, asi que si el valor p asociado al método es menor que 0.05, se rechaza la hipótesis nula y se concluye que existen diferencias significativas entre los métodos evaluados. Además, la comparación directa entre métodos de regresión y el sistema actual permite sustentar que la propuesta basada en métodos de regresión reduce significativamente el tiempo de fallas.

Se concluye que con un nivel de significancia del 5%, la propuesta que debería implementarse es Métodos de Regresión, debido a que presenta el menor tiempo promedio de fallas y mejora el desempeño respecto al sistema actual.