1 Introducción

Este informe resuelve el ejercicio 6 con la base IOP_1212_01_D.sav. El literal a solicita tablas de frecuencias de la administración de los ingresos del hogar (P29) y de la satisfacción con la vida familiar (P40). El literal b solicita intervalos de clase y tablas para la edad considerada ideal para que una mujer (P1) y un hombre (P2) se casen.

Las edades son opiniones de las personas encuestadas: no corresponden a sus edades personales ni a edades de matrimonio observadas. Se presentan frecuencias sin ponderación, de acuerdo con la tarea. No se realiza inferencia poblacional ni se presupone un diseño muestral que no fue proporcionado.

2 Objetivos

Objetivo general: Describir las cuatro variables mediante tablas de frecuencias y agrupación de edades en intervalos de clase.

3 Importación y criterios de análisis

Guarde el .Rmd y el .sav en la misma carpeta. Instale una sola vez install.packages(c("haven", "rmarkdown", "knitr")) y pulse Knit → Knit to HTML en RStudio.

Se leen los códigos y etiquetas originales con haven::read_sav(user_na = TRUE). Para las edades, haven::zap_missing() convierte a NA los valores perdidos definidos en SPSS. Cada variable se analiza con sus propios casos válidos: no se eliminan filas por faltantes en otras preguntas.

# IMPORTACIÓN: conservar códigos originales y convertir perdidos SPSS.
if (!requireNamespace("haven", quietly = TRUE)) {
  stop('Instale haven en la consola: install.packages("haven")')
}
base_original <- haven::read_sav(archivo, user_na = TRUE)
names(base_original) <- toupper(trimws(names(base_original)))
stopifnot(all(c("P29", "P40", "P1", "P2") %in% names(base_original)))
N <- nrow(base_original)

# TABLAS CATEGÓRICAS: cada variable utiliza su propio denominador válido.
tabla_categoria <- function(variable, codigos_validos, ordinal = FALSE) {
  original <- base_original[[variable]]
  etiquetas <- attr(original, "labels")
  codigos <- sort(unique(c(as.numeric(etiquetas), as.numeric(original))))
  codigos <- codigos[!is.na(codigos)]
  limpios <- as.numeric(haven::zap_missing(original))
  validos <- !is.na(limpios) & limpios %in% codigos_validos
  n_validos <- sum(validos)
  if (n_validos == 0) stop("No hay respuestas válidas en ", variable)
  fi <- vapply(codigos, function(k) sum(as.numeric(original) == k, na.rm = TRUE), numeric(1))
  nombres <- vapply(codigos, function(k) {
    j <- match(k, as.numeric(etiquetas))
    if (is.na(j)) paste("Código", k) else names(etiquetas)[j]
  }, character(1))
  valido <- codigos %in% codigos_validos
  tabla <- data.frame(Codigo = as.character(codigos), Respuesta = nombres,
                      fi = fi, Porcentaje_total = 100 * fi / N,
                      Porcentaje_valido = ifelse(valido, 100 * fi / n_validos, NA_real_))
  if (ordinal) {
    acum <- cumsum(ifelse(valido, fi, 0))
    tabla$Porcentaje_valido_acumulado <- ifelse(valido, 100 * acum / n_validos, NA_real_)
  }
  n_sistema <- sum(is.na(as.numeric(original)))
  if (n_sistema > 0) {
    extra <- tabla[1, , drop = FALSE]
    extra[1, ] <- NA
    extra$Codigo <- "NA"
    extra$Respuesta <- "Sin dato en el archivo (perdido de sistema)"
    extra$fi <- n_sistema
    extra$Porcentaje_total <- 100 * n_sistema / N
    tabla <- rbind(tabla, extra)
  }
  total <- tabla[1, , drop = FALSE]
  total[1, ] <- NA
  total$Codigo <- ""
  total$Respuesta <- "TOTAL"
  total$fi <- N
  total$Porcentaje_total <- 100
  total$Porcentaje_valido <- 100
  tabla <- rbind(tabla, total)
  rownames(tabla) <- NULL
  stopifnot(sum(tabla$fi[-nrow(tabla)]) == N,
            abs(sum(tabla$Porcentaje_valido[-nrow(tabla)], na.rm = TRUE) - 100) < 1e-8)
  list(tabla = tabla, n_validos = n_validos,
       no_validos = N - n_validos, n_sistema = n_sistema)
}
P29 <- tabla_categoria("P29", 1:5)
P40 <- tabla_categoria("P40", 1:7, ordinal = TRUE)

