← Catálogo · Capítulo 5

T05_mECO_Script_Panel.R

Ver crudo
# =============================================================================
# TEMA 05 — Datos de Panel: Efectos Fijos, Aleatorios y Diagnósticos
# =============================================================================
# Manual de Microeconometría — Carlos de Anta Puig
# Profesor de Econometría y Microeconometría — UNIR
# https://github.com/carlanta/MicroEconometrics  Versión 1.0 — 2026
#
# OBJETIVO: Estimar modelos de panel (Pooled, EF, EA) con datos de Grunfeld.
#           Realizar test de Hausman, diagnósticos y comparar estimadores.
# INSTRUCCIONES: Session > Set Working Directory > To Source File Location
# =============================================================================

pausa <- function(msg="\n>>> Pulsa ENTER para continuar...") {
  if (interactive()) readline(msg) else Sys.sleep(0.5)
}
pkgs <- c("plm","lmtest","sandwich","knitr")
for (p in pkgs) if (!requireNamespace(p, quietly=TRUE)) install.packages(p, quiet=TRUE)
suppressPackageStartupMessages({

  library(plm); library(lmtest); library(sandwich)
})
.get_script_dir <- function() {
  args <- commandArgs(trailingOnly = FALSE)
  for (a in args) {
    if (startsWith(a, "--file=")) return(dirname(normalizePath(substring(a, 8))))
  }
  for (i in seq_len(sys.nframe())) {
    ofile <- tryCatch(sys.frame(i)$ofile, error = function(e) NULL)
    if (!is.null(ofile)) return(dirname(normalizePath(ofile)))
  }
  return(normalizePath("."))
}
.sdir <- .get_script_dir()
OUTPUT_DIR <- normalizePath(file.path(.sdir, "..", "output"), mustWork=FALSE)
if (!dir.exists(OUTPUT_DIR)) dir.create(OUTPUT_DIR, recursive=TRUE)
cat("\n================================================================\n")
cat("  TEMA 05 — Datos de Panel: Grunfeld (1958)\n")
cat("================================================================\n\n")

# --- CARGA DE DATOS ---
data("Grunfeld", package="plm")
cat("Dataset de Grunfeld: inversión de", length(unique(Grunfeld$firm)),
    "empresas durante", length(unique(Grunfeld$year)), "años (",
    min(Grunfeld$year), "-", max(Grunfeld$year), ")\n")
cat("  N total:", nrow(Grunfeld), "observaciones (panel balanceado)\n\n")

cat("Variables:\n")
cat("  inv    — Inversión bruta (millones USD)\n")
cat("  value  — Valor de mercado de la empresa (millones USD)\n")
cat("  capital — Stock de capital (planta y equipo, millones USD)\n\n")

# --- DESCRIPTIVOS ---
cat("--- ESTADÍSTICOS DESCRIPTIVOS ---\n\n")
vars <- c("inv","value","capital")
cat(sprintf("  %-10s %10s %10s %10s %10s\n", "Variable", "Media", "D.E.", "Mín.", "Máx."))
cat(sprintf("  %s\n", paste(rep("-", 54), collapse="")))
for (v in vars) {
  x <- Grunfeld[[v]]
  cat(sprintf("  %-10s %10.1f %10.1f %10.1f %10.1f\n", v, mean(x), sd(x), min(x), max(x)))
}
pausa()

# --- DESCOMPOSICIÓN WITHIN/BETWEEN ---
cat("\n--- DESCOMPOSICIÓN WITHIN/BETWEEN ---\n\n")
pdata <- pdata.frame(Grunfeld, index=c("firm","year"))
for (v in vars) {
  ov <- var(Grunfeld[[v]])
  medias_i <- tapply(Grunfeld[[v]], Grunfeld$firm, mean)
  bv <- var(medias_i)
  within_vals <- Grunfeld[[v]] - rep(medias_i, each=length(unique(Grunfeld$year)))
  wv <- var(within_vals)
  cat(sprintf("  %-8s  Total: %10.1f | Between: %10.1f | Within: %10.1f\n", v, ov, bv, wv))
}
cat("\n  La variación between mide diferencias entre empresas.\n")
cat("  La variación within mide cambios en el tiempo de cada empresa.\n")
pausa()

