Markdown Estadística descriptiva
Variable cualitativa nominal: Región
Autor: Samantha Pacheco
Fecha: 11/11/2026

0.- Carga de librerías

library(dplyr)
library(knitr)
library(kableExtra)

1.- Leer Datos

load("pollution_2000_2023.RData")
datos <- as.data.frame(pollution_2000_2023)

n <- nrow(datos)   # tamaño muestral
n
## [1] 665414

El tamaño muestral es n = 665,414.

2.- Selección de la variable

Cada registro trae su dirección, ciudad, condado y estado. Para resumir un conjunto de datos tan extenso, las direcciones se agrupan en condados, los condados en estados y los estados en regiones (clasificación del Censo de EE.UU.). La variable que se analiza es la Región.

estado <- trimws(as.character(datos$State))

idx <- match(tolower(estado), tolower(state.name))
Region <- as.character(state.region[idx])


Region <- recode(Region,
                 "Northeast"     = "Noreste",
                 "South"         = "Sur",
                 "North Central" = "Midwest",
                 "Midwest"       = "Midwest",
                 "West"          = "Oeste")
Region[is.na(Region)] <- "Otra / Exterior"   # p. ej. Country Of Mexico

# Cuántas categorías hay en cada nivel de la jerarquía
jerarquia <- data.frame(
  Nivel = c("Dirección", "Ciudad", "Condado", "Estado", "Región"),
  `Categorías distintas` = c(n_distinct(datos$Address),
                             n_distinct(paste(datos$City, datos$County, estado)),
                             n_distinct(paste(datos$County, estado)),
                             n_distinct(estado),
                             n_distinct(Region)),
  check.names = FALSE
)
kable(jerarquia, align = "lr", format.args = list(big.mark = ",")) %>%
  kable_styling(full_width = FALSE)
Nivel Categorías distintas
Dirección 221
Ciudad 181
Condado 143
Estado 48
Región 5

3.- Conteo (frecuencias)

t <- table(Region)
conteo <- data.frame(Region = names(t), ni = as.numeric(t),
                     stringsAsFactors = FALSE)
conteo <- conteo[order(-conteo$ni), ]    # de mayor a menor
rownames(conteo) <- NULL
conteo
##            Region     ni
## 1           Oeste 296702
## 2             Sur 150716
## 3         Noreste 127933
## 4         Midwest  79546
## 5 Otra / Exterior  10517

4.- Tabla de distribución de frecuencias

Los cálculos se hacen con todos los decimales y solo se redondea al mostrar (hi con 4 decimales y %hi con 2 decimales). Por eso los totales dan exactamente n, 1.0000 y 100.00%.

# Tabla con todos los decimales (con esta se construyen los gráficos)
tdf_final_regiones <- conteo
tdf_final_regiones$hi     <- tdf_final_regiones$ni / n
tdf_final_regiones$pct_hi <- 100 * tdf_final_regiones$hi

# Verificación de totales
stopifnot(sum(tdf_final_regiones$ni) == n,
          isTRUE(all.equal(sum(tdf_final_regiones$hi), 1)),
          isTRUE(all.equal(sum(tdf_final_regiones$pct_hi), 100)))

# Presentación (redondeo solo aquí)
fmt <- function(v) format(v, big.mark = ",", scientific = FALSE, trim = TRUE)

salida <- data.frame(
  i   = c(seq_len(nrow(tdf_final_regiones)), ""),
  v   = c(tdf_final_regiones$Region, "TOTAL"),
  ni  = c(fmt(tdf_final_regiones$ni), fmt(sum(tdf_final_regiones$ni))),
  hi  = sprintf("%.4f", c(tdf_final_regiones$hi, sum(tdf_final_regiones$hi))),
  pct = paste0(sprintf("%.2f", c(tdf_final_regiones$pct_hi,
                                 sum(tdf_final_regiones$pct_hi))), "%"),
  stringsAsFactors = FALSE
)
names(salida) <- c("i", "Región", "ni", "hi", "%hi")

