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.
# 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
# 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.
# 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"
)
| 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 |
# 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.
# 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.
# 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
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:
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)
}
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
)
}
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 96
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 143
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 80
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 91
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 153
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 60
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 144
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 436
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 89
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 96
| 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
Tipo de variable: numeric
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 65
| 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
Tipo de variable: integer
Número de observaciones: 1599
Valores válidos: 1599
Valores faltantes: 0
Valores únicos: 6
| 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
| 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 |
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"
)
| 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()
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"
)
| 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 |
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.