Taller práctico: Análisis de datos para tesis cualitativas y cuantitativas con RStudio e IA

Factores asociados al rendimiento académico en estudiantes universitarios

Author

Jorge L. Cutipa Musaja

Published

April 10, 2026


🎯 Planteamiento del Caso

Contexto de investigación

Trabajaremos con una base de datos de 500 estudiantes universitarios para identificar factores asociados con su rendimiento académico (medido como promedio ponderado en escala 0-20).

Objetivo del taller: Demostrar un flujo de trabajo completo desde la importación de datos hasta la interpretación asistida con IA, aplicable directamente a tu proyecto de tesis.

Pregunta de investigación

¿Qué variables personales, académicas, tecnológicas y de hábitos se relacionan significativamente con el rendimiento académico de los estudiantes?

Variable Descripción Tipo
promedio Promedio académico ponderado (0-20) Numérica continua
aprobado Estado de aprobación (0=No, 1=Sí) Binaria
Variable Descripción Valores
edad Edad del estudiante 17-35 años
genero Género F, M
estado_civil Estado civil soltero, casado, conviviente
nivel_socioeconomico Nivel socioeconómico bajo, medio, alto
trabaja Condición de trabajar 0=No, 1=Sí
horas_trabajo Horas semanales de trabajo 0-40
Variable Descripción Valores
carrera Programa de estudios Marketing, Contabilidad, Economía, Administración, Finanzas
ciclo Ciclo académico actual 1-10
horas_estudio Horas semanales de estudio 0-40
asistencia Porcentaje de asistencia a clases 0-100
promedio_previo Promedio del ciclo anterior 0-20
Variable Descripción Valores
uso_ia Uso de herramientas de IA 0=No, 1=Sí
internet Acceso a internet 0=No, 1=Sí
horas_sueno Horas promedio de sueño 3-10
estres Nivel percibido de estrés 1-5 (Ordinal)
motivacion Nivel de motivación académica 1-5 (Ordinal)

📥 Importación y exploración inicial de datos

Code
datos <- read_csv("datos_estudiantes_500.csv")
glimpse(datos)
Rows: 500
Columns: 22
$ edad                 <dbl> 23, 31, 27, 24, 23, 35, 27, 27, 20, 24, 19, 18, 2…
$ genero               <chr> "F", "F", "M", "M", "M", "F", "F", "M", "M", "F",…
$ estado_civil         <chr> "soltero", "soltero", "soltero", "soltero", "solt…
$ nivel_socioeconomico <chr> "medio", "bajo", "medio", "bajo", "bajo", "medio"…
$ carrera              <chr> "Marketing", "Contabilidad", "Marketing", "Market…
$ ciclo                <dbl> 8, 2, 10, 4, 6, 2, 7, 3, 3, 2, 8, 6, 1, 1, 7, 8, …
$ horas_estudio        <dbl> 8.9, 10.2, 8.4, 9.9, 19.3, 9.8, 28.4, 17.8, 21.5,…
$ asistencia           <dbl> 100.0, 85.0, 72.1, 89.3, 68.4, 100.0, 80.3, 84.7,…
$ promedio_previo      <dbl> 12.2, 14.7, 10.1, 16.2, 13.7, 12.2, 11.5, 11.2, 1…
$ internet             <dbl> 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ tipo_dispositivo     <chr> "laptop", "laptop", "laptop", "celular", "laptop"…
$ uso_plataformas      <chr> "medio", "medio", "medio", "medio", "bajo", "alto…
$ horas_internet       <dbl> 3.6, 0.5, 2.2, 0.0, 3.1, 4.0, 1.2, 3.1, 4.3, 4.0,…
$ trabaja              <dbl> 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0…
$ horas_trabajo        <dbl> 30, 0, 0, 0, 39, 27, 39, 0, 34, 12, 0, 0, 0, 0, 0…
$ horas_sueno          <dbl> 5.3, 8.1, 8.8, 5.7, 6.9, 6.7, 6.8, 9.2, 5.7, 6.6,…
$ estres               <dbl> 4, 3, 3, 3, 1, 1, 1, 1, 5, 2, 5, 3, 5, 4, 1, 4, 4…
$ motivacion           <dbl> 4, 4, 2, 3, 5, 2, 2, 1, 5, 2, 1, 5, 4, 1, 4, 3, 3…
$ metodo_estudio       <chr> "lectura", "videos", "resúmenes", "mixto", "video…
$ uso_ia               <dbl> 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1…
$ promedio             <dbl> 12.82, 17.58, 10.60, 14.95, 10.05, 8.55, 11.36, 1…
$ aprobado             <dbl> 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0…

