Modalidad: Individual o en parejas
Entrega: Script .R comentado + informe en .Rmd o .pdf


1 Introducción

En esta práctica trabajaremos con dos bases de datos clásicas en psicometría y ciencias del comportamiento, ambas disponibles directamente en R sin necesidad de importar archivos externos:

Dataset Paquete Constructo principal
HolzingerSwineford1939 lavaan Habilidades cognitivas (inteligencia)
bfi psych Personalidad: Big Five (OCEAN)

Además, como base de práctica autónoma, se introduce un tercer dataset:

Dataset Paquete Constructo principal
affect psych Afecto positivo/negativo (estado emocional)

La práctica se divide en dos bloques:

  • Bloque A (Guía del profesor): CFA + SEM con HolzingerSwineford1939
  • Bloque B (Tarea del alumnado): CFA + SEM con bfi / affect

2 BLOQUE A — GUÍA DEL PROFESOR

2.1 Dataset: HolzingerSwineford1939

Este dataset clásico (Holzinger & Swineford, 1939) contiene puntuaciones de 301 estudiantes de séptimo y octavo grado en 26 tests de habilidades cognitivas. Se utilizan habitualmente 9 indicadores que representan tres factores latentes:

Factor latente Indicadores
Visual (vis) x1, x2, x3
Textual (tex) x4, x5, x6
Velocidad (spe) x7, x8, x9

2.2 A.1 — Análisis Factorial Confirmatorio (CFA)

2.2.1 Preparación del entorno

# Instalar paquetes si es necesario
# install.packages(c("lavaan", "semTools", "semPlot", "tidyverse"))

library(lavaan)
library(semTools)
library(semPlot)

# Cargar datos
data("HolzingerSwineford1939")
datos_hs <- HolzingerSwineford1939

# Exploración inicial
str(datos_hs[, paste0("x", 1:9)])
psych::describe(datos_hs[, paste0("x", 1:9)])

2.2.2 Especificación del modelo CFA

En lavaan, el operador =~ define la relación “es medido por” (factor → indicadores).

modelo_cfa <- '
  # Definición de factores latentes
  visual  =~ x1 + x2 + x3
  textual =~ x4 + x5 + x6
  speed   =~ x7 + x8 + x9
'

Nota de identificación: Por defecto, lavaan fija la carga del primer indicador de cada factor a 1.0 para identificar la escala del factor latente. Alternativamente, se puede estandarizar la varianza del factor (std.lv = TRUE).


2.2.3 Estimación del modelo

ajuste_cfa <- cfa(
  model     = modelo_cfa,
  data      = datos_hs,
  estimator = "MLR"       # ML Robusto: adecuado ante desviaciones de normalidad
)

summary(ajuste_cfa,
        fit.measures = TRUE,   # Índices de ajuste
        standardized = TRUE,   # Cargas estandarizadas
        rsquare      = TRUE)   # Varianza explicada de cada indicador

2.2.4 Evaluación del ajuste del modelo

Los principales índices de ajuste que debéis reportar e interpretar:

Índice Criterio de buen ajuste Referencia
χ² / df < 3.0 (aceptable < 5.0) Kline (2023)
CFI ≥ .95 (aceptable ≥ .90) Hu & Bentler (1999)
TLI (NNFI) ≥ .95 (aceptable ≥ .90) Tucker & Lewis (1973)
RMSEA ≤ .06 (aceptable ≤ .08) Browne & Cudeck (1993)
SRMR ≤ .08 Hu & Bentler (1999)
AIC / BIC Menor = mejor (comparativo)
# Extraer índices de ajuste de forma ordenada
fitMeasures(ajuste_cfa, c("chisq", "df", "pvalue",
                           "cfi", "tli", "rmsea",
                           "rmsea.ci.lower", "rmsea.ci.upper",
                           "srmr", "aic", "bic"))

2.2.5 Cargas factoriales y fiabilidad

# Cargas estandarizadas
standardizedSolution(ajuste_cfa) |>
  subset(op == "=~") |>
  print()

