vcov = "HC1" or vcov = "cluster" replaced
its model-based covariance, so vcov(fit, type = "classical") returned
the robust matrix. The model-based covariance is now kept on the fit.
vcov() gains type = NULL (the new default), which returns the
covariance the fit was made with, so vcov(fit) is unchanged.panglm_hausman() used the covariance stored on the FE fit, so
an FE fit with a robust vcov entered the classical contrast
V_fe - V_re, which is valid only for model-based covariances. The
classical test now always uses the model-based covariances.panglm_hausman() gains method = "mundlak": the regression-based
(correlated random effects) version of the test, a Wald test of the
individual-mean coefficients in the pooled model y ~ x + xbar, with a
cluster-robust (default), HC1 or classical covariance. With the
cluster-robust covariance the reference is F(k, G - 1). Gaussian
family, individual effects only.panglm_hausman() against plm::phtest() now passes fitted
plm models, since phtest()'s formula method calls plm() unqualified.data.table dependency entirely. build_panel_index()'s
panel sort-by-(id, time) now uses plain base R data.frame indexing;
its run-length grouping uses base::rle() instead of
data.table::rleidv() (no as.character() coercion needed, since the
id column is already atomic and pre-sorted). demean_twoway()'s dense
time-rank encoding uses a new internal dense_rank0() helper
(match(x, sort(unique(x))) - 1L) instead of data.table::frankv().
No basetable equivalent exists for either primitive, so this ended up
as a full drop rather than a basetable swap. Also removed several
dead @importFrom data.table/utils::globalVariables() declarations
for NSE symbols (.SD, ..cols, ..xnames, :=) that were already
unused before this change.\value documentation to confint.panglm(), fitted.panglm(),
summary.panglm(), and vcov.panglm(), describing the structure and
meaning of each return value (CRAN resubmission).DESCRIPTION as
<doi:...> links.model = "random", family = "gaussian" always reported logLik/AIC/
BIC as NA, even though fit_random_gaussian() already estimates the
Swamy-Arora variance components (sigma_v2, sigma_mu2) needed to
compute a real likelihood - they were simply discarded. Added the
closed-form Gaussian random-intercept log-likelihood (Sherman-Morrison
identity on Omega_i = sigma_v2 * I + sigma_mu2 * J), using the already-
estimated coefficients and variance components. Cross-validated: on the
bundled copd data, logLik(random) closely tracks logLik(pooling) on
the same formula, as expected since random-effects GLS nests pooled OLS
as sigma_mu2 -> 0.model = "within" (one-way and two-way) had the same loglik = NA_real_
gap. By the Frisch-Waugh-Lovell theorem the demeaned-regression residuals
are algebraically identical to the full dummy-variable-regression
residuals, so the standard OLS ML Gaussian log-likelihood applies
directly - added, using the npar (regressors + fixed-effect count + 1
dispersion parameter) that panglm_parameter_count() already computed
for these branches but previously had no likelihood to pair with.glance.panglm() now reports r.squared/adj.r.squared (computed from
fitted.values()/y) for every model type; previously not reported at
all.panglm(model = "random", effect = "individual", family = "gaussian")
printed a raw solve(): system is singular; attempting approx solution
from Armadillo whenever a covariate was time-invariant (e.g. baseline sex
alongside a pre/post indicator) or the panel was balanced with a
deterministic time indicator (e.g. any two-wave pre/post design) - both
make the intermediate within/between regressions used to estimate the
Swamy-Arora variance components exactly rank-deficient. The estimates
were already correct (verified against lme4::lmer), but the warning
bypassed R's condition system entirely. Switched those two intermediate
regressions to stats::lm.fit(), which handles rank deficiency via
pivoted QR the same way lm() does, with no near-singular solve and no
raw stderr output.CRAN submission candidate. Adds a bundled synthetic dataset, copd (a
simulated COPD follow-up panel), and runnable @examples to every exported
function and documented method.
plot.panglm() for coefficient, residual, and fitted-value views,
and plot.panglm_hurdle() for the two hurdle components.panglm_hurdle() through
count_family = "negbin".logLik() now counts fitted
dispersion, shape, variance, and absorbed fixed-effect parameters where
they belong to the reported likelihood. Gaussian pooled likelihoods use
the maximum-likelihood variance while retaining the unbiased residual
variance for covariance estimation. glance() now reports AIC, BIC, and
the likelihood degrees of freedom.na.exclude restores fitted values and residuals to the input length, and
panel indexes are checked for missing or duplicate individual-time keys.
Nontrivial weights and nonzero offsets now fail explicitly instead of
risking an apparently successful fit that ignores them.First CRAN submission candidate. Consolidates the estimators, inference, and
zero-inflated-count support added since 0.1.0 (see below); no functional
changes since the last version bump beyond package-metadata and
documentation cleanup for submission (URL/BugReports fields, vignette
housekeeping).
Initial release.
model = "pooling": gaussian/poisson/binomial GLM via IRLS, and genuine
NB2 (negative binomial) via theta-profiled IRLS -- matches
MASS::glm.nb() exactly. (family = "negbin" previously silently
aliased to Poisson at every model level; this is fixed everywhere below.)model = "within": gaussian (exact demeaning, one-way or effect = "twoways"), poisson (conditional MLE, one-way or effect = "twoways"
via outer-IRLS/inner-weighted-FWL -- the algorithm behind
fixest::feglm()), negative binomial (Allison & Waterman 2002
unconditional dummy-variable estimator -- matches fixest::fenegbin()
exactly; tractable for small-to-moderate N, not intended for very large
panels), and binomial (exact conditional logistic regression, Chamberlain
1980 -- matches survival::clogit(method = "exact"), no pglm
equivalent). effect = "twoways" is available for gaussian, poisson, and
negbin (the latter two via outer-IRLS/inner-weighted-FWL, matching
fixest::fenegbin() exactly); not yet for binomial (no general
closed-form two-way conditional logit exists).message()) before fitting the Allison-Waterman FE-NB2 estimator -- their
dummy-variable intercept is unbounded under the log link, the same
boundary case model = "within", family = "binomial" already screened
for. Previously left in, this could contaminate the shared covariate
coefficients via the joint solve, not just the offending group's own
intercept.model = "random": gaussian (Swamy-Arora), poisson (Poisson-Gamma, a
single dispersion parameter), negative binomial (a genuinely different,
2-parameter beta-negative-binomial mixture matching pglm's
lnl.negbin.R exactly -- previously aliased to the Poisson-Gamma model),
and binomial (Gauss-Hermite quadrature) random effects. The
random-effects Poisson fit now seeds from the pooled-Poisson MLE plus a
method-of-moments dispersion estimate, and uses Levenberg-Marquardt/ridge
damping (with a warning when it engages) instead of erroring on a
singular Hessian -- verified on heavily zero-inflated, extremely skewed
panels that previously crashed.vcov(fit, type = "HC1" | "cluster") for pooled/within models, including
effect = "twoways" (gaussian) and within negbin (Allison-Waterman): the
sandwich correction uses the full covariate+dummy score and bread (all
(k+G) parameters), not just the covariate block, since the dummy
intercepts are jointly estimated and contribute sampling variability of
their own. Random-effects models intentionally keep model-based
(information-matrix) SEs only, matching lme4/glmmTMB convention.confint.panglm() -- Wald intervals using whichever vcov the fit
currently carries.panglm_dispersiontest() -- Pearson chi-squared/df overdispersion test
for poisson/negbin fits with available fitted.values. Observations with
a zero or unavailable fitted value (e.g. rows in an all-zero group
screened out of a fixed-effects negbin fit) are excluded from the
statistic rather than producing a 0/0 = NaN; the excluded count is
reported as n_excluded on the returned object.panglm_hausman() for FE-vs-RE specification testing.tidy()/glance() methods registered against generics::tidy/glance
for broom/modelsummary compatibility.panglm_hurdle(): a fixed-effects hurdle model for panels with
structural zeros (a pattern that shows up as extreme, family-invariant
overdispersion under either family = "poisson" or family = "negbin"
in panglm()). Fits 1(y>0) via the existing conditional-logit FE
estimator, and a zero-truncated Poisson or NB2 FE model (an
Allison-Waterman-style dummy-variable estimator, Newton-Raphson with
exact Fisher information) on the y>0 subsample. The truncated-Poisson
math (no fixed effects) matches pscl::hurdle()'s count part exactly.
Both zero-truncated likelihoods are validated against pscl::hurdle().effect = "twoways" not yet available for binomial (no general
closed-form two-way conditional logit exists).