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: 4.44e-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 : nnr (nu = 46.0000)
#> 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: 360Key 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 = "nnr" (default): the
nuclear-norm-regularised (NNR) consistent initial estimator — the first
step of the two-step procedure of Su, Wang and Wang (2025, eq. 3.6) —
solves
\[(\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\|_*\]
Su, Wang and Wang (2025) solve this convex problem by ADMM;
xtife solves the same objective by the soft-impute
iteration of Mazumder, Hastie and Tibshirani (2010) — both reach the
same global optimum. The penalty \(\nu_{NT}\) is selected over \(c \cdot \max(N,T)\) with \(c \in \{0.01, 0.1, 1, 10\}\) (their
footnote 12), minimising the observed-cell residual sum of squares of
the rank-\(r\) fit.
init = "ols": plain OLS on observed
cells gives \(\hat\beta^{(0)}\); the EM
inner loop is then run from \((\hat F,
\hat\Lambda) = 0\). This is faster but is not covered by the
consistency theory for the initial estimator; 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 penalty selection 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 selected from the grid of Su,
Wang and Wang (2025, footnote 12).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 |
"nnr" |
initialisation: "nnr" (nuclear-norm, Su, Wang &
Wang 2025 first step) or "ols" |
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 (selected over \(c \cdot
\max(N,T)\), \(c \in \{0.01, 0.1, 1,
10\}\), 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