kable(salida, align = "clrrr", row.names = FALSE,
      caption = "Tabla 1. Distribución de frecuencias de la variable Región") %>%
  kable_styling(bootstrap_options = c("striped", "condensed"), full_width = FALSE) %>%
  row_spec(0, background = "#404040", color = "white") %>%
  row_spec(nrow(salida), bold = TRUE, background = "#D9D9D9")
Tabla 1. Distribución de frecuencias de la variable Región
i Región ni hi %hi
1 Oeste 296,702 0.4459 44.59%
2 Sur 150,716 0.2265 22.65%
3 Noreste 127,933 0.1923 19.23%
4 Midwest 79,546 0.1195 11.95%
5 Otra / Exterior 10,517 0.0158 1.58%
TOTAL 665,414 1.0000 100.00%

Fuente: Índice de Calidad del Aire (ICA), EE. UU., 2000–2023.
Conjunto de datos: https://www.kaggle.com/datasets/guslovesmath/us-pollution-data-200-to-2022/data
Autor: Samantha Pacheco
Nota: “Otra / Exterior” agrupa los registros que no pertenecen a ninguno de los 50 estados (Distrito de Columbia).

5.- Gráficos de distribución de frecuencias

  • Gráficas 1 y 3 (locales): el eje vertical se ajusta a las barras de la tabla.
  • Gráficas 2 y 4 (globales): el eje vertical llega hasta el tamaño muestral (n en ni, y 100% en hi) para poder comparar cada barra con el total. La línea punteada marca ese total.
  • Gráfica 5: diagrama circular con los porcentajes de la tabla.
grises <- gray.colors(nrow(tdf_final_regiones), start = 0.25, end = 0.85)

armar_titulo <- function(num, linea2) {
  paste0("Gráfica Nro. ", num, "\n", linea2,
         "\npara el análisis estadístico de la contaminación de EE. UU.")
}

graf_barras <- function(num, tipo, alcance) {
  y       <- tdf_final_regiones[[tipo]]
  nombres <- tdf_final_regiones$Region

  if (tipo == "ni") {
    nom    <- "absoluta (ni)"
    ylab   <- "Frecuencia absoluta (ni)"
    etq    <- format(y, big.mark = ",", scientific = FALSE, trim = TRUE)
    tope_g <- n
    l_tope <- paste0("n = ", format(n, big.mark = ",", scientific = FALSE))
    f_eje  <- function(v) format(v, big.mark = ",", scientific = FALSE, trim = TRUE)
  } else {
    nom    <- "relativa (hi)"
    ylab   <- "Frecuencia relativa (hi)"
    etq    <- sprintf("%.4f", y)
    tope_g <- 1
    l_tope <- "Total = 1"
    f_eje  <- function(v) sprintf("%.1f", v)
  }

  tope <- if (alcance == "global") tope_g else max(y) * 1.15

  par(mar = c(5, 7, 7, 2))
  b <- barplot(y, names.arg = nombres, ylim = c(0, tope),
               col = grises, border = "black", axes = FALSE, las = 1,
               main = armar_titulo(num, paste0("Distribución de la frecuencia ", nom,
                                               " ", alcance, " de la variable Región")),
               xlab = "Región", ylab = "", cex.main = 1.05)

  ticks <- pretty(c(0, tope))
  axis(2, at = ticks, labels = f_eje(ticks), las = 1)
  mtext(ylab, side = 2, line = 5)
  text(b, y, labels = etq, pos = 3, cex = 0.9, xpd = TRUE)

  if (alcance == "global") {
    abline(h = tope, lty = 2, col = "gray30")
    text(par("usr")[2], tope, labels = l_tope, adj = c(1.05, 1.6), cex = 0.9)
  }
}

