knitr::opts_chunk$set(echo = TRUE)

Introducción

En este trabajo se realizará un análisis exploratorio de una base de datos sobre vinos tintos. La información contiene diferentes características del vino, como acidez, azúcar, alcohol y otras propiedades, además de una variable que indica la calidad del vino.

El objetivo es conocer mejor la información disponible, revisar si existen datos faltantes o repetidos y obtener algunos estadísticos que permitan describir las variables.

También se analizará la distribución de la calidad y la relación entre las características del vino y su calidad.

Importación de los datos

Primero se importa la base de datos utilizando la función read.csv().

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 ...

Tamaño de la base

Para conocer el número de filas y columnas de la base utilizamos dim().

dim(BD)
## [1] 1599   12

La base contiene 1599 observaciones y 12 variables.

También podemos observar las primeras filas:

head(BD)
##   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

Revisión de los datos

Valores faltantes

Es importante verificar si existen datos faltantes, ya que estos pueden afectar los análisis posteriores.

faltantes <- colSums(is.na(BD))

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

El número total de valores faltantes es:

sum(is.na(BD))
## [1] 0

La base presenta un total de 0 valores faltantes.

Registros duplicados

También se revisará si existen filas repetidas.

duplicados <- sum(duplicated(BD))

duplicados
## [1] 240

Se encontraron 240 registros duplicados.

Los registros duplicados no serán eliminados en esta etapa, ya que primero es necesario determinar si realmente representan información repetida o si pueden ser observaciones válidas.

Estadísticos descriptivos

Para obtener una primera descripción de las variables utilizamos la función summary().

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

Esta función permite observar valores como el mínimo, máximo, mediana y los cuartiles de las variables numéricas.

Tabla de estadísticos

Para complementar el análisis se calcularán algunos estadísticos básicos de las variables numéricas.

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

tabla_descriptiva <- data.frame(
  Variable = variables_numericas,
  Minimo = sapply(BD[variables_numericas], min),
  Mediana = sapply(BD[variables_numericas], median),
  Media = sapply(BD[variables_numericas], mean),
  Maximo = sapply(BD[variables_numericas], max)
)

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

knitr::kable(
  tabla_descriptiva,
  caption = "Estadísticos descriptivos"
)
Estadísticos descriptivos
Variable Minimo Mediana Media Maximo
fixed.acidity fixed.acidity 4.60 7.90 8.32 15.90
volatile.acidity volatile.acidity 0.12 0.52 0.53 1.58
citric.acid citric.acid 0.00 0.26 0.27 1.00
residual.sugar residual.sugar 0.90 2.20 2.54 15.50
chlorides chlorides 0.01 0.08 0.09 0.61
free.sulfur.dioxide free.sulfur.dioxide 1.00 14.00 15.87 72.00
total.sulfur.dioxide total.sulfur.dioxide 6.00 38.00 46.47 289.00
density density 0.99 1.00 1.00 1.00
pH pH 2.74 3.31 3.31 4.01
sulphates sulphates 0.33 0.62 0.66 2.00
alcohol alcohol 8.40 10.20 10.42 14.90
quality quality 3.00 6.00 5.64 8.00

Función para analizar las variables

Para evitar repetir el mismo código para cada variable, se creó una función sencilla que permite obtener información básica de una variable.

analizar_variable <- function(datos, variable) {
  
  x <- datos[[variable]]
  
  cat("Variable:", variable, "\n")
  cat("Tipo:", class(x), "\n")
  cat("Valores faltantes:", sum(is.na(x)), "\n")
  cat("Valores diferentes:", length(unique(x)), "\n")
  
  if (is.numeric(x)) {
    
    cat("Media:", round(mean(x, na.rm = TRUE), 2), "\n")
    cat("Mediana:", round(median(x, na.rm = TRUE), 2), "\n")
    cat("Mínimo:", min(x, na.rm = TRUE), "\n")
    cat("Máximo:", max(x, na.rm = TRUE), "\n")
    
    hist(
      x,
      main = paste("Distribución de", variable),
      xlab = variable,
      ylab = "Frecuencia"
    )
    
    boxplot(
      x,
      main = paste("Diagrama de caja de", variable),
      ylab = variable
    )
    
  } else {
    
    print(table(x))
    
  }
}

Por ejemplo, podemos utilizar la función con la variable alcohol:

