## Tests for xtife package — converted from test_ife.R (12 tests) ## Data: cigar (46 US states x 30 years, 1963-1992) data(cigar, package = "xtife") # ============================================================================== # Fixtures shared across tests # ============================================================================== fit2_std <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "standard") # ============================================================================== # TEST 1: r=0 matches standard TWFE OLS to machine precision # ============================================================================== test_that("r=0 matches TWFE OLS to machine precision", { fit0 <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 0, force = "two-way", se = "standard") # two-way within transformation (manual demeaning) cigar2 <- cigar cigar2$y_dm <- cigar2$sales - ave(cigar2$sales, cigar2$state) - ave(cigar2$sales, cigar2$year) + mean(cigar2$sales) cigar2$x_dm <- cigar2$price - ave(cigar2$price, cigar2$state) - ave(cigar2$price, cigar2$year) + mean(cigar2$price) lm0 <- lm(y_dm ~ x_dm - 1, data = cigar2) expect_equal(unname(fit0$coef["price"]), unname(coef(lm0)["x_dm"]), tolerance = 1e-5) }) # ============================================================================== # TEST 2: r=2 coefficient in plausible range # ============================================================================== test_that("r=2 coefficient in plausible range (-0.65 to -0.30)", { expect_true(fit2_std$coef["price"] > -0.65 & fit2_std$coef["price"] < -0.30) expect_true(fit2_std$converged) }) # ============================================================================== # TEST 3: HC1 robust SE >= standard SE # ============================================================================== test_that("robust (HC1) SE >= standard SE", { fit2_rob <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "robust") expect_gte(fit2_rob$se["price"], fit2_std$se["price"]) }) # ============================================================================== # TEST 4: SE ordering cluster >= robust >= standard # ============================================================================== test_that("SE ordering: cluster >= robust >= standard (80% tolerance)", { fit2_rob <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "robust") fit2_cl <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "cluster") expect_gte(fit2_cl$se["price"], fit2_rob$se["price"] * 0.8) expect_gte(fit2_rob$se["price"], fit2_std$se["price"] * 0.8) }) # ============================================================================== # TEST 5: Factor normalization F'F/T = I_r # ============================================================================== test_that("factor normalization F'F/T = I_r to machine precision", { FtF_over_T <- crossprod(fit2_std$F_hat) / fit2_std$T off_diag <- max(abs(FtF_over_T - diag(nrow(FtF_over_T)))) expect_lt(off_diag, 1e-8) }) # ============================================================================== # TEST 6: force options produce convergence # ============================================================================== test_that("all force options converge", { for (frc in c("none", "unit", "time", "two-way")) { fit_f <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = frc, se = "standard") expect_true(fit_f$converged, label = paste0("force='", frc, "' convergence")) } }) # ============================================================================== # TEST 7: Static bias correction (Bai 2009) # ============================================================================== test_that("static bias correction produces corrected coef ~-0.5309", { fit_bc <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "standard", bias_corr = TRUE) expect_equal(unname(fit_bc$coef["price"]), -0.5309, tolerance = 0.005) expect_true(fit_bc$bias_applied) expect_false(is.null(fit_bc$B_hat)) expect_false(is.null(fit_bc$C_hat)) }) # ============================================================================== # TEST 8: Dynamic method, no bias correction # ============================================================================== test_that("dynamic method coef is in plausible range and converges", { fit_dyn <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "standard", method = "dynamic") expect_true(fit_dyn$coef["price"] > -0.70 & fit_dyn$coef["price"] < -0.30) expect_true(fit_dyn$converged) expect_identical(fit_dyn$method, "dynamic") }) # ============================================================================== # TEST 9: Dynamic bias correction (Moon & Weidner 2017) # ============================================================================== test_that("dynamic bias correction produces corrected coef ~-0.5343", { fit_dbc <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "standard", method = "dynamic", bias_corr = TRUE, M1 = 1L) expect_equal(unname(fit_dbc$coef["price"]), -0.5343, tolerance = 0.005) expect_true(fit_dbc$bias_applied) expect_false(is.null(fit_dbc$B1_hat)) expect_false(is.null(fit_dbc$B2_hat)) expect_false(is.null(fit_dbc$B3_hat)) # B1 near zero (price is approximately exogenous in cigar data) expect_lt(abs(fit_dbc$B1_hat["price"] / fit_dbc$T), 0.01) }) # ============================================================================== # TEST 10: Dynamic SE ordering # ============================================================================== test_that("dynamic SE ordering: cluster >= robust >= standard (80% tolerance)", { fit_dyn_cl <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "cluster", method = "dynamic") fit_dyn_rob <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "robust", method = "dynamic") fit_dyn_std <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "standard", method = "dynamic") expect_gte(fit_dyn_cl$se["price"], fit_dyn_rob$se["price"] * 0.8) expect_gte(fit_dyn_rob$se["price"], fit_dyn_std$se["price"] * 0.8) }) # ============================================================================== # TEST 11: ife_select_r() returns correct structure # ============================================================================== test_that("ife_select_r returns correct structure", { skip_on_cran() # fitting r=0..4, takes ~15 s sel <- ife_select_r(sales ~ price, data = cigar, index = c("state", "year"), r_max = 4, verbose = FALSE) expect_s3_class(sel, "data.frame") expect_equal(nrow(sel), 5L) expect_true(all(c("r", "IC1", "IC2", "IC3", "IC_bic", "PC") %in% names(sel))) sug <- attr(sel, "suggested") expect_true(!is.null(sug)) expect_true(all(c("IC1", "IC2", "IC3", "IC_bic", "PC") %in% names(sug))) }) # ============================================================================== # TEST 12: method="static" explicit == default (regression guard) # ============================================================================== test_that("method='static' explicit equals default to machine precision", { fit_default <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "standard") fit_explicit <- ife(sales ~ price, data = cigar, index = c("state", "year"), r = 2, force = "two-way", se = "standard", method = "static") expect_equal(unname(fit_default$coef["price"]), unname(fit_explicit$coef["price"]), tolerance = 1e-10) })