Verificaciones de calidad de datos

Code
# 1. Valores faltantes
missings <- datos %>% 
  summarise(across(everything(), ~sum(is.na(.)))) %>% 
  pivot_longer(everything(), names_to = "variable", values_to = "n_missing") %>% 
  filter(n_missing > 0)

if(nrow(missings) > 0) {
  cat("⚠️ Variables con valores faltantes:\n")
  print(missings)
} else {
  cat("✅ Sin valores faltantes detectados\n")
}
⚠️ Variables con valores faltantes:
# A tibble: 1 × 2
  variable        n_missing
  <chr>               <int>
1 promedio_previo        55
Code
# 2. Registros duplicados
n_unicos <- datos %>% distinct() %>% nrow()
cat(sprintf("📋 Registros únicos: %d de %d (%.1f%%)\n", 
            n_unicos, nrow(datos), 100*n_unicos/nrow(datos)))
📋 Registros únicos: 500 de 500 (100.0%)
Code
# 3. Distribución de la variable resultado
datos %>%
  ggplot(aes(x = promedio)) +
  geom_histogram(aes(y = after_stat(density)), bins = 50, 
                 fill = "#4E79A7", color = "white", alpha = 0.8) +
  geom_density(color = "#E15759", linewidth = 1) +
  geom_vline(xintercept = 11, linetype = "dashed", color = "#2E8B57", linewidth = 1) +
  labs(title = "Distribución del promedio académico",
       subtitle = "Línea verde: referencia de aprobación (11 puntos)",
       x = "Promedio ponderado (0-20)", y = "Densidad") +
  theme_minimal(base_size = 11) +
  theme(panel.grid.minor = element_blank())


Limpieza y preparación de variables

Code
datos_limpios <- datos %>%
    clean_names() %>%
    mutate(
      ciclo = factor(ciclo),
      estres = factor(estres),
      motivacion = factor(motivacion),
      estado_civil = factor(estado_civil),
      carrera = factor(carrera),
      tipo_dispositivo = factor(tipo_dispositivo),
      metodo_estudio = factor(metodo_estudio),
      genero = factor(genero, levels = c("F", "M"), 
                      labels = c("Femenino", "Masculino")),
      nivel_socioeconomico = factor(nivel_socioeconomico, 
                                   levels = c("bajo", "medio", "alto"), 
                                   ordered = TRUE),
      uso_plataformas = factor(uso_plataformas, 
                               levels = c("bajo", "medio", "alto"), 
                               ordered = TRUE),
      internet = factor(internet, levels = c(0, 1), 
                        labels = c("No tiene internet", "Sí tiene internet")),
      uso_ia = factor(uso_ia, levels = c(0, 1), 
                      labels = c("No usa IA", "Sí usa IA")),
      trabaja = factor(trabaja, levels = c(0, 1), 
                       labels = c("No trabaja", "Sí trabaja")),
      aprobado = factor(aprobado, levels = c(0, 1),
                        labels = c("No aprueba", "Sí aprueba"))
    )

