pkgs <- c(
"tidyverse","readxl","janitor","stringr","forcats","broom",
"tidytext","stopwords","nnet","ggplot2","writexl",
"apaTables","car","pscl","gt","officer","flextable",
"wordcloud","RColorBrewer","rpart","rpart.plot","caret","yardstick","glue"
)
to_install <- pkgs[!pkgs %in% rownames(installed.packages())]
if(length(to_install)) install.packages(to_install, repos = "https://cloud.r-project.org")
invisible(lapply(pkgs, library, character.only = TRUE))
# Ruta del archivo:
file_path <- "C:/Users/Quimica/Downloads/encuesta_limpia.xlsx"
df <- readxl::read_excel(file_path, sheet = 1) |>
janitor::clean_names() |>
mutate(across(where(is.character), ~str_squish(.x)))
# Normalizar "No aplica"
no_aplica_pat <- regex("^(no tengo|ninguna|6\\.?\\s*no aplica|no aplica)$", ignore_case = TRUE)
df <- df |> mutate(across(where(is.character), ~ if_else(str_detect(.x, no_aplica_pat), "No aplica", .x)))
# Matrícula a mayúsculas (si existe)
mat_cols <- names(df)[str_detect(names(df), "^matr[íi]cula")]
if (length(mat_cols) == 1) df <- df |> mutate("{mat_cols}" := toupper(.data[[mat_cols]]))
col_futuro <- names(df)[str_detect(names(df), "como_te_sientes_en_relacion_a_tu_futuro")]
col_beca <- names(df)[str_detect(names(df), "para_que_utilizas_principalmente_el_dinero_de_la_beca")]
col_genero <- names(df)[str_detect(names(df), "^x?1_\\.?genero|^genero$")]
col_edad <- names(df)[str_detect(names(df), "^x?2_\\.?edad|^edad$")]
col_cel <- names(df)[str_detect(names(df), "tienes_celular")]
col_compu <- names(df)[str_detect(names(df), "tienes_computadora_personal")]
stopifnot(length(col_futuro)==1, length(col_beca)==1, length(col_genero)==1,
length(col_edad)==1, length(col_cel)==1, length(col_compu)==1)
# Codificaciones
to_bin <- function(x) ifelse(str_to_lower(x) %in% c("sí","si"), 1L,
ifelse(str_to_lower(x)=="no", 0L, NA_integer_))
df <- df |>
mutate(
futuro_score = str_extract(.data[[col_futuro]], "^\\d+") |> as.integer(),
edad_num = str_extract(.data[[col_edad]], "\\d+") |> as.integer(),
genero = as.factor(.data[[col_genero]]),
cel_bin = to_bin(.data[[col_cel]]),
compu_bin = to_bin(.data[[col_compu]]),
beca_uso = as.factor(.data[[col_beca]])
)
model_df <- df |> select(futuro_score, beca_uso, genero, edad_num, cel_bin, compu_bin) |> drop_na()
summary(model_df)
## futuro_score beca_uso
## Min. :1.000 1. Transporte a la UNEVT : 4
## 1st Qu.:1.000 2. Comida durante mi estancia en la UNEVT: 2
## Median :2.000 3. Uniforme escolar UNEVT : 1
## Mean :1.917 4. Pago de colegiaturas : 2
## 3rd Qu.:3.000 No aplica :27
## Max. :5.000
## genero edad_num cel_bin compu_bin
## 1. Masculino:18 Min. :17.00 Min. :1 Min. :0.0000
## 2. Femenino :18 1st Qu.:18.00 1st Qu.:1 1st Qu.:1.0000
## Median :18.00 Median :1 Median :1.0000
## Mean :18.42 Mean :1 Mean :0.8333
## 3rd Qu.:19.00 3rd Qu.:1 3rd Qu.:1.0000
## Max. :25.00 Max. :1 Max. :1.0000
# Modelo base
mod_lin <- lm(futuro_score ~ genero + edad_num + cel_bin + compu_bin, data = model_df)
summary(mod_lin)
##
## Call:
## lm(formula = futuro_score ~ genero + edad_num + cel_bin + compu_bin,
## data = model_df)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.1166 -0.9328 0.0154 0.9561 2.8834
##
## Coefficients: (1 not defined because of singularities)
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.22569 2.47580 0.091 0.928
## genero2. Femenino -0.18374 0.34406 -0.534 0.597
## edad_num 0.08018 0.12728 0.630 0.533
## cel_bin NA NA NA NA
## compu_bin 0.36748 0.47490 0.774 0.445
##
## Residual standard error: 0.996 on 32 degrees of freedom
## Multiple R-squared: 0.03076, Adjusted R-squared: -0.06011
## F-statistic: 0.3385 on 3 and 32 DF, p-value: 0.7976
# --- BLOQUE SEGURO PARA VIF / ALIAS ---
model_df_vif <- model_df |> mutate(genero = forcats::fct_drop(genero))
X_check <- model.matrix(~ genero + edad_num + cel_bin + compu_bin, data = model_df_vif)[ , -1, drop = FALSE]
nzv_info <- caret::nearZeroVar(X_check, saveMetrics = TRUE)
# reconstruir fórmula con predictores conservados (a nivel de variables originales)
keep_vars <- c("genero","edad_num","cel_bin","compu_bin")
if (any(nzv_info$zeroVar)) {
# si hubo columnas dummy de varianza cero, intentamos mantener la variable madre igualmente
keep_vars <- keep_vars
}
form_lin_safe <- as.formula(paste("futuro_score ~", paste(keep_vars, collapse = " + ")))
mod_lin_safe <- lm(form_lin_safe, data = model_df_vif)
# Remover términos alias si existen
if (any(is.na(coef(mod_lin_safe)))) {
bad_terms <- names(coef(mod_lin_safe))[is.na(coef(mod_lin_safe))]
good_terms <- setdiff(attr(terms(mod_lin_safe), "term.labels"), bad_terms)
if (length(good_terms) >= 1) {
form_lin_safe2 <- as.formula(paste("futuro_score ~", paste(good_terms, collapse = " + ")))
mod_lin_safe <- lm(form_lin_safe2, data = model_df_vif)
}
}
cat("\n=== Modelo lineal (seguro) ===\n")
##
## === Modelo lineal (seguro) ===
summary(mod_lin_safe)
##
## Call:
## lm(formula = form_lin_safe2, data = model_df_vif)
##
## Residuals:
## Min 1Q Median 3Q Max
## -1.1166 -0.9328 0.0154 0.9561 2.8834
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 0.22569 2.47580 0.091 0.928
## genero2. Femenino -0.18374 0.34406 -0.534 0.597
## edad_num 0.08018 0.12728 0.630 0.533
## compu_bin 0.36748 0.47490 0.774 0.445
##
## Residual standard error: 0.996 on 32 degrees of freedom
## Multiple R-squared: 0.03076, Adjusted R-squared: -0.06011
## F-statistic: 0.3385 on 3 and 32 DF, p-value: 0.7976
# Tabla APA
apaTables::apa.reg.table(mod_lin_safe, filename = "tabla_regresion_futuro_APA.doc", table.number = 1)
##
##
## Table 1
##
## Regression results using futuro_score as the criterion
##
##
## Predictor b b_95%_CI sr2 sr2_95%_CI Fit
## (Intercept) 0.23 [-4.82, 5.27]
## genero2. Femenino -0.18 [-0.88, 0.52] .01 [-.05, .07]
## edad_num 0.08 [-0.18, 0.34] .01 [-.06, .08]
## compu_bin 0.37 [-0.60, 1.33] .02 [-.07, .10]
## R2 = .031
## 95% CI[.00,.13]
##
##
## Note. A significant b-weight indicates the semi-partial correlation is also significant.
## b represents unstandardized regression weights.
## sr2 represents the semi-partial correlation squared.
## Square brackets are used to enclose the lower and upper limits of a confidence interval.
## * indicates p < .05. ** indicates p < .01.
##
# Diagnósticos
par(mfrow = c(2,2)); plot(mod_lin_safe); par(mfrow = c(1,1))

