Introducción

El análisis exploratorio de datos permite conocer la estructura, calidad y comportamiento de una base antes de realizar modelos estadísticos. En este reporte se estudian las características fisicoquímicas de una muestra de vinos tintos y su relación con la calificación de calidad.

Los objetivos son:

Importación de la base

BD <- read.csv(
  "winequality-red.csv",
  header = TRUE,
  sep = ","
)

str(BD)
## '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 ...

Estructura de los datos

dim(BD)
## [1] 1599   12

La base contiene 1599 observaciones y 12 variables.

knitr::kable(
  head(BD),
  caption = "Primeras seis observaciones de la base"
)
Primeras seis observaciones de la base
fixed.acidity volatile.acidity citric.acid residual.sugar chlorides free.sulfur.dioxide total.sulfur.dioxide density pH sulphates alcohol quality
7.4 0.70 0.00 1.9 0.076 11 34 0.9978 3.51 0.56 9.4 5
7.8 0.88 0.00 2.6 0.098 25 67 0.9968 3.20 0.68 9.8 5
7.8 0.76 0.04 2.3 0.092 15 54 0.9970 3.26 0.65 9.8 5
11.2 0.28 0.56 1.9 0.075 17 60 0.9980 3.16 0.58 9.8 6
7.4 0.70 0.00 1.9 0.076 11 34 0.9978 3.51 0.56 9.4 5
7.4 0.66 0.00 1.8 0.075 13 40 0.9978 3.51 0.56 9.4 5

Calidad de los datos

Valores faltantes

faltantes <- data.frame(
  Variable = names(BD),
  Valores_faltantes = colSums(is.na(BD))
)

knitr::kable(
  faltantes,
  caption = "Valores faltantes por variable"
)
Valores faltantes por variable
Variable Valores_faltantes
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

La base presenta un total de 0 valores faltantes.

Registros duplicados

numero_duplicados <- sum(duplicated(BD))
numero_duplicados
## [1] 240

Se identificaron 240 registros duplicados. Estos registros se reportan como parte del control de calidad, pero no se eliminan automáticamente porque podrían representar observaciones reales con características idénticas.

Estadísticos descriptivos

summary(BD)
##  fixed.acidity   volatile.acidity  citric.acid    residual.sugar  
##  Min.   : 4.60   Min.   :0.1200   Min.   :0.000   Min.   : 0.900  
##  1st Qu.: 7.10   1st Qu.:0.3900   1st Qu.:0.090   1st Qu.: 1.900  
##  Median : 7.90   Median :0.5200   Median :0.260   Median : 2.200  
##  Mean   : 8.32   Mean   :0.5278   Mean   :0.271   Mean   : 2.539  
##  3rd Qu.: 9.20   3rd Qu.:0.6400   3rd Qu.:0.420   3rd Qu.: 2.600  
##  Max.   :15.90   Max.   :1.5800   Max.   :1.000   Max.   :15.500  
##    chlorides       free.sulfur.dioxide total.sulfur.dioxide    density      
##  Min.   :0.01200   Min.   : 1.00       Min.   :  6.00       Min.   :0.9901  
##  1st Qu.:0.07000   1st Qu.: 7.00       1st Qu.: 22.00       1st Qu.:0.9956  
##  Median :0.07900   Median :14.00       Median : 38.00       Median :0.9968  
##  Mean   :0.08747   Mean   :15.87       Mean   : 46.47       Mean   :0.9967  
##  3rd Qu.:0.09000   3rd Qu.:21.00       3rd Qu.: 62.00       3rd Qu.:0.9978  
##  Max.   :0.61100   Max.   :72.00       Max.   :289.00       Max.   :1.0037  
##        pH          sulphates         alcohol         quality     
##  Min.   :2.740   Min.   :0.3300   Min.   : 8.40   Min.   :3.000  
##  1st Qu.:3.210   1st Qu.:0.5500   1st Qu.: 9.50   1st Qu.:5.000  
##  Median :3.310   Median :0.6200   Median :10.20   Median :6.000  
##  Mean   :3.311   Mean   :0.6581   Mean   :10.42   Mean   :5.636  
##  3rd Qu.:3.400   3rd Qu.:0.7300   3rd Qu.:11.10   3rd Qu.:6.000  
##  Max.   :4.010   Max.   :2.0000   Max.   :14.90   Max.   :8.000