# Verificar cambios
glimpse(datos_limpios)
Rows: 500
Columns: 22
$ edad                 <dbl> 23, 31, 27, 24, 23, 35, 27, 27, 20, 24, 19, 18, 2…
$ genero               <fct> Femenino, Femenino, Masculino, Masculino, Masculi…
$ estado_civil         <fct> soltero, soltero, soltero, soltero, soltero, solt…
$ nivel_socioeconomico <ord> medio, bajo, medio, bajo, bajo, medio, medio, med…
$ carrera              <fct> Marketing, Contabilidad, Marketing, Marketing, Ma…
$ ciclo                <fct> 8, 2, 10, 4, 6, 2, 7, 3, 3, 2, 8, 6, 1, 1, 7, 8, …
$ horas_estudio        <dbl> 8.9, 10.2, 8.4, 9.9, 19.3, 9.8, 28.4, 17.8, 21.5,…
$ asistencia           <dbl> 100.0, 85.0, 72.1, 89.3, 68.4, 100.0, 80.3, 84.7,…
$ promedio_previo      <dbl> 12.2, 14.7, 10.1, 16.2, 13.7, 12.2, 11.5, 11.2, 1…
$ internet             <fct> Sí tiene internet, Sí tiene internet, Sí tiene in…
$ tipo_dispositivo     <fct> laptop, laptop, laptop, celular, laptop, laptop, …
$ uso_plataformas      <ord> medio, medio, medio, medio, bajo, alto, medio, me…
$ horas_internet       <dbl> 3.6, 0.5, 2.2, 0.0, 3.1, 4.0, 1.2, 3.1, 4.3, 4.0,…
$ trabaja              <fct> Sí trabaja, No trabaja, No trabaja, No trabaja, S…
$ horas_trabajo        <dbl> 30, 0, 0, 0, 39, 27, 39, 0, 34, 12, 0, 0, 0, 0, 0…
$ horas_sueno          <dbl> 5.3, 8.1, 8.8, 5.7, 6.9, 6.7, 6.8, 9.2, 5.7, 6.6,…
$ estres               <fct> 4, 3, 3, 3, 1, 1, 1, 1, 5, 2, 5, 3, 5, 4, 1, 4, 4…
$ motivacion           <fct> 4, 4, 2, 3, 5, 2, 2, 1, 5, 2, 1, 5, 4, 1, 4, 3, 3…
$ metodo_estudio       <fct> lectura, videos, resúmenes, mixto, videos, mixto,…
$ uso_ia               <fct> No usa IA, Sí usa IA, Sí usa IA, No usa IA, No us…
$ promedio             <dbl> 12.82, 17.58, 10.60, 14.95, 10.05, 8.55, 11.36, 1…
$ aprobado             <fct> Sí aprueba, Sí aprueba, Sí aprueba, Sí aprueba, N…

🔍 Encontrando relaciones entre los datos

Correlaciones entre variables numéricas

Code
vars_numericas <- datos_limpios %>%
  select(promedio, horas_estudio, asistencia, promedio_previo, 
         horas_sueno, horas_trabajo, horas_internet, edad)

cor_matrix <- cor(vars_numericas, use = "complete.obs", method = "pearson")

corr_plot <- as.data.frame(as.table(cor_matrix)) %>%
  rename(Var1 = Var1, Var2 = Var2, correlacion = Freq)

ggplot(corr_plot, aes(x = Var1, y = Var2, fill = correlacion)) +
  geom_tile(color = "white") +
  geom_text(aes(label = sprintf("%.2f", correlacion)), size = 3) +
  scale_fill_gradient2(low = "#2E86AB", mid = "white", high = "#A23B72", 
                       midpoint = 0, limits = c(-1, 1)) +
  theme_minimal(base_size = 9) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        panel.grid = element_blank()) +
  labs(title = "Matriz de correlaciones (Pearson)",
       x = NULL, y = NULL, fill = "r")

Tip

