1. Introducción

El presente análisis exploratorio tiene como objetivo describir las principales características de una base de datos relacionada con la calidad del vino tinto.

Se analizará individualmente cada variable mediante medidas estadísticas, tablas de frecuencia y representaciones gráficas.

2. Importación de los datos

# Se importa el archivo CSV.
# El archivo debe encontrarse en la misma carpeta que este RMarkdown.

datos <- read.csv(
  "winequality-red.csv",
  header = TRUE,
  sep = ",",
  stringsAsFactors = FALSE,
  check.names = FALSE
)

# Se muestran las primeras seis observaciones.
head(datos)
##   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

3. Dimensiones de la base de datos

# Número de filas y columnas.
dim(datos)
## [1] 1599   12
cat("Número de observaciones:", nrow(datos), "\n")
## Número de observaciones: 1599
cat("Número de variables:", ncol(datos), "\n")
## Número de variables: 12

La base de datos contiene 1599 observaciones y 12 variables.

4. Estructura de los datos

# Permite conocer el tipo de dato de cada variable.
str(datos)
## '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 ...

También podemos crear una tabla resumen con el tipo de variable, cantidad de valores únicos y valores faltantes.

estructura <- data.frame(
  Variable = names(datos),
  Tipo = sapply(datos, function(x) class(x)[1]),
  Valores_unicos = sapply(datos, function(x) length(unique(x))),
  Valores_faltantes = sapply(datos, function(x) sum(is.na(x)))
)

kable(
  estructura,
  caption = "Estructura de las variables"
)
Estructura de las variables
Variable Tipo Valores_unicos Valores_faltantes
fixed acidity fixed acidity numeric 96 0
volatile acidity volatile acidity numeric 143 0
citric acid citric acid numeric 80 0
residual sugar residual sugar numeric 91 0
chlorides chlorides numeric 153 0
free sulfur dioxide free sulfur dioxide numeric 60 0
total sulfur dioxide total sulfur dioxide numeric 144 0
density density numeric 436 0
pH pH numeric 89 0
sulphates sulphates numeric 96 0
alcohol alcohol numeric 65 0
quality quality integer 6 0

5. Revisión de valores faltantes

# Cantidad de valores faltantes por variable.
faltantes <- colSums(is.na(datos))

faltantes
##        fixed acidity     volatile acidity          citric acid 
##                    0                    0                    0 
##       residual sugar            chlorides  free sulfur dioxide 
##                    0                    0                    0 
## total sulfur dioxide              density                   pH 
##                    0                    0                    0 
##            sulphates              alcohol              quality 
##                    0                    0                    0
# Cantidad total de datos faltantes.
sum(is.na(datos))
## [1] 0

En esta base de datos se encontraron 0 valores faltantes.

6. Revisión de registros duplicados

# Identificamos registros completamente duplicados.
duplicados <- sum(duplicated(datos))

duplicados
## [1] 240

Se identificaron 240 registros duplicados.

Para este análisis exploratorio se conservarán las observaciones originales, ya que la eliminación de duplicados requeriría conocer previamente si corresponden a errores de registro o a observaciones legítimas.

7. Resumen estadístico general

# Resumen descriptivo inicial de todas las variables.
summary(datos)
##  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

8. Función para el análisis de cada variable

A continuación se crea una función que realiza automáticamente el análisis exploratorio de cada variable de la base de datos.

Para las variables numéricas se calculan medidas de tendencia central, dispersión y posición.

Además:

  • Para variables con pocos valores diferentes se presenta una tabla de frecuencias y un gráfico de barras.
  • Para variables continuas se presenta un histograma y un boxplot.