# --- GRÁFICO EDA ---
cat("\n--- GRÁFICO: INVERSIÓN POR EMPRESA A LO LARGO DEL TIEMPO ---\n\n")
firmas <- unique(Grunfeld$firm)
ltys <- rep(1:4, length.out=length(firmas))
pchs <- rep(c(16,1,17,2,15,0,18,3,4,8), length.out=length(firmas))

png(file.path(OUTPUT_DIR, "T05_panel_eda.png"), width=1600, height=700, res=150)
par(mfrow=c(1,2), mar=c(4.5,4.5,2.5,0.5), cex.main=0.88)

# Panel 1: series temporales por empresa
plot(NA, xlim=range(Grunfeld$year), ylim=range(Grunfeld$inv),
     xlab="Año", ylab="Inversión (mill. USD)", main="Inversión por empresa")
for (i in seq_along(firmas)) {
  sub <- Grunfeld[Grunfeld$firm==firmas[i],]
  lines(sub$year, sub$inv, lty=ltys[i], col=gray(0.1+0.07*i), lwd=1.3)
  points(sub$year, sub$inv, pch=pchs[i], col=gray(0.1+0.07*i), cex=0.5)
}

# Panel 2: within vs between
medias_inv <- tapply(Grunfeld$inv, Grunfeld$firm, mean)
plot(medias_inv, xlab="Empresa (ordenada)", ylab="Inversión media",
     main="Variación between", pch=16, col=gray(0.3), cex=1.3)
segments(1:10, medias_inv-15, 1:10, medias_inv+15, lwd=2, col=gray(0.6))
abline(h=mean(Grunfeld$inv), lty=2, lwd=1.5)
text(8, mean(Grunfeld$inv)+20, "Media global", cex=0.75, font=3)
dev.off()
cat("  Gráfico guardado: output/T05_panel_eda.png\n")
pausa()

# --- ESTIMACIÓN: POOLED, EF, EA ---
cat("\n--- ESTIMACIÓN DE MODELOS ---\n\n")
fml <- inv ~ value + capital
mod_pooled <- plm(fml, data=pdata, model="pooling")
mod_fe     <- plm(fml, data=pdata, model="within")
mod_re     <- plm(fml, data=pdata, model="random")

vars_m <- c("value","capital")
labs_m <- c("Valor mercado","Stock capital")
ct_p  <- coef(summary(mod_pooled))
ct_fe <- coef(summary(mod_fe))
ct_re <- coef(summary(mod_re))

cat(sprintf("  %-14s | %9s %8s | %9s %8s | %9s %8s\n",
            "Variable", "Pooled","EE","EF","EE","EA","EE"))
cat(sprintf("  %s\n", paste(rep("-",72), collapse="")))
for (i in seq_along(vars_m)) {
  v <- vars_m[i]
  cat(sprintf("  %-14s | %9.4f %8.4f | %9.4f %8.4f | %9.4f %8.4f\n",
              labs_m[i], ct_p[v,1], ct_p[v,2], ct_fe[v,1], ct_fe[v,2],
              ct_re[v,1], ct_re[v,2]))
}

cat("\n  INTERPRETACIÓN:\n")
cat("  El coeficiente de 'value' en EF indica que un aumento de 1 millón\n")
cat("  USD en el valor de mercado se asocia con un aumento de",
    sprintf("%.4f", coef(mod_fe)["value"]), "millones\n")
cat("  en inversión, controlando por el efecto fijo de cada empresa.\n")
pausa()

# --- TEST DE EFECTOS INDIVIDUALES (F-test) ---
cat("\n--- TEST F: ¿EXISTEN EFECTOS INDIVIDUALES? ---\n\n")
f_test <- pFtest(mod_fe, mod_pooled)
cat(sprintf("  H0: No hay efectos individuales (Pooled OLS es suficiente)\n"))
cat(sprintf("  H1: Existen efectos individuales significativos\n\n"))
cat(sprintf("  Estadístico F: %.2f\n", f_test$statistic))
cat(sprintf("  p-valor:       %.6f\n\n", f_test$p.value))
if (f_test$p.value < 0.05) {
  cat("  ✓ Se RECHAZA H0 (p<0.05). Los efectos individuales son significativos.\n")
  cat("    Pooled OLS NO es adecuado. Debemos usar EF o EA.\n\n")
} else {
  cat("  ✗ No se rechaza H0. Pooled OLS podría ser suficiente.\n\n")
}
pausa()