# Fiabilidad compuesta (omega) — función actualizada
semTools::compRelSEM(ajuste_cfa)

# Varianza Media Extraída (AVE) — validez convergente
semTools::AVE(ajuste_cfa)

Criterios de validez convergente y discriminante (Fornell & Larcker, 1981):

  • AVE ≥ .50 → el factor explica más varianza que el error (validez convergente)
  • √AVE > correlación entre factores → validez discriminante

2.2.6 Índices de modificación

Los índices de modificación (MI) estiman cuánto mejoraría el χ² si se liberara un parámetro actualmente restringido. Deben interpretarse con criterio teórico, no mecánicamente.

modindices(ajuste_cfa,
           sort.          = TRUE,
           maximum.number = 10,
           op             = c("=~", "~~"))  # Cargas cruzadas y covarianzas de error

⚠️ Importante: Un MI elevado no justifica por sí solo modificar el modelo. Cualquier respecificación debe tener justificación teórica. El uso puramente empírico de los MI es una fuente habitual de capitalización sobre el azar.


2.2.7 Visualización del modelo

semPlot::semPaths(
  object        = ajuste_cfa,
  what          = "std",
  layout        = "tree",
  edge.label.cex = 0.8,
  residuals     = TRUE,
  intercepts    = FALSE,
  rotation      = 2,
  title         = TRUE,
  title.label   = "CFA: Habilidades Cognitivas (Holzinger & Swineford, 1939)"
)

2.2.8 Comparación de modelos alternativos

# Modelo 1: Tres factores correlacionados (modelo teórico)
# → ajuste_cfa (ya estimado)

# Modelo 2: Un único factor general (hipótesis nula de unidimensionalidad)
modelo_unifactorial <- '
  g =~ x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9
'
ajuste_uni <- cfa(modelo_unifactorial, data = datos_hs, estimator = "MLR")

# Modelo 3: Tres factores ortogonales (sin correlación entre factores)
ajuste_ort <- cfa(modelo_cfa, data = datos_hs,
                  estimator  = "MLR",
                  orthogonal = TRUE)

# Tabla comparativa
comparar_modelos <- compareFit(ajuste_uni, ajuste_ort, ajuste_cfa)
summary(comparar_modelos)

# Prueba de diferencia chi-cuadrado (modelos anidados)
lavTestLRT(ajuste_uni, ajuste_cfa)

2.3 A.2 — Modelo de Ecuaciones Estructurales (SEM)

Una vez validada la estructura factorial, podemos especificar relaciones entre factores latentes. En este ejemplo, hipotetizamos que la habilidad Visual y Textual predicen la Velocidad de procesamiento, con un posible efecto indirecto (mediación).

2.3.1 Hipótesis del modelo estructural

Visual  ──────────────────────────→ Speed
Visual  ──→ Textual ──────────────→ Speed
              (mediación parcial)

2.3.2 Especificación del modelo SEM

En lavaan, la parte estructural utiliza el operador ~ (“es regresado sobre”).
Para la mediación, etiquetamos las rutas con nombres arbitrarios seguidos de *:

modelo_sem <- '
  # ── PARTE DE MEDICIÓN (CFA) ──────────────────────────────
  visual  =~ x1 + x2 + x3
  textual =~ x4 + x5 + x6
  speed   =~ x7 + x8 + x9

  # ── PARTE ESTRUCTURAL ────────────────────────────────────
  # Efecto directo de visual sobre speed (cd = "c direct")
  speed   ~ cd*visual

  # Ruta de mediación: visual → textual → speed
  textual ~ a*visual
  speed   ~ b*textual

  # ── EFECTOS INDIRECTOS Y TOTALES ─────────────────────────
  indirecto := a * b
  total     := cd + (a * b)
'

2.3.3 Estimación con bootstrapping

set.seed(123)  # Reproducibilidad

ajuste_sem <- sem(
  model     = modelo_sem,
  data      = datos_hs,
  estimator = "ML",
  se        = "bootstrap",
  bootstrap = 1000
)

summary(ajuste_sem,
        fit.measures = TRUE,
        standardized = TRUE)