💡 Interpretación rápida:
\(r > 0.7\): Correlación fuerte
\(0.4 < |r| < 0.7\): Correlación moderada
\(|r| < 0.4\): Correlación débil o nula

Asociación entre variables categóricas: Chi-cuadrado

Code
tabla_contingencia <- table(datos_limpios$trabaja, datos_limpios$aprobado)
chi_test <- chisq.test(tabla_contingencia)

cat("📋 Tabla de contingencia: Trabaja × Aprobación\n\n")
📋 Tabla de contingencia: Trabaja × Aprobación
Code
print(tabla_contingencia)
            
             No aprueba Sí aprueba
  No trabaja        131        171
  Sí trabaja        105         93
Code
cat(sprintf("\n🔍 Chi-cuadrado: χ² = %.3f, gl = %d, p = %.4f\n", 
            chi_test$statistic, chi_test$parameter, chi_test$p.value))

🔍 Chi-cuadrado: χ² = 4.092, gl = 1, p = 0.0431

El valor p = 0.0431 es menor que 0.05, por lo que rechazamos la hipótesis nula de independencia. Esto significa que la diferencia observada no se debe al azar.
✅ Existe una relación estadísticamente significativa entre trabajar y aprobar.

Comparación de promedios: ANOVA exploratorio

Code
anova_model <- aov(promedio ~ carrera, data = datos_limpios)

cat("📊 ANOVA: Promedio ~ Carrera\n")
📊 ANOVA: Promedio ~ Carrera
Code
print(summary(anova_model))
             Df Sum Sq Mean Sq F value Pr(>F)
carrera       4     36   8.896   1.107  0.352
Residuals   495   3977   8.035               

p-valor alto (0.352 > 0.05). No podemos rechazar la hipótesis nula de que todas las medias de las carreras son iguales. Las diferencias observadas en los promedios entre carreras son atribuibles al azar.
❌ No hay diferencias estadísticamente significativas en el promedio académico entre las diferentes carreras.


📈 Modelado: Regresión Logística

Fundamentos Teóricos

La regresión logística modela la probabilidad de que ocurra un evento binario mediante:

\[ P(Y=1|X) = \frac{1}{1 + e^{-(\beta_0 + \beta_1 X_1 + \cdots + \beta_k X_k)}} \]

Donde: - \(P(Y=1|X)\): Probabilidad de aprobación dado predictores \(X\) - \(\beta_0\): Intercepto (log-odds basal) - \(\beta_j\): Coeficiente del predictor \(X_j\)

\[ \text{logit}(p) = \ln\left(\frac{p}{1-p}\right) = \beta_0 + \beta_1 X_1 + \cdots + \beta_k X_k \]

  • \(\frac{p}{1-p}\): Odds (razón de probabilidades)
  • \(\ln(\cdot)\): Convierte odds en escala continua \((-\infty, +\infty)\)

\[ OR_j = e^{\beta_j} \]

Valor de OR Interpretación
OR = 1 El predictor no afecta la probabilidad
OR > 1 Por cada unidad de aumento en \(X_j\), los odds de aprobar se multiplican por OR
OR < 1 Por cada unidad de aumento en \(X_j\), los odds disminuyen (factor = 1/OR)

Ejemplo: Si \(OR_{\text{horas\_estudio}} = 1.08\) → cada hora adicional de estudio se asocia con 8% mayor probabilidad relativa de aprobar, controlando otras variables.

\[ L(\beta) = \prod_{i=1}^{n} p_i^{y_i} (1-p_i)^{1-y_i}, \quad y_i \in \{0,1\} \]

Especificación y ajuste del modelo

Code
modelo_logit <- glm(
  aprobado ~ edad + genero + estado_civil + nivel_socioeconomico + 
  carrera+ ciclo + horas_estudio + asistencia + promedio_previo + 
    tipo_dispositivo + uso_plataformas + horas_internet + horas_trabajo +
    horas_sueno + estres + motivacion + metodo_estudio + uso_ia,
  data = datos_limpios,
  family = binomial(link = "logit")
)
summary(modelo_logit)

