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:
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 ...
dim(BD)
## [1] 1599 12
La base contiene 1599 observaciones y 12 variables.
knitr::kable(
head(BD),
caption = "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 |
faltantes <- data.frame(
Variable = names(BD),
Valores_faltantes = colSums(is.na(BD))
)
knitr::kable(
faltantes,
caption = "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.
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.
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
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"
)
| 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 |
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"
)
}
}
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)
}
Variable: fixed acidity Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 96
| 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 |
Variable: volatile acidity Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 143
| 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 |
Variable: citric acid Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 80
| 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 |
Variable: residual sugar Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 91
| 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 |
Variable: chlorides Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 153
| 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 |
Variable: free sulfur dioxide Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 60
| 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 |
Variable: total sulfur dioxide Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 144
| 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 |
Variable: density Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 436
| 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 |
Variable: pH Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 89
| 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 |
Variable: sulphates Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 96
| 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 |
Variable: alcohol Tipo de variable: numeric Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 65
| 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 |
Variable: quality Tipo de variable: integer Número de observaciones: 1599 Valores faltantes: 0 Valores diferentes: 6
| 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 |
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"
)
| 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.
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"
)
| 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 |
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"
)
| 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 |