2.3.4 Inferencia sobre efectos indirectos

# Intervalos de confianza Bootstrap BCa
parameterEstimates(
  ajuste_sem,
  boot.ci.type = "bca.simple",
  level        = 0.95,
  ci           = TRUE
) |>
  subset(label %in% c("a", "b", "cd", "indirecto", "total"))

Interpretación: Si el IC al 95% del efecto indirecto (a × b) no incluye el cero, podemos concluir que existe mediación estadísticamente significativa.


2.3.5 Índices de modificación en el SEM

modindices(ajuste_sem,
           sort.          = TRUE,
           maximum.number = 15,
           op             = c("=~", "~", "~~"))

2.3.6 Comparación del SEM con modelos alternativos

# Modelo alternativo: mediación completa (sin efecto directo visual → speed)
modelo_sem_med_completa <- '
  visual  =~ x1 + x2 + x3
  textual =~ x4 + x5 + x6
  speed   =~ x7 + x8 + x9

  textual ~ a*visual
  speed   ~ b*textual

  indirecto := a * b
'

ajuste_med_completa <- sem(modelo_sem_med_completa,
                           data = datos_hs, estimator = "ML")

# Prueba de diferencia chi-cuadrado (modelos anidados)
lavTestLRT(ajuste_med_completa, ajuste_sem)

# Criterios de información
fitMeasures(ajuste_sem,          c("aic", "bic", "cfi", "rmsea"))
fitMeasures(ajuste_med_completa, c("aic", "bic", "cfi", "rmsea"))

2.3.7 Visualización del SEM

semPlot::semPaths(
  object        = ajuste_sem,
  what          = "std",
  whatLabels    = "std",
  layout        = "tree2",
  edge.label.cex = 0.8,
  residuals     = TRUE,
  intercepts    = FALSE,
  nCharNodes    = 6,
  title         = TRUE,
  title.label   = "SEM: Mediación de Habilidades Cognitivas"
)

2.3.8 Tabla de parámetros para el informe

parameterEstimates(ajuste_sem, standardized = TRUE) |>
  subset(op %in% c("=~", "~", ":=")) |>
  dplyr::select(lhs, op, rhs, label, est, se, z, pvalue,
                ci.lower, ci.upper, std.all) |>
  dplyr::mutate(across(where(is.numeric), round, 3)) |>
  print()


3 BLOQUE B — TAREA DEL ALUMNADO

3.1 Instrucciones generales

Tenéis acceso a dos datasets con constructos psicológicos distintos. Cada estudiante (o pareja) debe elegir uno como dataset principal, aunque se valorará positivamente la comparación entre ambos.


3.2 Datasets disponibles

3.2.1 Dataset 1: bfi (paquete psych)

library(psych)
data("bfi")
?bfi  # Documentación completa

Descripción: 2.800 participantes, 25 ítems de personalidad (Big Five) + variables sociodemográficas. Cada factor tiene 5 indicadores:

Factor Ítems Descripción
A (Agreeableness) A1–A5 Amabilidad
C (Conscientiousness) C1–C5 Responsabilidad
E (Extraversion) E1–E5 Extroversión
N (Neuroticism) N1–N5 Neuroticismo
O (Openness) O1–O5 Apertura a la experiencia

⚠️ Algunos ítems están formulados en sentido inverso (A1, C4, C5, E1, E2, O2, O5). Deberéis decidir si recodificarlos o especificar la dirección en el modelo.

bfi_limpio <- bfi[, 1:25]         # Solo los 25 ítems
bfi_limpio <- na.omit(bfi_limpio) # Eliminar NA (o usar FIML en lavaan)

3.2.2 Dataset 2: affect (paquete psych)

library(psych)
data("affect")
?affect

Descripción: 330 participantes en un estudio de estados emocionales. Contiene medidas de afecto positivo y negativo (PANAS), así como variables de bienestar y activación:

Bloque Variables Descripción
Afecto positivo PA1, PA2, PA3, PA4 Entusiasmo, interés, determinación, atención
Afecto negativo NA1, NA2, NA3, NA4 Angustia, molestia, culpa, nerviosismo
Bienestar TA, EA Activación total y emocional
Condición Film Tipo de película vista (experimental)
data("affect")
affect_limpio <- na.omit(affect[, c("PA1","PA2","PA3","PA4",
                                     "NA1","NA2","NA3","NA4",
                                     "TA","EA")])

3.3 Ejercicios

3.3.1 Ejercicio 1 — Análisis Factorial Confirmatorio (CFA)

1.1. Especificación del modelo

Especifica en lavaan el modelo CFA correspondiente a la estructura teórica del dataset elegido:

  • Para bfi: modelo de 5 factores (Big Five) con 5 indicadores cada uno
  • Para affect: modelo de 2 factores (PA y NA) con 4 indicadores cada uno

Justifica brevemente: ¿Por qué esta estructura y no otra? ¿Existe apoyo teórico o empírico previo?


1.2. Estimación y decisiones metodológicas

Estima el modelo con cfa(). Debes justificar:

  1. La elección del estimador (ML, MLR, WLSMV…). Pista: examina la distribución de los ítems con psych::describe() o psych::mardia().

  2. El tratamiento de los datos perdidos: eliminación por lista (na.omit) o estimación FIML (missing = "FIML" en lavaan). ¿Cuál es más apropiado y por qué?

  3. La estrategia de identificación: ¿fijas la primera carga o estandarizas la varianza del factor (std.lv = TRUE)? ¿Qué consecuencias tiene cada opción?


1.3. Evaluación del ajuste

Reporta e interpreta los siguientes índices:

χ²(df) =        p =
CFI =           TLI =
RMSEA =         IC 90% [    ,    ]
SRMR =
AIC =           BIC =

¿El ajuste es satisfactorio? Si no lo es, ¿qué tipo de misfit detectas (cargas bajas, covarianzas de error, cargas cruzadas)?


1.4. Validez de constructo

Calcula e interpreta:

  • Fiabilidad compuesta (ω) para cada factor → ¿supera el criterio de .70?
  • AVE para cada factor → ¿supera el criterio de .50?
  • Correlaciones entre factores → ¿se viola la validez discriminante (√AVE > r)?
semTools::compRelSEM(ajuste_cfa)   # Fiabilidad compuesta (omega)
semTools::AVE(ajuste_cfa)          # Varianza Media Extraída
inspect(ajuste_cfa, "cor.lv")      # Correlaciones entre factores latentes

1.5. Índices de modificación y respecificación (opcional con justificación)

modindices(ajuste_cfa, sort. = TRUE, maximum.number = 10)

¿Alguno tiene sentido teórico? Si decides respecificar el modelo, explica el razonamiento y compara el modelo original con el respecificado.


1.6. Comparación de modelos

Contrasta tu modelo teórico con al menos dos alternativas:

Modelo Descripción
M0 Un factor general (unidimensional)
M1 Tu modelo teórico (multidimensional)
M2 Modelo respecificado (si lo hay)

Usa lavTestLRT() para modelos anidados y compara AIC/BIC para modelos no anidados.


3.3.2 Ejercicio 2 — Modelo de Ecuaciones Estructurales (SEM)

2.1. Planteamiento de hipótesis

Formula explícitamente:

  • H1 (regresión): ¿Qué factor latente predice a otro? ¿En qué dirección?
  • H2 (mediación): ¿Existe alguna variable latente que medie la relación entre otras dos?

Sugerencias según dataset:

  • bfi: El Neuroticismo predice negativamente el Bienestar (operacionalizado como ausencia de rasgos negativos); la Responsabilidad media parcialmente esa relación.
  • affect: El Afecto Positivo (PA) predice positivamente la Activación Total (TA); el Afecto Negativo (NA) actúa como mediador o moderador.

2.2. Especificación del modelo SEM en lavaan

Construye el modelo completo con:

  • Parte de medición (CFA)
  • Parte estructural (rutas etiquetadas para mediación)
  • Definición de efectos indirectos y totales con el operador :=