Call:
glm(formula = aprobado ~ edad + genero + estado_civil + nivel_socioeconomico + 
    carrera + ciclo + horas_estudio + asistencia + promedio_previo + 
    tipo_dispositivo + uso_plataformas + horas_internet + horas_trabajo + 
    horas_sueno + estres + motivacion + metodo_estudio + uso_ia, 
    family = binomial(link = "logit"), data = datos_limpios)

Coefficients:
                         Estimate Std. Error z value Pr(>|z|)    
(Intercept)             -7.703435   1.892834  -4.070 4.71e-05 ***
edad                     0.000150   0.022090   0.007   0.9946    
generoMasculino         -0.038818   0.275252  -0.141   0.8878    
estado_civilconviviente -0.026031   0.466145  -0.056   0.9555    
estado_civilsoltero     -0.067821   0.411985  -0.165   0.8692    
nivel_socioeconomico.L  -0.026329   0.256264  -0.103   0.9182    
nivel_socioeconomico.Q   0.055593   0.202607   0.274   0.7838    
carreraContabilidad     -0.270296   0.394932  -0.684   0.4937    
carreraEconomía         -0.518860   0.406652  -1.276   0.2020    
carreraFinanzas         -0.335096   0.432516  -0.775   0.4385    
carreraMarketing         0.197643   0.406264   0.486   0.6266    
ciclo3                   0.065953   0.505470   0.130   0.8962    
ciclo4                  -0.028936   0.537892  -0.054   0.9571    
ciclo5                  -0.040107   0.514371  -0.078   0.9378    
ciclo6                   0.056428   0.533747   0.106   0.9158    
ciclo7                  -0.360356   0.541596  -0.665   0.5058    
ciclo8                  -0.416222   0.512918  -0.811   0.4171    
ciclo9                  -0.404648   0.540225  -0.749   0.4538    
ciclo10                 -0.067725   0.556787  -0.122   0.9032    
horas_estudio            0.035365   0.021231   1.666   0.0958 .  
asistencia               0.005448   0.012210   0.446   0.6554    
promedio_previo          0.636558   0.069635   9.141  < 2e-16 ***
tipo_dispositivolaptop   0.156078   0.256846   0.608   0.5434    
tipo_dispositivotablet   1.395636   0.928032   1.504   0.1326    
uso_plataformas.L       -0.203577   0.248360  -0.820   0.4124    
uso_plataformas.Q       -0.076296   0.207074  -0.368   0.7125    
horas_internet           0.003006   0.057111   0.053   0.9580    
horas_trabajo           -0.018795   0.009564  -1.965   0.0494 *  
horas_sueno             -0.101659   0.105007  -0.968   0.3330    
estres2                  0.203935   0.378682   0.539   0.5902    
estres3                 -0.271486   0.401296  -0.677   0.4987    
estres4                 -0.329677   0.384900  -0.857   0.3917    
estres5                 -0.547508   0.387605  -1.413   0.1578    
motivacion2              0.369371   0.418909   0.882   0.3779    
motivacion3              0.228484   0.396777   0.576   0.5647    
motivacion4              0.675435   0.419133   1.612   0.1071    
motivacion5              0.990828   0.396179   2.501   0.0124 *  
metodo_estudiomixto      0.281794   0.341559   0.825   0.4094    
metodo_estudioresúmenes  0.060605   0.366718   0.165   0.8687    
metodo_estudiovideos    -0.001376   0.350736  -0.004   0.9969    
uso_iaSí usa IA          0.349533   0.254199   1.375   0.1691    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 609.06  on 444  degrees of freedom
Residual deviance: 440.69  on 404  degrees of freedom
  (55 observations deleted due to missingness)
AIC: 522.69