res_lin <- residuals(mod_lin_safe)
shapiro.test(sample(res_lin, min(500, length(res_lin)))) # referencia
##
## Shapiro-Wilk normality test
##
## data: sample(res_lin, min(500, length(res_lin)))
## W = 0.89637, p-value = 0.002719
if (length(coef(mod_lin_safe)) > 2) car::vif(mod_lin_safe)
## genero edad_num compu_bin
## 1.074053 1.188196 1.136785
## ---- multinom_setup, message=FALSE, warning=FALSE ----
library(nnet)
library(dplyr)
library(forcats)
library(broom)
# model_df debe existir con estas columnas:
# futuro_score, beca_uso, genero, edad_num, cel_bin, compu_bin
stopifnot(all(c("beca_uso","genero","edad_num","cel_bin","compu_bin") %in% names(model_df)))
# Asegurar tipos correctos y niveles válidos
model_df_mult <- model_df %>%
mutate(
beca_uso = fct_drop(as.factor(beca_uso)), # quita niveles vacíos
genero = fct_drop(as.factor(genero)),
# (opcional) agrupar categorías raras de beca_uso si hay muchas con muy pocos casos
beca_uso = fct_lump_min(beca_uso, min = 5, other_level = "Otras")
) %>%
filter(!is.na(beca_uso), !is.na(genero), !is.na(edad_num),
!is.na(cel_bin), !is.na(compu_bin))
# Verificar que haya al menos 2 niveles en la respuesta
stopifnot(nlevels(model_df_mult$beca_uso) >= 2)
set.seed(123)
## ---- multinom_fit ----
mod_mult <- nnet::multinom(
beca_uso ~ genero + edad_num + cel_bin + compu_bin,
data = model_df_mult,
trace = FALSE
)
# Resumen del modelo
summary(mod_mult)
## Call:
## nnet::multinom(formula = beca_uso ~ genero + edad_num + cel_bin +
## compu_bin, data = model_df_mult, trace = FALSE)
##
## Coefficients:
## Values Std. Err.
## (Intercept) 2.9748216 3.5687713
## genero2. Femenino 0.4054474 0.8026298
## edad_num -0.3625345 0.3820815
## cel_bin 2.9748216 3.5687713
## compu_bin -0.7587933 1.0442515
##
## Residual Deviance: 38.95916
## AIC: 46.95916
## ---- multinom_full, message=FALSE, warning=FALSE ----
library(nnet)
library(dplyr)
library(forcats)
library(broom)
library(officer)
library(flextable)
# 1. Preprocesamiento
stopifnot(all(c("beca_uso","genero","edad_num","cel_bin","compu_bin") %in% names(model_df)))
model_df_mult <- model_df %>%
mutate(
beca_uso = fct_drop(as.factor(beca_uso)),
genero = fct_drop(as.factor(genero)),
beca_uso = fct_lump_min(beca_uso, min = 5, other_level = "Otras")
) %>%
filter(!is.na(beca_uso), !is.na(genero), !is.na(edad_num),
!is.na(cel_bin), !is.na(compu_bin))
stopifnot(nlevels(model_df_mult$beca_uso) >= 2)
# 2. Ajustar modelo multinomial
set.seed(123)
mod_mult <- multinom(beca_uso ~ genero + edad_num + cel_bin + compu_bin,
data = model_df_mult, trace = FALSE)
# 3. Tabla de OR e IC95%
mult_or <- tidy(mod_mult, conf.int = TRUE, exponentiate = TRUE)
# Si broom devuelve "y.level" en lugar de "level"
if ("y.level" %in% names(mult_or) && !"level" %in% names(mult_or)) {
mult_or <- rename(mult_or, level = y.level)
}
if (!"level" %in% names(mult_or)) {
mult_or <- mutate(mult_or, level = NA_character_)
}
mult_or <- mult_or %>%
mutate(across(where(is.numeric), ~ round(.x, 3))) %>%
relocate(level, .before = everything()) %>%
arrange(level, term)
# 4. Exportar tabla a Word
doc <- read_docx()
ft <- regulartable(mult_or) |> autofit()
doc <- body_add_par(doc, "Regresión logística multinomial — Uso de beca (OR e IC95%)", style = "heading 1")
doc <- body_add_flextable(doc, ft)
print(doc, target = "tabla_multinomial_OR_APA.docx")
# 5. Matriz de confusión y exactitud
pred_class <- predict(mod_mult, type = "class")
conf_mx <- table(Real = model_df_mult$beca_uso, Predicho = pred_class)
conf_mx
## Predicho
## Real No aplica Otras
## No aplica 26 1
## Otras 9 0
mean(pred_class == model_df_mult$beca_uso)
## [1] 0.7222222
# --- Pseudo R² (McFadden) robusto para multinomial nnet::multinom ---
# Si pscl::pR2 falla, calculamos con logLik
mcfadden_safe <- function(model, data, formula_null) {
ll_full <- as.numeric(logLik(model))
mod_null <- nnet::multinom(formula_null, data = data, trace = FALSE)
ll_null <- as.numeric(logLik(mod_null))
# k = número de parámetros estimados
k <- length(unlist(coef(model)))
list(
McFadden = 1 - (ll_full / ll_null),
McFadden_adj = 1 - ((ll_full - k) / ll_null),
ll_full = ll_full,
ll_null = ll_null,
k = k
)
}
# Intento con pscl::pR2 y fallback a McFadden manual
pR2_out <- try(pscl::pR2(mod_mult), silent = TRUE)
## fitting null model for pseudo-r2
if (inherits(pR2_out, "try-error")) {
pR2_out <- mcfadden_safe(mod_mult, model_df, beca_uso ~ 1)
}
pR2_out
## llh llhNull G2 McFadden r2ML r2CU
## -19.47958244 -20.24406521 1.52896553 0.03776330 0.04158199 0.06158102
# --- Árboles de decisión (CORREGIDOS) ---
library(rpart)
library(rpart.plot)
library(caret)
library(yardstick)
# Revisa que existan las columnas correctas
stopifnot(all(c("futuro_score","beca_uso","genero","edad_num","cel_bin","compu_bin") %in% names(model_df)))
set.seed(123)
# =========================
# 1) Árbol de REGRESIÓN
# (predice futuro_score)
# =========================
# Partición 70/30
idx_reg <- caret::createDataPartition(model_df$futuro_score, p = 0.7, list = FALSE)
train_reg <- model_df[idx_reg, ]
test_reg <- model_df[-idx_reg, ]
# Entrenar árbol (método anova para variable numérica)
tree_reg <- rpart(
futuro_score ~ genero + edad_num + cel_bin + compu_bin,
data = train_reg,
method = "anova",
control = rpart.control(minsplit = 15, cp = 0.01)
)
# Elegir cp óptimo por xerror mínimo y podar
best_cp_reg <- tree_reg$cptable[which.min(tree_reg$cptable[, "xerror"]), "CP"]
tree_reg_pruned <- prune(tree_reg, cp = best_cp_reg)
# Graficar (usa paleta válida para rpart.plot)
rpart.plot(
tree_reg_pruned,
type = 2, extra = 101, under = TRUE, faclen = 0, fallen.leaves = TRUE,
box.palette = "Greens", branch.lty = 3, shadow.col = "gray", nn = TRUE,
main = "Árbol de regresión — Percepción de futuro (futuro_score)"
)

