1 Propósito del análisis

Este archivo reproduce el análisis estadístico utilizado para caracterizar los puntajes del CAPL-2 en escolares colombianos y comparar los resultados entre las seis subregiones del país: Caribe, Pacífico, Centro-Oriente, Centro sur-Amazonía, Eje cafetero-Antioquia y Llanos-Orinoquía.

El análisis incluye: caracterización sociodemográfica, disponibilidad de datos por dominio, descripción de puntajes, figuras de dominios y categorías interpretativas, pruebas de chi-cuadrado para categorías, evaluación de normalidad, comparación de puntajes mediante Kruskal-Wallis, estimación de epsilon cuadrado como tamaño del efecto y comparaciones post hoc mediante prueba de Dunn.

2 Paquetes

paquetes <- c(
  "readxl", "writexl", "dplyr", "tidyr", "stringr", "forcats",
  "ggplot2", "scales", "rstatix", "janitor", "purrr", "flextable"
)

instalar <- paquetes[!paquetes %in% rownames(installed.packages())]
if(length(instalar) > 0){
  install.packages(instalar)
}

invisible(lapply(paquetes, library, character.only = TRUE))

3 Carga de la base analítica

La base de entrada debe corresponder a la base calculada con el paquete capl, es decir, debe contener los puntajes por dominio, el puntaje total, las variables de estado de completitud y las categorías interpretativas.

# Ajustar esta ruta si el archivo está en otra carpeta.
ruta_base <- "FASE3_validacion_calculo_CAPL2_caracterizacion.xlsx"

if(!file.exists(ruta_base)){
  stop("No se encontró el archivo de entrada. Verifique la ruta_base.")
}

hojas <- readxl::excel_sheets(ruta_base)
print(hojas)
##  [1] "resultados_capl"           "validacion_estructura"    
##  [3] "validacion_variables_capl" "na_scores_capl"           
##  [5] "status_capl"               "estatus_por_dominio"      
##  [7] "status_por_region"         "disponibilidad_dominios"  
##  [9] "disponibilidad_region"     "resumen_podometro_region" 
## [11] "dias_validos_region"       "rangos_scores_capl"
# El código intenta identificar automáticamente una hoja que contenga resultados CAPL.
hoja_resultados <- hojas[stringr::str_detect(tolower(hojas), "resultado|capl|calculo")][1]

# Si no se identifica, se usa la primera hoja.
if(is.na(hoja_resultados)){
  hoja_resultados <- hojas[1]
}

resultados_capl <- readxl::read_excel(ruta_base, sheet = hoja_resultados) %>%
  janitor::clean_names()

cat("Hoja utilizada:", hoja_resultados, "\n")
## Hoja utilizada: resultados_capl
cat("Dimensiones de la base:", nrow(resultados_capl), "filas x", ncol(resultados_capl), "columnas\n")
## Dimensiones de la base: 843 filas x 107 columnas

4 Estandarización mínima de nombres clave

# Esta sección permite que el archivo funcione aunque la variable de subregión
# haya quedado nombrada como region, subregion o sub_region.

if(!"region" %in% names(resultados_capl)){
  if("subregion" %in% names(resultados_capl)){
    resultados_capl <- resultados_capl %>% rename(region = subregion)
  } else if("sub_region" %in% names(resultados_capl)){
    resultados_capl <- resultados_capl %>% rename(region = sub_region)
  } else {
    stop("No se encontró una variable de región/subregión.")
  }
}

if(!"gender" %in% names(resultados_capl)){
  if("sexo" %in% names(resultados_capl)){
    resultados_capl <- resultados_capl %>% rename(gender = sexo)
  }
}

# Orden analítico de subregiones.
orden_region <- c(
  "Caribe",
  "Pacífico",
  "Centro-Oriente",
  "Centro sur-Amazonía",
  "Eje cafetero-Antioquia",
  "Llanos-Orinoquía"
)

resultados_capl <- resultados_capl %>%
  mutate(
    region = factor(region, levels = orden_region),
    gender = as.character(gender)
  )

5 Verificación de variables requeridas

vars_requeridas <- c(
  "region", "age", "gender", "grade",
  "pc_score", "db_score", "mc_score", "ku_score", "capl_score",
  "pc_status", "db_status", "mc_status", "ku_status", "capl_status",
  "pc_interpretation", "db_interpretation", "mc_interpretation", "ku_interpretation", "capl_interpretation"
)

faltantes <- setdiff(vars_requeridas, names(resultados_capl))

if(length(faltantes) > 0){
  stop(paste("Faltan variables requeridas:", paste(faltantes, collapse = ", ")))
}

cat("Todas las variables requeridas están disponibles.\n")
## Todas las variables requeridas están disponibles.

6 Caracterización sociodemográfica

