The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
Standard two-way fixed effects (TWFE) controls for unobserved heterogeneity through an additive structure:
\[y_{it} = \alpha_i + \xi_t + X_{it}'\beta + u_{it}\]
The unit effect \(\alpha_i\) absorbs any time-invariant unit characteristic; the time effect \(\xi_t\) absorbs any unit-invariant time shock. This works well when heterogeneous responses to common shocks are negligible.
Many empirical settings violate this assumption. Consider:
In each case, the unobserved confounder has the product form \(\lambda_i' F_t\): a unit-specific loading \(\lambda_i\) multiplied by a common factor \(F_t\). If \(X_{it}\) is correlated with \(\lambda_i' F_t\), TWFE is inconsistent even as \(N, T \to \infty\).
Bai (2009) proposes replacing the additive error structure with:
\[y_{it} = \alpha_i + \xi_t + X_{it}'\beta + \lambda_i' F_t + u_{it} \qquad (1)\]
where:
The \(r\) interactive terms \(\lambda_{ik} F_{kt}\) generalise both additive unit effects (\(r=1\), \(F_t = 1\)) and additive time effects (\(r=1\), \(\lambda_i = 1\)). With \(r\) unrestricted, (1) subsumes TWFE as the special case \(r = 0\).
xtife providesxtife is a pure base-R package (no compiled code, no external dependencies) offering:
| Feature | Function | Reference |
|---|---|---|
| Balanced IFE: analytical SEs | ife() |
Bai (2009) |
| Balanced IFE: static bias correction | ife(..., bias_corr=TRUE) |
Bai (2009, §7) |
| Balanced IFE: dynamic regressors | ife(..., method="dynamic") |
Moon & Weidner (2017) |
| Factor number selection (balanced) | ife_select_r() |
Bai & Ng (2002); Bai (2009) |
| Unbalanced IFE: estimation & analytical inference | ife_unbalanced() |
Su, Wang & Wang (2025); Bai (2009); Bai & Ng (2021) |
Unbalanced IFE: additive FE (force) |
ife_unbalanced(force=) |
Su, Wang & Wang (2025) |
| Unbalanced IFE: factor selection | ife_select_r_unb() |
Su, Wang & Wang (2025); Bai & Ng (2021) |
The package includes the cigar dataset: 46 US states \(\times\) 30 years (1963–1992) of cigarette sales and prices from Baltagi (1995).
library(xtife)
data(cigar)
dim(cigar)
#> [1] 1380 9
head(cigar[, c("state", "year", "sales", "price")], 3)
#> state year sales price
#> 1 1 63 93.9 28.6
#> 2 1 64 95.4 29.8
#> 3 1 65 98.5 29.8The panel is balanced: every state appears in every year.
fit <- ife(sales ~ price,
data = cigar,
index = c("state", "year"),
r = 2,
force = "two-way",
se = "cluster")
print(fit)
#>
#> Interactive Fixed Effects (Bai 2009, Econometrica)
#> -------------------------------------------------------
#> Panel : N = 46 units, T = 30 periods
#> Factors : r = 2
#> Force : two-way fixed effects
#> SE type : cluster (by state)
#> Outcome : sales
#> -------------------------------------------------------
#> Estimate Std.Error t.value Pr(>|t|) 95% CI
#> price -0.5242 0.0802 -6.5333 0.0000 [-0.6816, -0.3667] ***
#> ---
#> Signif. codes: *** <0.01 ** <0.05 * <0.1
#> -------------------------------------------------------
#> sigma^2 : 18.456076 | df = 1157
#> Converged: YES | Iterations: 10
#> -------------------------------------------------------
#> Factor selection criteria at r = 2 [IC1-3: Bai & Ng 2002; IC_bic/PC: Bai 2009]:
#> IC1 = 3.2347 | IC2 = 3.2900 | IC3 = 3.1421
#> PC = 36.8093 | IC (BIC-style) = 4.2661
#> -> Run ife_select_r() to compare criteria across r = 0, 1, ..., r_max
#> and identify the IC-minimising number of factors.The force argument controls additive fixed effects: "none" | "unit" | "time" | "two-way" (default).
Key output fields:
fit$coef # named coefficient vector
#> price
#> -0.5241574
fit$se # standard errors
#> price
#> 0.0802281
fit$ci # 95% confidence intervals (matrix: coef x 2)
#> CI.lower CI.upper
#> price -0.6815663 -0.3667486
fit$converged # TRUE if outer loop converged
#> [1] TRUE
fit$n_iter # number of outer iterations
#> [1] 10
dim(fit$F_hat) # T x r estimated factors
#> [1] 30 2
dim(fit$Lambda_hat) # N x r estimated loadings
#> [1] 46 2The Bai (2009) estimator alternates between two steps until convergence:
Step 1 — Factor extraction. Given \(\hat\beta\), form \(W_{it} = \ddot y_{it} - \ddot X_{it}'\hat\beta\) (residuals after additive FE demeaning). The top-\(r\) eigenvectors of \(WW'/NT\) (scaled to satisfy \(\hat F' \hat F / T = I_r\)) give \(\hat F\). The loadings follow by regression: \(\hat\Lambda = W'\hat F / T\).
Step 2 — Coefficient update. Project the demeaned regressors onto the orthogonal complement of \(\hat F\):
\[\tilde X_{it} = \ddot X_{it} - \hat F_t (\hat F' \hat F)^{-1} \hat F' \ddot X_i\]
Then \(\hat\beta = (\tilde X'\tilde X)^{-1}\tilde X'\ddot y\) (Frisch-Waugh-Lovell).
The outer loop converges when \(\max|\hat\beta^{(k+1)} - \hat\beta^{(k)}| < \text{tol}\) (default \(10^{-9}\)).
fit_std <- ife(sales ~ price, data = cigar,
index = c("state", "year"), r = 2, se = "standard")
fit_rob <- ife(sales ~ price, data = cigar,
index = c("state", "year"), r = 2, se = "robust")
fit_cl <- ife(sales ~ price, data = cigar,
index = c("state", "year"), r = 2, se = "cluster")
data.frame(
se_type = c("standard", "robust (HC1)", "cluster"),
coef = round(c(fit_std$coef, fit_rob$coef, fit_cl$coef), 5),
se = round(c(fit_std$se, fit_rob$se, fit_cl$se), 5),
t_stat = round(c(fit_std$tstat, fit_rob$tstat, fit_cl$tstat), 3)
)
#> se_type coef se t_stat
#> 1 standard -0.52416 0.03987 -13.146
#> 2 robust (HC1) -0.52416 0.04510 -11.623
#> 3 cluster -0.52416 0.08023 -6.533All three use the sandwich formula \(\widehat{\mathrm{Var}}(\hat\beta) = A^{-1} B A^{-1}\) where \(A = \tilde X'\tilde X\) and:
se= |
\(B\) matrix | Degrees of freedom |
|---|---|---|
"standard" |
\(\hat\sigma^2 A\) | \(NT - p - r(N+T-r) - \text{FE dof}\) |
"robust" |
\(\sum_{it} \hat u_{it}^2 \tilde x_{it}\tilde x_{it}'\) (HC1) | same |
"cluster" |
\(\sum_i \psi_i \psi_i'\), \(\psi_i = \sum_t \hat u_{it}\tilde x_{it}\) | same |
Rule of thumb: use "cluster" for most macro panels; "robust" when clustering by unit is not appropriate; "standard" only as a benchmark.
Setting \(r = 0\) reduces ife() to the standard TWFE estimator:
fit0 <- ife(sales ~ price, data = cigar,
index = c("state", "year"), r = 0, force = "two-way")
# Manual within-transformation
cigar$y_dm <- cigar$sales - ave(cigar$sales, cigar$state) -
ave(cigar$sales, cigar$year) + mean(cigar$sales)
cigar$x_dm <- cigar$price - ave(cigar$price, cigar$state) -
ave(cigar$price, cigar$year) + mean(cigar$price)
lm0 <- lm(y_dm ~ x_dm - 1, data = cigar)
cat(sprintf("ife (r=0): %.7f\n", fit0$coef["price"]))
#> ife (r=0): -1.0847117
cat(sprintf("lm TWFE: %.7f\n", coef(lm0)["x_dm"]))
#> lm TWFE: -1.0847117
cat(sprintf("diff: %.2e\n",
abs(fit0$coef["price"] - coef(lm0)["x_dm"])))
#> diff: 6.66e-16The difference is at machine precision, confirming algebraic equivalence. TWFE gives \(\hat\beta \approx -0.38\); IFE with \(r=2\) gives \(\approx -0.52\): the gap reflects the OVB from ignoring interactive confounders.
ife_select_r() fits models for \(r = 0, \ldots, r_{\max}\) and reports five information criteria.
# Not run during build (takes ~20 s); set verbose=FALSE in scripts
sel <- ife_select_r(sales ~ price, data = cigar,
index = c("state", "year"),
r_max = 6, force = "two-way", verbose = FALSE)
attr(sel, "suggested") # named vector: r chosen by each ICThe five criteria:
| Criterion | Penalty | Notes |
|---|---|---|
| IC1 | \(\hat\sigma^2 (N+T-r)\log(NT)/(NT)\) | Bai & Ng (2002) |
| IC2 | \(\hat\sigma^2 (N+T-r)\log(\min(N,T)^2)/(NT)\) | Bai & Ng (2002) |
| IC3 | \(\hat\sigma^2 (N+T-r)\log(\min(N,T))/(NT)\) | Bai & Ng (2002) |
| IC(BIC) | \(V(r)\log(NT)/(NT)\) | Bai (2009, §4.3) |
| PC | \(V(r) + r \cdot \hat\sigma^2 (N+T)\log(NT)/(NT)\) | Bai (2009) |
Recommendation: IC(BIC) for small-to-moderate panels (\(\min(N,T) < 60\)); IC1 or IC2 for large panels.
The IFE estimator has asymptotic bias \(O(1/N + 1/T)\) from the incidental parameters in \(\hat\Lambda\) and \(\hat F\). The analytical correction is:
\[\hat\beta^\dagger = \hat\beta - \hat B / N - \hat C / T\]
where \(\hat B\) and \(\hat C\) are closed-form expressions involving \(\hat F\), \(\hat\Lambda\), and the regression residuals (Bai 2009, Theorem 2).
fit_bc <- ife(sales ~ price, data = cigar,
index = c("state", "year"), r = 2,
se = "standard", bias_corr = TRUE)
cat(sprintf("Raw IFE: %.5f\n", fit_bc$coef_raw["price"]))
#> Raw IFE: -0.52416
cat(sprintf("Corrected:%.5f\n", fit_bc$coef["price"]))
#> Corrected:-0.52847
cat(sprintf("B_hat: %.5f (B_hat/N = %.5f)\n",
fit_bc$B_hat["price"], fit_bc$B_hat["price"] / fit_bc$N))
#> B_hat: 0.19466 (B_hat/N = 0.00423)
cat(sprintf("C_hat: %.5f (C_hat/T = %.5f)\n",
fit_bc$C_hat["price"], fit_bc$C_hat["price"] / fit_bc$T))
#> C_hat: 0.00254 (C_hat/T = 0.00008)The correction is most important when \(T/N\) is non-negligible. For the cigar panel (\(N=46\), \(T=30\), \(T/N \approx 0.65\)) it shifts the coefficient by about 0.007.
When regressors include lagged outcomes or are otherwise only weakly exogenous (correlated with past errors), the static bias correction is insufficient. The method = "dynamic" option applies the Moon and Weidner (2017) double-projection algorithm and three-term bias correction:
\[\hat\beta^* = \hat\beta + \hat W_X^{-1}\!\left(\frac{\hat B_1}{T} + \frac{\hat B_2}{N} + \frac{\hat B_3}{T}\right)\]
\(\hat B_1\) (Nickell-type) is near zero for approximately exogenous regressors.
fit_dyn <- ife(sales ~ price, data = cigar,
index = c("state", "year"), r = 2,
se = "standard", method = "dynamic",
bias_corr = TRUE, M1 = 1L)
cat(sprintf("Dynamic BC coef: %.5f\n", fit_dyn$coef["price"]))
#> Dynamic BC coef: -0.53161
cat(sprintf("B1/T (Nickell): %.5f\n",
fit_dyn$B1_hat["price"] / fit_dyn$T))
#> B1/T (Nickell): -0.00009For price (approximately exogenous), \(\hat B_1 / T \approx 0\), so the static and dynamic corrected estimates nearly coincide.
Real-world panels are rarely balanced. Survey non-response, merger events, data-collection gaps, and rotating samples all cause missing \((i,t)\) cells. Simply deleting units with incomplete records (list-wise deletion) induces selection bias; imputation by column means distorts the factor structure.
ife_unbalanced() extends the Bai (2009) interactive fixed effects estimator to genuinely unbalanced panels, following the estimation and inference theory of Su, Wang and Wang (2025), and combines three published ideas:
In addition, additive unit and/or time fixed effects can be removed jointly with the factors through the force argument (Section 3.2).
Let \(d_{it} \in \{0,1\}\) indicate whether \((i,t)\) is observed. The model is:
\[y_{it} = X_{it}'\beta + \lambda_i' F_t + u_{it} \qquad \text{for all } (i,t) \text{ with } d_{it}=1 \qquad (2)\]
By default (force = "none") there are no additive fixed effects in (2): individual and time heterogeneity is absorbed entirely by the interactive factor structure. Additive unit and/or time effects can instead be modelled explicitly through the force argument (see below), which is the recommended route when the data contain a strong common level or trend.
The normalisation \(\hat F' \hat F / T = I_r\) and \(\hat\Lambda' \hat\Lambda\) diagonal identify \(F\) and \(\Lambda\) up to an \(r \times r\) rotation. Only the column space of \(F\) (the factor space) is identified, which suffices for inference on \(\beta\).
forceThe force argument adds additive unit (\(\alpha_i\)) and/or time (\(\xi_t\)) effects to model (2):
\[y_{it} = \alpha_i + \xi_t + X_{it}'\beta + \lambda_i' F_t + u_{it}.\]
Setting force = "unit", "time", or "two-way" estimates these effects jointly with the factors and slope, by removing them from the imputed (completed) panel inside the EM loop. Because the demeaning is performed on the imputed — rather than the observed-only — panel, it is robust to informative (selection-related) missingness, unlike a naïve observed-cell demeaning. The default force = "none" (intercept-free) is appropriate when level/trend structure is already captured by the factors; choose force = "two-way" (matching the balanced ife() default) for data with a pronounced common level or trend. The degrees-of-freedom adjustment for the additive parameters (\(N-1\), \(T-1\), or \(N+T-2\)) is carried through to the standard errors automatically.
# Create a 10% randomly missing version of the cigar panel
set.seed(42)
cigar_unb <- cigar[sample(nrow(cigar), size = floor(0.9 * nrow(cigar))), ]
cat(sprintf("Observations: %d / %d (%.0f%% fill)\n",
nrow(cigar_unb), nrow(cigar), 100 * nrow(cigar_unb) / nrow(cigar)))
#> Observations: 1242 / 1380 (90% fill)fit_unb <- ife_unbalanced(sales ~ price,
data = cigar_unb,
index = c("state", "year"),
r = 2L,
se = "cluster")
print(fit_unb)
#>
#> Unbalanced Panel Interactive Fixed Effects
#> ----------------------------------------------------------
#> Panel : N = 46 units, TT = 30 periods (max)
#> Observed : n_obs = 1242 cells (90.0% of N x TT)
#> Factors : r = 2
#> SE type : cluster-robust by unit (state)
#> Exog : strict (exogenous regressors)
#> Init : ols
#> Outcome : sales
#> ----------------------------------------------------------
#> Estimate Std.Error t.value Pr(>|t|) 95% CI
#> price 0.0754 0.1108 0.6801 0.4966 [-0.1421, 0.2928]
#> ---
#> Signif. codes: *** < 0.01 ** < 0.05 * < 0.10
#> ----------------------------------------------------------
#> sigma^2 = 53.1211 | df = 1093
#> Converged: YES | Outer iterations: 362Key output fields mirror those of ife():
fit_unb$coef # estimated beta
#> price
#> 0.07537269
fit_unb$se # standard errors
#> price
#> 0.110819
fit_unb$ci # 95% confidence intervals
#> 2.5 % 97.5 %
#> price -0.1420693 0.2928146
fit_unb$converged # outer-loop convergence
#> [1] TRUE
fit_unb$n_obs # number of observed (i,t) pairs
#> [1] 1242
fit_unb$N # number of units
#> [1] 46
fit_unb$TT # number of distinct time periods
#> [1] 30
dim(fit_unb$F_hat) # TT x r
#> [1] 30 2
dim(fit_unb$Lambda_hat) # N x r
#> [1] 46 2ife_unbalanced() solves the unbalanced least-squares problem:
\[(\hat\beta, \hat F, \hat\Lambda) = \arg\min_{\beta, F, \Lambda} \sum_{i,t} d_{it}\left(y_{it} - X_{it}'\beta - \lambda_i' F_t\right)^2\]
subject to \(F'F/T = I_r\).
The outer loop alternates:
Within each AM outer iteration, \((\hat F, \hat\Lambda)\) are updated by the EM algorithm of Bai (2009, Appendix B), which treats the missing \(W_{it}\) as latent variables imputed from the current factor estimate:
The EM loop converges when \(\max_{(i,t): d_{it}=1} |\hat\Lambda_i^{(k+1)}\!\!'\hat F_t^{(k+1)} - \hat\Lambda_i^{(k)}\!\!'\hat F_t^{(k)}| < \text{tol}_{\text{EM}}\) (convergence on fitted values at observed cells; the rotational indeterminacy of \(F\) makes direct comparison of \(\hat F\) meaningless).
Two options for the initial \((\hat\beta^{(0)}, \hat F^{(0)}, \hat\Lambda^{(0)})\):
init = "ols" (default): plain OLS on observed cells gives \(\hat\beta^{(0)}\); the EM inner loop is then run from \((\hat F, \hat\Lambda) = 0\).
init = "nnr" (nuclear-norm regularisation): solves the nuclear-norm penalised problem
\[(\hat\beta^{(0)}, \hat\Theta^{(0)}) = \arg\min_{\beta, \Theta} \tfrac{1}{2}\!\sum_{it} d_{it}(y_{it} - \Theta_{it} - X_{it}'\beta)^2 + \nu_{NT}\|\Theta\|_*\]
via soft-impute iteration. The penalty \(\nu_{NT}\) is cross-validated over \(c \cdot \sqrt{\max(N,T)}\) with \(c \in \{0.01, 0.1, 1, 10\}\).
NNR initialisation is recommended when the panel is severely unbalanced (fill rate \(< 60\%\)) or has a rich factor structure (\(r \geq 3\)). For mildly unbalanced panels, both initialisations converge to the same fixed point.
# Both converge to the same coefficient for mild unbalancedness
fit_ols <- ife_unbalanced(sales ~ price, data = cigar_unb,
index = c("state", "year"), r = 2, init = "ols")
fit_nnr <- ife_unbalanced(sales ~ price, data = cigar_unb,
index = c("state", "year"), r = 2, init = "nnr")
cat(sprintf("OLS init: coef = %.5f, %d outer iterations\n",
fit_ols$coef["price"], fit_ols$n_iter))
#> OLS init: coef = 0.07537, 362 outer iterations
cat(sprintf("NNR init: coef = %.5f, %d outer iterations\n",
fit_nnr$coef["price"], fit_nnr$n_iter))
#> NNR init: coef = 0.07537, 360 outer iterationsThe sandwich variance of \(\hat\beta\) follows Su, Wang and Wang (2025) and requires projecting each regressor out of both the factor space and the loading space, giving doubly-projected regressors \(\hat x_{itk}\) (the unbalanced analogue of the Frisch–Waugh projection used in the balanced case):
\[\hat x_{itk} = x_{itk} - \hat\delta_{ki}'\hat F_t - \hat\omega_{kt}'\hat\lambda_i\]
where \(\hat\delta_{ki}\) and \(\hat\omega_{kt}\) solve, over the observed cells, the alternating least-squares system
\[\hat\delta_{ki} = \Bigl(\sum_t d_{it}\hat F_t\hat F_t'\Bigr)^{-1} \!\sum_t d_{it}\hat F_t(x_{itk} - \hat\lambda_i'\hat\omega_{kt})\]
\[\hat\omega_{kt} = \Bigl(\sum_i d_{it}\hat\lambda_i\hat\lambda_i'\Bigr)^{-1} \!\sum_i d_{it}\hat\lambda_i(x_{itk} - \hat F_t'\hat\delta_{ki})\]
The sandwich variance is then
\[\widehat{\mathrm{Var}}(\hat\beta) = \hat W_X^{-1} \hat\Omega_X \hat W_X^{-1}\]
with \(\hat W_X = \frac{1}{NT}\sum_{it} d_{it}\hat x_{it}\hat x_{it}'\) and \(\hat\Omega_X\) depending on the se argument:
se= |
\(\hat\Omega_X\) | Assumption |
|---|---|---|
"standard" |
\(\frac{1}{NT}\sum_{it} d_{it}\hat u_{it}^2\hat x_{it}\hat x_{it}'\) | Homoskedastic errors |
"robust" |
same formula, HC1-scaled | Heteroskedastic errors |
"cluster" |
\(\frac{1}{NT}\sum_i \psi_i\psi_i'\), \(\psi_i = \sum_t d_{it}\hat u_{it}\hat x_{it}\) | Serial correlation within units |
"hac" |
Bartlett kernel over \((t,s)\) pairs: \(\frac{1}{NT}\sum_i\sum_{t,s}\Gamma\!\left(\frac{|s-t|}{L_T}\right)d_{it}d_{is}\hat u_{it}\hat u_{is}\hat x_{it}\hat x_{is}'\) | Serial correlation over time within units |
The cluster-robust variant follows the within-unit clustering of Arellano (1987) and Cameron, Gelbach and Miller (2011); the "hac" variant uses the Bartlett kernel of Newey and West (1987), \(\Gamma(u) = (1-u)\mathbf{1}\{u \leq 1\}\), with bandwidth defaulting to \(L_T = \lfloor 2T^{1/5}\rfloor\).
fit_std <- ife_unbalanced(sales ~ price, data = cigar_unb,
index = c("state", "year"), r = 2, se = "standard")
fit_rob <- ife_unbalanced(sales ~ price, data = cigar_unb,
index = c("state", "year"), r = 2, se = "robust")
fit_cl <- ife_unbalanced(sales ~ price, data = cigar_unb,
index = c("state", "year"), r = 2, se = "cluster")
fit_hac <- ife_unbalanced(sales ~ price, data = cigar_unb,
index = c("state", "year"), r = 2, se = "hac")
data.frame(
se_type = c("standard", "robust", "cluster", "hac"),
coef = round(c(fit_std$coef, fit_rob$coef, fit_cl$coef, fit_hac$coef), 5),
se = round(c(fit_std$se, fit_rob$se, fit_cl$se, fit_hac$se), 5)
)
#> se_type coef se
#> 1 standard 0.07537 0.02503
#> 2 robust 0.07537 0.03657
#> 3 cluster 0.07537 0.11082
#> 4 hac 0.07537 0.05090When to use "hac": when the errors \(u_{it}\) are serially correlated (e.g., in dynamic models or when residuals show autocorrelation). For strictly exogenous regressors with i.i.d. errors, "cluster" is sufficient.
For unbalanced panels, ife_select_r_unb() uses the singular value thresholding (SVT) rule of Su, Wang and Wang (2025) applied to the nuclear-norm regularised matrix \(\hat\Theta^{(0)}\) — a missing-data counterpart of the eigenvalue-ratio / information-criterion rules of Bai and Ng (2002):
\[\hat r = \sum_{s=1}^{N \wedge T} \mathbf{1}\!\left\{ \sigma_s\!\left(\frac{\hat\Theta^{(0)}}{\sqrt{NT}}\right) \geq c_f\sqrt{c_{NT}^{-1/4}\,\sigma_1\!\left(\frac{\hat\Theta^{(0)}}{\sqrt{NT}}\right)} \right\}\]
where \(c_{NT} = \min(\sqrt{N}, \sqrt{T})\) and \(c_f = 0.6\) is the default threshold constant, which performs well for moderate degrees of unbalancedness.
# Not run during build (NNR cross-validation takes ~10 s)
sel_unb <- ife_select_r_unb(sales ~ price, data = cigar_unb,
index = c("state", "year"), verbose = FALSE)
cat(sprintf("SVT selects r_hat = %d\n", sel_unb$r_hat))The function returns (invisibly):
r_hat: the selected number of factors (at least 1);sv: the normalised singular values used in the threshold comparison;threshold: the computed threshold value;nu_used: the NNR penalty chosen by cross-validation.Even with the correct \(r\), the unbalanced IFE estimator carries an incidental-parameter bias of order \(O(N^{-1}) + O(T^{-1})\), analogous to the \(B/N + C/T\) bias of the balanced estimator (Bai 2009) and the dynamic bias of Moon and Weidner (2017). Setting bias_corr = TRUE applies the analytical correction of Su, Wang and Wang (2025), \(\hat\beta^{abc} = \hat\beta - (NT)^{-1/2}\hat W_X^{-1}\hat b\), where, for strictly exogenous regressors, \(\hat b = \hat b_3 + \hat b_4 + \hat b_5 + \hat b_6\) with
\[\hat b_{3k} = \frac{1}{\sqrt{NT}}\sum_{i,t} d_{it}\hat u_{it}^2 \hat\delta_{ki}'[\hat L_{ff}^{-1}]_t\hat\lambda_i\]
\[\hat b_{4k} = \frac{1}{\sqrt{NT}}\sum_{i,t} d_{it}\hat u_{it}^2 \hat\omega_{kt}'[\hat L_{\lambda\lambda}^{-1}]_i\hat F_t\]
\[\hat b_{5k} = \frac{1}{\sqrt{NT}}\sum_{i,t} d_{it}\hat u_{it}^2 \hat\lambda_i'\hat\Xi_{kt}\hat\lambda_i\]
\[\hat b_{6k} = \frac{1}{\sqrt{NT}}\sum_{i,t} d_{it}\hat u_{it}^2 \hat F_t'\hat\Delta_{ki}\hat F_t\]
Here \([\hat L_{ff}^{-1}]_t = (\sum_i d_{it}\hat\lambda_i\hat\lambda_i')^{-1}\), \([\hat L_{\lambda\lambda}^{-1}]_i = (\sum_t d_{it}\hat F_t\hat F_t')^{-1}\), and \(\hat\Delta_{ki}\), \(\hat\Xi_{kt}\) are \(r \times r\) auxiliary matrices formed from \(\hat\delta\), \(\hat\omega\) and the above inverses; \(\hat u_{it}\) are the model residuals. Full expressions are given in the .ife_bias_unb() source documentation.
exog = "strict")Use when \(\mathbb{E}[u_{it} \mid X, F, \Lambda] = 0\) for all \((i,t)\). This is the default and covers the vast majority of cross-sectional panel applications.
fit_bc_strict <- ife_unbalanced(sales ~ price, data = cigar_unb,
index = c("state", "year"), r = 2,
se = "standard", bias_corr = TRUE,
exog = "strict")
cat(sprintf("Raw beta: %.5f\n", fit_bc_strict$coef_raw["price"]))
#> Raw beta: 0.07537
cat(sprintf("Corrected beta: %.5f\n", fit_bc_strict$coef["price"]))
#> Corrected beta: 0.08183
cat(sprintf("b_hat (b3+...+b6): %.5f\n", fit_bc_strict$b_hat["price"]))
#> b_hat (b3+...+b6): NAexog = "weak")Use when the regressor includes the lagged dependent variable or is otherwise predetermined (correlated with past but not future errors). This adds the dynamic bias term \(\hat b_2\):
\[\hat b_{2k} = \frac{1}{\sqrt{NT}}\sum_i\sum_{t < s} \Gamma\!\left(\frac{s-t}{L_T}\right) \hat u_{it}\, x_{isk}\,\hat F_t'[\hat L_{\lambda\lambda}^{-1}]_i\hat F_s\]
The total bias correction becomes \(\hat b = \hat b_2 + \hat b_3 + \hat b_4 + \hat b_5 + \hat b_6\).
# Not run: exog="weak" adds the b2 dynamic term
# (requires HAC SE for valid inference in dynamic models)
fit_bc_weak <- ife_unbalanced(y ~ lag_y + x, data = df_dynamic,
index = c("unit", "time"), r = 2,
se = "hac", bias_corr = TRUE,
exog = "weak")Summary of exog × se combinations:
| Regressor type | Errors | Recommended | Bias term |
|---|---|---|---|
| Strictly exogenous | i.i.d. | se="standard" or "cluster" |
\(b_3\)–\(b_6\) |
| Strictly exogenous | Serially correlated | se="hac" |
\(b_3\)–\(b_6\) (HAC versions) |
| Weakly exogenous (lag \(y\)) | i.i.d. | se="cluster" + exog="weak" |
\(b_2\)–\(b_6\) |
| Weakly exogenous (lag \(y\)) | Serially correlated | se="hac" + exog="weak" |
\(b_2\)–\(b_6\) (HAC) |
As a sanity check, ife_unbalanced() on a complete (balanced) panel should agree with ife() under the same force specification. We use a simulated mean-zero factor panel (on data with a large level, such as cigar, force = "none" is ill-conditioned for both estimators – the factors must absorb the level – so use force = "two-way" there):
set.seed(99)
N <- 30; TT <- 22
F0 <- matrix(rnorm(TT * 2), TT, 2); L0 <- matrix(rnorm(N * 2), N, 2)
X <- matrix(rnorm(N * TT), TT, N)
Y <- F0 %*% t(L0) + 0.7 * X + matrix(rnorm(N * TT), TT, N)
sim <- data.frame(unit = rep(seq_len(N), each = TT),
time = rep(seq_len(TT), times = N),
Y = as.vector(Y), X = as.vector(X))
fit_bal <- ife(Y ~ X, data = sim, index = c("unit", "time"),
r = 2, force = "none", se = "standard")
fit_unb2 <- ife_unbalanced(Y ~ X, data = sim, index = c("unit", "time"),
r = 2, force = "none", se = "standard")
cat(sprintf("ife (balanced): %.6f\n", fit_bal$coef["X"]))
#> ife (balanced): 0.635347
cat(sprintf("ife_unbalanced: %.6f\n", fit_unb2$coef["X"]))
#> ife_unbalanced: 0.632448
cat(sprintf("Difference: %.2e\n",
abs(fit_bal$coef["X"] - fit_unb2$coef["X"])))
#> Difference: 2.90e-03The two estimators agree closely (they follow different iteration paths, so agreement is to roughly \(10^{-2}\) rather than machine precision).
Both ife() and ife_unbalanced() perform strict input validation and stop with an informative message if any check fails.
| Check | Error message |
|---|---|
data is not a data.frame |
"'data' must be a data.frame" |
index columns not in data |
"'index' columns not found: ..." |
| NA in outcome or covariates | "Missing values (NA) in variable '...'" |
| Duplicate (unit, time) pairs | "Duplicate (unit, time) pairs found" |
r < 1 (unbalanced) |
"'r' must be a positive integer" |
Panel unbalanced in ife() |
"Panel is not balanced: ..." |
se not in allowed set |
"'se' must be one of: ..." |
exog not "strict"/"weak" |
"'exog' must be 'strict' or 'weak'" |
L_T < 1 |
"'L_T' must be a positive integer" |
ife() — balanced panel| Argument | Default | Description |
|---|---|---|
formula |
— | outcome ~ covariate1 + ... |
data |
— | balanced long-format data.frame |
index |
— | c("unit_col", "time_col") |
r |
1L |
number of factors |
force |
"two-way" |
additive FE: "none" / "unit" / "time" / "two-way" |
se |
"standard" |
"standard" / "robust" / "cluster" |
method |
"static" |
"static" / "dynamic" (Moon & Weidner 2017) |
bias_corr |
FALSE |
apply analytical bias correction |
M1 |
1L |
MA order for dynamic BC (used when method="dynamic") |
tol |
1e-9 |
outer-loop convergence tolerance |
max_iter |
10000L |
outer-loop iteration cap |
ife_unbalanced() — unbalanced panel| Argument | Default | Description |
|---|---|---|
formula |
— | outcome ~ covariate1 + ... |
data |
— | long-format data.frame (may be unbalanced) |
index |
— | c("unit_col", "time_col") |
r |
1L |
number of factors |
force |
"none" |
additive FE: "none" / "unit" / "time" / "two-way" (estimated jointly with the factors; default is intercept-free) |
se |
"standard" |
"standard" / "robust" / "cluster" / "hac" |
init |
"ols" |
initialisation: "ols" or "nnr" (nuclear-norm) |
bias_corr |
FALSE |
apply analytical incidental-parameter bias correction |
exog |
"strict" |
exogeneity: "strict" or "weak" |
L_T |
NULL |
HAC bandwidth (\(\lfloor 2T^{1/5}\rfloor\) if NULL) |
c_f |
0.6 |
SVT threshold constant (used with ife_select_r_unb()) |
nu_NT |
NULL |
NNR penalty (cross-validated if NULL) |
tol |
1e-9 |
outer-loop convergence tolerance |
max_iter |
10000L |
outer-loop iteration cap |
tol_em |
1e-7 |
EM inner-loop convergence tolerance |
max_iter_em |
500L |
EM inner-loop iteration cap |
Bai, J. (2009). Panel data models with interactive fixed effects. Econometrica, 77(4), 1229–1279. doi:10.3982/ECTA6135
Bai, J. and Ng, S. (2002). Determining the number of factors in approximate factor models. Econometrica, 70(1), 191–221. doi:10.1111/1468-0262.00273
Bai, J. and Ng, S. (2021). Matrix completion, counterfactuals, and factor analysis of missing data. Journal of the American Statistical Association, 116(536), 1746–1763. doi:10.1080/01621459.2021.1967163
Baltagi, B. H. (1995). Econometric Analysis of Panel Data. Wiley.
Arellano, M. (1987). Computing robust standard errors for within-groups estimators. Oxford Bulletin of Economics and Statistics, 49(4), 431–434.
Cameron, A. C., Gelbach, J. B. and Miller, D. L. (2011). Robust inference with multiway clustering. Journal of Business & Economic Statistics, 29(2), 238–249. doi:10.1198/jbes.2010.07136
Mazumder, R., Hastie, T. and Tibshirani, R. (2010). Spectral regularization algorithms for learning large incomplete matrices. Journal of Machine Learning Research, 11, 2287–2322.
Moon, H. R. and Weidner, M. (2017). Dynamic linear panel regression models with interactive fixed effects. Econometric Theory, 33, 158–195. doi:10.1017/S0266466615000328
Newey, W. K. and West, K. D. (1987). A simple, positive semi-definite, heteroskedasticity and autocorrelation consistent covariance matrix. Econometrica, 55(3), 703–708. doi:10.2307/1913610
Su, L., Wang, F. and Wang, Y. (2025). Estimation and inference for interactive fixed effects panel data models with unbalanced panels. SSRN Working Paper No. 5177283. doi:10.2139/ssrn.5177283
These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.