# Desempeño en prueba
pred_reg <- predict(tree_reg_pruned, newdata = test_reg)
rmse_reg <- yardstick::rmse_vec(truth = test_reg$futuro_score, estimate = pred_reg)
mae_reg <- yardstick::mae_vec(truth = test_reg$futuro_score, estimate = pred_reg)
cat(sprintf("REGRESIÓN — RMSE: %.3f | MAE: %.3f\n", rmse_reg, mae_reg))
## REGRESIÓN — RMSE: 0.738 | MAE: 0.580
# =========================
# 2) Árbol de CLASIFICACIÓN
# (predice beca_uso)
# =========================
# Asegurar que la variable objetivo es factor
train_cls <- train_reg
test_cls <- test_reg
train_cls$beca_uso <- as.factor(train_cls$beca_uso)
test_cls$beca_uso <- as.factor(test_cls$beca_uso)
tree_cls <- rpart(
beca_uso ~ genero + edad_num + cel_bin + compu_bin,
data = train_cls,
method = "class",
control = rpart.control(minsplit = 15, cp = 0.01)
)
best_cp_cls <- tree_cls$cptable[which.min(tree_cls$cptable[, "xerror"]), "CP"]
tree_cls_pruned <- prune(tree_cls, cp = best_cp_cls)
rpart.plot(
tree_cls_pruned,
type = 2, extra = 104, under = TRUE, faclen = 0, fallen.leaves = TRUE,
box.palette = "Blues", branch.lty = 3, shadow.col = "gray", nn = TRUE,
main = "Árbol de clasificación — Uso de beca (beca_uso)"
)

# Métricas
pred_class <- predict(tree_cls_pruned, newdata = test_cls, type = "class")
cm <- caret::confusionMatrix(pred_class, reference = test_cls$beca_uso)
cm
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1. Transporte a la UNEVT
## 1. Transporte a la UNEVT 0
## 2. Comida durante mi estancia en la UNEVT 0
## 3. Uniforme escolar UNEVT 0
## 4. Pago de colegiaturas 0
## No aplica 0
## Reference
## Prediction 2. Comida durante mi estancia en la UNEVT
## 1. Transporte a la UNEVT 0
## 2. Comida durante mi estancia en la UNEVT 0
## 3. Uniforme escolar UNEVT 0
## 4. Pago de colegiaturas 0
## No aplica 2
## Reference
## Prediction 3. Uniforme escolar UNEVT
## 1. Transporte a la UNEVT 0
## 2. Comida durante mi estancia en la UNEVT 0
## 3. Uniforme escolar UNEVT 0
## 4. Pago de colegiaturas 0
## No aplica 0
## Reference
## Prediction 4. Pago de colegiaturas No aplica
## 1. Transporte a la UNEVT 0 0
## 2. Comida durante mi estancia en la UNEVT 0 0
## 3. Uniforme escolar UNEVT 0 0
## 4. Pago de colegiaturas 0 0
## No aplica 1 6
##
## Overall Statistics
##
## Accuracy : 0.6667
## 95% CI : (0.2993, 0.9251)
## No Information Rate : 0.6667
## P-Value [Acc > NIR] : 0.6503
##
## Kappa : 0
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1. Transporte a la UNEVT
## Sensitivity NA
## Specificity 1
## Pos Pred Value NA
## Neg Pred Value NA
## Prevalence 0
## Detection Rate 0
## Detection Prevalence 0
## Balanced Accuracy NA
## Class: 2. Comida durante mi estancia en la UNEVT
## Sensitivity 0.0000
## Specificity 1.0000
## Pos Pred Value NaN
## Neg Pred Value 0.7778
## Prevalence 0.2222
## Detection Rate 0.0000
## Detection Prevalence 0.0000
## Balanced Accuracy 0.5000
## Class: 3. Uniforme escolar UNEVT
## Sensitivity NA
## Specificity 1
## Pos Pred Value NA
## Neg Pred Value NA
## Prevalence 0
## Detection Rate 0
## Detection Prevalence 0
## Balanced Accuracy NA
## Class: 4. Pago de colegiaturas Class: No aplica
## Sensitivity 0.0000 1.0000
## Specificity 1.0000 0.0000
## Pos Pred Value NaN 0.6667
## Neg Pred Value 0.8889 NaN
## Prevalence 0.1111 0.6667
## Detection Rate 0.0000 0.6667
## Detection Prevalence 0.0000 1.0000
## Balanced Accuracy 0.5000 0.5000
cat(sprintf("CLASIFICACIÓN — Accuracy: %.3f\n", cm$overall["Accuracy"]))
## CLASIFICACIÓN — Accuracy: 0.667
col_herr <- names(df)[str_detect(names(df), "que_herramientas_digitales_dominas")]
if (length(col_herr) == 1) {
texto_df <- df |> select(texto = all_of(col_herr)) |> filter(!is.na(texto), texto != "No aplica")
palabras <- texto_df |>
unnest_tokens(word, texto) |>
filter(!str_detect(word, "^\\d+$")) |>
mutate(word = str_remove_all(word, "[[:punct:]]")) |>
filter(nchar(word) > 2)
sw <- stopwords::stopwords("es", source = "snowball")
palabras <- palabras |> filter(!word %in% sw)
top_pal <- palabras |> count(word, sort = TRUE) |> slice_max(n, n = 30)
ggplot(top_pal, aes(x = reorder(word, n), y = n)) +
geom_col() + coord_flip() +
labs(title = "Top 30 palabras (Herramientas digitales)", x = NULL, y = "Frecuencia")
set.seed(123)
wordcloud(words = top_pal$word, freq = top_pal$n, min.freq = 1,
colors = RColorBrewer::brewer.pal(8, "Dark2"), scale = c(3,0.7))
bigramas <- texto_df |>
unnest_tokens(bigram, texto, token = "ngrams", n = 2) |>
separate(bigram, into = c("w1","w2"), sep = " ", remove = TRUE) |>
mutate(across(c(w1,w2), ~str_remove_all(.x, "[[:punct:]]"))) |>
filter(!w1 %in% sw, !w2 %in% sw, nchar(w1)>2, nchar(w2)>2) |>
unite(bigram, w1, w2, sep = " ")
top_bi <- bigramas |> count(bigram, sort = TRUE) |> slice_max(n, n = 20)
ggplot(top_bi, aes(x = reorder(bigram, n), y = n)) +
geom_col() + coord_flip() +
labs(title = "Top 20 bigramas (Herramientas digitales)", x = NULL, y = "Frecuencia")
} else {
message("Ajusta el patrón de columna 'col_herr' si el nombre difiere.")
}