Tabla descriptiva de variables numéricas

variables_numericas <- names(BD)[sapply(BD, is.numeric)]

tabla_descriptiva <- data.frame(
  Variable = variables_numericas,
  Minimo = sapply(BD[variables_numericas], min, na.rm = TRUE),
  Q1 = sapply(BD[variables_numericas], quantile, probs = 0.25, na.rm = TRUE),
  Mediana = sapply(BD[variables_numericas], median, na.rm = TRUE),
  Media = sapply(BD[variables_numericas], mean, na.rm = TRUE),
  Q3 = sapply(BD[variables_numericas], quantile, probs = 0.75, na.rm = TRUE),
  Maximo = sapply(BD[variables_numericas], max, na.rm = TRUE),
  Desv_Estandar = sapply(BD[variables_numericas], sd, na.rm = TRUE)
)

tabla_descriptiva[, -1] <- round(tabla_descriptiva[, -1], 3)

knitr::kable(
  tabla_descriptiva,
  caption = "Estadísticos descriptivos de las variables numéricas"
)
Estadísticos descriptivos de las variables numéricas
Variable Minimo Q1 Mediana Media Q3 Maximo Desv_Estandar
fixed.acidity fixed.acidity 4.600 7.100 7.900 8.320 9.200 15.900 1.741
volatile.acidity volatile.acidity 0.120 0.390 0.520 0.528 0.640 1.580 0.179
citric.acid citric.acid 0.000 0.090 0.260 0.271 0.420 1.000 0.195
residual.sugar residual.sugar 0.900 1.900 2.200 2.539 2.600 15.500 1.410
chlorides chlorides 0.012 0.070 0.079 0.087 0.090 0.611 0.047
free.sulfur.dioxide free.sulfur.dioxide 1.000 7.000 14.000 15.875 21.000 72.000 10.460
total.sulfur.dioxide total.sulfur.dioxide 6.000 22.000 38.000 46.468 62.000 289.000 32.895
density density 0.990 0.996 0.997 0.997 0.998 1.004 0.002
pH pH 2.740 3.210 3.310 3.311 3.400 4.010 0.154
sulphates sulphates 0.330 0.550 0.620 0.658 0.730 2.000 0.170
alcohol alcohol 8.400 9.500 10.200 10.423 11.100 14.900 1.066
quality quality 3.000 5.000 6.000 5.636 6.000 8.000 0.808

Función para analizar cada variable

Se construyó una función que recibe la base de datos y el nombre de una variable. La función presenta sus principales estadísticos y genera automáticamente gráficos según el tipo de variable.

Para las variables numéricas continuas se utiliza un histograma y un diagrama de caja. Cuando una variable presenta diez o menos valores diferentes, se muestra un gráfico de frecuencias, debido a su naturaleza discreta.

analizar_variable <- function(datos, nombre) {

  variable <- datos[[nombre]]
  nombre_grafico <- gsub(".", " ", nombre, fixed = TRUE)

  cat("\n\nVariable:", nombre_grafico, "\n")
  cat("Tipo de variable:", class(variable), "\n")
  cat("Número de observaciones:", length(variable), "\n")
  cat("Valores faltantes:", sum(is.na(variable)), "\n")
  cat("Valores diferentes:", length(unique(na.omit(variable))), "\n\n")

  if (is.numeric(variable)) {

    estadisticos <- data.frame(
      Estadistico = c(
        "Mínimo", "Primer cuartil", "Mediana", "Media",
        "Tercer cuartil", "Máximo", "Desviación estándar"
      ),
      Valor = round(
        c(
          min(variable, na.rm = TRUE),
          quantile(variable, 0.25, na.rm = TRUE),
          median(variable, na.rm = TRUE),
          mean(variable, na.rm = TRUE),
          quantile(variable, 0.75, na.rm = TRUE),
          max(variable, na.rm = TRUE),
          sd(variable, na.rm = TRUE)
        ),
        3
      )
    )

    print(
      knitr::kable(
        estadisticos,
        caption = paste("Resumen de", nombre_grafico)
      )
    )

    configuracion_original <- par(no.readonly = TRUE)
    on.exit(par(configuracion_original))

    par(mfrow = c(1, 2))

    if (length(unique(na.omit(variable))) <= 10) {

      barplot(
        table(variable),
        main = paste("Frecuencia de", nombre_grafico),
        xlab = nombre_grafico,
        ylab = "Frecuencia",
        col = "steelblue",
        border = "white"
      )

    } else {

      hist(
        variable,
        main = paste("Distribución de", nombre_grafico),
        xlab = nombre_grafico,
        ylab = "Frecuencia",
        col = "steelblue",
        border = "white"
      )
    }

    boxplot(
      variable,
      main = paste("Diagrama de caja de", nombre_grafico),
      ylab = nombre_grafico,
      col = "tomato"
    )

  } else {

    frecuencias <- table(variable)

    print(
      knitr::kable(
        as.data.frame(frecuencias),
        caption = paste("Frecuencias de", nombre_grafico)
      )
    )

    barplot(
      frecuencias,
      main = paste("Frecuencia de", nombre_grafico),
      xlab = nombre_grafico,
      ylab = "Frecuencia",
      col = "steelblue"
    )
  }
}