Number of Fisher Scoring iterations: 5
Code
modelo_logit <- glm(
  aprobado ~ horas_estudio + promedio_previo + horas_trabajo + motivacion,
  data = datos_limpios,
  family = binomial(link = "logit")
)
summary(modelo_logit)

Call:
glm(formula = aprobado ~ horas_estudio + promedio_previo + horas_trabajo + 
    motivacion, family = binomial(link = "logit"), data = datos_limpios)

Coefficients:
                 Estimate Std. Error z value Pr(>|z|)    
(Intercept)     -7.769208   0.900599  -8.627   <2e-16 ***
horas_estudio    0.034078   0.019565   1.742   0.0815 .  
promedio_previo  0.617621   0.065662   9.406   <2e-16 ***
horas_trabajo   -0.019151   0.008758  -2.187   0.0288 *  
motivacion2      0.374046   0.391820   0.955   0.3398    
motivacion3      0.116459   0.378248   0.308   0.7582    
motivacion4      0.583596   0.389931   1.497   0.1345    
motivacion5      0.941034   0.374420   2.513   0.0120 *  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 609.06  on 444  degrees of freedom
Residual deviance: 460.22  on 437  degrees of freedom
  (55 observations deleted due to missingness)
AIC: 476.22

Number of Fisher Scoring iterations: 4

Capacidad discriminativa: AUC-ROC

Code
# Extraer datos usados en el modelo (excluye NAs consistentemente)
datos_modelo <- model.frame(modelo_logit)
respuesta <- datos_modelo$aprobado
probabilidades <- fitted(modelo_logit)

roc_obj <- roc(response = respuesta, 
               predictor = probabilidades,
               levels = c("No aprueba", "Sí aprueba"),
               direction = "<",
               quiet = TRUE)

auc_val <- auc(roc_obj)
cat(sprintf("\n🎯 AUC-ROC: %.3f\n", auc_val))

🎯 AUC-ROC: 0.812
Code
cat("✅ Criterio: AUC > 0.7 = aceptable; > 0.8 = bueno; > 0.9 = excelente\n")
✅ Criterio: AUC > 0.7 = aceptable; > 0.8 = bueno; > 0.9 = excelente
Code
# Gráfico ROC 
plot(roc_obj, 
     col = "#2E86AB", 
     lwd = 2,
     main = "Curva ROC: Capacidad discriminativa del modelo",
     sub = sprintf("AUC = %.3f", auc_val),
     print.auc = TRUE,
     print.auc.x = 0.4,
     print.auc.y = 0.2,
     grid = TRUE,
     grid.col = "gray80")
abline(a = 0, b = 1, lty = 2, col = "gray50")  # Línea de no-discriminación


Matriz de confusión y métricas de clasificación

Code
# model.frame() extrae SOLO las filas que glm() utilizó (sin NAs)
datos_modelo <- model.frame(modelo_logit)

# Extraer valores reales y predicciones alineados
real <- datos_modelo$aprobado
prob <- fitted(modelo_logit)
pred <- factor(ifelse(prob > 0.5, "Sí aprueba", "No aprueba"), levels = c("No aprueba", "Sí aprueba"))

# Calcular matriz de confusión
conf_matrix <- confusionMatrix(pred, real, positive = "Sí aprueba")

# Visualización
conf_df <- as.data.frame(conf_matrix$table) %>%
  rename(Predicho = Prediction, Real = Reference, Freq = Freq)

ggplot(conf_df, aes(x = Predicho, y = Real, fill = Freq)) +
  geom_tile(color = "white", size = 1) +
  geom_text(aes(label = Freq), size = 6, fontface = "bold") +
  scale_fill_gradient(low = "#E8F4FD", high = "#8E125B") +
  labs(title = "Matriz de Confusión",
       subtitle = sprintf("Exactitud: %.1f%% | Sensibilidad: %.1f%% | Especificidad: %.1f%%",
                         conf_matrix$overall["Accuracy"]*100,
                         conf_matrix$byClass["Sensitivity"]*100,
                         conf_matrix$byClass["Specificity"]*100),
       x = "Clasificación del modelo", y = "Valor real", fill = "Frecuencia") +
  theme_minimal(base_size = 12) + 
  theme(panel.grid = element_blank())