2.3. Estimación con bootstrapping

set.seed(_____)   # Introduce tu número de semilla y justifica

ajuste_sem <- sem(
  model     = modelo_sem,
  data      = ___________,
  estimator = "ML",
  se        = "bootstrap",
  bootstrap = 1000
)

2.4. Interpretación de la mediación

parameterEstimates(ajuste_sem,
                   boot.ci.type = "bca.simple",
                   level = 0.95) |>
  subset(label %in% c("a", "b", "cd", "indirecto", "total"))

Responde:

  • ¿Es el efecto indirecto significativo (IC no incluye 0)?
  • ¿Se trata de mediación parcial o completa? ¿Cómo lo determinas?
  • ¿Cuál es el tamaño del efecto de la mediación (ratio indirecto/total)?

2.5. Índices de modificación en el SEM

modindices(ajuste_sem, sort. = TRUE, maximum.number = 15,
           op = c("=~", "~", "~~"))

¿Hay rutas estructurales no hipotetizadas que merezcan consideración teórica?


2.6. Comparación de modelos estructurales alternativos

Modelo Descripción
SEM_A Mediación parcial (efecto directo + indirecto)
SEM_B Mediación completa (solo efecto indirecto)
SEM_C Modelo saturado / alternativo teórico

Usa lavTestLRT() y criterios AIC/BIC. Justifica la elección del modelo final.


3.3.3 Ejercicio 3 — Informe de resultados

Redacta un informe de resultados siguiendo las normas APA 7ª edición para modelos SEM (ver Kline, 2023). El informe debe incluir:

  1. Tabla 1: Estadísticos descriptivos e intercorrelaciones de los indicadores observados
  2. Tabla 2: Índices de ajuste comparativos de todos los modelos CFA
  3. Tabla 3: Cargas factoriales estandarizadas con errores estándar e IC
  4. Tabla 4: Parámetros estructurales y efectos de mediación con IC bootstrap
  5. Figura 1: Diagrama path del modelo CFA final (con semPlot)
  6. Figura 2: Diagrama path del modelo SEM final

El informe no debe superar 2.500 palabras (sin contar tablas ni figuras).



4 Criterios de evaluación

Criterio Peso Descripción
Especificación correcta del modelo CFA 20% Sintaxis lavaan, identificación, estimador
Evaluación e interpretación del ajuste 20% Índices, MI, respecificación justificada
Validez de constructo 10% ω, AVE, validez discriminante
Planteamiento teórico del SEM 15% Hipótesis motivadas, diagrama previo
Análisis de mediación y bootstrapping 20% IC, interpretación, tamaño del efecto
Comparación de modelos 10% Modelos alternativos, criterios de selección
Calidad del informe y presentación 5% APA, claridad, reproducibilidad del código

5 Referencias recomendadas

  • Kline, R. B. (2023). Principles and practice of structural equation modeling (5th ed.). Guilford Press.
  • Rosseel, Y. (2012). lavaan: An R package for structural equation modeling. Journal of Statistical Software, 48(2), 1–36. https://doi.org/10.18637/jss.v048.i02
  • Hu, L., & Bentler, P. M. (1999). Cutoff criteria for fit indexes in covariance structure analysis. Structural Equation Modeling, 6(1), 1–55.
  • Fornell, C., & Larcker, D. F. (1981). Evaluating structural equation models with unobservable variables and measurement error. Journal of Marketing Research, 18(1), 39–50.
  • Brown, T. A. (2015). Confirmatory factor analysis for applied research (2nd ed.). Guilford Press.
  • Revelle, W. (2024). psych: Procedures for psychological, psychometric, and personality research. R package. https://CRAN.R-project.org/package=psych

6 Recursos adicionales

# Documentación interactiva de lavaan
vignette("lavaan")
browseVignettes("lavaan")

# Web oficial con tutoriales
# https://lavaan.ugent.be

# Cheatsheet de semTools
# https://github.com/simsem/semTools/wiki

Documento preparado para el Máster en Análisis de Datos en Ciencias Sociales.
Curso académico 2025–2026.