1. Importación,
limpieza y construcción de puntajes
library(tidyverse) # dplyr, tidyr, ggplot2, stringr
library(readxl)
library(janitor)
library(stringi)
library(psych)
# --- Funciones auxiliares (sin backslashes problemáticos) ---
norm_name <- function(x){
x %>% stringi::stri_trans_general("Latin-ASCII") %>% tolower() %>% gsub("[[:space:]]+"," ",.) %>% trimws()
}
pick_sheet <- function(all_sheets, target){
s_norm <- norm_name(all_sheets); t_norm <- norm_name(target)
idx <- which(s_norm == t_norm); if(length(idx)==1) return(all_sheets[idx])
idx2 <- which(grepl(t_norm, s_norm, fixed=TRUE)); if(length(idx2)>=1) return(all_sheets[idx2[1]])
NA_character_
}
# Detectar hojas de forma robusta
sheets <- readxl::excel_sheets(path = params$archivo_excel)
sheet_datos <- pick_sheet(sheets, params$hoja_datos)
sheet_factores <- pick_sheet(sheets, params$hoja_factores)
sheet_diccionario <- pick_sheet(sheets, params$hoja_diccionario)
if(is.na(sheet_datos)) stop("No se encontró la hoja 'datos'.")
if(is.na(sheet_factores)) stop("No se encontró la hoja 'factores'.")
# Leer 'datos' y limpiar
raw <- readxl::read_excel(params$archivo_excel, sheet = sheet_datos) %>% janitor::clean_names()
# Si existe 'licenciatura', renombrar a 'ingenieria'
if("licenciatura" %in% names(raw)) raw <- dplyr::rename(raw, ingenieria = licenciatura)
# Homologar
raw <- raw %>% dplyr::mutate(
sexo = stringr::str_to_title(sexo),
ingenieria = stringr::str_trim(ingenieria),
semestre = as.factor(semestre)
)
# Ítems p1..p34
item_cols <- names(raw)[grepl("^p[0-9]+$", names(raw))]
raw <- raw %>% dplyr::mutate(dplyr::across(dplyr::all_of(item_cols), as.numeric))
# Leer 'factores'
factores_tbl <- readxl::read_excel(params$archivo_excel, sheet = sheet_factores) %>% janitor::clean_names()
names(factores_tbl) <- norm_name(names(factores_tbl))
col_factor <- if("factores" %in% names(factores_tbl)) "factores" else names(factores_tbl)[1]
col_items <- if("items" %in% names(factores_tbl)) "items" else names(factores_tbl)[2]
factores_tbl <- factores_tbl %>% dplyr::select(dplyr::all_of(c(col_factor, col_items))) %>% dplyr::rename(nombre_factor = !!col_factor, items_txt = !!col_items)
# Separar lista de ítems con coma + espacios normales
factores_tbl$items <- strsplit(factores_tbl$items_txt, ", *")
# Lista nombrada y estandarizada
factores <- setNames(factores_tbl$items, factores_tbl$nombre_factor)
factores <- lapply(factores, function(v) gsub(" +","", tolower(v)))
# Puntajes por escala
for(fname in names(factores)){
its <- intersect(factores[[fname]], names(raw))
if(length(its) > 0){ raw[[paste0("score_", fname)]] <- rowMeans(raw[, its], na.rm = TRUE) }
}
# Puntaje global
items_1_34 <- paste0("p", 1:34)
presentes <- intersect(items_1_34, names(raw))
raw$score_global <- rowMeans(raw[, presentes], na.rm = TRUE)
cat(sprintf("Filas: %d\nColumnas: %d\n", nrow(raw), ncol(raw)))
## Filas: 95
## Columnas: 46
1.1 Resumen de la
muestra
tabla_sexo <- raw %>% dplyr::count(sexo)
tabla_ing <- raw %>% dplyr::count(ingenieria)
tabla_sem <- raw %>% dplyr::count(semestre)
knitr::kable(tabla_sexo, caption = "Distribución por sexo")
Distribución por sexo
| Hombre |
65 |
| Mujer |
30 |
knitr::kable(tabla_ing, caption = "Distribución por ingeniería")
Distribución por ingeniería
| Ambiental |
12 |
| Civil |
21 |
| Industrial |
11 |
| Mecatronica |
17 |
| Software y Sistemas Computacionales |
34 |
knitr::kable(tabla_sem, caption = "Distribución por semestre")
Distribución por semestre
| 6 |
54 |
| 8 |
41 |
2. Descriptivos y
frecuencias Likert
items_presentes <- presentes
# Descriptivos por ítem
desc_items <- raw %>% dplyr::summarise(dplyr::across(dplyr::all_of(items_presentes), list(Media = ~mean(.x, na.rm=TRUE), SD = ~sd(.x, na.rm=TRUE)), .names="{.col}_{.fn}")) %>%
tidyr::pivot_longer(dplyr::everything(), names_to = c("item", ".value"), names_sep = "_")
knitr::kable(desc_items, digits = 2, caption = "Descriptivos por ítem (Media y Desviación estándar)")
Descriptivos por ítem (Media y Desviación estándar)
| p1 |
3.76 |
0.78 |
| p2 |
3.35 |
0.77 |
| p3 |
3.23 |
0.82 |
| p4 |
3.49 |
0.84 |
| p5 |
3.58 |
0.86 |
| p6 |
3.77 |
0.82 |
| p7 |
3.67 |
0.87 |
| p8 |
3.57 |
0.91 |
| p9 |
3.71 |
0.85 |
| p10 |
3.83 |
0.75 |
| p11 |
3.81 |
0.85 |
| p12 |
3.61 |
0.91 |
| p13 |
3.44 |
0.99 |
| p14 |
3.41 |
0.79 |
| p15 |
3.45 |
0.83 |
| p16 |
3.71 |
0.84 |
| p17 |
3.41 |
0.81 |
| p18 |
3.95 |
0.79 |
| p19 |
3.62 |
0.84 |
| p20 |
3.45 |
0.91 |
| p21 |
3.12 |
0.84 |
| p22 |
3.25 |
0.85 |
| p23 |
3.12 |
0.94 |
| p24 |
3.44 |
0.81 |
| p25 |
3.69 |
0.83 |
| p26 |
3.62 |
0.75 |
| p27 |
3.93 |
0.84 |
| p28 |
3.84 |
0.75 |
| p29 |
4.03 |
0.82 |
| p30 |
3.85 |
0.81 |
| p31 |
3.86 |
0.79 |
| p32 |
3.58 |
0.89 |
| p33 |
3.87 |
0.79 |
| p34 |
3.69 |
0.93 |
likert_long <- raw %>% dplyr::select(dplyr::all_of(items_presentes)) %>% tidyr::pivot_longer(dplyr::everything(), names_to = "item", values_to = "respuesta")
freq_likert <- likert_long %>% dplyr::count(item, respuesta) %>% dplyr::group_by(item) %>% dplyr::mutate(prop = n/sum(n)) %>% dplyr::ungroup()
readr::write_csv(freq_likert, "salidas/tablas/frecuencias_likert.csv")
library(ggplot2)
g1 <- ggplot(freq_likert, aes(x = item, y = prop, fill = factor(respuesta, levels = 1:5, labels = c("Nunca","Rara vez","A veces","Frecuentemente","Siempre")))) +
geom_col(width = 0.9) + scale_y_continuous(labels = scales::percent_format()) + scale_fill_brewer(palette = "RdYlGn", direction = 1, name = "Respuesta") +
labs(title = "Distribución de respuestas Likert por ítem", x = "Ítem", y = "%") + theme_minimal() + theme(axis.text.x = element_text(angle = 90, vjust = 0.5))
ggsave(filename = "salidas/figuras/likert_items.png", plot = g1, width = params$ancho_png, height = params$alto_png, dpi = params$dpi_png)
3. Modelo global (sexo
× ingeniería × semestre) y funciones auxiliares
library(car)
library(emmeans)
library(effectsize)
library(ARTool)
library(tidyr)
library(ggplot2)
# Gráfico emmeans
plot_emmeans <- function(emm_obj, y, nombre){
plt <- plot(emm_obj, comparisons = TRUE) +
ggplot2::labs(title = paste0("Medias marginales (IC95): ", y), x = "Grupo", y = "Puntaje (1–5)") +
ggplot2::theme_minimal()
ggplot2::ggsave(file.path("salidas/figuras", paste0(nombre, ".png")), plot = plt, width = params$ancho_png, height = params$alto_png, dpi = params$dpi_png)
}
# Wrapper seguro para ANOVA
safe_Anova <- function(fit){
res <- try(car::Anova(fit, type = "III", singular.ok = TRUE), silent = TRUE)
if (inherits(res, "try-error")){
res <- car::Anova(fit, type = "II")
}
res
}
# Chequeo de supuestos
checar_supuestos <- function(df, y){
df <- df %>% tidyr::drop_na(sexo, ingenieria, semestre, !!rlang::sym(y)) %>%
dplyr::mutate(sexo = as.factor(sexo), ingenieria = as.factor(ingenieria), semestre = as.factor(semestre)) %>%
droplevels()
fit <- stats::lm(stats::as.formula(paste(y, "~ sexo * ingenieria * semestre")), data = df)
shapiro_p <- stats::shapiro.test(stats::residuals(fit))$p.value
lev_p <- car::leveneTest(stats::as.formula(paste(y, "~ interaction(sexo, ingenieria, semestre)")), data = df)$`Pr(>F)`[1]
list(fit=fit, shapiro_p=shapiro_p, lev_p=lev_p)
}
# Analiza escala con LM o ART (ART requiere efectos completamente cruzados)
analiza_escala <- function(df, y, usar_art = FALSE){
df <- df %>% tidyr::drop_na(sexo, ingenieria, semestre, !!rlang::sym(y)) %>%
dplyr::mutate(sexo = as.factor(sexo), ingenieria = as.factor(ingenieria), semestre = as.factor(semestre)) %>%
droplevels()
if(!usar_art){
fit <- stats::lm(stats::as.formula(paste(y, "~ sexo * ingenieria * semestre")), data = df)
aov_tab <- safe_Anova(fit)
eff <- effectsize::eta_squared(fit, partial = TRUE)
emm <- emmeans::emmeans(fit, ~ sexo * ingenieria | semestre)
posthoc <- emmeans::contrast(emm, method = "pairwise", adjust = "holm")
list(model="LM", aov=aov_tab, efectos=eff, emm=emm, posthoc=posthoc)
} else {
fit <- ARTool::art(stats::as.formula(paste(y, "~ sexo * ingenieria * semestre")), data = df)
aov_tab <- stats::anova(fit)
emm <- emmeans::emmeans(fit, ~ sexo * ingenieria | semestre)
posthoc <- emmeans::contrast(emm, method = "pairwise", adjust = "holm")
list(model="ART", aov=aov_tab, efectos=NULL, emm=emm, posthoc=posthoc)
}
}
sup_global <- checar_supuestos(raw, "score_global")
usar_art_global <- !(sup_global$shapiro_p > 0.05 && sup_global$lev_p > 0.05)
res_global <- analiza_escala(raw, "score_global", usar_art = usar_art_global)
# Exportar resultados (independiente del tipo)
aov_global <- res_global$aov
write.csv(as.data.frame(aov_global), "salidas/tablas/anova_global.csv", row.names = TRUE)
if(!is.null(res_global$efectos)) write.csv(as.data.frame(res_global$efectos), "salidas/tablas/eta2parcial_global.csv", row.names = FALSE)
posthoc_global <- summary(res_global$posthoc, infer = TRUE)
write.csv(as.data.frame(posthoc_global), "salidas/tablas/posthoc_global.csv", row.names = FALSE)
plot_emmeans(res_global$emm, "score_global", "emmeans_global")
# Raincloud global
draw_raincloud <- function(df, y, nombre){
p <- df %>% ggplot2::ggplot(ggplot2::aes(x = interaction(sexo, ingenieria, drop = TRUE), y = .data[[y]], fill = sexo)) +
ggplot2::geom_violin(alpha = 0.5, trim = FALSE) + ggplot2::geom_boxplot(width = 0.15, outlier.alpha = 0.2) +
ggplot2::geom_jitter(width = 0.1, alpha = 0.25) + ggplot2::facet_wrap(~ semestre) +
ggplot2::labs(title = paste0("Distribución por sexo × ingeniería (facet semestre): ", y), x = "Sexo × Ingeniería", y = "Puntaje (1–5)") +
ggplot2::theme_minimal() + ggplot2::guides(fill = "none")
ggplot2::ggsave(file.path("salidas/figuras", paste0(nombre, "_raincloud.png")), plot = p, width = 12, height = 6.5, dpi = params$dpi_png)
}
draw_raincloud(raw, "score_global", "global")
4. Secundarios
(escalas) y FDR
library(rstatix)
escalas <- c("score_generar_motivacion","score_comunicacion_no_verbal","score_empatia",
"score_expresion_emocional","score_expresion_oral",
"score_transmision_informativa","score_abierta_autentica","score_escucha")
resultados_escalas <- list(); pv_escalas <- c(); labels <- c(); escala_vec <- c()
for(y in escalas){
sup <- checar_supuestos(raw, y)
usar_art <- !(sup$shapiro_p > 0.05 && sup$lev_p > 0.05)
res <- analiza_escala(raw, y, usar_art = usar_art)
resultados_escalas[[y]] <- res
aov_tab <- res$aov
write.csv(as.data.frame(aov_tab), file.path("salidas/tablas", paste0("anova_", y, ".csv")))
if(!is.null(res$efectos)) write.csv(as.data.frame(res$efectos), file.path("salidas/tablas", paste0("eta2parcial_", y, ".csv")), row.names = FALSE)
pv <- aov_tab$`Pr(>F)`; rn <- rownames(aov_tab)
idx <- which(rn %in% c("sexo","ingenieria","semestre","sexo:ingenieria","sexo:semestre","ingenieria:semestre","sexo:ingenieria:semestre"))
pv_escalas <- c(pv_escalas, pv[idx]); labels <- c(labels, rn[idx]); escala_vec <- c(escala_vec, rep(y, length(idx)))
posthoc <- summary(res$posthoc, infer = TRUE)
write.csv(as.data.frame(posthoc), file.path("salidas/tablas", paste0("posthoc_", y, ".csv")), row.names = FALSE)
plot_emmeans(res$emm, y, paste0("emmeans_", y))
draw_raincloud(raw, y, y)
}
# FDR por escala x efecto
fdr_tab <- tibble::tibble(
escala = escala_vec,
efecto = labels,
p_raw = pv_escalas,
p_fdr = p.adjust(pv_escalas, method = "BH")
)
knitr::kable(fdr_tab, digits = 4, caption = "FDR (Benjamini–Hochberg) en efectos por escala")
FDR (Benjamini–Hochberg) en efectos por escala
| score_generar_motivacion |
sexo |
NA |
NA |
| score_generar_motivacion |
ingenieria |
NA |
NA |
| score_generar_motivacion |
semestre |
NA |
NA |
| score_generar_motivacion |
sexo:ingenieria |
NA |
NA |
| score_generar_motivacion |
sexo:semestre |
NA |
NA |
| score_generar_motivacion |
ingenieria:semestre |
NA |
NA |
| score_generar_motivacion |
sexo:ingenieria:semestre |
NA |
NA |
| score_comunicacion_no_verbal |
sexo |
NA |
NA |
| score_comunicacion_no_verbal |
ingenieria |
NA |
NA |
| score_comunicacion_no_verbal |
semestre |
NA |
NA |
| score_comunicacion_no_verbal |
sexo:ingenieria |
NA |
NA |
| score_comunicacion_no_verbal |
sexo:semestre |
NA |
NA |
| score_comunicacion_no_verbal |
ingenieria:semestre |
NA |
NA |
| score_comunicacion_no_verbal |
sexo:ingenieria:semestre |
NA |
NA |
| score_empatia |
sexo |
NA |
NA |
| score_empatia |
ingenieria |
NA |
NA |
| score_empatia |
semestre |
NA |
NA |
| score_empatia |
sexo:ingenieria |
NA |
NA |
| score_empatia |
sexo:semestre |
NA |
NA |
| score_empatia |
ingenieria:semestre |
NA |
NA |
| score_empatia |
sexo:ingenieria:semestre |
NA |
NA |
| score_expresion_emocional |
sexo |
NA |
NA |
| score_expresion_emocional |
ingenieria |
NA |
NA |
| score_expresion_emocional |
semestre |
NA |
NA |
| score_expresion_emocional |
sexo:ingenieria |
NA |
NA |
| score_expresion_emocional |
sexo:semestre |
NA |
NA |
| score_expresion_emocional |
ingenieria:semestre |
NA |
NA |
| score_expresion_emocional |
sexo:ingenieria:semestre |
NA |
NA |
| score_expresion_oral |
sexo |
NA |
NA |
| score_expresion_oral |
ingenieria |
NA |
NA |
| score_expresion_oral |
semestre |
NA |
NA |
| score_expresion_oral |
sexo:ingenieria |
NA |
NA |
| score_expresion_oral |
sexo:semestre |
NA |
NA |
| score_expresion_oral |
ingenieria:semestre |
NA |
NA |
| score_expresion_oral |
sexo:ingenieria:semestre |
NA |
NA |
| score_transmision_informativa |
sexo |
NA |
NA |
| score_transmision_informativa |
ingenieria |
NA |
NA |
| score_transmision_informativa |
semestre |
NA |
NA |
| score_transmision_informativa |
sexo:ingenieria |
NA |
NA |
| score_transmision_informativa |
sexo:semestre |
NA |
NA |
| score_transmision_informativa |
ingenieria:semestre |
NA |
NA |
| score_transmision_informativa |
sexo:ingenieria:semestre |
NA |
NA |
| score_abierta_autentica |
sexo |
NA |
NA |
| score_abierta_autentica |
ingenieria |
NA |
NA |
| score_abierta_autentica |
semestre |
NA |
NA |
| score_abierta_autentica |
sexo:ingenieria |
NA |
NA |
| score_abierta_autentica |
sexo:semestre |
NA |
NA |
| score_abierta_autentica |
ingenieria:semestre |
NA |
NA |
| score_abierta_autentica |
sexo:ingenieria:semestre |
NA |
NA |
| score_escucha |
sexo |
NA |
NA |
| score_escucha |
ingenieria |
NA |
NA |
| score_escucha |
semestre |
NA |
NA |
| score_escucha |
sexo:ingenieria |
NA |
NA |
| score_escucha |
sexo:semestre |
NA |
NA |
| score_escucha |
ingenieria:semestre |
NA |
NA |
| score_escucha |
sexo:ingenieria:semestre |
NA |
NA |
readr::write_csv(fdr_tab, "salidas/tablas/fdr_escalas.csv")
5. Descriptivos por
sexo × ingeniería × semestre
desc_tab <- raw %>% dplyr::group_by(sexo, ingenieria, semestre) %>% dplyr::summarise(
n = dplyr::n(),
across(c(score_global, all_of(escalas)), list(Media = ~mean(.x, na.rm = TRUE), SD = ~sd(.x, na.rm = TRUE)), .names = "{.col}_{.fn}"),
.groups = "drop"
)
knitr::kable(desc_tab, digits = 2, caption = "Descriptivos por sexo × ingeniería × semestre (global y escalas)")
Descriptivos por sexo × ingeniería × semestre (global y
escalas)
| Hombre |
Ambiental |
8 |
2 |
4.07 |
0.48 |
4.00 |
0.47 |
4.20 |
0.57 |
4.50 |
0.71 |
3.67 |
0.47 |
4.12 |
0.18 |
3.88 |
0.53 |
4.40 |
0.57 |
3.50 |
0.24 |
| Hombre |
Civil |
6 |
8 |
3.30 |
0.34 |
3.17 |
0.48 |
3.50 |
0.68 |
3.66 |
0.68 |
3.04 |
0.70 |
3.28 |
0.47 |
3.19 |
0.73 |
3.18 |
0.73 |
3.38 |
0.52 |
| Hombre |
Civil |
8 |
9 |
3.46 |
0.42 |
3.37 |
0.54 |
3.67 |
0.53 |
3.61 |
0.53 |
3.15 |
0.47 |
3.56 |
0.72 |
3.25 |
0.71 |
3.60 |
0.39 |
3.37 |
0.54 |
| Hombre |
Industrial |
6 |
5 |
3.44 |
0.39 |
3.50 |
0.46 |
3.72 |
0.58 |
3.75 |
0.47 |
3.40 |
0.55 |
2.85 |
0.58 |
3.30 |
0.33 |
3.48 |
0.56 |
3.33 |
0.94 |
| Hombre |
Mecatronica |
6 |
9 |
3.53 |
0.55 |
3.39 |
0.70 |
3.42 |
0.52 |
3.89 |
0.64 |
3.41 |
0.76 |
3.50 |
0.48 |
3.50 |
0.50 |
3.47 |
0.70 |
3.85 |
0.69 |
| Hombre |
Mecatronica |
8 |
5 |
3.56 |
0.42 |
3.20 |
0.45 |
3.52 |
0.48 |
4.05 |
0.62 |
3.40 |
0.55 |
3.80 |
0.76 |
3.70 |
0.74 |
3.52 |
0.59 |
3.40 |
0.43 |
| Hombre |
Software y Sistemas Computacionales |
6 |
16 |
3.51 |
0.41 |
3.39 |
0.49 |
3.52 |
0.58 |
3.89 |
0.68 |
3.17 |
0.67 |
3.55 |
0.59 |
3.31 |
0.45 |
3.49 |
0.54 |
3.77 |
0.57 |
| Hombre |
Software y Sistemas Computacionales |
8 |
11 |
3.73 |
0.69 |
3.68 |
0.71 |
3.75 |
0.84 |
3.84 |
0.85 |
3.36 |
0.95 |
3.82 |
0.82 |
3.52 |
0.71 |
3.91 |
0.72 |
3.85 |
0.78 |
| Mujer |
Ambiental |
6 |
5 |
3.97 |
0.28 |
4.03 |
0.38 |
4.00 |
0.40 |
4.35 |
0.29 |
3.53 |
0.93 |
4.00 |
0.00 |
3.80 |
0.57 |
3.96 |
0.26 |
3.93 |
0.55 |
| Mujer |
Ambiental |
8 |
5 |
3.73 |
0.24 |
3.87 |
0.36 |
3.68 |
0.41 |
4.15 |
0.60 |
3.00 |
0.97 |
4.05 |
0.45 |
3.20 |
0.27 |
3.88 |
0.50 |
3.73 |
0.43 |
| Mujer |
Civil |
6 |
2 |
3.94 |
0.37 |
4.42 |
0.59 |
3.50 |
0.14 |
4.50 |
0.71 |
3.50 |
0.24 |
3.25 |
0.35 |
4.00 |
1.06 |
4.10 |
0.14 |
4.00 |
0.47 |
| Mujer |
Civil |
8 |
2 |
3.71 |
0.17 |
3.25 |
0.12 |
4.20 |
0.85 |
4.12 |
0.18 |
3.00 |
0.00 |
4.12 |
0.53 |
3.25 |
0.00 |
3.90 |
0.14 |
3.67 |
0.00 |
| Mujer |
Industrial |
6 |
6 |
3.91 |
0.43 |
3.92 |
0.39 |
4.13 |
0.55 |
4.17 |
0.65 |
3.44 |
0.69 |
3.96 |
0.83 |
3.75 |
0.47 |
3.83 |
0.53 |
3.94 |
0.44 |
| Mujer |
Mecatronica |
6 |
3 |
3.49 |
0.62 |
3.33 |
0.60 |
3.87 |
0.95 |
3.25 |
0.43 |
3.56 |
0.96 |
3.67 |
0.72 |
3.58 |
0.80 |
3.13 |
0.42 |
3.67 |
0.88 |
| Mujer |
Software y Sistemas Computacionales |
8 |
7 |
3.66 |
0.46 |
3.79 |
0.46 |
3.63 |
0.79 |
4.07 |
0.40 |
3.05 |
0.99 |
3.82 |
0.89 |
3.57 |
0.66 |
3.54 |
0.40 |
3.62 |
0.71 |
readr::write_csv(desc_tab, "salidas/tablas/descriptivos_por_grupo.csv")
6. (Opcional)
Validación del instrumento — deshabilitada para evitar
interrupciones
library(lavaan)
# EFA
png("salidas/figuras/fa_parallel.png", width = params$ancho_png*96, height = params$alto_png*96)
psych::fa.parallel(raw[, presentes], fa = "fa", fm = "ml")
dev.off()
fa_ml <- psych::fa(raw[, presentes], nfactors = 8, fm = "ml", rotate = "oblimin")
loadings_df <- as.data.frame(unclass(fa_ml$loadings))
readr::write_csv(loadings_df, "salidas/tablas/efa_cargas.csv")
# CFA desde 'factores'
model_lines <- c()
for(fname in names(factores)){
its <- intersect(factores[[fname]], presentes)
if(length(its)>0) model_lines <- c(model_lines, paste0(gsub("[^A-Za-z0-9]", "_", fname), " =~ ", paste(its, collapse = " + ")))
}
model_cfa <- paste(model_lines, collapse = "\n")
fit_cfa <- lavaan::cfa(model_cfa, data = raw, estimator = "MLR")
summary(fit_cfa, fit.measures = TRUE, standardized = TRUE)
fit_measures <- data.frame(t(lavaan::fitMeasures(fit_cfa, c("cfi","tli","rmsea","srmr","chisq","df","pvalue"))))
readr::write_csv(fit_measures, "salidas/tablas/cfa_indices_ajuste.csv")
# Invariancia por sexo
fit_config <- lavaan::cfa(model_cfa, data = raw, group = "sexo", estimator = "MLR")
fit_metric <- lavaan::cfa(model_cfa, data = raw, group = "sexo", group.equal = c("loadings"), estimator = "MLR")
fit_scalar <- lavaan::cfa(model_cfa, data = raw, group = "sexo", group.equal = c("loadings","intercepts"), estimator = "MLR")
comp_invar <- lavaan::anova(fit_config, fit_metric, fit_scalar)
readr::write_csv(as.data.frame(comp_invar), "salidas/tablas/invariancia_sexo_comp.csv")
7. Margen de error
(95%) con corrección por población finita
N <- params$N_poblacion
n <- nrow(raw)
conf <- 0.95
z <- qnorm(1 - (1-conf)/2)
p <- 0.5
moe_prop <- z * sqrt(p*(1-p)/n) * sqrt((N-n)/(N-1))
factor_scores <- raw %>% dplyr::select(dplyr::starts_with("score_"), score_global)
moe_medias <- factor_scores %>% dplyr::summarise(dplyr::across(dplyr::everything(), ~ z * sd(.x, na.rm=TRUE)/sqrt(n) * sqrt((N-n)/(N-1)))) %>% tidyr::pivot_longer(dplyr::everything(), names_to = "escala", values_to = "MOE")
knitr::kable(tibble::tibble(
`Tamaño de muestra (n)` = n,
`Población (N)` = N,
`Nivel de confianza` = paste0(conf*100, "%"),
`Margen de error (proporción, p=0.5)` = scales::percent(moe_prop, accuracy = 0.1)
), caption = "Parámetros de estimación con corrección por población finita")
Parámetros de estimación con corrección por población
finita
| 95 |
122 |
95% |
4.7% |
knitr::kable(moe_medias, digits = 3, caption = "Margen de error de la media por escala (95% CI, FPC)")
Margen de error de la media por escala (95% CI, FPC)
| score_generar_motivacion |
0.054 |
| score_comunicacion_no_verbal |
0.059 |
| score_empatia |
0.060 |
| score_expresion_emocional |
0.068 |
| score_expresion_oral |
0.064 |
| score_transmision_informativa |
0.056 |
| score_abierta_autentica |
0.056 |
| score_escucha |
0.058 |
| score_global |
0.045 |
readr::write_csv(moe_medias, "salidas/tablas/margen_error_medias.csv")
8. Decisiones
editoriales
cat("Se utiliza α = ", params$alpha, " para pruebas principales.\n")
## Se utiliza α = 0.05 para pruebas principales.
cat("Secundarios (escalas): FDR (Benjamini–Hochberg) en efectos principales e interacción.\n")
## Secundarios (escalas): FDR (Benjamini–Hochberg) en efectos principales e interacción.
cat("Post hoc: emmeans::contrast(method = 'pairwise', ajuste Holm) con IC95.\n")
## Post hoc: emmeans::contrast(method = 'pairwise', ajuste Holm) con IC95.