Tip

📋 Métricas clave:

Métrica Fórmula Interpretación
Exactitud \((VP+VN)/Total\) % de predicciones correctas
Sensibilidad \(VP/(VP+FN)\) Capacidad para detectar aprobados reales
Especificidad \(VN/(VN+FP)\) Capacidad para detectar no-aprobados reales

Visualización de resultados: Odds Ratios con interpretación

Code
coeficientes_or <- broom::tidy(modelo_logit, conf.int = TRUE, exponentiate = TRUE) %>%
  mutate(
    significativo = p.value < 0.05,
    across(c(estimate, std.error, p.value), ~round(.x, 3)),
    across(c(conf.low, conf.high), ~round(.x, 2))
  )

coef_plot_or <- coeficientes_or %>%
  filter(term != "(Intercept)") %>%
  mutate(
    variable = fct_reorder(term, estimate),
    interpretacion = case_when(
      significativo & estimate > 1 ~ sprintf("↑ %.0f%% más odds", (estimate-1)*100),
      significativo & estimate < 1 ~ sprintf("↓ %.0f%% menos odds", (1-estimate)*100),
      TRUE ~ "NS"
    )
  )

coef_plot_or %>%
  ggplot(aes(x = variable, y = estimate)) +
  geom_hline(yintercept = 1, linetype = "dashed", color = "gray50", linewidth = 0.6) +
  geom_point(aes(color = significativo), size = 3.5, alpha = 0.9) +
  geom_errorbar(aes(ymin = conf.low, ymax = conf.high, color = significativo), 
                width = 0.25, alpha = 0.7, linewidth = 0.9) +
  geom_text(aes(label = interpretacion, 
                y = if_else(significativo, pmax(estimate, conf.high) * 1.15, 
                           pmin(estimate, conf.low) * 0.85)), 
            size = 2.8, hjust = if_else(coef_plot_or$significativo, 0, 1)) +
  scale_y_log10(breaks = c(0.3, 0.5, 1, 2, 3, 5), labels = c("0.3", "0.5", "1", "2", "3", "5")) +
  scale_color_manual(values = c("TRUE" = "#2E86AB", "FALSE" = "#A23B72"), 
                     labels = c("No significativo", "Significativo (p < 0.05)"), name = NULL) +
  coord_flip() +
  labs(title = "Factores asociados a la aprobación académica",
       subtitle = "Odds Ratios con IC 95%",
       x = NULL, y = "Odds Ratio (OR) [escala log]") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "bottom", panel.grid.major.y = element_blank(),
        plot.caption = element_text(size = 9, color = "gray40", margin = margin(t = 10)))

Tabla de resultados lista para tesis (estilo APA)

Code
tabla_resultados <- coeficientes_or %>%
  filter(term != "(Intercept)") %>%
  mutate(
    Predictor = str_replace_all(term, "_", " ") %>% str_to_sentence(),
    OR = sprintf("%.2f", estimate),
    IC_95 = sprintf("[%.2f, %.2f]", conf.low, conf.high),
    p_valor = if_else(p.value < 0.001, "< .001", 
                     if_else(p.value < 0.01, sprintf("%.3f", p.value),
                            if_else(p.value < 0.05, sprintf("%.3f*", p.value), 
                                   sprintf("%.3f", p.value)))),
    Significativo = if_else(p.value < 0.05, "✅", "❌")
  ) %>%
  select(Predictor, OR, IC_95, p_valor, Significativo)