# CLASES DE EDAD: límites reales para edades registradas en años enteros.
tabla_edad <- function(variable) {
  x <- as.numeric(haven::zap_missing(base_original[[variable]]))
  # El diccionario del SAV define 99 como perdido en P1 y P2.
  stopifnot(!any(x == 99, na.rm = TRUE))
  x <- x[!is.na(x)]
  stopifnot(length(x) > 1, all(is.finite(x)), all(x == floor(x)))
  n <- length(x)
  minimo <- min(x)
  maximo <- max(x)
  rango <- maximo - minimo
  k_teorico <- 1 + log2(n)
  k_sturges <- ceiling(k_teorico)
  # Con límites reales, la cobertura total es máximo - mínimo + 1.
  cobertura <- rango + 1
  amplitud <- max(1, ceiling(cobertura / k_sturges))
  k_final <- ceiling(cobertura / amplitud)
  limites <- minimo - 0.5 + (0:k_final) * amplitud
  grupos <- cut(x, breaks = limites, right = FALSE, include.lowest = TRUE)
  fi <- as.numeric(table(grupos))
  li <- head(limites, -1)
  ls <- tail(limites, -1)
  tabla <- data.frame(
    Clase = seq_len(k_final),
    Intervalo_anos = paste(li + .5, ls - .5, sep = "–"),
    Limite_real_inferior = li, Limite_real_superior = ls,
    Marca_clase = (li + ls)/2, fi = fi, Fi = cumsum(fi),
    hi = fi/n, Porcentaje_valido = 100 * fi/n,
    Porcentaje_valido_acumulado = 100 * cumsum(fi)/n,
    Porcentaje_total = 100 * fi/N
  )
  stopifnot(!anyNA(grupos), sum(fi) == n,
            abs(sum(tabla$hi) - 1) < 1e-10, tail(tabla$Fi, 1) == n)
  parametros <- data.frame(Variable = variable, N_total = N, n_validos = n,
                           Perdidos = N - n, Minimo = minimo, Maximo = maximo,
                           Rango = rango, Cobertura_real = cobertura,
                           Sturges_sin_redondear = k_teorico,
                           Clases_Sturges = k_sturges, Amplitud = amplitud,
                           Clases_finales = k_final)
  list(tabla = tabla, parametros = parametros, x = x)
}
P1 <- tabla_edad("P1")
P2 <- tabla_edad("P2")
parametros_edades <- rbind(P1$parametros, P2$parametros)
control <- data.frame(Variable = c("P29", "P40", "P1", "P2"), Total = N,
                       Validos = c(P29$n_validos, P40$n_validos, length(P1$x), length(P2$x)),
                       No_validos = c(P29$no_validos, P40$no_validos, N-length(P1$x), N-length(P2$x)))

# Funciones de presentación: no modifican los valores originales.
redondear <- function(tabla, digitos = 2) {
  numericas <- vapply(tabla, is.numeric, logical(1))
  tabla[numericas] <- lapply(tabla[numericas], round, digits = digitos)
  tabla
}
cat_interpretacion <- function(objeto) {
  t <- objeto$tabla
  v <- which(!is.na(t$Porcentaje_valido) & t$Respuesta != "TOTAL")
  j <- v[which.max(t$fi[v])]
  paste0('La respuesta más frecuente es «', t$Respuesta[j], '»: ', t$fi[j],
         ' respuestas (', round(t$Porcentaje_valido[j], 2),
         ' % de las respuestas válidas).')
}
edad_interpretacion <- function(objeto) {
  t <- objeto$tabla
  j <- which.max(t$fi)
  paste0('La clase modal es ', t$Intervalo_anos[j], ' años: ', t$fi[j],
         ' respuestas (', round(t$Porcentaje_valido[j], 2),
         ' % de las edades válidas). La mediana sin agrupar es ', median(objeto$x), ' años.')
}
Tabla 1. Control de respuestas por variable
Variable Total Validos No_validos
P29 1203 677 526
P40 1203 1191 12
P1 1203 1161 42
P2 1203 1162 41

Nota. La base contiene 1203 registros. En P29 hay 513 perdidos de sistema y 13 respuestas “No contesta”. En P40 hay una respuesta “No sabe” y 11 “No contesta”. En P1 y P2 se excluyen, respectivamente, 42 y 41 códigos 99. El tratamiento se sustenta en las etiquetas y metadatos del archivo.

4 Literal a. Tablas de frecuencias categóricas

fi es la frecuencia absoluta. El porcentaje total se calcula como 100 × fi / 1203. El porcentaje válido usa solo las respuestas sustantivas de cada pregunta. El símbolo “—” indica que el porcentaje no aplica a esa fila. Las sumas pueden diferir de 100 % por redondeo.