df_riesgo <- df |>
mutate(
riesgo_nivel = case_when(
futuro_score <= 2 ~ "Riesgo alto",
futuro_score == 3 ~ "Riesgo medio",
futuro_score >= 4 ~ "Sin riesgo",
TRUE ~ "Sin datos"
),
riesgo_nivel = case_when(
riesgo_nivel == "Sin riesgo" & (cel_bin == 0 & compu_bin == 0) ~ "Riesgo alto",
riesgo_nivel == "Sin riesgo" & (cel_bin == 0 | compu_bin == 0) ~ "Riesgo medio",
TRUE ~ riesgo_nivel
)
)
tabla_riesgo <- df_riesgo |> count(riesgo_nivel) |> mutate(prop = round(n/sum(n)*100,1))
tabla_riesgo
## # A tibble: 3 × 3
## riesgo_nivel n prop
## <chr> <int> <dbl>
## 1 Riesgo alto 26 72.2
## 2 Riesgo medio 9 25
## 3 Sin riesgo 1 2.8
ggplot(tabla_riesgo, aes(x = riesgo_nivel, y = n, fill = riesgo_nivel)) +
geom_col() +
scale_fill_manual(values = c("Riesgo alto"="red","Riesgo medio"="gold","Sin riesgo"="forestgreen","Sin datos"="grey70")) +
labs(title = "Distribución de niveles de riesgo", x = NULL, y = "Número de alumnos") +
theme_minimal()