# --- TEST LM BREUSCH-PAGAN ---
cat("\n--- TEST LM DE BREUSCH-PAGAN ---\n\n")
bp_test <- plmtest(mod_pooled, type="bp")
cat(sprintf("  H0: sigma_alpha² = 0 (no hay heterogeneidad individual)\n"))
cat(sprintf("  H1: sigma_alpha² > 0\n\n"))
cat(sprintf("  Estadístico chi²: %.2f\n", bp_test$statistic))
cat(sprintf("  p-valor:          %.6f\n\n", bp_test$p.value))
if (bp_test$p.value < 0.05) {
  cat("  ✓ Se RECHAZA H0: existe heterogeneidad individual no observada.\n\n")
}
pausa()

# --- TEST DE HAUSMAN ---
cat("\n--- TEST DE HAUSMAN: EF vs EA ---\n\n")
hausman <- phtest(mod_fe, mod_re)
cat(sprintf("  H0: Efectos Aleatorios es consistente y eficiente\n"))
cat(sprintf("      (alpha_i NO correlacionado con x_it)\n"))
cat(sprintf("  H1: Solo Efectos Fijos es consistente\n"))
cat(sprintf("      (alpha_i SÍ correlacionado con x_it)\n\n"))
cat(sprintf("  Estadístico H: %.2f\n", hausman$statistic))
cat(sprintf("  g.l.:          %d\n", hausman$parameter))
cat(sprintf("  p-valor:       %.4f\n\n", hausman$p.value))
if (hausman$p.value < 0.05) {
  cat("  ✓ Se RECHAZA H0 (p<0.05). EFECTOS FIJOS es el estimador correcto.\n")
  cat("    Los efectos individuales están correlacionados con los regresores.\n\n")
} else {
  cat("  ✗ No se rechaza H0. EFECTOS ALEATORIOS es adecuado y más eficiente.\n\n")
}
pausa()

# --- TEST DE AUTOCORRELACIÓN (WOOLDRIDGE) ---
cat("\n--- TEST DE WOOLDRIDGE: AUTOCORRELACIÓN SERIAL ---\n\n")
wo_test <- pwartest(mod_fe)
cat(sprintf("  H0: No hay autocorrelación de primer orden en los errores\n"))
cat(sprintf("  H1: Existe autocorrelación AR(1)\n\n"))
cat(sprintf("  Estadístico F: %.3f\n", wo_test$statistic))
cat(sprintf("  p-valor:       %.4f\n\n", wo_test$p.value))
if (wo_test$p.value < 0.05) {
  cat("  ✓ Se RECHAZA H0: hay autocorrelación serial.\n")
  cat("    Es imprescindible usar errores clustered.\n\n")
} else {
  cat("  ✗ No se rechaza H0. No hay evidencia de autocorrelación serial.\n\n")
}
pausa()

# --- ERRORES CLUSTERED ---
cat("\n--- EFECTOS FIJOS CON ERRORES CLUSTERED (HC1) ---\n\n")
rob_fe <- coeftest(mod_fe, vcov=vcovHC(mod_fe, type="HC1", cluster="group"))
cat(sprintf("  %-14s | %9s %8s %8s %10s\n", "Variable","Coef.","EE rob.","t","p-valor"))
cat(sprintf("  %s\n", paste(rep("-",56), collapse="")))
for (v in vars_m) {
  sig <- ifelse(rob_fe[v,4]<0.001,"***",ifelse(rob_fe[v,4]<0.01,"**",
               ifelse(rob_fe[v,4]<0.05,"*","   ")))
  cat(sprintf("  %-14s | %9.4f %8.4f %8.3f %10.6f %s\n",
              v, rob_fe[v,1], rob_fe[v,2], rob_fe[v,3], rob_fe[v,4], sig))
}
cat("\n  Los errores robustos clustered por empresa corrigen por\n")
cat("  heterocedasticidad y autocorrelación dentro de cada empresa.\n")
pausa()