4.1 P29. Administración de los ingresos

Tabla 2. Administración de ingresos con el cónyuge o pareja
Código Respuesta fi % total % válido
1 Yo administro todo el dinero y doy a mi pareja su parte 185 15.38 27.33
2 Mi cónyuge/pareja administra todo el dinero y me da mi parte 126 10.47 18.61
3 Juntamos todo el dinero y cada uno toma lo que necesita 264 21.95 39.00
4 Juntamos parte del dinero y mantenemos separado el resto 66 5.49 9.75
5 Mantenemos nuestro dinero separado 36 2.99 5.32
9 No contesta 13 1.08
NA Sin dato en el archivo (perdido de sistema) 513 42.64
TOTAL 1203 100.00 100.00

Nota. Fuente: elaboración propia con P29. Denominador válido: 677. No se presentan acumulados porque es una variable nominal: sus categorías no tienen un orden cuantitativo.

Interpretación: La respuesta más frecuente es «Juntamos todo el dinero y cada uno toma lo que necesita»: 264 respuestas (39 % de las respuestas válidas). El porcentaje válido describe a quienes dieron una respuesta sustantiva y no a los 1203 registros completos de la base.

4.2 P40. Satisfacción con la vida familiar

Tabla 3. Satisfacción con la vida familiar
Código Respuesta fi % total % válido % válido acum.
1 Me siento completamente satisfecho/a 161 13.38 13.52 13.52
2 Muy satisfecho/a 406 33.75 34.09 47.61
3 Bastante satisfecho/a 409 34.00 34.34 81.95
4 Ni satisfecho ni insatisfecho/a 175 14.55 14.69 96.64
5 Bastante insatisfecho/a 25 2.08 2.10 98.74
6 Muy insatisfecho/a 11 0.91 0.92 99.66
7 Completamente insatisfecho/a 4 0.33 0.34 100.00
8 No sabe 1 0.08
9 No contesta 11 0.91
TOTAL 1203 100.00 100.00

Nota. Fuente: elaboración propia con P40. Denominador válido: 1191. Se conserva el orden ordinal de los códigos 1–7, desde completa satisfacción hasta completa insatisfacción. El acumulado avanza en ese sentido.

Interpretación: La respuesta más frecuente es «Bastante satisfecho/a»: 409 respuestas (34.34 % de las respuestas válidas). Las categorías 1–3 reúnen 976 respuestas, equivalentes a 81.95 % de las válidas; en conjunto expresan algún grado de satisfacción.

5 Literal b. Intervalos de clase para edades

5.1 Método de agrupación

Se adopta Sturges como punto de partida: \(k^*=1+\log_2(n)\) y \(k_0=\lceil k^*\rceil\). Como las edades están registradas en años enteros, se usan límites reales desde mínimo − 0,5 hasta máximo + 0,5. Así:

\[ R=x_{max}-x_{min},\quad C=R+1,\quad h=\left\lceil\frac{C}{k_0}\right\rceil,\quad k_{final}=\left\lceil\frac{C}{h}\right\rceil. \]

Aquí C es la cobertura entre límites reales, h la amplitud entera y k final el número de clases necesarias para cubrir los datos. Redondear la amplitud puede cambiar el número final de clases respecto de Sturges. Esta decisión evita añadir clases vacías más allá de la cobertura necesaria. Se conservan las clases vacías dentro del rango observado.

Tabla 4. Parámetros para construir los intervalos
P1: mujer P2: hombre
N_total 1203.0000 1203.0000
n_validos 1161.0000 1162.0000
Perdidos 42.0000 41.0000
Minimo 15.0000 18.0000
Maximo 60.0000 42.0000
Rango 45.0000 24.0000
Cobertura_real 46.0000 25.0000
Sturges_sin_redondear 11.1812 11.1824
Clases_Sturges 12.0000 12.0000
Amplitud 4.0000 3.0000
Clases_finales 12.0000 9.0000

Para P1, n = 1161, k₀ = 12, C = 46 y h = ⌈46/12⌉ = 4 años; se obtienen 12 clases. Para P2, n = 1162, k₀ = 12, C = 25 y h = ⌈25/12⌉ = 3 años; se obtienen 9 clases. Se agrupa cada variable de manera independiente.

5.2 P1. Edad ideal para que una mujer se case