analisis_variable <- function(data, variable) {
  
  # Extraemos la variable que deseamos analizar.
  x <- data[[variable]]
  
  # Información general.
  cat("**Tipo de variable:**", class(x)[1], "  \n")
  cat("**Número de observaciones:**", length(x), "  \n")
  cat("**Valores válidos:**", sum(!is.na(x)), "  \n")
  cat("**Valores faltantes:**", sum(is.na(x)), "  \n")
  cat("**Valores únicos:**", length(unique(na.omit(x))), "  \n\n")
  
  
  # -------------------------------------------------------------
  # ANÁLISIS DE VARIABLES NUMÉRICAS
  # -------------------------------------------------------------
  
  if (is.numeric(x)) {
    
    # Se calculan los principales estadísticos descriptivos.
    resumen <- data.frame(
      Estadistico = c(
        "Media",
        "Mediana",
        "Desviación estándar",
        "Mínimo",
        "Primer cuartil (Q1)",
        "Tercer cuartil (Q3)",
        "Máximo",
        "Rango intercuartílico"
      ),
      
      Valor = c(
        mean(x, na.rm = TRUE),
        median(x, na.rm = TRUE),
        sd(x, na.rm = TRUE),
        min(x, na.rm = TRUE),
        quantile(x, 0.25, na.rm = TRUE),
        quantile(x, 0.75, na.rm = TRUE),
        max(x, na.rm = TRUE),
        IQR(x, na.rm = TRUE)
      )
    )
    
    # Redondeamos los resultados para facilitar la lectura.
    resumen$Valor <- round(resumen$Valor, 4)
    
    # Mostramos la tabla.
    print(
      kable(
        resumen,
        caption = paste("Estadísticos descriptivos de", variable)
      )
    )
    
    
    # -----------------------------------------------------------
    # DETECCIÓN DE POSIBLES VALORES ATÍPICOS
    # -----------------------------------------------------------
    
    Q1 <- quantile(x, 0.25, na.rm = TRUE)
    Q3 <- quantile(x, 0.75, na.rm = TRUE)
    
    RIQ <- IQR(x, na.rm = TRUE)
    
    limite_inferior <- Q1 - 1.5 * RIQ
    limite_superior <- Q3 + 1.5 * RIQ
    
    atipicos <- sum(
      x < limite_inferior | x > limite_superior,
      na.rm = TRUE
    )
    
    cat("\n**Posibles valores atípicos según 1.5 × RIQ:**",
        atipicos, "\n\n")
    
    
    # -----------------------------------------------------------
    # VARIABLES NUMÉRICAS DISCRETAS
    # -----------------------------------------------------------
    
    # Si existen 10 valores diferentes o menos,
    # utilizaremos una tabla de frecuencias y gráfico de barras.
    
    if (length(unique(na.omit(x))) <= 10) {
      
      tabla <- as.data.frame(
        table(x, useNA = "ifany")
      )
      
      names(tabla) <- c("Valor", "Frecuencia")
      
      tabla$Porcentaje <- round(
        tabla$Frecuencia / sum(tabla$Frecuencia) * 100,
        2
      )
      
      print(
        kable(
          tabla,
          caption = paste("Distribución de frecuencia de", variable)
        )
      )
      
      
      # Creamos un data frame únicamente para realizar el gráfico.
      grafico_datos <- data.frame(
        valor = factor(x)
      )
      
      grafico <- ggplot(
        grafico_datos,
        aes(x = valor)
      ) +
        geom_bar() +
        labs(
          title = paste("Distribución de", variable),
          x = variable,
          y = "Frecuencia"
        ) +
        theme_minimal()
      
      print(grafico)
      
      
    } else {
      
      # ---------------------------------------------------------
      # VARIABLES NUMÉRICAS CONTINUAS
      # ---------------------------------------------------------
      
      grafico_datos <- data.frame(
        valor = x
      )
      
      
      # Histograma.
      histograma <- ggplot(
        grafico_datos,
        aes(x = valor)
      ) +
        geom_histogram(
          bins = 30,
          color = "black",
          fill = "lightblue"
        ) +
        labs(
          title = paste("Histograma de", variable),
          x = variable,
          y = "Frecuencia"
        ) +
        theme_minimal()
      
      print(histograma)
      
      
      # Boxplot.
      boxplot_grafico <- ggplot(
        grafico_datos,
        aes(y = valor)
      ) +
        geom_boxplot(
          fill = "lightgray"
        ) +
        labs(
          title = paste("Boxplot de", variable),
          y = variable,
          x = ""
        ) +
        theme_minimal()
      
      print(boxplot_grafico)
    }
  }
  
  invisible(NULL)
}

9. Análisis exploratorio por variable

La función creada anteriormente se aplicará a cada una de las variables de la base de datos.

for (variable in names(datos)) {
  
  cat("\n\n##", variable, "\n\n")
  
  analisis_variable(
    data = datos,
    variable = variable
  )
}

fixed acidity

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 96

Estadísticos descriptivos de fixed acidity
Estadistico Valor
Media 8.3196
Mediana 7.9000
Desviación estándar 1.7411
Mínimo 4.6000
Primer cuartil (Q1) 7.1000
Tercer cuartil (Q3) 9.2000
Máximo 15.9000
Rango intercuartílico 2.1000

Posibles valores atípicos según 1.5 × RIQ: 49

volatile acidity

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 143

Estadísticos descriptivos de volatile acidity
Estadistico Valor
Media 0.5278
Mediana 0.5200
Desviación estándar 0.1791
Mínimo 0.1200
Primer cuartil (Q1) 0.3900
Tercer cuartil (Q3) 0.6400
Máximo 1.5800
Rango intercuartílico 0.2500

Posibles valores atípicos según 1.5 × RIQ: 19

citric acid

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 80