# --- EFECTOS FIJOS INDIVIDUALES ---
cat("\n--- EFECTOS FIJOS INDIVIDUALES ESTIMADOS ---\n\n")
ef <- fixef(mod_fe)
cat(sprintf("  %-10s  %10s\n", "Empresa", "alpha_i"))
cat(sprintf("  %s\n", paste(rep("-",24), collapse="")))
for (i in seq_along(ef)) {
  cat(sprintf("  %-10s  %10.2f\n", names(ef)[i], ef[i]))
}

png(file.path(OUTPUT_DIR, "T05_efectos_fijos.png"), width=1200, height=600, res=150)
par(mar=c(5,5,2.5,1))
bp <- barplot(sort(ef), col=gray(seq(0.3,0.8,length.out=length(ef))),
              border="white", las=2, ylab="Efecto fijo estimado",
              main="Efectos individuales (Grunfeld)", cex.names=0.8)
abline(h=0, col=gray(0.4), lty=2, lwd=1.5)
dev.off()
cat("\n  Gráfico guardado: output/T05_efectos_fijos.png\n")
cat("\n  Cada barra representa el nivel base de inversión de cada empresa,\n")
cat("  una vez descontado el efecto de las covariables.\n")
pausa()

# --- GRÁFICO: COMPARACIÓN POOLED vs EF ---
cat("\n--- GRÁFICO: POOLED OLS vs EFECTOS FIJOS ---\n\n")
png(file.path(OUTPUT_DIR, "T05_pooled_vs_fe.png"), width=1400, height=700, res=150)
par(mfrow=c(1,2), mar=c(4.5,4.5,2.5,0.5), cex.main=0.88)

# Pooled
plot(Grunfeld$value, Grunfeld$inv, pch=1, col=gray(0.6), cex=0.6,
     xlab="Valor de mercado", ylab="Inversión", main="Pooled OLS")
abline(coef(mod_pooled)[1], coef(mod_pooled)["value"], lwd=2, lty=1)

# EF (una recta por empresa)
plot(Grunfeld$value, Grunfeld$inv, pch=1, col=gray(0.6), cex=0.6,
     xlab="Valor de mercado", ylab="Inversión", main="Efectos Fijos")
for (i in seq_along(firmas)) {
  abline(ef[i], coef(mod_fe)["value"], lty=ltys[i], col=gray(0.3+0.04*i), lwd=0.8)
}
dev.off()
cat("  Gráfico guardado: output/T05_pooled_vs_fe.png\n")
cat("  En el panel EF, cada empresa tiene su propio intercepto pero la\n")
cat("  pendiente es común: el efecto de 'value' sobre 'inv' es el mismo.\n")
pausa()

# --- RESUMEN FINAL ---
cat("\n================================================================\n")
cat("  RESUMEN DEL ANÁLISIS\n")
cat("================================================================\n\n")
cat("  1. Los efectos individuales son significativos (F-test y BP-LM).\n")
cat("     Pooled OLS NO es adecuado.\n\n")
cat("  2. El test de Hausman indica que", ifelse(hausman$p.value < 0.05,
    "EFECTOS FIJOS es el estimador correcto.", "EFECTOS ALEATORIOS es adecuado."), "\n\n")
cat("  3. Los coeficientes EF con errores clustered:\n")
cat(sprintf("     value:   %.4f (EE=%.4f) → +1M USD en valor → +%.2f miles en inversión\n",
            rob_fe["value",1], rob_fe["value",2], rob_fe["value",1]*1000))
cat(sprintf("     capital: %.4f (EE=%.4f) → +1M USD en capital → +%.2f miles en inversión\n",
            rob_fe["capital",1], rob_fe["capital",2], rob_fe["capital",1]*1000))
cat("\n  4. Las 10 empresas tienen interceptos muy distintos, reflejando\n")
cat("     la heterogeneidad no observada (tamaño, sector, cultura).\n\n")
cat("================================================================\n")
cat("  Fin del script T05\n")
cat("================================================================\n")