tabla_resultados %>%
  gt() %>%
  tab_header(
    title = "Resultados de regresión logística: Factores asociados a aprobación académica",
    subtitle = "Variable dependiente: aprobado (0=No aprueba, 1=Sí aprueba)"
  ) %>%
  cols_label(Predictor = "Predictor", OR = "Odds Ratio", IC_95 = "IC 95%", 
             p_valor = "p", Significativo = "Sig.") %>%
  fmt_markdown(columns = everything()) %>%
  tab_style(style = cell_text(weight = "bold"),
            locations = cells_body(columns = "Significativo", rows = Significativo == "✅")) %>%
  tab_source_note("Nota. * p < .05. OR > 1 indica mayor probabilidad de aprobar.") %>%
  opt_align_table_header(align = "center")
Resultados de regresión logística: Factores asociados a aprobación académica
Variable dependiente: aprobado (0=No aprueba, 1=Sí aprueba)
Predictor Odds Ratio IC 95% p Sig.
Horas estudio 1.03 [1.00, 1.08] 0.082
Promedio previo 1.85 [1.64, 2.12] < .001
Horas trabajo 0.98 [0.96, 1.00] 0.029*
Motivacion2 1.45 [0.68, 3.15] 0.340
Motivacion3 1.12 [0.53, 2.37] 0.758
Motivacion4 1.79 [0.84, 3.88] 0.134
Motivacion5 2.56 [1.24, 5.39] 0.012*
Nota. * p < .05. OR > 1 indica mayor probabilidad de aprobar.
Important

Los odds ratios indican asociación, no causalidad. Factores no medidos podrían influir en ambas variables.


🤖 Interpretación asistida con IA: Prompt Engineering para Tesis

**Rol**: Eres un asistente de investigación especializado en estadística aplicada.

**Contexto del estudio**:
- Variable resultado: Aprobación académica (binaria: 0=No aprueba, 1=Sí aprueba)
- Muestra: 500 estudiantes universitarios peruanos
- Diseño: Transversal, datos auto-reportados
- Modelo: Regresión logística

**Resultados clave del modelo**:
# A tibble: 8 × 7
  term      estimate std.error statistic  p.value conf.low conf.high
  <chr>        <dbl>     <dbl>     <dbl>    <dbl>    <dbl>     <dbl>
1 (Interce… 0.000423   0.901      -8.63  6.31e-18  6.70e-5   0.00230
2 horas_es… 1.03       0.0196      1.74  8.15e- 2  9.96e-1   1.08   
3 promedio… 1.85       0.0657      9.41  5.15e-21  1.64e+0   2.12   
4 horas_tr… 0.981      0.00876    -2.19  2.88e- 2  9.64e-1   0.998  
5 motivaci… 1.45       0.392       0.955 3.40e- 1  6.76e-1   3.15   
6 motivaci… 1.12       0.378       0.308 7.58e- 1  5.35e-1   2.37   
7 motivaci… 1.79       0.390       1.50  1.34e- 1  8.38e-1   3.88   
8 motivaci… 2.56       0.374       2.51  1.20e- 2  1.24e+0   5.39 

**Solicito**:
1. Redactar un párrafo de resultados en estilo APA 7ª edición para los 3 predictores más relevantes
2. Interpretar el Odds Ratio en lenguaje accesible para lectores no estadísticos

**Restricciones**:
- Mantén rigor estadístico: evita lenguaje causal ('afecta', 'causa')
- Usa términos precisos: 'se asocia con', 'mayor probabilidad de'
- Si citas literatura, indica que es ilustrativa y requiere verificación
- Formato: Markdown, listo para copiar en mi documento de tesis
Warning

⚠️ Buenas prácticas con IA en investigación:
1. Valida siempre: Contrasta la interpretación de la IA con literatura especializada
2. Documenta: Registra el prompt y la herramienta utilizada en metodología
3. Responsabilidad: Tú eres el autor académico; la IA es una herramienta de apoyo
4. Ética: No uses IA para generar datos, solo para asistir en análisis e interpretación


📋 Checklist final: Calidad para tu tesis