Análisis descriptivo de la base de datos de cervezas
Jacobo Raffan Perez
07 de agosto de 2026
Este informe calcula los indicadores estadísticos correspondientes a
los dos videos de apoyo de la actividad. El primero corresponde a
indicadores calculados sobre datos individuales: n, mínimo, máximo,
media, mediana, cuartiles, rango, varianza, desviación estándar,
coeficiente de variación, coeficiente de asimetría y coeficiente de
curtosis. El segundo corresponde a esos mismos indicadores calculados a
partir de una tabla de distribución de frecuencias por intervalos,
usando interpolación lineal. Todos los cálculos se aplican sobre la base
de datos beer2.csv, compuesta por 1.586.614 registros de
cervezas.
library(data.table)
cerveza <- fread("beer2.csv")
dim(cerveza)
## [1] 1586614 15
str(cerveza)
## Classes 'data.table' and 'data.frame': 1586614 obs. of 15 variables:
## $ id : int 47986 48213 48215 47969 64883 52159 52159 52159 52159 52159 ...
## $ marca : chr "Vecchio Birraio" "Vecchio Birraio" "Vecchio Birraio" "Vecchio Birraio" ...
## $ precio : num 4.99 4.45 4.95 5.58 3.9 5.07 5.07 5.07 5.07 5.07 ...
## $ alcohol : num 13.8 16.9 17.7 13.8 20.7 ...
## $ carbohidratos : num 11.5 14.2 15 10.5 15.2 12.1 12.1 12.1 12.1 12.1 ...
## $ calorias : int 148 181 190 144 212 145 145 145 145 145 ...
## $ nacionalidad : chr "Canadá" "Canadá" "Canadá" "Canadá" ...
## $ calificacion : num 1.5 3 3 3 4 3 3.5 3 4 4.5 ...
## $ aroma : num 2 2.5 2.5 3 4.5 3.5 3.5 2.5 3 3.5 ...
## $ apariencia : num 2.5 3 3 3.5 4 3.5 3.5 3.5 3.5 5 ...
## $ paladar : num 1.5 3 3 2.5 4 3 4 2 3.5 4 ...
## $ sabor : num 1.5 3 3 3 4.5 3.5 4 3.5 4 4 ...
## $ nombre_cerveza: chr "Sausa Weizen" "Red Moon" "Black Horse Black Beer" "Sausa Pils" ...
## $ tipo : chr "clara artesanal" "cerveza normal y helada" "cerveza normal y helada" "lager importada" ...
## $ origen : chr "importada" "importada" "importada" "importada" ...
## - attr(*, ".internal.selfref")=<pointer: 0x000001d5a014d900>
summary(cerveza)
## id marca precio alcohol
## Min. : 3 Length :1586614 Min. :2.37 Min. : 0.03
## 1st Qu.: 1717 N.unique : 5743 1st Qu.:4.36 1st Qu.:14.28
## Median :13906 N.blank : 0 Median :5.00 Median :17.67
## Mean :21713 Min.nchar: 3 Mean :5.00 Mean :18.71
## 3rd Qu.:39441 Max.nchar: 66 3rd Qu.:5.64 3rd Qu.:22.63
## Max. :77317 Max. :7.72 Max. :59.45
## carbohidratos calorias nacionalidad calificacion
## Min. : 3.50 Min. : 21.0 Length :1586614 Min. :0.000
## 1st Qu.:12.40 1st Qu.:157.0 N.unique : 10 1st Qu.:3.500
## Median :13.80 Median :182.0 N.blank : 0 Median :4.000
## Mean :14.05 Mean :193.2 Min.nchar: 5 Mean :3.816
## 3rd Qu.:15.60 3rd Qu.:224.0 Max.nchar: 15 3rd Qu.:4.500
## Max. :30.20 Max. :541.0 Max. :5.000
## aroma apariencia paladar sabor
## Min. :1.000 Min. :0.000 Min. :1.000 Min. :1.000
## 1st Qu.:3.500 1st Qu.:3.500 1st Qu.:3.500 1st Qu.:3.500
## Median :4.000 Median :4.000 Median :4.000 Median :4.000
## Mean :3.736 Mean :3.842 Mean :3.744 Mean :3.793
## 3rd Qu.:4.000 3rd Qu.:4.000 3rd Qu.:4.000 3rd Qu.:4.500
## Max. :5.000 Max. :5.000 Max. :5.000 Max. :5.000
## nombre_cerveza tipo origen
## Length :1586614 Length :1586614 Length :1586614
## N.unique : 56857 N.unique : 5 N.unique : 2
## N.blank : 0 N.blank : 0 N.blank : 0
## Min.nchar: 1 Min.nchar: 15 Min.nchar: 8
## Max.nchar: 76 Max.nchar: 30 Max.nchar: 9
##
Las variables cuantitativas de la base son: precio, alcohol, carbohidratos, calorías, calificación, aroma, apariencia, paladar y sabor.
calcular_indicadores <- function(x) {
n <- length(x)
minimo <- min(x)
maximo <- max(x)
media <- mean(x)
mediana <- median(x)
q1 <- as.numeric(quantile(x, 0.25))
q3 <- as.numeric(quantile(x, 0.75))
rango <- maximo - minimo
varianza <- var(x)
desv_est <- sd(x)
cv <- (desv_est / media) * 100
asimetria <- (sum((x - media)^3) / n) / (desv_est^3)
curtosis <- (sum((x - media)^4) / n) / (desv_est^4) - 3
data.frame(
n = n, minimo = minimo, maximo = maximo, media = media,
mediana = mediana, Q1 = q1, Q3 = q3, rango = rango,
varianza = varianza, desviacion = desv_est, CV_pct = cv,
asimetria = asimetria, curtosis = curtosis
)
}
variables_numericas <- c("precio", "alcohol", "carbohidratos", "calorias",
"calificacion", "aroma", "apariencia",
"paladar", "sabor")
resumen_indicadores <- do.call(rbind, lapply(variables_numericas, function(v) {
res <- calcular_indicadores(cerveza[[v]])
res$variable <- v
res
}))
resumen_indicadores <- resumen_indicadores[, c("variable",
setdiff(names(resumen_indicadores), "variable"))]
knitr::kable(resumen_indicadores, digits = 3,
caption = "Indicadores estadísticos por variable")
| variable | n | minimo | maximo | media | mediana | Q1 | Q3 | rango | varianza | desviacion | CV_pct | asimetria | curtosis |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| precio | 1586614 | 2.37 | 7.72 | 5.000 | 5.00 | 4.36 | 5.64 | 5.35 | 0.784 | 0.885 | 17.708 | 0.000 | -0.500 |
| alcohol | 1586614 | 0.03 | 59.45 | 18.712 | 17.67 | 14.28 | 22.63 | 59.42 | 29.768 | 5.456 | 29.158 | 0.959 | 1.310 |
| carbohidratos | 1586614 | 3.50 | 30.20 | 14.048 | 13.80 | 12.40 | 15.60 | 26.70 | 5.645 | 2.376 | 16.913 | 0.461 | 0.869 |
| calorias | 1586614 | 21.00 | 541.00 | 193.173 | 182.00 | 157.00 | 224.00 | 520.00 | 2168.646 | 46.569 | 24.107 | 0.915 | 1.318 |
| calificacion | 1586614 | 0.00 | 5.00 | 3.816 | 4.00 | 3.50 | 4.50 | 5.00 | 0.519 | 0.721 | 18.886 | -1.024 | 1.631 |
| aroma | 1586614 | 1.00 | 5.00 | 3.736 | 4.00 | 3.50 | 4.00 | 4.00 | 0.487 | 0.698 | 18.675 | -0.839 | 1.145 |
| apariencia | 1586614 | 0.00 | 5.00 | 3.842 | 4.00 | 3.50 | 4.00 | 5.00 | 0.380 | 0.616 | 16.037 | -0.902 | 1.739 |
| paladar | 1586614 | 1.00 | 5.00 | 3.744 | 4.00 | 3.50 | 4.00 | 4.00 | 0.465 | 0.682 | 18.223 | -0.869 | 1.303 |
| sabor | 1586614 | 1.00 | 5.00 | 3.793 | 4.00 | 3.50 | 4.50 | 4.00 | 0.536 | 0.732 | 19.299 | -0.973 | 1.342 |
El precio tiene una media de 5.00 y una mediana de 5.00, casi iguales, y un coeficiente de asimetría muy cercano a cero (-0.0004), lo que quiere decir que los datos están repartidos de forma pareja a ambos lados de la media. Su curtosis negativa (-0.50) muestra una distribución plana, sin un punto donde se junten muchos datos.
El alcohol es la variable con más variabilidad (CV = 29.16%) y con la asimetría positiva más alta (0.96). Esto significa que la mayoría de las cervezas tienen un grado de alcohol moderado, pero hay un grupo de cervezas con mucho más alcohol que las demás, y eso alarga la distribución hacia la derecha. Su curtosis positiva (1.31) indica que los datos están más juntos alrededor de la media que en una distribución normal.
Las calorías se comportan de forma parecida al alcohol: tienen una variabilidad alta (CV = 24.11%) y asimetría positiva (0.92). Esto tiene sentido porque las cervezas con más alcohol suelen tener también más calorías.
Los carbohidratos tienen una variabilidad media (CV = 16.91%) y una asimetría positiva baja (0.46), es decir, sus datos están bastante equilibrados, con muy pocos valores altos que se alejan del resto.
Las cinco variables de calificación (calificación, aroma, apariencia, paladar y sabor) se comportan parecido entre sí: todas tienen asimetría negativa (entre -0.84 y -1.02). Esto muestra que la mayoría de las cervezas reciben calificaciones altas (entre 3.5 y 4.5 sobre 5), y solo unas pocas reciben calificaciones bajas. Todas tienen también curtosis positiva, y apariencia es la que tiene los datos más juntos (1.74) y la menor variabilidad (CV = 16.04%), o sea, es la calificación en la que más se ponen de acuerdo los evaluadores. En cambio, calificación general es la que tiene más variabilidad (CV = 18.89%) y la asimetría negativa más fuerte (-1.02).
Esta sección trabaja con la variable precio.
variable_agrupada <- "precio"
x <- cerveza[[variable_agrupada]]
n <- length(x)
k <- ceiling(1 + 3.322 * log10(n))
rango <- max(x) - min(x)
amplitud <- rango / k
limites <- seq(min(x), max(x), by = amplitud)
limites[length(limites)] <- max(x) + 1e-9
intervalos <- cut(x, breaks = limites, right = FALSE, include.lowest = TRUE)
tabla <- as.data.frame(table(intervalos))
names(tabla) <- c("intervalo", "ni")
tabla$Li <- limites[-length(limites)]
tabla$Ls <- limites[-1]
tabla$xi <- (tabla$Li + tabla$Ls) / 2
tabla$hi <- tabla$ni / n
tabla$Ni <- cumsum(tabla$ni)
tabla$Hi <- cumsum(tabla$hi)
knitr::kable(tabla[, c("intervalo", "Li", "Ls", "xi", "ni", "hi", "Ni", "Hi")],
digits = 4,
caption = paste("Tabla de distribución de frecuencias -", variable_agrupada))
| intervalo | Li | Ls | xi | ni | hi | Ni | Hi |
|---|---|---|---|---|---|---|---|
| [2.37,2.61) | 2.3700 | 2.6132 | 2.4916 | 674 | 0.0004 | 674 | 0.0004 |
| [2.61,2.86) | 2.6132 | 2.8564 | 2.7348 | 4067 | 0.0026 | 4741 | 0.0030 |
| [2.86,3.1) | 2.8564 | 3.0995 | 2.9780 | 12118 | 0.0076 | 16859 | 0.0106 |
| [3.1,3.34) | 3.0995 | 3.3427 | 3.2211 | 27506 | 0.0173 | 44365 | 0.0280 |
| [3.34,3.59) | 3.3427 | 3.5859 | 3.4643 | 45047 | 0.0284 | 89412 | 0.0564 |
| [3.59,3.83) | 3.5859 | 3.8291 | 3.7075 | 68328 | 0.0431 | 157740 | 0.0994 |
| [3.83,4.07) | 3.8291 | 4.0723 | 3.9507 | 97011 | 0.0611 | 254751 | 0.1606 |
| [4.07,4.32) | 4.0723 | 4.3155 | 4.1939 | 116909 | 0.0737 | 371660 | 0.2342 |
| [4.32,4.56) | 4.3155 | 4.5586 | 4.4370 | 136622 | 0.0861 | 508282 | 0.3204 |
| [4.56,4.8) | 4.5586 | 4.8018 | 4.6802 | 156811 | 0.0988 | 665093 | 0.4192 |
| [4.8,5.04) | 4.8018 | 5.0450 | 4.9234 | 157873 | 0.0995 | 822966 | 0.5187 |
| [5.04,5.29) | 5.0450 | 5.2882 | 5.1666 | 155878 | 0.0982 | 978844 | 0.6169 |
| [5.29,5.53) | 5.2882 | 5.5314 | 5.4098 | 152753 | 0.0963 | 1131597 | 0.7132 |
| [5.53,5.77) | 5.5314 | 5.7745 | 5.6530 | 130084 | 0.0820 | 1261681 | 0.7952 |
| [5.77,6.02) | 5.7745 | 6.0177 | 5.8961 | 108164 | 0.0682 | 1369845 | 0.8634 |
| [6.02,6.26) | 6.0177 | 6.2609 | 6.1393 | 87416 | 0.0551 | 1457261 | 0.9185 |
| [6.26,6.5) | 6.2609 | 6.5041 | 6.3825 | 59394 | 0.0374 | 1516655 | 0.9559 |
| [6.5,6.75) | 6.5041 | 6.7473 | 6.6257 | 37819 | 0.0238 | 1554474 | 0.9797 |
| [6.75,6.99) | 6.7473 | 6.9905 | 6.8689 | 21128 | 0.0133 | 1575602 | 0.9931 |
| [6.99,7.23) | 6.9905 | 7.2336 | 7.1120 | 8446 | 0.0053 | 1584048 | 0.9984 |
| [7.23,7.48) | 7.2336 | 7.4768 | 7.3552 | 2327 | 0.0015 | 1586375 | 0.9998 |
| [7.48,7.72] | 7.4768 | 7.7200 | 7.5984 | 239 | 0.0002 | 1586614 | 1.0000 |
Con una base de 1.586.614 datos, la regla de Sturges arroja 22 intervalos de amplitud aproximada 0.243, cubriendo el rango de precios entre 2.37 y 7.72.
\[\bar{x} = \sum x_i \cdot h_i\]
media_agrupada <- sum(tabla$xi * tabla$hi)
media_agrupada
## [1] 5.000018
\[S^2 = \frac{\sum n_i (x_i - \bar{x})^2}{n - 1}\]
varianza_agrupada <- sum(tabla$ni * (tabla$xi - media_agrupada)^2) / (n - 1)
varianza_agrupada
## [1] 0.7880641
sqrt(varianza_agrupada)
## [1] 0.8877298
calcular_percentil_agrupado <- function(tabla, n, p) {
objetivo <- p * n
fila <- which(tabla$Ni >= objetivo)[1]
Li <- tabla$Li[fila]
Ls <- tabla$Ls[fila]
amp <- Ls - Li
ni <- tabla$ni[fila]
N_anterior <- ifelse(fila == 1, 0, tabla$Ni[fila - 1])
Li + ((objetivo - N_anterior) / ni) * amp
}
mediana_agrupada <- calcular_percentil_agrupado(tabla, n, 0.5)
mediana_agrupada
## [1] 4.999314
Q1_agrupado <- calcular_percentil_agrupado(tabla, n, 0.25)
Q3_agrupado <- calcular_percentil_agrupado(tabla, n, 0.75)
data.frame(Q1 = Q1_agrupado, Q3 = Q3_agrupado)
## Q1 Q3
## 1 4.359942 5.64047
valor_referencia <- media_agrupada
calcular_pct_menor_que <- function(tabla, n, valor) {
fila <- which(tabla$Li <= valor & valor < tabla$Ls)[1]
Li <- tabla$Li[fila]
Ls <- tabla$Ls[fila]
amp <- Ls - Li
ni <- tabla$ni[fila]
N_anterior <- ifelse(fila == 1, 0, tabla$Ni[fila - 1])
N_valor <- N_anterior + ((valor - Li) / amp) * ni
N_valor / n
}
proporcion <- calcular_pct_menor_que(tabla, n, valor_referencia)
proporcion
## [1] 0.5002881
proporcion * 100
## [1] 50.02881
El 50.03% de las cervezas tienen un precio menor al precio promedio (5.00). Esto va de acuerdo con lo que ya se vio antes: la variable precio está muy bien repartida, casi sin inclinarse hacia ningún lado.
hist(x, breaks = limites, col = "steelblue", border = "white",
main = paste("Distribución de", variable_agrupada),
xlab = variable_agrupada, ylab = "Frecuencia absoluta")
abline(v = media_agrupada, col = "red", lwd = 2, lty = 2)
legend("topright", legend = "Media", col = "red", lty = 2, lwd = 2)
El histograma confirma lo que ya se vio con la asimetría y la curtosis: la distribución del precio es plana y bastante pareja, sin un intervalo que tenga muchas más cervezas que los demás.
individuales <- resumen_indicadores[resumen_indicadores$variable == variable_agrupada, ]
comparacion <- data.frame(
indicador = c("Media", "Mediana", "Q1", "Q3", "Varianza"),
datos_individuales = c(individuales$media, individuales$mediana,
individuales$Q1, individuales$Q3, individuales$varianza),
datos_agrupados = c(media_agrupada, mediana_agrupada,
Q1_agrupado, Q3_agrupado, varianza_agrupada)
)
knitr::kable(comparacion, digits = 4,
caption = "Comparación de indicadores: individuales vs. agrupados")
| indicador | datos_individuales | datos_agrupados |
|---|---|---|
| Media | 5.000 | 5.0000 |
| Mediana | 5.000 | 4.9993 |
| Q1 | 4.360 | 4.3599 |
| Q3 | 5.640 | 5.6405 |
| Varianza | 0.784 | 0.7881 |
Los resultados con datos individuales y con datos agrupados son casi iguales: la media pasa de 5.0000 a 5.0000, la mediana de 5.0000 a 4.9993, el primer cuartil de 4.3600 a 4.3599, el tercer cuartil de 5.6400 a 5.6405, y la varianza de 0.7840 a 0.7881. Las pequeñas diferencias se dan porque el método agrupado supone que los datos se reparten igual dentro de cada intervalo, mientras que el cálculo individual usa cada dato tal cual es. Que los dos métodos den resultados tan parecidos muestra que la tabla de distribución de frecuencias (con 22 intervalos y más de 1.5 millones de datos) refleja bien el comportamiento real de la variable.
El precio es la variable más pareja y bien repartida de la base de datos, con una variabilidad moderada (CV = 17.71%) y sin inclinarse hacia ningún lado, lo que sugiere que los precios entre marcas y orígenes no varían demasiado. El alcohol y las calorías, en cambio, son las que más varían y las que más se inclinan hacia valores altos, lo que muestra que existe un grupo de cervezas fuertes y con muchas calorías que se aleja del resto. Las variables de calificación (calificación, aroma, apariencia, paladar y sabor) siempre se inclinan hacia valores bajos en su asimetría, lo que quiere decir que la mayoría de las cervezas reciben notas altas, entre 3.5 y 4.5, y son pocas las que reciben notas bajas. Apariencia es la calificación en la que más se ponen de acuerdo los evaluadores, mientras que calificación general es en la que más varían las opiniones. Por último, al comparar el cálculo hecho con los datos individuales y el hecho con datos agrupados para el precio, los resultados salieron casi iguales, con diferencias muy pequeñas que se deben solo a que trabajar con intervalos es menos exacto que trabajar con cada dato por separado.