analizar_variable(BD, "alcohol")
## Variable: alcohol 
## Tipo: numeric 
## Valores faltantes: 0 
## Valores diferentes: 65 
## Media: 10.42 
## Mediana: 10.2 
## Mínimo: 8.4 
## Máximo: 14.9

También podemos utilizarla con la variable quality:

analizar_variable(BD, "quality")
## Variable: quality 
## Tipo: integer 
## Valores faltantes: 0 
## Valores diferentes: 6 
## Media: 5.64 
## Mediana: 6 
## Mínimo: 3 
## Máximo: 8

Análisis de valores atípicos

Los diagramas de caja permiten observar posibles valores atípicos en las variables numéricas.

Para realizar una revisión sencilla, se utilizará el rango intercuartílico.

contar_atipicos <- function(x) {
  
  Q1 <- quantile(x, 0.25, na.rm = TRUE)
  Q3 <- quantile(x, 0.75, na.rm = TRUE)
  
  RIQ <- Q3 - Q1
  
  limite_inferior <- Q1 - 1.5 * RIQ
  limite_superior <- Q3 + 1.5 * RIQ
  
  sum(
    x < limite_inferior | x > limite_superior,
    na.rm = TRUE
  )
}

Ahora se aplica la función a las variables numéricas:

atipicos <- sapply(
  BD[variables_numericas],
  contar_atipicos
)

atipicos
##        fixed.acidity     volatile.acidity          citric.acid 
##                   49                   19                    1 
##       residual.sugar            chlorides  free.sulfur.dioxide 
##                  155                  112                   30 
## total.sulfur.dioxide              density                   pH 
##                   55                   45                   35 
##            sulphates              alcohol              quality 
##                   59                   13                   28

Estos valores se consideran posibles valores atípicos, por lo que no necesariamente deben eliminarse. Es necesario revisar cada caso antes de tomar una decisión.

Distribución de la calidad

La variable quality representa la calificación del vino. Primero observamos cuántos vinos existen para cada nivel de calidad.

frecuencia_calidad <- table(BD$quality)

frecuencia_calidad
## 
##   3   4   5   6   7   8 
##  10  53 681 638 199  18

Podemos representar esta información mediante un gráfico de barras.

barplot(
  frecuencia_calidad,
  main = "Calidad de los vinos",
  xlab = "Calidad",
  ylab = "Cantidad",
  col = "darkred"
)

También calculamos el porcentaje de cada nivel de calidad:

porcentaje_calidad <- round(
  prop.table(frecuencia_calidad) * 100,
  2
)

porcentaje_calidad
## 
##     3     4     5     6     7     8 
##  0.63  3.31 42.59 39.90 12.45  1.13

La calidad que aparece con mayor frecuencia es:

calidad_mas_frecuente <- names(
  which.max(frecuencia_calidad)
)

calidad_mas_frecuente
## [1] "5"

Relación entre las variables y la calidad

Para conocer qué variables presentan una mayor relación con la calidad del vino, se calcularán las correlaciones.

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

cor_quality <- correlaciones[, "quality"]

cor_quality <- sort(
  cor_quality,
  decreasing = TRUE
)

cor_quality
##              quality              alcohol            sulphates 
##           1.00000000           0.47616632           0.25139708 
##          citric.acid        fixed.acidity       residual.sugar 
##           0.22637251           0.12405165           0.01373164 
##  free.sulfur.dioxide                   pH            chlorides 
##          -0.05065606          -0.05773139          -0.12890656 
##              density total.sulfur.dioxide     volatile.acidity 
##          -0.17491923          -0.18510029          -0.39055778

Podemos representar las correlaciones mediante un gráfico:

barplot(
  cor_quality,
  las = 2,
  main = "Correlación con la calidad",
  ylab = "Correlación"
)

abline(h = 0, lty = 2)

Una correlación positiva indica que, cuando una variable aumenta, la calidad tiende a aumentar. Por otro lado, una correlación negativa indica una relación en sentido contrario.

La variable que presenta la mayor correlación en valor absoluto con la calidad es:

cor_sin_quality <- cor_quality[
  names(cor_quality) != "quality"
]

variable_mayor <- names(
  sort(abs(cor_sin_quality), decreasing = TRUE)
)[1]

valor_correlacion <- cor_sin_quality[
  variable_mayor
]

variable_mayor
## [1] "alcohol"
valor_correlacion
##   alcohol 
## 0.4761663