# Recodificación simple del sexo para presentación.
base_caracterizacion <- resultados_capl %>%
  mutate(
    sexo_rec = case_when(
      str_to_lower(gender) %in% c("male", "m", "masculino", "niño", "nino", "hombre") ~ "Niños",
      str_to_lower(gender) %in% c("female", "f", "femenino", "niña", "nina", "mujer") ~ "Niñas",
      TRUE ~ as.character(gender)
    )
  )

tabla_1 <- base_caracterizacion %>%
  group_by(region) %>%
  summarise(
    n = n(),
    edad_media = mean(age, na.rm = TRUE),
    edad_de = sd(age, na.rm = TRUE),
    edad_mediana = median(age, na.rm = TRUE),
    edad_q1 = quantile(age, 0.25, na.rm = TRUE),
    edad_q3 = quantile(age, 0.75, na.rm = TRUE),
    n_ninos = sum(sexo_rec == "Niños", na.rm = TRUE),
    n_ninas = sum(sexo_rec == "Niñas", na.rm = TRUE),
    pct_ninos = n_ninos / n * 100,
    pct_ninas = n_ninas / n * 100,
    .groups = "drop"
  ) %>%
  mutate(
    `Edad media ± DE` = sprintf("%.2f ± %.2f", edad_media, edad_de),
    `Edad mediana [RIQ]` = sprintf("%.0f [%.0f–%.0f]", edad_mediana, edad_q1, edad_q3),
    `Niños n (%)` = sprintf("%d (%.1f%%)", n_ninos, pct_ninos),
    `Niñas n (%)` = sprintf("%d (%.1f%%)", n_ninas, pct_ninas)
  ) %>%
  select(
    Subregión = region,
    n,
    `Edad media ± DE`,
    `Edad mediana [RIQ]`,
    `Niños n (%)`,
    `Niñas n (%)`
  )

tabla_1
## # A tibble: 6 × 6
##   Subregión               n `Edad media ± DE` `Edad mediana [RIQ]` `Niños n (%)`
##   <fct>               <int> <chr>             <chr>                <chr>        
## 1 Caribe                140 9.91 ± 1.36       10 [9–11]            0 (0.0%)     
## 2 Pacífico              140 9.99 ± 1.40       10 [9–11]            0 (0.0%)     
## 3 Centro-Oriente        140 10.01 ± 1.44      10 [9–11]            0 (0.0%)     
## 4 Centro sur-Amazonía   141 10.00 ± 1.41      10 [9–11]            0 (0.0%)     
## 5 Eje cafetero-Antio…   141 10.01 ± 1.42      10 [9–11]            0 (0.0%)     
## 6 Llanos-Orinoquía      141 10.01 ± 1.42      10 [9–11]            0 (0.0%)     
## # ℹ 1 more variable: `Niñas n (%)` <chr>
resumen_grado <- base_caracterizacion %>%
  count(grade, name = "n") %>%
  mutate(porcentaje = n / sum(n) * 100) %>%
  arrange(grade)

resumen_grado
## # A tibble: 8 × 3
##   grade     n porcentaje
##   <dbl> <int>      <dbl>
## 1     2    17      2.02 
## 2     3   140     16.6  
## 3     4   207     24.6  
## 4     5   131     15.5  
## 5     6   208     24.7  
## 6     7   111     13.2  
## 7     8     1      0.119
## 8    NA    28      3.32

7 Disponibilidad de datos válidos por dominio

tabla_2 <- tibble::tibble(
  Dominio = c(
    "Competencia física",
    "Comportamiento diario",
    "Motivación y confianza",
    "Conocimiento y comprensión",
    "CAPL total"
  ),
  status = c("pc_status", "db_status", "mc_status", "ku_status", "capl_status")
) %>%
  mutate(
    n_total = nrow(resultados_capl),
    n_valido = purrr::map_int(status, ~sum(resultados_capl[[.x]] == "complete", na.rm = TRUE)),
    pct_valido = n_valido / n_total * 100,
    n_incompleto = n_total - n_valido,
    pct_incompleto = n_incompleto / n_total * 100
  ) %>%
  select(
    Dominio,
    `n total` = n_total,
    `n válido` = n_valido,
    `% válido` = pct_valido,
    `n incompleto` = n_incompleto,
    `% incompleto` = pct_incompleto
  ) %>%
  mutate(
    across(c(`% válido`, `% incompleto`), ~round(.x, 1))
  )

tabla_2
## # A tibble: 5 × 6
##   Dominio          `n total` `n válido` `% válido` `n incompleto` `% incompleto`
##   <chr>                <int>      <int>      <dbl>          <int>          <dbl>
## 1 Competencia fís…       843        829       98.3             14            1.7
## 2 Comportamiento …       843        819       97.2             24            2.8
## 3 Motivación y co…       843        834       98.9              9            1.1
## 4 Conocimiento y …       843        839       99.5              4            0.5
## 5 CAPL total             843        816       96.8             27            3.2