Tabla 5. Distribución agrupada de P1
Edad (años) Marca xi fi Fi hi % válido % acum.
15–18 16.5 13 13 0.0112 1.12 1.12
19–22 20.5 147 160 0.1266 12.66 13.78
23–26 24.5 536 696 0.4617 46.17 59.95
27–30 28.5 401 1097 0.3454 34.54 94.49
31–34 32.5 21 1118 0.0181 1.81 96.30
35–38 36.5 38 1156 0.0327 3.27 99.57
39–42 40.5 4 1160 0.0034 0.34 99.91
43–46 44.5 0 1160 0.0000 0.00 99.91
47–50 48.5 0 1160 0.0000 0.00 99.91
51–54 52.5 0 1160 0.0000 0.00 99.91
55–58 56.5 0 1160 0.0000 0.00 99.91
59–62 60.5 1 1161 0.0009 0.09 100.00

Nota. Fuente: elaboración propia con P1. Total válido: 1161; perdidos: 42. Los intervalos escritos como 15–18 incluyen ambas edades; sus límites reales son [14,5; 18,5). La marca de clase es el punto medio. Fi y el porcentaje acumulado se calculan de menor a mayor edad; hi = fi / n. La suma de fi es 1161 y la de hi es 1.

Interpretación: La clase modal es 23–26 años: 536 respuestas (46.17 % de las edades válidas). La mediana sin agrupar es 25 años. El valor 60 está presente una vez y se conserva, pues no figura como perdido; explica que se incluyan intervalos altos, algunos con frecuencia cero. No se elimina por ser poco frecuente.

5.3 P2. Edad ideal para que un hombre se case

Tabla 6. Distribución agrupada de P2
Edad (años) Marca xi fi Fi hi % válido % acum.
18–20 19 44 44 0.0379 3.79 3.79
21–23 22 35 79 0.0301 3.01 6.80
24–26 25 278 357 0.2392 23.92 30.72
27–29 28 237 594 0.2040 20.40 51.12
30–32 31 409 1003 0.3520 35.20 86.32
33–35 34 134 1137 0.1153 11.53 97.85
36–38 37 9 1146 0.0077 0.77 98.62
39–41 40 15 1161 0.0129 1.29 99.91
42–44 43 1 1162 0.0009 0.09 100.00

Nota. Fuente: elaboración propia con P2. Total válido: 1162; perdidos: 41. El primer intervalo 18–20 tiene límites reales [17,5; 20,5). La suma de fi es 1162 y la de hi es 1. El último intervalo 42–44 cubre el máximo observado, 42, aunque el límite superior de clase sea mayor.

Interpretación: La clase modal es 30–32 años: 409 respuestas (35.2 % de las edades válidas). La mediana sin agrupar es 29 años.

6 Conclusiones

  1. La separación de no respuesta, perdidos de sistema y valores sustantivos evita usar denominadores incorrectos o interpretar el código 99 como una edad.
  2. En P29 predomina juntar todo el dinero y tomar lo necesario; en P40 predomina la respuesta “Bastante satisfecho/a”, seguida de cerca por “Muy satisfecho/a”.
  3. Las clases modales son 23–26 años para P1 y 30–32 para P2. Sus amplitudes son distintas, por lo que no deben compararse sus porcentajes como si fueran intervalos equivalentes.
  4. Las medianas sin agrupar son 25 años para P1 y 29 para P2. Descriptivamente, la edad ideal expresada para los hombres es mayor. Esto no constituye una prueba de diferencia estadísticamente significativa.

7 Fuentes

8 Reproducibilidad

## R version 4.5.2 (2025-10-31 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
## 
## Matrix products: default
##   LAPACK version 3.12.1
## 
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8  LC_CTYPE=Spanish_Colombia.utf8   
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C                     
## [5] LC_TIME=Spanish_Colombia.utf8    
## 
## time zone: America/Bogota
## tzcode source: internal
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## loaded via a namespace (and not attached):
##  [1] vctrs_0.7.1       cli_3.6.5         knitr_1.52        rlang_1.1.7      
##  [5] xfun_0.56         otel_0.2.0        forcats_1.0.1     haven_2.5.5      
##  [9] jsonlite_2.0.0    glue_1.8.0        htmltools_0.5.9   sass_0.4.10      
## [13] hms_1.1.4         rmarkdown_2.32    evaluate_1.0.5    jquerylib_0.1.4  
## [17] tibble_3.3.1      tzdb_0.5.0        fastmap_1.2.0     yaml_2.3.12      
## [21] lifecycle_1.0.5   compiler_4.5.2    pkgconfig_2.0.3   rstudioapi_0.18.0
## [25] digest_0.6.39     R6_2.6.1          readr_2.2.0       pillar_1.11.1    
## [29] magrittr_2.0.4    bslib_0.10.0      tools_4.5.2       cachem_1.1.0