Análisis individual de las variables

La función se aplica a todas las columnas mediante un bucle for.

for (nombre in names(BD)) {

  cat(
    "\n\n## Análisis de",
    gsub(".", " ", nombre, fixed = TRUE),
    "\n\n"
  )

  analizar_variable(BD, nombre)
}

Análisis de fixed acidity

Variable: fixed acidity Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 96

Resumen de fixed acidity
Estadistico Valor
Mínimo 4.600
Primer cuartil 7.100
Mediana 7.900
Media 8.320
Tercer cuartil 9.200
Máximo 15.900
Desviación estándar 1.741

Análisis de volatile acidity

Variable: volatile acidity Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 143

Resumen de volatile acidity
Estadistico Valor
Mínimo 0.120
Primer cuartil 0.390
Mediana 0.520
Media 0.528
Tercer cuartil 0.640
Máximo 1.580
Desviación estándar 0.179

Análisis de citric acid

Variable: citric acid Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 80

Resumen de citric acid
Estadistico Valor
Mínimo 0.000
Primer cuartil 0.090
Mediana 0.260
Media 0.271
Tercer cuartil 0.420
Máximo 1.000
Desviación estándar 0.195

Análisis de residual sugar

Variable: residual sugar Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 91

Resumen de residual sugar
Estadistico Valor
Mínimo 0.900
Primer cuartil 1.900
Mediana 2.200
Media 2.539
Tercer cuartil 2.600
Máximo 15.500
Desviación estándar 1.410

Análisis de chlorides

Variable: chlorides Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 153

Resumen de chlorides
Estadistico Valor
Mínimo 0.012
Primer cuartil 0.070
Mediana 0.079
Media 0.087
Tercer cuartil 0.090
Máximo 0.611
Desviación estándar 0.047

Análisis de free sulfur dioxide

Variable: free sulfur dioxide Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 60

Resumen de free sulfur dioxide
Estadistico Valor
Mínimo 1.000
Primer cuartil 7.000
Mediana 14.000
Media 15.875
Tercer cuartil 21.000
Máximo 72.000
Desviación estándar 10.460

Análisis de total sulfur dioxide

Variable: total sulfur dioxide Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 144

Resumen de total sulfur dioxide
Estadistico Valor
Mínimo 6.000
Primer cuartil 22.000
Mediana 38.000
Media 46.468
Tercer cuartil 62.000
Máximo 289.000
Desviación estándar 32.895

Análisis de density

Variable: density Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 436

Resumen de density
Estadistico Valor
Mínimo 0.990
Primer cuartil 0.996
Mediana 0.997
Media 0.997
Tercer cuartil 0.998
Máximo 1.004
Desviación estándar 0.002

Análisis de pH

Variable: pH Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 89

Resumen de pH
Estadistico Valor
Mínimo 2.740
Primer cuartil 3.210
Mediana 3.310
Media 3.311
Tercer cuartil 3.400
Máximo 4.010
Desviación estándar 0.154

Análisis de sulphates

Variable: sulphates Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 96

Resumen de sulphates
Estadistico Valor
Mínimo 0.330
Primer cuartil 0.550
Mediana 0.620
Media 0.658
Tercer cuartil 0.730
Máximo 2.000
Desviación estándar 0.170

Análisis de alcohol

Variable: alcohol Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 65

Resumen de alcohol
Estadistico Valor
Mínimo 8.400
Primer cuartil 9.500
Mediana 10.200
Media 10.423
Tercer cuartil 11.100
Máximo 14.900
Desviación estándar 1.066