alumnos_riesgo_alto <- df_riesgo |> filter(riesgo_nivel == "Riesgo alto")
writexl::write_xlsx(alumnos_riesgo_alto, "alumnos_riesgo_alto.xlsx")
lin_tidy <- broom::tidy(mod_lin_safe, conf.int = TRUE)
lin_glance <- broom::glance(mod_lin_safe)
top_terms <- lin_tidy %>% filter(term != "(Intercept)", p.value < .05) %>%
arrange(p.value) %>% pull(term) %>% paste(collapse = ", ")
glue("
**Modelo lineal (futuro_score)**
R² = {round(lin_glance$r.squared,3)}; Ajustado = {round(lin_glance$adj.r.squared,3)}; p = {signif(lin_glance$p.value,3)}.
Predictores significativos: {ifelse(nchar(top_terms)>0, top_terms, 'ninguno a α=0.05')}.
")
## **Modelo lineal (futuro_score)**
## R² = 0.031; Ajustado = -0.06; p = 0.798.
## Predictores significativos: ninguno a α=0.05.
resultados <- list(
coef_lineal_seguro = broom::tidy(mod_lin_safe, conf.int = TRUE),
ajuste_lineal_seguro = broom::glance(mod_lin_safe),
multinomial_OR = mult_or,
conteo_riesgo = tabla_riesgo
)
writexl::write_xlsx(resultados, "resultados_finales.xlsx")
cat("Archivos exportados:\n- tabla_regresion_futuro_APA.doc\n- tabla_multinomial_OR_APA.docx\n- alumnos_riesgo_alto.xlsx\n- resultados_finales.xlsx\n")
## Archivos exportados:
## - tabla_regresion_futuro_APA.doc
## - tabla_multinomial_OR_APA.docx
## - alumnos_riesgo_alto.xlsx
## - resultados_finales.xlsx
library(dplyr)
library(ggplot2)
# 1) Intentar importancia del árbol podado y, si no hay, la del árbol sin podar
imp_vec <- tree_cls_pruned$variable.importance
if (is.null(imp_vec) || length(imp_vec) == 0) {
imp_vec <- tree_cls$variable.importance
}
# 2) Si sigue vacía, refitar con hiperparámetros más permisivos para forzar splits
if (is.null(imp_vec) || length(imp_vec) == 0) {
message("El árbol no generó splits con los parámetros actuales. Reajustando con cp=0.001 y minsplit=5...")
tree_cls_alt <- rpart(
beca_uso ~ genero + edad_num + cel_bin + compu_bin,
data = train_cls,
method = "class",
control = rpart.control(minsplit = 5, cp = 0.001, maxdepth = 10)
)
best_cp_alt <- tree_cls_alt$cptable[which.min(tree_cls_alt$cptable[, "xerror"]), "CP"]
tree_cls_pruned_alt <- prune(tree_cls_alt, cp = best_cp_alt)
imp_vec <- tree_cls_pruned_alt$variable.importance
}
# 3) Construir el data frame y graficar solo si hay importancia
if (!is.null(imp_vec) && length(imp_vec) > 0) {
imp_cls <- tibble(
Variable = names(imp_vec),
Importancia = as.numeric(imp_vec)
) %>% arrange(desc(Importancia))
ggplot(imp_cls, aes(x = reorder(Variable, Importancia), y = Importancia)) +
geom_col() +
coord_flip() +
labs(
title = "Importancia de variables — Árbol de clasificación",
x = "Variable", y = "Importancia"
) +
theme_minimal()
} else {
message("No hay importancia de variables disponible (el modelo no generó divisiones útiles).
Intenta bajar cp, bajar minsplit o revisar que las variables tengan variación y niveles suficientes.")
}
library(reshape2)
cm_table <- as.data.frame(cm$table)
ggplot(cm_table, aes(Prediction, Reference, fill = Freq)) +
geom_tile() +
geom_text(aes(label = Freq), color = "white", size = 5) +
scale_fill_gradient(low = "lightblue", high = "darkblue") +
labs(
title = "Matriz de confusión — Árbol de clasificación",
x = "Predicho",
y = "Real"
) +
theme_minimal()

library(pROC)
# Solo si es binaria
if (nlevels(test_cls$beca_uso) == 2) {
probas <- predict(tree_cls_pruned, newdata = test_cls, type = "prob")[,2]
roc_obj <- roc(test_cls$beca_uso, probas)
plot(roc_obj, col = "blue", main = "Curva ROC — Árbol de clasificación")
auc_val <- auc(roc_obj)
cat("AUC:", auc_val, "\n")
}
# Dispersión por edad y futuro_score
ggplot(model_df, aes(x = edad_num, y = futuro_score, color = genero)) +
geom_point(alpha = 0.6) +
geom_smooth(method = "lm", se = FALSE) +
labs(title = "Relación entre edad y percepción de futuro",
x = "Edad", y = "Futuro (score)") +
theme_minimal()

# Densidad de futuro_score según uso de beca
ggplot(model_df, aes(x = futuro_score, fill = beca_uso)) +
geom_density(alpha = 0.4) +
labs(title = "Distribución de futuro_score por uso de beca") +
theme_minimal()

library(caret)
pred_class <- predict(tree_cls_pruned, newdata = test_cls, type = "class")
conf_mat <- confusionMatrix(pred_class, test_cls$beca_uso)
conf_mat
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1. Transporte a la UNEVT
## 1. Transporte a la UNEVT 0
## 2. Comida durante mi estancia en la UNEVT 0
## 3. Uniforme escolar UNEVT 0
## 4. Pago de colegiaturas 0
## No aplica 0
## Reference
## Prediction 2. Comida durante mi estancia en la UNEVT
## 1. Transporte a la UNEVT 0
## 2. Comida durante mi estancia en la UNEVT 0
## 3. Uniforme escolar UNEVT 0
## 4. Pago de colegiaturas 0
## No aplica 2
## Reference
## Prediction 3. Uniforme escolar UNEVT
## 1. Transporte a la UNEVT 0
## 2. Comida durante mi estancia en la UNEVT 0
## 3. Uniforme escolar UNEVT 0
## 4. Pago de colegiaturas 0
## No aplica 0
## Reference
## Prediction 4. Pago de colegiaturas No aplica
## 1. Transporte a la UNEVT 0 0
## 2. Comida durante mi estancia en la UNEVT 0 0
## 3. Uniforme escolar UNEVT 0 0
## 4. Pago de colegiaturas 0 0
## No aplica 1 6
##
## Overall Statistics
##
## Accuracy : 0.6667
## 95% CI : (0.2993, 0.9251)
## No Information Rate : 0.6667
## P-Value [Acc > NIR] : 0.6503
##
## Kappa : 0
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1. Transporte a la UNEVT
## Sensitivity NA
## Specificity 1
## Pos Pred Value NA
## Neg Pred Value NA
## Prevalence 0
## Detection Rate 0
## Detection Prevalence 0
## Balanced Accuracy NA
## Class: 2. Comida durante mi estancia en la UNEVT
## Sensitivity 0.0000
## Specificity 1.0000
## Pos Pred Value NaN
## Neg Pred Value 0.7778
## Prevalence 0.2222
## Detection Rate 0.0000
## Detection Prevalence 0.0000
## Balanced Accuracy 0.5000
## Class: 3. Uniforme escolar UNEVT
## Sensitivity NA
## Specificity 1
## Pos Pred Value NA
## Neg Pred Value NA
## Prevalence 0
## Detection Rate 0
## Detection Prevalence 0
## Balanced Accuracy NA
## Class: 4. Pago de colegiaturas Class: No aplica
## Sensitivity 0.0000 1.0000
## Specificity 1.0000 0.0000
## Pos Pred Value NaN 0.6667
## Neg Pred Value 0.8889 NaN
## Prevalence 0.1111 0.6667
## Detection Rate 0.0000 0.6667
## Detection Prevalence 0.0000 1.0000
## Balanced Accuracy 0.5000 0.5000
library(pROC)
# Probabilidades por clase
probs <- predict(tree_cls_pruned, newdata = test_cls, type = "prob")
y <- droplevels(factor(test_cls$beca_uso))
# Alinear niveles del factor con columnas de probas (intersección común)
common <- intersect(colnames(probs), levels(y))
if (length(common) < 2) {
stop("No hay al menos 2 clases en común entre las columnas de probabilidad y los niveles de la respuesta.")
}
probs <- probs[, common, drop = FALSE]
y <- factor(y, levels = common)
# Clases con soporte en el set de prueba
tab <- table(y)
# válidas para ROC binaria: al menos un positivo y un negativo
valid_bin <- names(tab)[tab > 0 & (length(y) - tab) > 0]
cat("Clases detectadas:", paste(common, collapse = " | "), "\n")
## Clases detectadas: 2. Comida durante mi estancia en la UNEVT | 4. Pago de colegiaturas | No aplica
cat("Clases con soporte para ROC binaria:", paste(valid_bin, collapse = " | "), "\n")
## Clases con soporte para ROC binaria: 2. Comida durante mi estancia en la UNEVT | 4. Pago de colegiaturas | No aplica
# --- AUC multiclass (Hand & Till) si hay >= 2 clases con soporte ---
if (length(valid_bin) >= 2) {
# pROC acepta una matriz de probabilidades con columnas por clase
mroc <- multiclass.roc(response = y, predictor = as.matrix(probs[, valid_bin, drop = FALSE]))
cat("AUC (multiclase, Hand & Till) =", as.numeric(mroc$auc), "\n")
} else {
cat("No es posible calcular AUC multiclase: hay menos de 2 clases con soporte en el set de prueba.\n")
}
## AUC (multiclase, Hand & Till) = 0.5
# --- Curvas ROC One-vs-Rest por cada clase válida ---
if (length(valid_bin) >= 1) {
cols <- grDevices::rainbow(length(valid_bin))
first <- TRUE
for (i in seq_along(valid_bin)) {
cls <- valid_bin[i]
y_bin <- as.numeric(y == cls)
# seguridad extra: debe haber 0 y 1
if (all(y_bin == 0) || all(y_bin == 1)) {
message("Se omite la clase '", cls, "' por falta de positivos o negativos en test.")
next
}
roc_i <- roc(response = y_bin, predictor = probs[, cls], quiet = TRUE)
plot(roc_i, col = cols[i], add = !first,
main = "Curvas ROC One-vs-Rest — Árbol de clasificación")
first <- FALSE
}
if (!first) legend("bottomright", legend = valid_bin, col = cols, lwd = 2, cex = .8)
} else {
cat("No hay clases con soporte suficiente para trazar ROC One-vs-Rest.\n")
}

## ---- comparacion_modelos_segura, message=FALSE, warning=FALSE ----
library(dplyr)
library(forcats)
library(caret)
# Requisitos previos:
# - model_df con: beca_uso, genero, edad_num, cel_bin, compu_bin
# - (ideal) ya tener tree_cls_pruned, train_cls, test_cls
# - (ideal) ya tener mod_mult (nnet::multinom)
# 1) Si NO existe el split train/test para clasificación, créalo
if (!exists("train_cls") || !exists("test_cls")) {
stopifnot(all(c("beca_uso","genero","edad_num","cel_bin","compu_bin") %in% names(model_df)))
set.seed(123)
idx2 <- caret::createDataPartition(as.factor(model_df$beca_uso), p = 0.7, list = FALSE)
train_cls <- model_df[idx2, ]
test_cls <- model_df[-idx2, ]
}
# Asegurar tipos correctos en train/test
train_cls <- train_cls %>%
mutate(
beca_uso = fct_drop(as.factor(beca_uso)),
genero = fct_drop(as.factor(genero))
)
test_cls <- test_cls %>%
mutate(
beca_uso = fct_drop(as.factor(beca_uso)),
genero = fct_drop(as.factor(genero))
)
# 2) Si NO existe el árbol podado, ajústalo y pódalo
if (!exists("tree_cls_pruned")) {
library(rpart)
tree_cls <- rpart(
beca_uso ~ genero + edad_num + cel_bin + compu_bin,
data = train_cls, method = "class",
control = rpart.control(minsplit = 15, cp = 0.01)
)
best_cp_cls <- tree_cls$cptable[which.min(tree_cls$cptable[, "xerror"]), "CP"]
tree_cls_pruned <- prune(tree_cls, cp = best_cp_cls)
}
# 3) Si NO existe la multinomial, ajústala sobre el mismo split (para que prediga bien en test_cls)
if (!exists("mod_mult")) {
library(nnet)
mod_mult <- nnet::multinom(
beca_uso ~ genero + edad_num + cel_bin + compu_bin,
data = train_cls, trace = FALSE
)
}
# 4) Preparar newdata para predicción (asegurar columnas y tipos)
vars_x <- c("genero","edad_num","cel_bin","compu_bin")
newdata_test <- test_cls[, vars_x, drop = FALSE]
# Alinear niveles de factores con los del entrenamiento
newdata_test$genero <- factor(newdata_test$genero, levels = levels(train_cls$genero))
# 5) Predicciones de ambos modelos (crea pred_tree y pred_mult)
pred_tree <- predict(tree_cls_pruned, newdata = newdata_test, type = "class")
pred_mult <- try(predict(mod_mult, newdata = newdata_test, type = "class"), silent = TRUE)
# Si la predicción multinomial falló por niveles, reintenta alineando beca_uso del entrenamiento
if (inherits(pred_mult, "try-error")) {
# re-entrena multinomial con niveles "definitivos"
library(nnet)
train_tmp <- train_cls %>%
mutate(
beca_uso = fct_drop(beca_uso),
genero = factor(genero)
)
mod_mult <- nnet::multinom(beca_uso ~ genero + edad_num + cel_bin + compu_bin,
data = train_tmp, trace = FALSE)
pred_mult <- predict(mod_mult, newdata = newdata_test, type = "class")
}
# 6) Alinear niveles de las predicciones con el "ground truth"
lvl_ref <- levels(test_cls$beca_uso)
pred_tree <- factor(pred_tree, levels = lvl_ref)
pred_mult <- factor(pred_mult, levels = lvl_ref)
# 7) Métricas y comparación
cm_tree <- caret::confusionMatrix(pred_tree, reference = test_cls$beca_uso)
cm_mult <- caret::confusionMatrix(pred_mult, reference = test_cls$beca_uso)
comp <- data.frame(
Modelo = c("Árbol (rpart)", "Reg. Multinomial"),
Accuracy = c(cm_tree$overall["Accuracy"], cm_mult$overall["Accuracy"]),
Kappa = c(cm_tree$overall["Kappa"], cm_mult$overall["Kappa"])
)
comp
## Modelo Accuracy Kappa
## 1 Árbol (rpart) 0.6666667 0
## 2 Reg. Multinomial 0.6666667 0
# Mostrar matrices de confusión
cm_tree
## Confusion Matrix and Statistics
##
## Reference
## Prediction 2. Comida durante mi estancia en la UNEVT
## 2. Comida durante mi estancia en la UNEVT 0
## 4. Pago de colegiaturas 0
## No aplica 2
## Reference
## Prediction 4. Pago de colegiaturas No aplica
## 2. Comida durante mi estancia en la UNEVT 0 0
## 4. Pago de colegiaturas 0 0
## No aplica 1 6
##
## Overall Statistics
##
## Accuracy : 0.6667
## 95% CI : (0.2993, 0.9251)
## No Information Rate : 0.6667
## P-Value [Acc > NIR] : 0.6503
##
## Kappa : 0
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 2. Comida durante mi estancia en la UNEVT
## Sensitivity 0.0000
## Specificity 1.0000
## Pos Pred Value NaN
## Neg Pred Value 0.7778
## Prevalence 0.2222
## Detection Rate 0.0000
## Detection Prevalence 0.0000
## Balanced Accuracy 0.5000
## Class: 4. Pago de colegiaturas Class: No aplica
## Sensitivity 0.0000 1.0000
## Specificity 1.0000 0.0000
## Pos Pred Value NaN 0.6667
## Neg Pred Value 0.8889 NaN
## Prevalence 0.1111 0.6667
## Detection Rate 0.0000 0.6667
## Detection Prevalence 0.0000 1.0000
## Balanced Accuracy 0.5000 0.5000
cm_mult
## Confusion Matrix and Statistics
##
## Reference
## Prediction 2. Comida durante mi estancia en la UNEVT
## 2. Comida durante mi estancia en la UNEVT 0
## 4. Pago de colegiaturas 0
## No aplica 2
## Reference
## Prediction 4. Pago de colegiaturas No aplica
## 2. Comida durante mi estancia en la UNEVT 0 0
## 4. Pago de colegiaturas 0 0
## No aplica 1 6
##
## Overall Statistics
##
## Accuracy : 0.6667
## 95% CI : (0.2993, 0.9251)
## No Information Rate : 0.6667
## P-Value [Acc > NIR] : 0.6503
##
## Kappa : 0
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 2. Comida durante mi estancia en la UNEVT
## Sensitivity 0.0000
## Specificity 1.0000
## Pos Pred Value NaN
## Neg Pred Value 0.7778
## Prevalence 0.2222
## Detection Rate 0.0000
## Detection Prevalence 0.0000
## Balanced Accuracy 0.5000
## Class: 4. Pago de colegiaturas Class: No aplica
## Sensitivity 0.0000 1.0000
## Specificity 1.0000 0.0000
## Pos Pred Value NaN 0.6667
## Neg Pred Value 0.8889 NaN
## Prevalence 0.1111 0.6667
## Detection Rate 0.0000 0.6667
## Detection Prevalence 0.0000 1.0000
## Balanced Accuracy 0.5000 0.5000
# Alinear niveles de pred_mult y test_cls$beca_uso
pred_mult <- factor(pred_mult, levels = levels(test_cls$beca_uso))
pred_tree <- factor(pred_tree, levels = levels(test_cls$beca_uso))
# Comparación de modelos
data.frame(
Modelo = c("Árbol", "Reg. Multinomial"),
Accuracy = c(
mean(pred_tree == test_cls$beca_uso, na.rm = TRUE),
mean(pred_mult == test_cls$beca_uso, na.rm = TRUE)
)
)
## Modelo Accuracy
## 1 Árbol 0.6666667
## 2 Reg. Multinomial 0.6666667
library(ggplot2)
res <- residuals(mod_mult, type = "deviance")
ggplot(data.frame(Residuos = res), aes(x = Residuos)) +
geom_histogram(bins = 30, fill = "skyblue", color = "white") +
theme_minimal() +
labs(title = "Distribución de residuos — Reg. Logística Multinomial")

library(DALEX)
explainer <- explain(tree_cls_pruned, data = train_cls[, -1], y = train_cls$beca_uso)
## Preparation of a new explainer is initiated
## -> model label : rpart ( default )
## -> data : 27 rows 5 cols
## -> data : tibble converted into a data.frame
## -> target variable : 27 values
## -> predict function : yhat.rpart will be used ( default )
## -> predicted values : No value for predict function target column. ( default )
## -> model_info : package rpart , ver. 4.1.24 , task multiclass ( default )
## -> predicted values : predict function returns multiple columns: 5 ( default )
## -> residual function : difference between 1 and probability of true class ( default )
## -> residuals : numerical, min = 0.2222222 , mean = 0.3703704 , max = 0.962963
## A new explainer has been created!
vi <- model_parts(explainer)
plot(vi)

library(rpart.plot)
rpart.plot(tree_cls_pruned, type = 2, extra = 101, under = TRUE, box.palette = "GnBu")

## ---- estadisticos_centrales, message=FALSE ----
library(dplyr)
# Función para calcular moda
moda <- function(x) {
ux <- unique(na.omit(x))
ux[which.max(tabulate(match(x, ux)))]
}
# Seleccionamos solo variables numéricas
datos_numericos <- model_df %>%
select(where(is.numeric))
# Calculamos media, mediana y moda
estadisticos <- datos_numericos %>%
summarise(across(everything(),
list(
Media = ~mean(., na.rm = TRUE),
Mediana = ~median(., na.rm = TRUE),
Moda = ~moda(.)
),
.names = "{.col}_{.fn}"
))
estadisticos
## # A tibble: 1 × 12
## futuro_score_Media futuro_score_Mediana futuro_score_Moda edad_num_Media
## <dbl> <dbl> <int> <dbl>
## 1 1.92 2 1 18.4
## # ℹ 8 more variables: edad_num_Mediana <dbl>, edad_num_Moda <int>,
## # cel_bin_Media <dbl>, cel_bin_Mediana <dbl>, cel_bin_Moda <int>,
## # compu_bin_Media <dbl>, compu_bin_Mediana <dbl>, compu_bin_Moda <int>
## ---- graficas_generales, message=FALSE, warning=FALSE ----
library(tidyverse)
library(forcats)
# 0) Carpeta de salida
dir.create("graficas", showWarnings = FALSE)
# 1) Funciones de utilería ---------------------------------------------
etiquetas_pct <- function(n) scales::percent(n / sum(n), accuracy = 0.1)
plot_barras <- function(data, var, top_n = Inf, titulo = NULL) {
v <- rlang::ensym(var)
dfp <- data %>%
mutate({{var}} := fct_explicit_na(as.factor(!!v), na_level = "No aplica")) %>%
count({{var}}, name = "n") %>%
mutate(pct = n / sum(n),
label = scales::percent(pct, accuracy = 0.1)) %>%
arrange(desc(n)) %>%
{ if (is.finite(top_n)) slice_head(., n = top_n) else . }
ggplot(dfp, aes(x = reorder(!!v, n), y = n)) +
geom_col() +
geom_text(aes(label = label), hjust = -0.1, size = 3) +
coord_flip() +
labs(title = titulo %||% rlang::as_string(v),
x = NULL, y = "Frecuencia") +
theme_minimal() +
expand_limits(y = max(dfp$n) * 1.15)
}
plot_pastel <- function(data, var, top_n = Inf, titulo = NULL) {
v <- rlang::ensym(var)
dfp <- data %>%
mutate({{var}} := fct_explicit_na(as.factor(!!v), na_level = "No aplica")) %>%
count({{var}}, name = "n") %>%
arrange(desc(n)) %>%
{ if (is.finite(top_n)) slice_head(., n = top_n) else . } %>%
mutate(pct = n / sum(n),
label = paste0(scales::percent(pct, accuracy = 0.1), " ", as.character(!!v)))
ggplot(dfp, aes(x = "", y = pct, fill = !!v)) +
geom_col(width = 1, color = "white") +
coord_polar(theta = "y") +
labs(title = titulo %||% paste0("Pastel: ", rlang::as_string(v)), x = NULL, y = NULL, fill = NULL) +
theme_void() +
geom_text(aes(label = scales::percent(pct, accuracy = 0.1)),
position = position_stack(vjust = 0.5), size = 3)
}
# 2) Detectar tipos de variables ----------------------------------------
# Criterio: variable "categórica" si es factor/character con <= 20 categorías únicas
# (evita graficar texto libre muy disperso)
MAX_NIVELES <- 20
es_categorica <- function(x) {
is.factor(x) ||
(is.character(x) && dplyr::n_distinct(x, na.rm = TRUE) <= MAX_NIVELES)
}
vars_cat <- names(df)[vapply(df, es_categorica, logical(1))]
vars_num <- names(df)[vapply(df, is.numeric, logical(1))]
cat("Variables categóricas detectadas (máx 20 niveles):\n")
## Variables categóricas detectadas (máx 20 niveles):
print(vars_cat)
## [1] "plantel_al_que_perteneces"
## [2] "licenciatura_que_cursas"
## [3] "semestre"
## [4] "grupo"
## [5] "x1_genero"
## [6] "a_que_municipio_perteneces"
## [7] "si_no_eres_originario_del_estado_de_mexico_a_que_estado_o_pais_perteneces"
## [8] "x3_cual_es_tu_estado_civil"
## [9] "tienes_una_relacion_sentimental"
## [10] "tiempo_de_relacion_con_tu_pareja"
## [11] "x4_tienes_hijos"
## [12] "cuantos"
## [13] "alguien_depende_de_ti_economicamente"
## [14] "x5_sales_a_fiestas_o_reuniones_con_tus_amigas_os"
## [15] "en_reuniones_y_o_actividades_sociales_ingieres_bebidas_alcoholicas_y_o_tabaco"
## [16] "te_expulsaron_y_o_suspendieron_en_alguna_ocasion"
## [17] "tuviste_problemas_con_algun_maestro_a_o_algun_companero_a"
## [18] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_la_bibliografia_del_programa_de_estudio_de_la_licenciatura"
## [19] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_bibliografia_que_busco_por_mi_cuenta"
## [20] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_antologias"
## [21] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_revistas_especializadas"
## [22] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_enciclopedias"
## [23] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_diccionarios"
## [24] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_libros_de_texto"
## [25] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_paginas_web"
## [26] "que_tipo_de_lecturas_acostumbras_a_utilizar_al_cursar_tus_estudios_redes_sociales"
## [27] "donde_obtienes_los_materiales_para_realizar_tus_actividades_academicas_biblioteca"
## [28] "donde_obtienes_los_materiales_para_realizar_tus_actividades_academicas_los_compro"
## [29] "donde_obtienes_los_materiales_para_realizar_tus_actividades_academicas_los_fotocopio"
## [30] "donde_obtienes_los_materiales_para_realizar_tus_actividades_academicas_los_pido_prestados"
## [31] "donde_obtienes_los_materiales_para_realizar_tus_actividades_academicas_via_internet"
## [32] "cuales_son_las_formas_de_estudio_y_o_realizacion_de_trabajos_escolares_que_empleas_regularmente_individual"
## [33] "cuales_son_las_formas_de_estudio_y_o_realizacion_de_trabajos_escolares_que_empleas_regularmente_grupal"
## [34] "en_promedio_cuantas_horas_a_la_semana_dedicas_para_el_desarrollo_de_actividades_extraclases_comprende_lecturas_trabajos_y_tareas"
## [35] "a_lo_largo_de_tu_trayectoria_en_la_unevt_que_opinion_tienes_sobre_los_trabajos_y_evidencias_que_te_piden_tus_docentes_marca_una_opcion_por_renglon_las_y_los_profesores_revisan_los_trabajos"
## [36] "a_lo_largo_de_tu_trayectoria_en_la_unevt_que_opinion_tienes_sobre_los_trabajos_y_evidencias_que_te_piden_tus_docentes_marca_una_opcion_por_renglon_las_y_los_profesores_regresan_los_trabajos"
## [37] "a_lo_largo_de_tu_trayectoria_en_la_unevt_que_opinion_tienes_sobre_los_trabajos_y_evidencias_que_te_piden_tus_docentes_marca_una_opcion_por_renglon_las_y_los_profesores_regresan_los_trabajos_con_correcciones_y_comentarios"
## [38] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_presentacion"
## [39] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_uso_de_bibliografia"
## [40] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_ortografia"
## [41] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_redaccion"
## [42] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_extension"
## [43] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_dominio_de_la_materia"
## [44] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_ideas_originales"
## [45] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_capacidad_de_analisis"
## [46] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_capacidad_de_critica"
## [47] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_capacidad_de_sintesis"
## [48] "que_aspectos_fueron_revisados_en_tus_trabajos_marca_una_opcion_por_cada_renglon_orden_y_coherencia"
## [49] "con_que_frecuencia_asistes_a_los_siguientes_eventos_marca_una_opcion_por_renglon_musica"
## [50] "con_que_frecuencia_asistes_a_los_siguientes_eventos_marca_una_opcion_por_renglon_exposiciones_artisticas"
## [51] "con_que_frecuencia_asistes_a_los_siguientes_eventos_marca_una_opcion_por_renglon_cine"
## [52] "con_que_frecuencia_asistes_a_los_siguientes_eventos_marca_una_opcion_por_renglon_presentacion_de_libros"
## [53] "con_que_frecuencia_asistes_a_los_siguientes_eventos_marca_una_opcion_por_renglon_danza"
## [54] "con_que_frecuencia_asistes_a_los_siguientes_eventos_marca_una_opcion_por_renglon_eventos_deportivos"
## [55] "con_que_frecuencia_asistes_a_los_siguientes_eventos_marca_una_opcion_por_renglon_teatro"
## [56] "con_que_frecuencia_asistes_a_los_siguientes_eventos_marca_una_opcion_por_renglon_museos"
## [57] "realizas_alguna_de_las_siguientes_actividades_socio_recreativas_a_deportiva"
## [58] "realizas_alguna_de_las_siguientes_actividades_socio_recreativas_b_culturales"
## [59] "hablas_algun_dialecto_o_lengua_indigena"
## [60] "tus_papas_o_tus_abuelos_as_son_indigenas"
## [61] "tus_papas_o_tus_abuelos_as_hablan_o_entienden_alguna_lengua_indigena"
## [62] "actualmente_vive_con"
## [63] "cual_es_tu_lugar_de_residencia_mientras_estudias_en_la_unevt"
## [64] "selecciona_los_servicios_con_los_que_cuenta_la_casa_donde_vives_servicios"
## [65] "que_lugar_ocupan_tus_estudios_dentro_de_las_prioridades_de_tu_familia"
## [66] "cuanto_tiempo_haces_diariamente_para_trasladarte_de_tu_lugar_de_residencia_a_la_escuela"
## [67] "tienes_alguna_discapacidad"
## [68] "padeces_o_padeciste_alguna_enfermedad"
## [69] "cual"
## [70] "algun_familiar_de_linea_directa_padece_alguna_enfermedad"
## [71] "presentas_alguna_de_las_siguientes_condiciones_usas_lentes"
## [72] "presentas_alguna_de_las_siguientes_condiciones_estas_en_tratamiento_dental"
## [73] "presentas_alguna_de_las_siguientes_condiciones_tienes_alguna_discapacidad_auditiva"
## [74] "presentas_alguna_de_las_siguientes_condiciones_estas_en_algun_tratamiento_medico"
## [75] "presentas_alguna_de_las_siguientes_condiciones_tienes_algun_padecimiento_cronico"
## [76] "presentas_alguna_de_las_siguientes_condiciones_tienes_alguna_alergia"
## [77] "haz_llevado_algun_seguimiento_de_atencion_psicologica"
## [78] "actualmente_continuas_con_un_plan_de_atencion_psicologica"
## [79] "en_cual_de_las_siguientes_instituciones_de_salud_tienes_derecho_a_recibir_servicio_medico_senale_todas_las_opciones_que_aplica"
## [80] "la_licenciatura_estas_estudiando_fue_tu_primera_opcion"
## [81] "asistes_a_la_universidad_porque_crees_que_te_sera_de_utilidad"
## [82] "te_gusta_la_universidad"
## [83] "que_tanto_dominas_el_idioma_ingles"
## [84] "en_que_areas_considera_utilizar_el_idioma_ingles_areas"
## [85] "que_nivel_de_idioma_ingles_tienes"
## [86] "con_que_documento_probatorio_compruebas_tu_nivel_de_idioma_ingles"
## [87] "en_la_actualidad_en_cual_de_las_siguientes_situaciones_te_encuentras"
## [88] "cual_es_la_principal_razon_por_la_que_dejarias_de_estudiar"
## [89] "los_recursos_economicos_con_los_que_cuentas_para_desarrollar_tus_actividades_academicas_son"
## [90] "cual_es_el_medio_de_transporte_que_utilizas_regularmente_para_trasladarte_a_la_unevt_puedes_marcar_mas_de_una_opcion"
## [91] "a_cuanto_ascienden_tus_gastos_escolares_sepanles"
## [92] "quien_cubre_tus_gastos_escolares"
## [93] "cual_fue_la_escolaridad_maxima_alcanzada_por_tu_padre"
## [94] "cual_fue_la_escolaridad_maxima_alcanzada_por_tu_madre"
## [95] "recibes_alguna_beca_para_apoyar_tus_estudios"
## [96] "cual_beca_recibes"
## [97] "en_caso_de_tener_otra_beca_especifica"
## [98] "para_que_utilizas_principalmente_el_dinero_de_la_beca"
## [99] "como_te_sientes_en_relacion_a_tu_futuro"
## [100] "tienes_celular"
## [101] "tienes_computadora_personal"
## [102] "principalmente_en_donde_usas_internet"
## [103] "de_las_siguientes_redes_sociales_selecciona_en_las_que_estas_registrado"
## [104] "genero"
## [105] "beca_uso"
cat("\nVariables numéricas detectadas:\n")
##
## Variables numéricas detectadas:
print(vars_num)
## [1] "x2_edad" "futuro_score" "edad_num" "cel_bin" "compu_bin"
# 3) Graficar categóricas: Barras + Pastel ------------------------------
for (v in vars_cat) {
titulo <- stringr::str_to_sentence(gsub("_", " ", v))
p_bar <- plot_barras(df, !!rlang::sym(v), titulo = titulo)
p_pie <- plot_pastel(df, !!rlang::sym(v), titulo = titulo)
# Mostrar en el documento
print(p_bar)
print(p_pie)
# Guardar (PNG)
ggsave(filename = file.path("graficas", paste0("barra_", v, ".png")),
plot = p_bar, width = 7, height = 5, dpi = 150)
ggsave(filename = file.path("graficas", paste0("pastel_", v, ".png")),
plot = p_pie, width = 6, height = 6, dpi = 150)
}


















































































































































































































# 4) Graficar numéricas: Histograma + Boxplot ---------------------------
for (v in vars_num) {
titulo <- stringr::str_to_sentence(gsub("_", " ", v))
p_hist <- ggplot(df, aes(x = .data[[v]])) +
geom_histogram(bins = 20, color = "white") +
labs(title = paste0("Histograma: ", titulo), x = v, y = "Frecuencia") +
theme_minimal()
p_box <- ggplot(df, aes(y = .data[[v]])) +
geom_boxplot(outlier.alpha = 0.5) +
labs(title = paste0("Boxplot: ", titulo), x = NULL, y = v) +
theme_minimal()
print(p_hist)
print(p_box)
ggsave(filename = file.path("graficas", paste0("hist_", v, ".png")),
plot = p_hist, width = 7, height = 5, dpi = 150)
ggsave(filename = file.path("graficas", paste0("box_", v, ".png")),
plot = p_box, width = 5, height = 5, dpi = 150)
}









