← Catálogo
· Capítulo 5
T05_mECO_Script_Panel.R
# =============================================================================
# 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")