graf_circular <- function(num) {
  par(mar = c(2, 2, 7, 2))
  etq <- paste0(sprintf("%.2f", tdf_final_regiones$pct_hi), " %")
  pie(tdf_final_regiones$ni, labels = etq, col = grises, border = "black",
      clockwise = TRUE, init.angle = 90, radius = 0.85, cex = 1.05,
      main = armar_titulo(num, "Distribución circular de la variable Región"),
      cex.main = 1.05)
  legend("right", legend = tdf_final_regiones$Region, fill = grises,
         title = "Leyenda", bty = "o", cex = 1)
}

# ---- Versión con hi en porcentaje (reemplaza a la anterior) ----
graf_barras <- function(num, tipo, alcance) {
  y       <- tdf_final_regiones[[tipo]]
  nombres <- tdf_final_regiones$Region

  if (tipo == "ni") {
    nom    <- "absoluta (ni)"
    ylab   <- "Frecuencia absoluta (ni)"
    etq    <- format(y, big.mark = ",", scientific = FALSE, trim = TRUE)
    tope_g <- n
    l_tope <- paste0("n = ", format(n, big.mark = ",", scientific = FALSE))
    f_eje  <- function(v) format(v, big.mark = ",", scientific = FALSE, trim = TRUE)
  } else {
    y      <- tdf_final_regiones$pct_hi     # hi expresado en porcentaje
    nom    <- "relativa porcentual (%hi)"
    ylab   <- "Frecuencia relativa porcentual (%hi)"
    etq    <- paste0(sprintf("%.2f", y), "%")
    tope_g <- 100
    l_tope <- "Total = 100%"
    f_eje  <- function(v) paste0(v, "%")
  }

  tope <- if (alcance == "global") tope_g else max(y) * 1.15

  par(mar = c(5, 7, 7, 2))
  b <- barplot(y, names.arg = nombres, ylim = c(0, tope),
               col = grises, border = "black", axes = FALSE, las = 1,
               main = armar_titulo(num, paste0("Distribución de la frecuencia ", nom,
                                               " ", alcance, " de la variable Región")),
               xlab = "Región", ylab = "", cex.main = 1.05)

  ticks <- pretty(c(0, tope))
  axis(2, at = ticks, labels = f_eje(ticks), las = 1)
  mtext(ylab, side = 2, line = 5)
  text(b, y, labels = etq, pos = 3, cex = 0.9, xpd = TRUE)

  if (alcance == "global") {
    abline(h = tope, lty = 2, col = "gray30")
    text(par("usr")[2], tope, labels = l_tope, adj = c(1.05, 1.6), cex = 0.9)
  }
}
graf_barras(1, "ni", "local")

graf_barras(2, "ni", "global")

graf_barras(3, "hi", "local")

graf_barras(4, "hi", "global")

graf_circular(5)

6.- Medidas de tendencia central y de posición

Al ser una variable cualitativa nominal, la única medida de tendencia central aplicable es la moda. La media, la mediana y las medidas de posición (cuantiles) requieren valores numéricos u ordenados, por lo que no se pueden calcular.

max_ni <- max(tdf_final_regiones$ni)
modas  <- tdf_final_regiones$Region[tdf_final_regiones$ni == max_ni]
moda   <- modas[1]

kable(data.frame(Medida = "Moda",
                 Región = paste(modas, collapse = " ; "),
                 ni = fmt(max_ni),
                 `%hi` = paste0(sprintf("%.2f", 100 * max_ni / n), "%"),
                 check.names = FALSE),
      align = "llrr") %>%
  kable_styling(full_width = FALSE)
Medida Región ni %hi
Moda Oeste 296,702 44.59%

7.- Conclusiones

  • La región con mayor número de registros (moda) es Oeste, con 296,702 observaciones, es decir 44.59% de los n = 665,414 registros.
  • Las dos regiones más frecuentes (Oeste y Sur) reúnen 67.24% de la muestra.
  • La categoría con menos registros es Otra / Exterior, con solo 1.58%.
  • (Escribe aquí tu propia interpretación de las tablas y gráficos.)