Estadísticos descriptivos de citric acid
Estadistico Valor
Media 0.2710
Mediana 0.2600
Desviación estándar 0.1948
Mínimo 0.0000
Primer cuartil (Q1) 0.0900
Tercer cuartil (Q3) 0.4200
Máximo 1.0000
Rango intercuartílico 0.3300

Posibles valores atípicos según 1.5 × RIQ: 1

residual sugar

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 91

Estadísticos descriptivos de residual sugar
Estadistico Valor
Media 2.5388
Mediana 2.2000
Desviación estándar 1.4099
Mínimo 0.9000
Primer cuartil (Q1) 1.9000
Tercer cuartil (Q3) 2.6000
Máximo 15.5000
Rango intercuartílico 0.7000

Posibles valores atípicos según 1.5 × RIQ: 155

chlorides

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 153

Estadísticos descriptivos de chlorides
Estadistico Valor
Media 0.0875
Mediana 0.0790
Desviación estándar 0.0471
Mínimo 0.0120
Primer cuartil (Q1) 0.0700
Tercer cuartil (Q3) 0.0900
Máximo 0.6110
Rango intercuartílico 0.0200

Posibles valores atípicos según 1.5 × RIQ: 112

free sulfur dioxide

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 60

Estadísticos descriptivos de free sulfur dioxide
Estadistico Valor
Media 15.8749
Mediana 14.0000
Desviación estándar 10.4602
Mínimo 1.0000
Primer cuartil (Q1) 7.0000
Tercer cuartil (Q3) 21.0000
Máximo 72.0000
Rango intercuartílico 14.0000

Posibles valores atípicos según 1.5 × RIQ: 30

total sulfur dioxide

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 144

Estadísticos descriptivos de total sulfur dioxide
Estadistico Valor
Media 46.4678
Mediana 38.0000
Desviación estándar 32.8953
Mínimo 6.0000
Primer cuartil (Q1) 22.0000
Tercer cuartil (Q3) 62.0000
Máximo 289.0000
Rango intercuartílico 40.0000

Posibles valores atípicos según 1.5 × RIQ: 55

density

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 436

Estadísticos descriptivos de density
Estadistico Valor
Media 0.9967
Mediana 0.9968
Desviación estándar 0.0019
Mínimo 0.9901
Primer cuartil (Q1) 0.9956
Tercer cuartil (Q3) 0.9978
Máximo 1.0037
Rango intercuartílico 0.0022

Posibles valores atípicos según 1.5 × RIQ: 45

pH

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 89

Estadísticos descriptivos de pH
Estadistico Valor
Media 3.3111
Mediana 3.3100
Desviación estándar 0.1544
Mínimo 2.7400
Primer cuartil (Q1) 3.2100
Tercer cuartil (Q3) 3.4000
Máximo 4.0100
Rango intercuartílico 0.1900

Posibles valores atípicos según 1.5 × RIQ: 35

sulphates

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 96

Estadísticos descriptivos de sulphates
Estadistico Valor
Media 0.6581
Mediana 0.6200
Desviación estándar 0.1695
Mínimo 0.3300
Primer cuartil (Q1) 0.5500
Tercer cuartil (Q3) 0.7300
Máximo 2.0000
Rango intercuartílico 0.1800

Posibles valores atípicos según 1.5 × RIQ: 59

alcohol

Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 65

Estadísticos descriptivos de alcohol
Estadistico Valor
Media 10.4230
Mediana 10.2000
Desviación estándar 1.0657
Mínimo 8.4000
Primer cuartil (Q1) 9.5000
Tercer cuartil (Q3) 11.1000
Máximo 14.9000
Rango intercuartílico 1.6000

Posibles valores atípicos según 1.5 × RIQ: 13

quality

Tipo de variable: integer
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 6

Estadísticos descriptivos de quality
Estadistico Valor
Media 5.6360
Mediana 6.0000
Desviación estándar 0.8076
Mínimo 3.0000
Primer cuartil (Q1) 5.0000
Tercer cuartil (Q3) 6.0000
Máximo 8.0000
Rango intercuartílico 1.0000

Posibles valores atípicos según 1.5 × RIQ: 28

Distribución de frecuencia de quality
Valor 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

10. Distribución de la calidad del vino

Debido a que quality representa la valoración de calidad del vino, es importante observar específicamente su distribución.

tabla_quality <- as.data.frame(
  table(datos$quality)
)

names(tabla_quality) <- c(
  "Calidad",
  "Frecuencia"
)

tabla_quality$Porcentaje <- round(
  tabla_quality$Frecuencia /
    sum(tabla_quality$Frecuencia) * 100,
  2
)