Análisis de quality

Variable: quality Tipo de variable: integer Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 6

Resumen de quality
Estadistico Valor
Mínimo 3.000
Primer cuartil 5.000
Mediana 6.000
Media 5.636
Tercer cuartil 6.000
Máximo 8.000
Desviación estándar 0.808

Identificación de posibles valores atípicos

Se emplea el criterio del rango intercuartílico. Un valor se considera potencialmente atípico cuando se encuentra por debajo de \(Q1-1.5\times RIQ\) o por encima de \(Q3+1.5\times RIQ\).

contar_atipicos <- function(variable) {

  q1 <- quantile(variable, 0.25, na.rm = TRUE)
  q3 <- quantile(variable, 0.75, na.rm = TRUE)
  rango_intercuartil <- q3 - q1

  limite_inferior <- q1 - 1.5 * rango_intercuartil
  limite_superior <- q3 + 1.5 * rango_intercuartil

  sum(
    variable < limite_inferior |
    variable > limite_superior,
    na.rm = TRUE
  )
}

tabla_atipicos <- data.frame(
  Variable = variables_numericas,
  Posibles_atipicos = sapply(
    BD[variables_numericas],
    contar_atipicos
  )
)

knitr::kable(
  tabla_atipicos,
  caption = "Posibles valores atípicos según el criterio del RIQ"
)
Posibles valores atípicos según el criterio del RIQ
Variable Posibles_atipicos
fixed.acidity fixed.acidity 49
volatile.acidity volatile.acidity 19
citric.acid citric.acid 1
residual.sugar residual.sugar 155
chlorides chlorides 112
free.sulfur.dioxide free.sulfur.dioxide 30
total.sulfur.dioxide total.sulfur.dioxide 55
density density 45
pH pH 35
sulphates sulphates 59
alcohol alcohol 13
quality quality 28

Los posibles valores atípicos no se eliminan, ya que primero deben analizarse dentro del contexto del estudio.

Distribución de la calidad del vino

frecuencia_calidad <- table(BD$quality)

barplot(
  frecuencia_calidad,
  main = "Distribución de la calidad del vino",
  xlab = "Calificación de calidad",
  ylab = "Cantidad de vinos",
  col = "darkred",
  border = "white"
)

tabla_calidad <- data.frame(
  Calidad = names(frecuencia_calidad),
  Frecuencia = as.numeric(frecuencia_calidad),
  Porcentaje = round(
    100 * as.numeric(frecuencia_calidad) / sum(frecuencia_calidad),
    2
  )
)

knitr::kable(
  tabla_calidad,
  caption = "Distribución de la variable quality"
)
Distribución de la variable quality
Calidad Frecuencia Porcentaje
3 10 0.63
4 53 3.31
5 681 42.59
6 638 39.90
7 199 12.45
8 18 1.13

Relación de las variables con la calidad

Se calcula el coeficiente de correlación de Pearson para explorar la dirección e intensidad de la relación lineal entre las características numéricas y la calificación de calidad.

matriz_correlaciones <- cor(
  BD[variables_numericas],
  use = "complete.obs"
)

correlacion_calidad <- matriz_correlaciones[, "quality"]
correlacion_calidad <- correlacion_calidad[
  names(correlacion_calidad) != "quality"
]

correlacion_calidad <- sort(correlacion_calidad)

colores <- ifelse(
  correlacion_calidad >= 0,
  "steelblue",
  "tomato"
)

barplot(
  correlacion_calidad,
  horiz = TRUE,
  las = 1,
  col = colores,
  border = NA,
  main = "Correlación de las variables con la calidad",
  xlab = "Coeficiente de correlación de Pearson"
)

abline(v = 0, lty = 2)

tabla_correlaciones <- data.frame(
  Variable = names(correlacion_calidad),
  Correlacion_con_quality = round(
    as.numeric(correlacion_calidad),
    3
  )
)

knitr::kable(
  tabla_correlaciones,
  caption = "Correlaciones con la calidad del vino"
)
Correlaciones con la calidad del vino
Variable Correlacion_con_quality
volatile.acidity -0.391
total.sulfur.dioxide -0.185
density -0.175
chlorides -0.129
pH -0.058
free.sulfur.dioxide -0.051
residual.sugar 0.014
fixed.acidity 0.124
citric.acid 0.226
sulphates 0.251
alcohol 0.476

Conclusiones