8 Puntaje total CAPL-2 por subregión

tabla_3 <- resultados_capl %>%
  filter(capl_status == "complete", !is.na(capl_score), !is.na(region)) %>%
  group_by(region) %>%
  summarise(
    n = n(),
    media = mean(capl_score, na.rm = TRUE),
    de = sd(capl_score, na.rm = TRUE),
    mediana = median(capl_score, na.rm = TRUE),
    q1 = quantile(capl_score, 0.25, na.rm = TRUE),
    q3 = quantile(capl_score, 0.75, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(
    `Media ± DE` = sprintf("%.2f ± %.2f", media, de),
    `Mediana [RIQ]` = sprintf("%.2f [%.2f–%.2f]", mediana, q1, q3)
  ) %>%
  select(Subregión = region, n, `Media ± DE`, `Mediana [RIQ]`)

tabla_3
## # A tibble: 6 × 4
##   Subregión                  n `Media ± DE`  `Mediana [RIQ]`    
##   <fct>                  <int> <chr>         <chr>              
## 1 Caribe                   125 49.35 ± 7.40  49.80 [44.80–55.30]
## 2 Pacífico                 139 56.82 ± 8.14  57.10 [52.20–63.30]
## 3 Centro-Oriente           134 54.62 ± 11.42 53.05 [46.02–62.00]
## 4 Centro sur-Amazonía      138 60.18 ± 12.02 59.33 [51.15–69.07]
## 5 Eje cafetero-Antioquia   141 47.50 ± 12.73 45.30 [37.50–57.60]
## 6 Llanos-Orinoquía         139 63.55 ± 8.32  62.63 [57.25–69.62]

9 Dominios del CAPL-2 por subregión

Los dominios se expresan como porcentaje del máximo teórico para facilitar la comparación visual, dado que conocimiento y comprensión tiene un máximo de 10 puntos, mientras que los demás dominios tienen máximo de 30 puntos.

base_dominios_larga <- bind_rows(
  resultados_capl %>%
    filter(pc_status == "complete") %>%
    transmute(region, dominio = "Competencia física", puntaje = pc_score, maximo = 30),
  resultados_capl %>%
    filter(db_status == "complete") %>%
    transmute(region, dominio = "Comportamiento diario", puntaje = db_score, maximo = 30),
  resultados_capl %>%
    filter(mc_status == "complete") %>%
    transmute(region, dominio = "Motivación y confianza", puntaje = mc_score, maximo = 30),
  resultados_capl %>%
    filter(ku_status == "complete") %>%
    transmute(region, dominio = "Conocimiento y comprensión", puntaje = ku_score, maximo = 10)
) %>%
  filter(!is.na(region), !is.na(puntaje)) %>%
  mutate(
    region = factor(region, levels = orden_region),
    dominio = factor(
      dominio,
      levels = c(
        "Competencia física",
        "Comportamiento diario",
        "Motivación y confianza",
        "Conocimiento y comprensión"
      )
    ),
    porcentaje_maximo = puntaje / maximo * 100
  )

resumen_dominios_region <- base_dominios_larga %>%
  group_by(region, dominio) %>%
  summarise(
    n = n(),
    mediana = median(puntaje, na.rm = TRUE),
    q1 = quantile(puntaje, 0.25, na.rm = TRUE),
    q3 = quantile(puntaje, 0.75, na.rm = TRUE),
    mediana_porcentaje = median(porcentaje_maximo, na.rm = TRUE),
    q1_porcentaje = quantile(porcentaje_maximo, 0.25, na.rm = TRUE),
    q3_porcentaje = quantile(porcentaje_maximo, 0.75, na.rm = TRUE),
    .groups = "drop"
  )

resumen_dominios_region
## # A tibble: 24 × 9
##    region     dominio     n mediana    q1    q3 mediana_porcentaje q1_porcentaje
##    <fct>      <fct>   <int>   <dbl> <dbl> <dbl>              <dbl>         <dbl>
##  1 Caribe     Compet…   140     9     6    13.5               30            20  
##  2 Caribe     Compor…   127     9     7    12                 30            23.3
##  3 Caribe     Motiva…   138    24.4  22.1  27                 81.3          73.7
##  4 Caribe     Conoci…   140     4     3     6                 40            30  
##  5 Pacífico   Compet…   139     7.5   6    12                 25            20  
##  6 Pacífico   Compor…   139    17    12    20                 56.7          40  
##  7 Pacífico   Motiva…   140    25.0  21.9  27.4               83.2          72.9
##  8 Pacífico   Conoci…   140     7     6     9                 70            60  
##  9 Centro-Or… Compet…   128    12     9    16.5               40            30  
## 10 Centro-Or… Compor…   135    11     8    18                 36.7          26.7
## # ℹ 14 more rows
## # ℹ 1 more variable: q3_porcentaje <dbl>
figura_1 <- ggplot(
  resumen_dominios_region,
  aes(x = region, y = mediana_porcentaje)
) +
  geom_errorbar(
    aes(ymin = q1_porcentaje, ymax = q3_porcentaje),
    width = 0.18,
    linewidth = 0.45
  ) +
  geom_point(size = 2.8) +
  coord_flip() +
  facet_wrap(~ dominio, ncol = 2) +
  scale_y_continuous(
    limits = c(0, 100),
    breaks = seq(0, 100, 20),
    labels = function(x) paste0(x, "%")
  ) +
  labs(
    title = NULL,
    subtitle = "Mediana e intervalo intercuartílico expresados como porcentaje del máximo teórico",
    x = NULL,
    y = "Porcentaje del máximo teórico"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.subtitle = element_text(size = 10),
    strip.text = element_text(face = "bold", size = 10),
    axis.text.y = element_text(size = 9),
    axis.text.x = element_text(size = 9),
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank()
  )

figura_1
Figura 1. Dominios del CAPL-2 según subregión. Los puntos representan la mediana y las barras el rango intercuartílico. Los valores se expresan como porcentaje del máximo teórico de cada dominio.

Figura 1. Dominios del CAPL-2 según subregión. Los puntos representan la mediana y las barras el rango intercuartílico. Los valores se expresan como porcentaje del máximo teórico de cada dominio.

10 Categorías interpretativas del CAPL-2

orden_categorias <- c("beginning", "progressing", "achieving", "excelling")

# Función para estandarizar categorías.
normalizar_categoria <- function(x){
  x %>%
    as.character() %>%
    stringr::str_trim() %>%
    stringr::str_to_lower()
}
base_cat_capl <- resultados_capl %>%
  filter(capl_status == "complete", !is.na(capl_interpretation), !is.na(region)) %>%
  mutate(
    categoria = factor(normalizar_categoria(capl_interpretation), levels = orden_categorias),
    region = factor(region, levels = orden_region)
  )

resumen_cat_capl <- base_cat_capl %>%
  count(region, categoria, name = "n") %>%
  group_by(region) %>%
  mutate(
    porcentaje = n / sum(n) * 100
  ) %>%
  ungroup()

figura_2 <- ggplot(
  resumen_cat_capl,
  aes(x = region, y = porcentaje, fill = categoria)
) +
  geom_col(width = 0.72) +
  coord_flip() +
  scale_y_continuous(
    limits = c(0, 100),
    breaks = seq(0, 100, 25),
    labels = function(x) paste0(x, "%")
  ) +
  labs(
    title = NULL,
    x = NULL,
    y = "Proporción",
    fill = "Categoría"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    axis.text.y = element_text(size = 10),
    axis.text.x = element_text(size = 10),
    legend.position = "right",
    panel.grid.major.y = element_blank(),
    panel.grid.minor = element_blank()
  )

figura_2

11 Asociación entre subregión y categorías interpretativas

Se utilizó la prueba de chi-cuadrado de Pearson para evaluar la asociación entre subregión y categorías interpretativas. El análisis se realizó para el puntaje total del CAPL-2 y para cada dominio.

calcular_chi <- function(datos, variable_categoria, variable_status, dominio_nombre){
  base <- datos %>%
    filter(.data[[variable_status]] == "complete") %>%
    mutate(
      categoria = factor(normalizar_categoria(.data[[variable_categoria]]), levels = orden_categorias),
      region = factor(region, levels = orden_region)
    ) %>%
    filter(!is.na(region), !is.na(categoria))
  
  tabla <- table(base$region, base$categoria)
  prueba <- suppressWarnings(chisq.test(tabla, correct = FALSE))
  
  tibble::tibble(
    dominio = dominio_nombre,
    metodo = "Chi-cuadrado de Pearson",
    estadistico = unname(prueba$statistic),
    gl = unname(prueba$parameter),
    p_valor = prueba$p.value,
    n = sum(tabla),
    celdas_esperadas_menor_5 = sum(prueba$expected < 5),
    significancia = case_when(
      p_valor < 0.001 ~ "<0.001",
      TRUE ~ sprintf("%.3f", p_valor)
    ),
    nota = ifelse(
      celdas_esperadas_menor_5 > 0,
      "Interpretar con cautela por frecuencias esperadas bajas",
      "Frecuencias esperadas adecuadas"
    )
  )
}

tabla_chi_categorias <- bind_rows(
  calcular_chi(resultados_capl, "pc_interpretation", "pc_status", "Competencia física"),
  calcular_chi(resultados_capl, "db_interpretation", "db_status", "Comportamiento diario"),
  calcular_chi(resultados_capl, "mc_interpretation", "mc_status", "Motivación y confianza"),
  calcular_chi(resultados_capl, "ku_interpretation", "ku_status", "Conocimiento y comprensión"),
  calcular_chi(resultados_capl, "capl_interpretation", "capl_status", "CAPL total")
) %>%
  mutate(
    estadistico = round(estadistico, 3),
    p = significancia
  ) %>%
  select(
    Dominio = dominio,
    Prueba = metodo,
    `χ²` = estadistico,
    gl,
    n,
    p,
    Nota = nota
  )

tabla_chi_categorias
## # A tibble: 5 × 7
##   Dominio                    Prueba                 `χ²`    gl     n p     Nota 
##   <chr>                      <chr>                 <dbl> <int> <int> <chr> <chr>
## 1 Competencia física         Chi-cuadrado de Pear…  90.0    15   829 <0.0… Inte…
## 2 Comportamiento diario      Chi-cuadrado de Pear… 155.     15   819 <0.0… Frec…
## 3 Motivación y confianza     Chi-cuadrado de Pear… 212.     15   834 <0.0… Frec…
## 4 Conocimiento y comprensión Chi-cuadrado de Pear… 322.     15   839 <0.0… Frec…
## 5 CAPL total                 Chi-cuadrado de Pear… 213.     15   816 <0.0… Frec…

12 Normalidad de los puntajes

La normalidad se evaluó mediante la prueba de Shapiro-Wilk en cada subregión para el puntaje total y los dominios del CAPL-2. Estos resultados se utilizaron para definir la prueba comparativa entre subregiones.

base_puntajes_larga <- bind_rows(
  resultados_capl %>%
    filter(capl_status == "complete") %>%
    transmute(region, dominio = "CAPL total", puntaje = capl_score),
  resultados_capl %>%
    filter(pc_status == "complete") %>%
    transmute(region, dominio = "Competencia física", puntaje = pc_score),
  resultados_capl %>%
    filter(db_status == "complete") %>%
    transmute(region, dominio = "Comportamiento diario", puntaje = db_score),
  resultados_capl %>%
    filter(mc_status == "complete") %>%
    transmute(region, dominio = "Motivación y confianza", puntaje = mc_score),
  resultados_capl %>%
    filter(ku_status == "complete") %>%
    transmute(region, dominio = "Conocimiento y comprensión", puntaje = ku_score)
) %>%
  filter(!is.na(region), !is.na(puntaje)) %>%
  mutate(
    region = factor(region, levels = orden_region),
    dominio = factor(
      dominio,
      levels = c(
        "CAPL total",
        "Competencia física",
        "Comportamiento diario",
        "Motivación y confianza",
        "Conocimiento y comprensión"
      )
    )
  )

normalidad <- base_puntajes_larga %>%
  group_by(dominio, region) %>%
  shapiro_test(puntaje) %>%
  ungroup() %>%
  mutate(
    decision = ifelse(p < 0.05, "No normal", "Compatible con normalidad")
  )

normalidad
## # A tibble: 30 × 6
##    region                 dominio            variable statistic       p decision
##    <fct>                  <fct>              <chr>        <dbl>   <dbl> <chr>   
##  1 Caribe                 CAPL total         puntaje      0.990 5.28e-1 Compati…
##  2 Pacífico               CAPL total         puntaje      0.987 2.02e-1 Compati…
##  3 Centro-Oriente         CAPL total         puntaje      0.981 5.18e-2 Compati…
##  4 Centro sur-Amazonía    CAPL total         puntaje      0.982 7.38e-2 Compati…
##  5 Eje cafetero-Antioquia CAPL total         puntaje      0.963 7.77e-4 No norm…
##  6 Llanos-Orinoquía       CAPL total         puntaje      0.990 3.98e-1 Compati…
##  7 Caribe                 Competencia física puntaje      0.941 1.16e-5 No norm…
##  8 Pacífico               Competencia física puntaje      0.922 6.32e-7 No norm…
##  9 Centro-Oriente         Competencia física puntaje      0.970 6.52e-3 No norm…
## 10 Centro sur-Amazonía    Competencia física puntaje      0.981 5.13e-2 Compati…
## # ℹ 20 more rows

13 Comparación de puntajes continuos entre subregiones

Dado que los puntajes no cumplieron normalidad en todos los grupos, se utilizó la prueba no paramétrica de Kruskal-Wallis para comparar subregiones. El tamaño del efecto se estimó mediante epsilon cuadrado.

calcular_kruskal <- function(datos, dominio_nombre){
  prueba <- kruskal.test(puntaje ~ region, data = datos)
  
  n <- nrow(datos)
  k <- dplyr::n_distinct(datos$region)
  H <- unname(prueba$statistic)
  gl <- unname(prueba$parameter)
  p <- prueba$p.value
  
  # Epsilon cuadrado para Kruskal-Wallis.
  # Fórmula usada: epsilon² = (H - k + 1) / (n - k)
  epsilon2 <- (H - k + 1) / (n - k)
  
  tibble::tibble(
    Dominio = dominio_nombre,
    Prueba = "Kruskal-Wallis",
    H = H,
    gl = gl,
    p_valor = p,
    n = n,
    epsilon2 = epsilon2,
    Magnitud = case_when(
      epsilon2 < 0.01 ~ "Muy pequeña",
      epsilon2 < 0.06 ~ "Pequeña",
      epsilon2 < 0.14 ~ "Moderada",
      TRUE ~ "Grande"
    )
  )
}

tabla_kruskal <- base_puntajes_larga %>%
  group_split(dominio) %>%
  purrr::map_dfr(~calcular_kruskal(.x, as.character(unique(.x$dominio)))) %>%
  mutate(
    H = round(H, 3),
    p = ifelse(p_valor < 0.001, "<0.001", sprintf("%.3f", p_valor)),
    epsilon2 = round(epsilon2, 3)
  ) %>%
  select(
    Dominio,
    Prueba,
    H,
    gl,
    p,
    `ε²` = epsilon2,
    Magnitud
  )

tabla_kruskal
## # A tibble: 5 × 7
##   Dominio                    Prueba             H    gl p       `ε²` Magnitud
##   <chr>                      <chr>          <dbl> <int> <chr>  <dbl> <chr>   
## 1 CAPL total                 Kruskal-Wallis  190.     5 <0.001 0.228 Grande  
## 2 Competencia física         Kruskal-Wallis  120.     5 <0.001 0.14  Grande  
## 3 Comportamiento diario      Kruskal-Wallis  154.     5 <0.001 0.183 Grande  
## 4 Motivación y confianza     Kruskal-Wallis  221.     5 <0.001 0.261 Grande  
## 5 Conocimiento y comprensión Kruskal-Wallis  296.     5 <0.001 0.349 Grande

14 Comparaciones post hoc mediante prueba de Dunn

Se aplicó la prueba de Dunn cuando Kruskal-Wallis mostró diferencias estadísticamente significativas. Para el cuerpo del documento se seleccionaron contrastes significativos relevantes por dominio.

posthoc_dunn <- base_puntajes_larga %>%
  group_by(dominio) %>%
  dunn_test(puntaje ~ region, p.adjust.method = "holm") %>%
  ungroup()

posthoc_dunn_resumen <- posthoc_dunn %>%
  mutate(
    p_ajustada = p.adj,
    significancia = case_when(
      p_ajustada < 0.001 ~ "<0.001",
      TRUE ~ sprintf("%.3f", p_ajustada)
    ),
    z_abs = abs(statistic)
  ) %>%
  arrange(dominio, desc(z_abs))

posthoc_dunn_resumen
## # A tibble: 75 × 13
##    dominio    .y.     group1      group2    n1    n2 statistic        p    p.adj
##    <fct>      <chr>   <chr>       <chr>  <int> <int>     <dbl>    <dbl>    <dbl>
##  1 CAPL total puntaje Eje cafete… Llano…   141   139     11.3  2.08e-29 3.12e-28
##  2 CAPL total puntaje Caribe      Llano…   125   139     10.5  6.20e-26 8.68e-25
##  3 CAPL total puntaje Centro sur… Eje c…   138   141     -8.09 5.89e-16 7.65e-15
##  4 CAPL total puntaje Caribe      Centr…   125   138      7.46 8.59e-14 1.03e-12
##  5 CAPL total puntaje Centro-Ori… Llano…   134   139      6.75 1.47e-11 1.62e-10
##  6 CAPL total puntaje Pacífico    Eje c…   139   141     -6.47 9.71e-11 9.71e-10
##  7 CAPL total puntaje Caribe      Pacíf…   125   139      5.89 3.90e- 9 3.51e- 8
##  8 CAPL total puntaje Pacífico    Llano…   139   139      4.77 1.83e- 6 1.47e- 5
##  9 CAPL total puntaje Centro-Ori… Eje c…   134   141     -4.38 1.18e- 5 8.28e- 5
## 10 CAPL total puntaje Caribe      Centr…   125   134      3.87 1.10e- 4 6.61e- 4
## # ℹ 65 more rows
## # ℹ 4 more variables: p.adj.signif <chr>, p_ajustada <dbl>,
## #   significancia <chr>, z_abs <dbl>

15 Tabla post hoc seleccionada para el documento

Esta tabla reproduce la lógica usada en el documento: se seleccionan contrastes significativos de mayor utilidad para interpretar los patrones descriptivos observados previamente.

# Medianas por dominio y subregión para anexarlas a los contrastes.
medianas_dominios <- base_puntajes_larga %>%
  group_by(dominio, region) %>%
  summarise(mediana = median(puntaje, na.rm = TRUE), .groups = "drop")

posthoc_significativos <- posthoc_dunn_resumen %>%
  filter(p_ajustada < 0.05) %>%
  left_join(
    medianas_dominios,
    by = c("dominio", "group1" = "region")
  ) %>%
  rename(mediana_group1 = mediana) %>%
  left_join(
    medianas_dominios,
    by = c("dominio", "group2" = "region")
  ) %>%
  rename(mediana_group2 = mediana) %>%
  mutate(
    grupo_mayor = ifelse(mediana_group1 >= mediana_group2, as.character(group1), as.character(group2)),
    grupo_menor = ifelse(mediana_group1 >= mediana_group2, as.character(group2), as.character(group1)),
    mediana_mayor = pmax(mediana_group1, mediana_group2),
    mediana_menor = pmin(mediana_group1, mediana_group2),
    contraste = paste0(grupo_mayor, " > ", grupo_menor)
  )

# Selección manual de los contrastes que se reportaron en resultados.
contrastes_seleccionados <- tribble(
  ~dominio, ~grupo_mayor, ~grupo_menor,
  "CAPL total", "Llanos-Orinoquía", "Eje cafetero-Antioquia",
  "CAPL total", "Llanos-Orinoquía", "Caribe",
  "CAPL total", "Centro sur-Amazonía", "Eje cafetero-Antioquia",
  "CAPL total", "Pacífico", "Eje cafetero-Antioquia",
  "Competencia física", "Centro sur-Amazonía", "Pacífico",
  "Competencia física", "Centro sur-Amazonía", "Caribe",
  "Competencia física", "Llanos-Orinoquía", "Pacífico",
  "Comportamiento diario", "Llanos-Orinoquía", "Caribe",
  "Comportamiento diario", "Pacífico", "Caribe",
  "Comportamiento diario", "Llanos-Orinoquía", "Centro-Oriente",
  "Conocimiento y comprensión", "Llanos-Orinoquía", "Eje cafetero-Antioquia",
  "Conocimiento y comprensión", "Pacífico", "Eje cafetero-Antioquia",
  "Conocimiento y comprensión", "Llanos-Orinoquía", "Caribe",
  "Motivación y confianza", "Centro sur-Amazonía", "Eje cafetero-Antioquia",
  "Motivación y confianza", "Pacífico", "Eje cafetero-Antioquia",
  "Motivación y confianza", "Caribe", "Eje cafetero-Antioquia"
)

tabla_posthoc_seleccionada <- posthoc_significativos %>%
  semi_join(contrastes_seleccionados, by = c("dominio", "grupo_mayor", "grupo_menor")) %>%
  mutate(
    `Contraste significativo` = paste0(grupo_mayor, " > ", grupo_menor),
    `Mediana grupo mayor` = round(mediana_mayor, 2),
    `Mediana grupo menor` = round(mediana_menor, 2),
    `|Z| Dunn` = round(z_abs, 3),
    `p ajustada` = ifelse(p_ajustada < 0.001, "<0.001", sprintf("%.3f", p_ajustada))
  ) %>%
  select(
    Dominio = dominio,
    `Contraste significativo`,
    `Mediana grupo mayor`,
    `Mediana grupo menor`,
    `|Z| Dunn`,
    `p ajustada`
  )

tabla_posthoc_seleccionada
## # A tibble: 16 × 6
##    Dominio    Contraste significat…¹ `Mediana grupo mayor` `Mediana grupo menor`
##    <fct>      <chr>                                  <dbl>                 <dbl>
##  1 CAPL total Llanos-Orinoquía > Ej…                  62.6                  45.3
##  2 CAPL total Llanos-Orinoquía > Ca…                  62.6                  49.8
##  3 CAPL total Centro sur-Amazonía >…                  59.3                  45.3
##  4 CAPL total Pacífico > Eje cafete…                  57.1                  45.3
##  5 Competenc… Centro sur-Amazonía >…                  16                     7.5
##  6 Competenc… Centro sur-Amazonía >…                  16                     9  
##  7 Competenc… Llanos-Orinoquía > Pa…                  12                     7.5
##  8 Comportam… Llanos-Orinoquía > Ca…                  19                     9  
##  9 Comportam… Pacífico > Caribe                       17                     9  
## 10 Comportam… Llanos-Orinoquía > Ce…                  19                    11  
## 11 Motivació… Centro sur-Amazonía >…                  25.1                  18.3
## 12 Motivació… Pacífico > Eje cafete…                  25.0                  18.3
## 13 Motivació… Caribe > Eje cafetero…                  24.4                  18.3
## 14 Conocimie… Llanos-Orinoquía > Ej…                   8                     4  
## 15 Conocimie… Pacífico > Eje cafete…                   7                     4  
## 16 Conocimie… Llanos-Orinoquía > Ca…                   8                     4  
## # ℹ abbreviated name: ¹​`Contraste significativo`
## # ℹ 2 more variables: `|Z| Dunn` <dbl>, `p ajustada` <chr>

16 Exportación de tablas y figuras

dir.create("salidas_CAPL2_caracterizacion", showWarnings = FALSE)
dir.create("salidas_CAPL2_caracterizacion/figuras", showWarnings = FALSE)

writexl::write_xlsx(
  list(
    "tabla_1_caracterizacion" = tabla_1,
    "grado_escolar" = resumen_grado,
    "tabla_2_disponibilidad" = tabla_2,
    "tabla_3_capl_total" = tabla_3,
    "resumen_dominios_region" = resumen_dominios_region,
    "categorias_capl_total" = resumen_cat_capl,
    "tabla_chi_categorias" = tabla_chi_categorias,
    "normalidad" = normalidad,
    "tabla_kruskal" = tabla_kruskal,
    "posthoc_dunn_completo" = posthoc_dunn_resumen,
    "tabla_posthoc_seleccionada" = tabla_posthoc_seleccionada
  ),
  "salidas_CAPL2_caracterizacion/tablas_CAPL2_caracterizacion.xlsx"
)

ggsave(
  filename = "salidas_CAPL2_caracterizacion/figuras/Figura_1_dominios_CAPL2_subregion.png",
  plot = figura_1,
  width = 10,
  height = 7,
  dpi = 600
)

ggsave(
  filename = "salidas_CAPL2_caracterizacion/figuras/Figura_2_categorias_CAPL_total_subregion.png",
  plot = figura_2,
  width = 10,
  height = 6,
  dpi = 600
)

cat("\nAnálisis finalizado. Revise la carpeta salidas_CAPL2_caracterizacion.\n")
## 
## Análisis finalizado. Revise la carpeta salidas_CAPL2_caracterizacion.

17 Información de sesión

sessionInfo()
## R version 4.5.1 (2025-06-13 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     
## 
## other attached packages:
##  [1] flextable_0.9.11 purrr_1.1.0      janitor_2.2.1    rstatix_0.7.2   
##  [5] scales_1.4.0     ggplot2_4.0.1    forcats_1.0.0    stringr_1.5.1   
##  [9] tidyr_1.3.1      dplyr_1.2.0      writexl_1.5.4    readxl_1.4.5    
## 
## loaded via a namespace (and not attached):
##  [1] gtable_0.3.6            xfun_0.55               bslib_0.9.0            
##  [4] vctrs_0.7.1             tools_4.5.1             generics_0.1.4         
##  [7] tibble_3.3.0            pkgconfig_2.0.3         data.table_1.17.8      
## [10] RColorBrewer_1.1-3      S7_0.2.1                uuid_1.2-1             
## [13] lifecycle_1.0.5         compiler_4.5.1          farver_2.1.2           
## [16] textshaping_1.0.1       carData_3.0-5           snakecase_0.11.1       
## [19] fontquiver_0.2.1        fontLiberation_0.1.0    htmltools_0.5.8.1      
## [22] sass_0.4.10             yaml_2.3.10             Formula_1.2-5          
## [25] pillar_1.11.0           car_3.1-3               jquerylib_0.1.4        
## [28] openssl_2.3.3           cachem_1.1.0            abind_1.4-8            
## [31] fontBitstreamVera_0.1.1 zip_2.3.3               tidyselect_1.2.1       
## [34] digest_0.6.37           stringi_1.8.7           fastmap_1.2.0          
## [37] grid_4.5.1              cli_3.6.6               magrittr_2.0.3         
## [40] patchwork_1.3.1         utf8_1.2.6              broom_1.0.8            
## [43] withr_3.0.2             gdtools_0.5.0           backports_1.5.0        
## [46] lubridate_1.9.4         timechange_0.3.0        rmarkdown_2.29         
## [49] officer_0.7.3           cellranger_1.1.0        askpass_1.2.1          
## [52] ragg_1.4.0              evaluate_1.0.4          knitr_1.50             
## [55] rlang_1.2.0             Rcpp_1.1.0              glue_1.8.0             
## [58] xml2_1.3.8              rstudioapi_0.17.1       jsonlite_2.0.0         
## [61] R6_2.6.1                systemfonts_1.3.1