kable(
  tabla_quality,
  caption = "Distribución de la calidad del vino"
)
Distribución de la calidad del vino
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
ggplot(
  datos,
  aes(x = factor(quality))
) +
  geom_bar(
    fill = "steelblue"
  ) +
  labs(
    title = "Distribución de la calidad del vino",
    x = "Calidad",
    y = "Número de vinos"
  ) +
  theme_minimal()

11. Análisis de correlación

Debido a que las variables de la base de datos son numéricas, se puede complementar el análisis exploratorio mediante el coeficiente de correlación de Pearson.

# Matriz de correlaciones.
matriz_correlacion <- cor(
  datos,
  use = "complete.obs"
)

round(matriz_correlacion, 2)
##                      fixed acidity volatile acidity citric acid residual sugar
## fixed acidity                 1.00            -0.26        0.67           0.11
## volatile acidity             -0.26             1.00       -0.55           0.00
## citric acid                   0.67            -0.55        1.00           0.14
## residual sugar                0.11             0.00        0.14           1.00
## chlorides                     0.09             0.06        0.20           0.06
## free sulfur dioxide          -0.15            -0.01       -0.06           0.19
## total sulfur dioxide         -0.11             0.08        0.04           0.20
## density                       0.67             0.02        0.36           0.36
## pH                           -0.68             0.23       -0.54          -0.09
## sulphates                     0.18            -0.26        0.31           0.01
## alcohol                      -0.06            -0.20        0.11           0.04
## quality                       0.12            -0.39        0.23           0.01
##                      chlorides free sulfur dioxide total sulfur dioxide density
## fixed acidity             0.09               -0.15                -0.11    0.67
## volatile acidity          0.06               -0.01                 0.08    0.02
## citric acid               0.20               -0.06                 0.04    0.36
## residual sugar            0.06                0.19                 0.20    0.36
## chlorides                 1.00                0.01                 0.05    0.20
## free sulfur dioxide       0.01                1.00                 0.67   -0.02
## total sulfur dioxide      0.05                0.67                 1.00    0.07
## density                   0.20               -0.02                 0.07    1.00
## pH                       -0.27                0.07                -0.07   -0.34
## sulphates                 0.37                0.05                 0.04    0.15
## alcohol                  -0.22               -0.07                -0.21   -0.50
## quality                  -0.13               -0.05                -0.19   -0.17
##                         pH sulphates alcohol quality
## fixed acidity        -0.68      0.18   -0.06    0.12
## volatile acidity      0.23     -0.26   -0.20   -0.39
## citric acid          -0.54      0.31    0.11    0.23
## residual sugar       -0.09      0.01    0.04    0.01
## chlorides            -0.27      0.37   -0.22   -0.13
## free sulfur dioxide   0.07      0.05   -0.07   -0.05
## total sulfur dioxide -0.07      0.04   -0.21   -0.19
## density              -0.34      0.15   -0.50   -0.17
## pH                    1.00     -0.20    0.21   -0.06
## sulphates            -0.20      1.00    0.09    0.25
## alcohol               0.21      0.09    1.00    0.48
## quality              -0.06      0.25    0.48    1.00

Para facilitar la interpretación, analizaremos específicamente la relación de las variables con la calidad del vino.

correlacion_quality <- matriz_correlacion[, "quality"]

correlacion_quality <- correlacion_quality[
  names(correlacion_quality) != "quality"
]

tabla_correlacion <- data.frame(
  Variable = names(correlacion_quality),
  Correlacion = round(
    as.numeric(correlacion_quality),
    3
  )
)

tabla_correlacion <- tabla_correlacion[
  order(
    abs(tabla_correlacion$Correlacion),
    decreasing = TRUE
  ),
]

kable(
  tabla_correlacion,
  caption = "Correlación de las variables con quality"
)
Correlación de las variables con quality
Variable Correlacion
11 alcohol 0.476
2 volatile acidity -0.391
10 sulphates 0.251
3 citric acid 0.226
7 total sulfur dioxide -0.185
8 density -0.175
5 chlorides -0.129
1 fixed acidity 0.124
9 pH -0.058
6 free sulfur dioxide -0.051
4 residual sugar 0.014

12. Conclusiones

El análisis exploratorio permitió conocer la estructura y distribución de las variables presentes en la base de datos de calidad del vino.

La base contiene 1599 observaciones y 12 variables, sin presencia de valores faltantes.

La variable de calidad presenta una mayor concentración en las categorías intermedias, principalmente en los valores 5 y 6.

Asimismo, el análisis de correlaciones permite identificar las variables que presentan una mayor asociación con la calidad del vino. Estas relaciones pueden ser utilizadas posteriormente como punto de partida para análisis estadísticos o modelos predictivos.

Finalmente, mediante histogramas y diagramas de caja se observaron las características de distribución, dispersión y presencia de posibles valores atípicos en las variables analizadas.