Fits generalized linear models efficiently using 'RcppEigen'. The iteratively reweighted least squares
implementation utilizes the step-halving approach of Marschner (2011)
The fastglm package provides fast regression modeling, mean bias
reduction, and inference for continuous, binary, count, and ordered
outcomes. C++ fitting routines and numerical checks support familiar R
workflows, including summary(), coef(), vcov(), predict(),
residuals(), and logLik().
| Model | Main interface | Estimation and examples |
|---|---|---|
| Generalized linear models (GLMs) | fastglm() |
Ordinary fitting and mean bias reduction for supported family/link combinations; GLM example |
| Negative-binomial counts | fastglm_nb() |
Maximum likelihood (ML) with estimated size; negative-binomial regression |
| Positive counts and hurdle models | fastglm_truncated(), fastglm_hurdle() |
Zero-truncated Poisson or negative-binomial regression, alone or with a binary occurrence model; hurdle examples |
| Zero-inflated counts | fastglm_zi() |
Poisson or negative-binomial counts with an additional zero component; zero-inflated models |
| Zero and positive continuous outcomes | fastglm_twopart() |
Binary and Gamma components, with ML or mean bias reduction; two-part models |
| Ordered outcomes | fastglm_ordinal() |
Proportional odds and selected nonparallel effects; logit, probit, and complementary log-log links; ML or mean bias reduction; ordinal functionality |
Mean bias reduction uses firth=TRUE where supported. Its scope depends
on the model and family/link combination; the bias-reduction
section describes the GLM choices. Truncated,
hurdle, and zero-inflated count fitting uses ML.
The package overview maps these methods to their supported models. Dedicated guides cover bias reduction, counts, ordered outcomes, two-part outcomes, and large-data computation. See NEWS for the version history.
Installation · GLM example · Model extensions · Bias reduction · Inference and repeated fits · Large data · Speed · Stability · Benchmarks
Install the released version from CRAN or the development version from GitHub:
install.packages("fastglm")
pak::pak("jaredhuling/fastglm")
fastglm() accepts a numeric design matrix, a response, and an R
family object. In the esoph example, the two response columns
contain the numbers of cases and controls within each covariate group:
library(fastglm)
data(esoph)
X_esoph <- model.matrix(~ agegp + unclass(tobgp) + unclass(alcgp), data=esoph)
y_esoph <- cbind(esoph$ncases, esoph$ncontrols)
fit <- fastglm(X_esoph, y_esoph, family=binomial("cloglog"))
summary(fit)
#> Generalized linear model
#>
#> Call:
#> fastglm.default(x = X_esoph, y = y_esoph, family = binomial("cloglog"))
#> Family: binomial (cloglog link)
#>
#> Coefficients:
#> Estimate Std. Error z value Pr(>|z|)
#> (Intercept) -4.29199 0.29777 -14.414 <1e-04 ***
#> agegp.L 3.30677 0.63456 5.211 <1e-04 ***
#> agegp.Q -1.35525 0.57311 -2.365 0.018 *
#> agegp.C 0.20296 0.43334 0.468 0.640
#> agegp^4 0.15056 0.29318 0.514 0.608
#> agegp^5 -0.23425 0.17855 -1.312 0.190
#> unclass(tobgp) 0.33276 0.07285 4.568 <1e-04 ***
#> unclass(alcgp) 0.80384 0.07538 10.664 <1e-04 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Dispersion Null deviance Null df Deviance Residual df AIC
#> 1 368 87 96.95 80 228
#> Convergence: converged (6 iterations)
A formula and data frame can also be supplied directly. Formula fits retain factor coding, contrasts, and offsets for subsequent prediction:
fit_formula <- fastglm(
cbind(ncases, ncontrols) ~ agegp + unclass(tobgp) + unclass(alcgp),
data=esoph, family=binomial("cloglog"))
predict(fit_formula, newdata=esoph[1:3,], type="response", se.fit=TRUE)
#> Predictions with standard errors
#> Scale: response mean
#> Residual scale: 1
#>
#> Row Estimate Std. Error
#> 1 0.002727 0.002746
#> 2 0.003801 0.003806
#> 3 0.005298 0.005300
The fitting function can also be passed to base R’s glm():
glm(cbind(ncases, ncontrols) ~ agegp + unclass(tobgp) + unclass(alcgp),
data=esoph, family=binomial("cloglog"), method=fastglm_fit)
fastglmPure() exposes the lower-level fit without the final
dispersion, AIC, and null-deviance calculations performed by
fastglm(). For workflows that only need coefficient estimates,
fastglm(..., inference="none") skips the final covariance calculation.
Prepared designs, described below, are useful when several outcomes
share the same predictors.
fastglm_nb() estimates negative-binomial regression coefficients
together with the size parameter theta, which determines variation
beyond the Poisson variance. Dense fits alternate coefficient and theta
updates in C++, in the spirit of MASS::glm.nb():
set.seed(123)
n <- 5000
X_nb <- cbind(1, matrix(rnorm(n*3), n, 3))
mu_nb <- exp(X_nb %*% c(.5, .4, -.2, .3))
y_nb <- MASS::rnegbin(n, mu=mu_nb, theta=2)
f_nb <- fastglm_nb(X_nb, y_nb)
c(coef(f_nb), theta=f_nb$theta)
#> x1 x2 x3 x4 theta
#> 0.4926199 0.3862258 -0.2200450 0.2991296 1.9923309
Sparse designs and repeatable sources are also supported.
vcov(f_nb, full=TRUE) includes theta jointly with the coefficients,
and fastglm_confint_theta() obtains a profile-likelihood interval by
varying theta and refitting the coefficients. The negative-binomial
stability vignette examines
challenging cases and the limit as the model approaches a Poisson
distribution.
A hurdle model combines a binary regression for whether an outcome is
positive with a zero-truncated regression for its positive count. It is
useful when the occurrence of an event and its positive frequency need
separate explanations. fastglm_hurdle() supports Poisson and
negative-binomial count components, corresponding to the models in
pscl::hurdle() (Zeileis, Kleiber, and Jackman, 2008).
set.seed(123)
n <- 5000
x1 <- rnorm(n)
x2 <- rnorm(n)
lambda <- exp(.7 + .4*x1 - .3*x2)
is_positive <- rbinom(n, 1, plogis(-.4 + .5*x1 + .2*x2))
positive_counts <- integer(n)
for (i in seq_len(n)) {
repeat {
value <- rpois(1, lambda[i])
if (value > 0) { positive_counts[i] <- value; break }
}
}
hurdle_data <- data.frame(y=is_positive*positive_counts, x1=x1, x2=x2)
f_h <- fastglm_hurdle(y ~ x1+x2 | x1+x2, data=hurdle_data, dist="poisson")
coef(f_h)
#> count_(Intercept) count_x1 count_x2 zero_(Intercept)
#> 0.6793788 0.4162547 -0.3137229 -0.3767285
#> zero_x1 zero_x2
#> 0.4447325 0.1999711
The formula before | specifies the count predictors; the formula after
it specifies the predictors of a positive outcome. These can differ.
fastglm_truncated() fits the positive-count regression on its own when
observations are sampled conditional on being above zero.
A zero-inflated model allows zeros to arise from both an additional zero
component and the count distribution. This distinction changes the
meaning of the binary component relative to a hurdle model.
fastglm_zi() fits Poisson or negative-binomial versions using an
expectation-maximization algorithm: it alternates estimating each
observation’s component membership with updating the component
regressions.
set.seed(123)
n <- 5000
x1 <- rnorm(n)
x2 <- rnorm(n)
lambda <- exp(.7 + .4*x1 - .3*x2)
extra_zero <- rbinom(n, 1, plogis(-.4 + .5*x1 + .2*x2))
zi_data <- data.frame(y=ifelse(extra_zero==1, 0L, rpois(n, lambda)), x1=x1, x2=x2)
f_zi <- fastglm_zi(y ~ x1+x2, data=zi_data, dist="poisson")
coef(f_zi)
#> count_(Intercept) count_x1 count_x2 zero_(Intercept)
#> 0.6647396 0.3965153 -0.3407168 -0.3750258
#> zero_x1 zero_x2
#> 0.4512788 0.1819040
Count-model predictions distinguish the overall response mean, the probability of zero, and the means of the fitted components. Prediction standard errors combine uncertainty from the relevant parameters. The count-model vignette develops the interpretation and inference examples.
fastglm_twopart() combines a binary model for a positive outcome with
a Gamma model for its positive amount. This is useful for outcomes such
as expenditure that are exactly zero for some observations and positive
and skewed for others:
set.seed(124)
amounts <- data.frame(x=rnorm(1000))
is_positive <- rbinom(1000, 1, plogis(-.3 + .4*amounts$x))
amounts$y <- is_positive*rgamma(1000, shape=2, scale=exp(.5 + .3*amounts$x)/2)
f_two <- fastglm_twopart(y ~ x, data=amounts)
predict(f_two, newdata=amounts[1:3,], type="response", se.fit=TRUE)
#> Predictions with standard errors
#> Scale: response mean
#>
#> Row Estimate Std. Error
#> 1 0.3117 0.03290
#> 2 0.7218 0.03732
#> 3 0.4540 0.03324
The overall mean combines the probability of a positive outcome with its conditional mean amount. The semicontinuous-outcome vignette explains this interpretation and illustrates average contrasts, clustered inference, and bootstrap uncertainty.
fastglm_ordinal() supports logit, probit, and complementary log-log
cumulative links, with maximum likelihood or mean bias reduction
(firth=TRUE). The logit model gives proportional odds with common
slopes and partial proportional odds when selected effects vary through
nonparallel=~term.
| Task | Ordinal interface and scope |
|---|---|
| Specify the model | Dense formula or matrix; individual outcomes, grouped category frequencies, and offsets; selected nonparallel effects require every category to be observed |
| Interpret predictions | Category and cumulative probabilities, prediction standard errors, average probabilities and contrasts; automatic marginal effects for continuous predictors |
| Test coefficients | Joint Wald tests and named contrasts; ML profile and ordinary or adjusted-score inference for retained dense models with all categories observed |
| Assess common slopes | fastglm_po_test() tests the restriction jointly or by term in dense or streamed common-slope and partial fits |
| Assess fit and uncertainty | Prediction scores and calibration for dense or streamed fits; optional row-level boundary diagnostics; robust covariance for eligible fits; refitting bootstraps for retained common-slope fits |
| Repeat fits or read large datasets | fastglm_ordinal_prepare() / fastglm_ordinal_batch() reuse dense designs; fastglm_ordinal_streaming() fits common-slope or partial models from repeatable blocks, including mean bias reduction |
Dense partial fits support robust covariance, marginal effects, ML
profiles, and constrained score tests, including equality of selected
slopes across thresholds. Streamed fits provide model-based or robust
covariance, Wald inference, common-slope tests, prediction scores, and
calibration by rereading their source.
fastglm_diagnostics(fit,boundary=TRUE) locates rows near probability
boundaries. Profile intervals and general constrained coefficient-score
tests require retained data. Empty-category behavior also depends on the
model and estimation method. The ordinal
vignette begins with the full support
guide and develops examples for each workflow.
firth=TRUE adjusts the estimating equations to reduce leading
small-sample mean bias (Firth, 1993; Kosmidis and Firth, 2009). For
logistic regression with a logit link and a full-rank design, the
adjustment also yields finite coefficient estimates under separation,
where predictors perfectly distinguish successes from failures (Kosmidis
and Firth, 2021).
data(sex2, package="logistf")
X_firth <- model.matrix(~ age+oc+vic+vicl+vis+dia, data=sex2)
f_firth <- fastglm(X_firth, sex2$case, binomial(), firth=TRUE,
tol=1e-10, maxit=200)
coef(f_firth)
#> (Intercept) age oc vic vicl vis
#> 0.12025405 -1.10598133 -0.06881673 2.26887465 -2.11140819 -0.78831695
#> dia
#> 3.09601183
Mean bias reduction supports the implemented binomial, Poisson, Gaussian, Gamma, inverse-Gaussian, and fixed-theta negative-binomial family/link combinations. It is available for dense, sparse, mapped, and streaming fits using the solvers supported by each format. Gamma fitting also adjusts dispersion. Quasi-likelihood and Tweedie families are not supported for bias reduction.
For retained dense binomial-logit Firth fits, profile inference re-estimates the other coefficients as the coefficient of interest varies, retaining the full original bias-reduction penalty. This allows confidence intervals to reflect an asymmetric penalized likelihood:
profile_ci <- confint(f_firth, parm=c("age", "dia"), method="profile")
cbind(estimate=coef(f_firth)[rownames(profile_ci)], profile_ci)
#> estimate 2.5 % 97.5 %
#> age -1.105981 -1.9737884 -0.3074251
#> dia 3.096012 0.7745682 8.0302935
fastglm_profile_test(f_firth, parm=c("oc", "vic"), null=0)
#> Firth penalized likelihood ratio test (asymptotic chi-square reference)
#> Null fit: Firth penalized likelihood
#>
#> Statistic df p-value
#> 28.46 2 < 1e-04
#>
#> Tested coefficients:
#> Estimate Null
#> oc -0.06882 0.00000
#> vic 2.26887 0.00000
The profile procedures support independent binomial counts and frequency
weights. Their chi-square reference is an approximation, and failed
optimizations return diagnostics. The bias-reduction
vignette explains the assumptions and
compares the results with logistf and brglm2.
summary(), vcov(), and predict(..., se.fit=TRUE) provide
model-based uncertainty. Robust covariance allows variation beyond the
fitted variance model; clustered covariance additionally allows
dependence within specified independent groups:
vcov(fit_formula, type="HC1")
vcov(fit_formula, cluster=group_id, type="HC1")
# The sandwich interfaces are also available for ordinary GLMs.
sandwich::vcovHC(fit_formula, type="HC0")
sandwich::vcovCL(fit_formula, cluster=group_id, type="HC1")
fastglm_average() reports average predictions or specified contrasts
with uncertainty. fastglm_bootstrap() provides uncertainty by
resampling and refitting, with failed fits recorded in its output. The
supported covariance calculations depend on the model and estimation
method; the overview and
model-specific vignettes describe those choices.
fastglm_prepare() records predictors and common weights or offsets.
fastglm_batch() then fits several responses using that design:
set.seed(125)
X_batch <- cbind(1, matrix(rnorm(1000*3), 1000, 3))
mu_batch <- exp(X_batch %*% c(.2, .3, -.2, .1))
Y_batch <- cbind(first=rgamma(1000, shape=2, scale=mu_batch/2),
second=rgamma(1000, shape=4, scale=mu_batch/4))
design <- fastglm_prepare(X_batch, firth=TRUE)
batch <- fastglm_batch(design, Y_batch, family=Gamma("log"))
sapply(batch, coef)
#> first second
#> (Intercept) 0.1528201 0.2011954
#> X2 0.2764287 0.2982692
#> X3 -0.2119394 -0.1813117
#> X4 0.1212752 0.1062714
Gaussian-identity fits can reuse their matrix decomposition because it
depends only on predictors and weights. Dense Firth batches with
Gaussian-identity, Gamma-log, or Poisson-square-root links can also
reuse their decomposition and bias-adjustment setup with the default
automatic solver or explicit "llt". Dispersion and response-dependent
adjustments remain specific to each outcome. Changes to the predictors
or weights invalidate the saved calculations.
workers runs independent fits in separate R processes. This can reduce
elapsed time when the fitting work is large enough to offset process
startup and data-transfer costs. inference="none" skips covariance
calculation, and retain.data=FALSE omits the design and response from
the returned models; methods requiring those data then need new data or
a refit.
Different storage formats suit different datasets:
Matrix::dgCMatrix stores only nonzero predictor entries. It can be
passed directly to fastglm(), for example when indicator variables
produce a mostly zero design.bigmemory::big.matrix provides a mapped view of predictors, which
may be backed by a file. Fitting reads row blocks without making a
complete ordinary R-matrix copy.fastglm_streaming() repeatedly requests manageable blocks from a
supplied function, called a callback. This supports data that need to
be read from an external source or constructed as fitting proceeds.The following example divides one dataset into four blocks and compares the fitted coefficients with an in-memory fit:
set.seed(321)
n <- 4000
X_stream <- cbind(1, matrix(rnorm(n*3), n, 3))
y_stream <- rbinom(n, 1, plogis(X_stream %*% c(.2, .4, -.2, .3)))
chunks <- function(k) {
rows <- ((k-1)*1000+1):(k*1000)
list(X=X_stream[rows,,drop=FALSE], y=y_stream[rows])
}
fit_stream <- fastglm_streaming(chunks, n_chunks=4, family=binomial(), tol=1e-10)
fit_full <- fastglm(X_stream, y_stream, binomial(), method="llt", tol=1e-10)
max(abs(coef(fit_stream)-coef(fit_full)))
#> [1] 2.775558e-16
fastglm_source() combines a repeatable reader with formula
information, keeping factor coding and transformations consistent across
blocks. Optional fastglm_source_arrow() and fastglm_source_dbi()
adapters read Parquet files and database queries. Each read must
reproduce the same observations in the same order throughout fitting and
inference.
Predictions normally accumulate in a vector. When that output is too
large, sink accepts a function that receives one block of results at a
time and saves or summarizes it. Here only the mean prediction in each
block is retained:
block_means <- numeric(4)
predict(fit_stream, type="response",
sink=function(value, k) block_means[k] <<- mean(value))
block_means
#> [1] 0.5530598 0.5507933 0.5519271 0.5422198
Robust covariance and average predictions can also read the source in blocks. Clustered inference additionally stores a contribution vector for each group, and collecting all predictions still requires storage proportional to the number of observations. The large-data vignette explains these memory and repeated-read costs, including the Firth calculations reused across passes.
Iteratively reweighted least squares (IRLS) fits a GLM through a sequence of weighted linear regressions. A matrix decomposition arranges each regression calculation so that its coefficients can be solved numerically. fastglm provides six explicit methods through RcppEigen (Bates and Eddelbuettel, 2013), together with an automatic choice:
| Name | Integer | Decomposition or selection rule |
|---|---|---|
"qr" |
0 |
Column-pivoted Householder QR; the default for dense fastglm() fits |
"qr_unpivoted" |
1 |
Householder QR without column pivoting |
"llt" |
2 |
LLT Cholesky |
"ldlt" |
3 |
LDLT Cholesky |
"qr_full" |
4 |
Full-pivoted Householder QR |
"svd" |
5 |
Bidiagonal divide-and-conquer singular value decomposition |
"auto" |
Scale columns, attempt LLT, and fall back to pivoted QR for a problematic dense system |
For dense fits, pivoted QR can identify redundant columns, allowing
aliased coefficients to be reported as NA. Cholesky works with the
smaller predictor crossproduct and can be faster when the design is well
conditioned, meaning that small numerical errors are not strongly
amplified. Explicit Cholesky fits require full column rank and report an
error when the information matrix is singular or ill conditioned. Sparse
and mapped fits use Cholesky methods. Streaming GLMs also support
method="qr", which updates a triangular factor across blocks for
greater stability with nearly redundant predictors. Streaming fits
require full column rank with either method.
fastglm(X, y, family=binomial(), method="qr")
fastglm(X, y, family=binomial(), method="llt")
fastglm(X, y, family=binomial(), method="auto")
For recognized family/link combinations, variance, mean derivatives, inverse links, and deviance contributions are evaluated directly in C++. This reduces repeated calls from the fitting loop back into R. The native combinations are:
| Family | Links |
|---|---|
| Gaussian | identity, log, inverse |
| Binomial | logit, probit, complementary log-log, log |
| Poisson | log, identity, square root |
| Gamma | log, inverse, identity |
| Inverse Gaussian | inverse squared (1/mu^2), log, identity, inverse |
| Negative binomial with fixed theta | log, identity, square root |
| Tweedie with specified variance power | log, identity, inverse, square root |
Quasibinomial and quasipoisson fits share the corresponding mean and variance calculations, with dispersion handled separately. Family detection checks the supplied functions as well as the family name, so a modified family retains its R behavior. Other valid R family/link combinations use the supplied R functions. Firth bias reduction has a narrower supported scope, described in the bias-reduction section and its vignette.
The C++ implementation reduces repeated work in fitting and offers several ways to solve the weighted regressions. The resulting speed advantage depends on the family, the number of rows and columns, the solver, and the requested inference. The following comparison uses a logistic-regression design with 10,000 observations, 100 predictors, and 25 timing repetitions per method.
set.seed(123)
n.obs <- 10000
n.vars <- 100
X <- matrix(rnorm(n.obs*n.vars, sd=3), n.obs, n.vars)
y <- as.numeric(drop(X[,1:25] %*% runif(25, -.1, .1)) > rnorm(n.obs))
# Include coefficient covariance computation in each timed fit.
with_covariance <- function(fit, raw_glm=FALSE) {
if (raw_glm) class(fit) <- c("glm", "lm")
fit$benchmark.covariance <- vcov(fit)
fit
}
microbenchmark::microbenchmark(
glm.fit = with_covariance(glm.fit(X, y, family=binomial(), intercept=FALSE,
control=glm.control(epsilon=1e-10, maxit=100)), raw_glm=TRUE),
fastglm_QR = with_covariance(fastglm(X, y, binomial(), method="qr", tol=1e-10)),
fastglm_LLT = with_covariance(fastglm(X, y, binomial(), method="llt", tol=1e-10)),
fastglm_LDLT = with_covariance(fastglm(X, y, binomial(), method="ldlt", tol=1e-10)),
fastglm_auto = with_covariance(fastglm(X, y, binomial(), method="auto", tol=1e-10)),
times=25L
)
Times are for fastglm 0.1.3 and R version 4.5.1 (2025-06-13) on Darwin (arm64).
Logistic-regression fitting times for
glm.fit and four fastglm solvers. Lower times indicate faster fitting;
the points mark medians.
The table reports median elapsed time and the largest absolute
differences from glm.fit() in coefficients and fitted probabilities.
Times include ordinary covariance calculations.
| Method | Median time (ms) | Max. coefficient difference | Max. fitted-probability difference |
|---|---|---|---|
| glm.fit | 282.64 | 0.00e+00 | 0.00e+00 |
| fastglm QR | 98.77 | 1.30e-15 | 6.22e-15 |
| fastglm LLT | 23.18 | 1.22e-15 | 6.55e-15 |
| fastglm LDLT | 23.55 | 1.22e-15 | 6.44e-15 |
| fastglm auto | 28.98 | 9.44e-16 | 5.22e-15 |
For this design, the median time for glm.fit() was 282.6 ms, compared
with 23.2 ms for LLT. The coefficient and fitted-probability differences
are close to floating-point rounding error, so the fits agree to
numerical precision.
The benchmarks compare scaling across model classes.
Fitting can become difficult when a proposed coefficient update produces invalid fitted means or moves to a poorer likelihood value. For ordinary GLMs, fastglm uses a step-halving safeguard following Marschner (2011): an unsuccessful update is shortened until an acceptable step is found. Its initialization and solver choices also affect the subsequent iterations.
A Gamma model using a square-root link provides one example. The simulated response is highly skewed, and the fitted mean uses a different functional form from the generating model. The calculation below uses the same iteration limit and nominal tolerance for both fitting methods:
set.seed(1)
X_stability <- matrix(rnorm(10000*100), ncol=100)
y_stability <- exp(.25*X_stability[,1] - .25*X_stability[,3] +
.5*X_stability[,4] - .5*X_stability[,5] + rnorm(10000)) + .1
gfit_fast <- fastglm(y_stability ~ X_stability, family=Gamma("sqrt"),
tol=1e-8, maxit=100)
gfit_base <- suppressWarnings(glm(y_stability ~ X_stability,
family=Gamma("sqrt"), control=glm.control(epsilon=1e-8, maxit=100)))
data.frame(method=c("fastglm", "glm"),
converged=c(gfit_fast$converged, gfit_base$converged),
iterations=c(gfit_fast$iter, gfit_base$iter),
log_likelihood=c(as.numeric(logLik(gfit_fast)), as.numeric(logLik(gfit_base))))
#> method converged iterations log_likelihood
#> 1 fastglm TRUE 19 -16046.66
#> 2 glm FALSE 100 -16068.27
fastglm converged in 19 iterations, while glm reached its limit of 100 iterations without convergence.
Convergence status matters when comparing likelihoods or runtimes: an
unfinished fit is not an alternative estimate of the same quality. The
computational stability vignette includes
comparisons with glm2 and speedglm, and fastglm_diagnostics()
reports convergence, the chosen solver, aliased coefficients, and
numerical diagnostics for a fitted model.
These comparisons span model classes and sample sizes: ordinary logistic
and Poisson fits with glm.fit, glm2, and speedglm;
negative-binomial regression with MASS::glm.nb; hurdle and
zero-inflated Poisson models with pscl; and mean bias reduction with
brglm2 using type="AS_mean". Ordinary and Firth GLMs use
method="auto" in fastglm, while the dedicated count-model functions
use their default solvers.
For ordinary logistic and Poisson regression, the plotted reference is the fastest of the three comparison functions at each sample size. The other models use the named reference implementation. The ratio is reference time divided by fastglm time: a value of two means fastglm took half as long. Firth families are shown separately because their computational requirements differ.
Fitting-time ratios across sample sizes,
with separate panels for maximum-likelihood models and Firth mean bias
reduction. Values above one favor fastglm.
At 100,000 observations, using medians of five repetitions:
| Model | Design columns | fastglm (ms) | Reference | Reference (ms) | Time ratio |
|---|---|---|---|---|---|
| Logistic | 30 | 57.17 | speedglm.wfit | 164.04 | 2.87 |
| Poisson | 30 | 62.05 | speedglm.wfit | 221.72 | 3.57 |
| Negative binomial | 4 | 124.87 | MASS::glm.nb | 571.93 | 4.58 |
| Hurdle Poisson | 3 | 42.30 | pscl::hurdle | 597.84 | 14.13 |
| Zero-inflated Poisson | 3 | 313.61 | pscl::zeroinfl | 1209.55 | 3.86 |
| Firth binomial-logit | 6 | 45.28 | brglm2::brglmFit | 81.77 | 1.81 |
| Firth Poisson-log | 6 | 37.01 | brglm2::brglmFit | 157.35 | 4.25 |
| Firth Gamma-log | 6 | 30.86 | brglm2::brglmFit | 1005.47 | 32.58 |
| Firth Gaussian-identity | 6 | 11.43 | brglm2::brglmFit | 184.63 | 16.15 |
At this sample size, the largest time ratio is 32.6 for Firth Gamma-log. Gamma bias-reduction fits combine identical prior-weight contributions in the dispersion equation and reuse matrix calculations that remain constant during fitting.
Speed depends on the design and computing environment. Times include
ordinary covariance calculation, but not profile intervals or bootstrap
refits. GLM and Firth comparisons use matrix interfaces, and both hurdle
and zero-inflated comparisons use formulas. For negative-binomial
regression, fastglm_nb() takes a matrix while MASS::glm.nb() also
constructs its design from a formula. Packages use different convergence
criteria, so every comparison also checks numerical agreement.
The benchmark vignette adds sparse
and mapped-matrix comparisons. Code for all comparisons is in the
benchmarks directory of the
repository.
These comparisons use packages that fit the same cumulative-link models:
rms::orm, ordinal::clm, MASS::polr, VGAM::vglm, and
WeightIt::ordinal_weightit. Each design has 10 predictors, and times
include coefficient covariance. A time is compared only when that fit
converges and agrees with fastglm on the log likelihood and common
slopes.
Ordinal fitting times across sample sizes
for common and threshold-specific slopes. Lower times indicate faster
fitting.
| Link | Method | Seconds | Time ratio |
|---|---|---|---|
| logit | fastglm | 0.067 | 1.0 |
| logit | rms::orm | 0.197 | 2.9 |
| logit | ordinal::clm | 0.690 | 10.3 |
| logit | MASS::polr | 1.405 | 20.9 |
| logit | VGAM::vglm | 1.762 | 26.2 |
| logit | WeightIt::ordinal_weightit | 3.049 | 45.4 |
| probit | fastglm | 0.101 | 1.0 |
| probit | rms::orm | 0.219 | 2.2 |
| probit | ordinal::clm | 0.688 | 6.8 |
| probit | MASS::polr | 1.800 | 17.9 |
| probit | VGAM::vglm | 2.007 | 20.0 |
| probit | WeightIt::ordinal_weightit | 6.429 | 63.9 |
| cloglog | fastglm | 0.100 | 1.0 |
| cloglog | ordinal::clm | 0.763 | 7.7 |
| cloglog | MASS::polr | 1.477 | 14.8 |
| cloglog | VGAM::vglm | 6.579 | NA |
| cloglog | WeightIt::ordinal_weightit | 5.922 | 59.5 |
With 100,000 observations and 5 categories, rms::orm is the closest
comparison, at 2.9 times the fastglm time for logit and 2.2 times for
probit. rms::orm was timed for these two links only. For cloglog,
VGAM::vglm warned about intersecting predictors and returned a
different fit, so it has no ratio.
rms::orm is designed for outcomes with very many distinct values, and
it overtakes fastglm between 30 and 300 categories:
Ordinal fitting times against the number
of outcome categories, with 20,000 observations. Lower times indicate
faster fitting.
| Categories | Method | Seconds | Time ratio |
|---|---|---|---|
| 30 | fastglm | 0.017 | 1.00 |
| 30 | rms::orm | 0.044 | 2.56 |
| 30 | ordinal::clm | 0.649 | 38.06 |
| 30 | MASS::polr | 0.891 | 52.25 |
| 30 | VGAM::vglm | 5.443 | 319.08 |
| 30 | WeightIt::ordinal_weightit | 2.578 | 151.13 |
| 300 | fastglm | 0.098 | 1.00 |
| 300 | rms::orm | 0.049 | 0.50 |
| 300 | ordinal::clm | 41.008 | 418.83 |
| 300 | MASS::polr | 29.603 | 302.35 |
| 300 | WeightIt::ordinal_weightit | 47.976 | 490.00 |
| 1000 | fastglm | 0.316 | 1.00 |
| 1000 | rms::orm | 0.085 | 0.27 |
| 1000 | ordinal::clm | 483.794 | 1530.82 |
| 1000 | MASS::polr | 277.030 | 876.58 |
| 1000 | WeightIt::ordinal_weightit | 330.964 | 1047.24 |
With 20,000 observations, rms::orm takes 0.5 times the fastglm time
at 300 categories and 0.3 times at 1,000, so it is the faster choice in
that range. Most of the fastglm time in that range is the
expected-information calculation behind the reported covariance.
VGAM::vglm was not run above 30 categories because it would need about
160 GB of memory at 1,000.
For threshold-specific slopes with 1,000 observations, fastglm
reported nonconvergence because the best fit with ordered cumulative
probabilities at every row lies on the ordering boundary, so there is no
time comparison at that size. ordinal::clm reported convergence with a
higher log likelihood, but its cumulative predictors cross at 3 of the
1,000 rows, which is not a valid probability model at those rows. At
100,000 observations, ordinal::clm took 78.9 seconds and reported that
it had not converged, although its log likelihood agreed with fastglm,
so it is omitted there as well.
None of these packages fits mean bias-reduced cumulative-link models or reads ordinal data in blocks. The last column compares each streamed fit with the dense fit of the same estimator:
Dense and streamed ordinal fitting times
for maximum likelihood and mean bias reduction. Lower times indicate
faster fitting.
| Observations | Fit | Seconds | Largest coefficient difference from dense fit |
|---|---|---|---|
| 100,000 | Dense ML | 0.068 | 0.0e+00 |
| 100,000 | Dense mean bias reduction | 0.154 | 0.0e+00 |
| 100,000 | Streamed ML | 0.157 | 2.2e-16 |
| 100,000 | Streamed mean bias reduction | 0.362 | 1.1e-16 |
| 1,000,000 | Dense ML | 0.673 | 0.0e+00 |
| 1,000,000 | Dense mean bias reduction | 1.536 | 0.0e+00 |
| 1,000,000 | Streamed ML | 1.570 | 1.1e-16 |
| 1,000,000 | Streamed mean bias reduction | 3.540 | 3.0e-17 |
Streamed fits use blocks of 10,000 rows.
Use citation("fastglm") for the package citation.