{"id":32209978,"url":"https://github.com/jaredhuling/fastglm","last_synced_at":"2026-06-08T00:01:38.179Z","repository":{"id":56936954,"uuid":"107818291","full_name":"jaredhuling/fastglm","owner":"jaredhuling","description":"Fast glm fitting via RcppEigen","archived":false,"fork":false,"pushed_at":"2026-06-02T02:19:36.000Z","size":23735,"stargazers_count":62,"open_issues_count":4,"forks_count":17,"subscribers_count":3,"default_branch":"master","last_synced_at":"2026-06-02T03:20:49.178Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://jaredhuling.org/fastglm/","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jaredhuling.png","metadata":{"files":{"readme":"README.Rmd","changelog":"NEWS.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-10-21T21:48:01.000Z","updated_at":"2026-06-02T02:19:40.000Z","dependencies_parsed_at":"2022-08-21T05:50:48.516Z","dependency_job_id":null,"html_url":"https://github.com/jaredhuling/fastglm","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/jaredhuling/fastglm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredhuling%2Ffastglm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredhuling%2Ffastglm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredhuling%2Ffastglm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredhuling%2Ffastglm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaredhuling","download_url":"https://codeload.github.com/jaredhuling/fastglm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaredhuling%2Ffastglm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34042554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-07T02:00:07.652Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-22T06:22:10.160Z","updated_at":"2026-06-08T00:01:38.163Z","avatar_url":"https://github.com/jaredhuling.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"---\noutput: github_document\n---\n\n\u003c!-- README.md is generated from README.Rmd. Please edit that file --\u003e\n\n```{r, echo = FALSE}\nknitr::opts_chunk$set(\n  collapse = TRUE,\n  warning = FALSE,\n  message = FALSE,\n  tidy = FALSE,\n  fig.align = 'center',\n  comment = \"#\u003e\",\n  fig.path = \"man/figures/README-\"\n)\n```\n\n[![version](http://www.r-pkg.org/badges/version/fastglm)](https://cran.r-project.org/package=fastglm)\n[![downloads](http://cranlogs.r-pkg.org/badges/grand-total/fastglm)](https://cran.r-project.org/package=fastglm)\n\n# fastglm\n\nThe *fastglm* package is a **fast** and **stable** alternative to `stats::glm()` for fitting generalized linear models. It is built on *RcppEigen* and is fully compatible with R's `family` objects: the downstream methods you expect (`summary()`, `vcov()`, `predict()`, `coef()`, `residuals()`, `logLik()`) all work exactly as they do for a `glm`.\n\nBeyond standard GLMs, *fastglm* provides dedicated fitting functions for negative-binomial regression, hurdle and zero-inflated count models, and Firth bias-reduced GLMs, all of which reuse the same C++ IRLS solver.\n\n### Features\n\n- Six decomposition methods for the IRLS weighted least-squares step: column-pivoted QR (default, rank-revealing), unpivoted QR, LLT Cholesky, LDLT Cholesky, full-pivoted QR, and bidiagonal divide-and-conquer SVD.\n- Robust convergence via step-halving safeguard following Marschner (2011), with better-initialized starting values than `glm()` or `glm2()`.\n- Large scale data features: Sparse design matrices (`Matrix::dgCMatrix`), on-disc `big.matrix` objects (`bigmemory`), and a streaming callback interface (`fastglm_streaming()`) for fitting on data that does not fit in memory.\n- Firth bias-reduced GLMs (`firth = TRUE`) implementing the AS_mean adjustment of Kosmidis and Firth (2009, 2021) for all standard families, all six decomposition methods, and all three large-data paths.\n- Negative-binomial regression (`fastglm_nb()`) with joint `(beta, theta)` MLE entirely in C++.\n- Hurdle count models (`fastglm_hurdle()`) with Poisson or NB count components and a binary zero/non-zero component.\n- Zero-inflated count models (`fastglm_zi()`) with Poisson or NB count distributions, fit by an EM algorithm in C++.\n- Inference: `vcov()`, `summary()`, `predict(se.fit = TRUE)`, and compatibility with `sandwich::vcovHC()` and `sandwich::vcovCL()` for robust standard errors.\n\n## Installation\n\nInstall from CRAN:\n\n```{r, eval = FALSE}\ninstall.packages(\"fastglm\")\n```\n\nor the development version from GitHub:\n\n```{r, eval = FALSE}\npak::pak(\"jaredhuling/fastglm\")\n```\n\n## Fitting a GLM\n\nThe main function is `fastglm()`. It takes a numeric design matrix `x`, a response `y`, and an R `family` object:\n\n```{r}\nlibrary(fastglm)\n\ndata(esoph)\nx \u003c- model.matrix(~ agegp + unclass(tobgp) + unclass(alcgp), data = esoph)\ny \u003c- cbind(esoph$ncases, esoph$ncontrols)\n\nfit \u003c- fastglm(x, y, family = binomial(link = \"cloglog\"))\nsummary(fit)\n```\n\n`fastglm()` operates on a pre-built design matrix. To use a formula and a data frame, pass `fastglm_fit` as the fitting function to base `glm()`:\n\n```{r}\nfit2 \u003c- glm(cbind(ncases, ncontrols) ~ agegp + unclass(tobgp) + unclass(alcgp),\n            data = esoph, family = binomial(link = \"cloglog\"),\n            method = fastglm_fit)\n```\n\nA third, minimal-use function, `fastglmPure()`, returns only the coefficient vector and working quantities, skipping dispersion, AIC, and null-deviance computation. Use this when calling *fastglm* from another package and you only need the coefficients.\n\n## Decomposition methods\n\nThe IRLS algorithm reduces every iteration to a weighted least-squares problem. *fastglm* supports six different matrix decompositions for solving that WLS step, all from *RcppEigen* (Bates and Eddelbuettel, 2013); the choice trades off speed against numerical stability and rank-revealing behavior:\n\n| `method` | decomposition |\n|---|---|\n| `0` | column-pivoted Householder QR (default; rank-revealing) |\n| `1` | unpivoted Householder QR |\n| `2` | LLT Cholesky |\n| `3` | LDLT Cholesky |\n| `4` | full-pivoted Householder QR |\n| `5` | bidiagonal divide-and-conquer SVD |\n\nThe default (`method = 0`) is the safe choice: it is rank-revealing, so it handles aliased or collinear columns gracefully. The Cholesky methods (`2` and `3`) are roughly 3--4x faster but assume full column rank.\n\n```{r}\nset.seed(123)\nn \u003c- 5000; p \u003c- 30\nx \u003c- matrix(rnorm(n * p), n, p)\ny \u003c- rbinom(n, 1, plogis(x %*% rnorm(p) * 0.05))\n\nsystem.time(f0 \u003c- fastglm(x, y, family = binomial()))                 # default QR\nsystem.time(f2 \u003c- fastglm(x, y, family = binomial(), method = 2))     # LLT\n```\n\n## Speed\n\n*fastglm* runs the same IRLS algorithm as `glm.fit()` but executes the per-iteration WLS solve in C++ via *RcppEigen*, which is often substantially faster than the compiled-R + LAPACK path that `glm.fit()` uses. The gap widens with sample size because the R-side overhead in `glm.fit()` is fixed per iteration:\n\n```{r glm_bench, echo = TRUE, out.width = \"100%\", fig.width = 9, fig.height = 5}\nlibrary(microbenchmark)\nlibrary(ggplot2)\n\nset.seed(123)\nn.obs  \u003c- 10000\nn.vars \u003c- 100\n\nx \u003c- matrix(rnorm(n.obs * n.vars, sd = 3), n.obs, n.vars)\ny \u003c- 1 * (drop(x[, 1:25] %*% runif(25, -0.1, 0.1)) \u003e rnorm(n.obs))\n\nct \u003c- microbenchmark(\n    glm.fit          = glm.fit(x, y, family = binomial()),\n    fastglm_QR       = fastglm(x, y, family = binomial(), method = 0),\n    fastglm_LLT      = fastglm(x, y, family = binomial(), method = 2),\n    fastglm_LDLT     = fastglm(x, y, family = binomial(), method = 3),\n    times = 25L\n)\n\nautoplot(ct, log = FALSE) +\n    ggplot2::stat_summary(fun = median, geom = 'point', size = 2) +\n    ggplot2::theme_bw()\n```\n\nCoefficient estimates agree with `glm.fit()` to floating-point precision:\n\n```{r}\ngl \u003c- glm.fit(x, y, family = binomial())\nc(fastglm_QR   = max(abs(coef(gl) - coef(fastglm(x, y, family = binomial(), method = 0)))),\n  fastglm_LLT  = max(abs(coef(gl) - coef(fastglm(x, y, family = binomial(), method = 2)))),\n  fastglm_LDLT = max(abs(coef(gl) - coef(fastglm(x, y, family = binomial(), method = 3)))))\n```\n\n## Stability\n\n*fastglm* does not compromise computational stability for speed. It uses a step-halving safeguard following Marschner (2011) and starts from better-initialized values than `glm()` or `glm2::glm2()`, so it tends to converge in cases where the standard IRLS algorithm fails. As an example, consider a Gamma model with a `sqrt` link --- a mild response misspecification combined with a badly misspecified link. In such scenarios the standard IRLS algorithm tends to have convergence issues:\n\n```{r}\nset.seed(1)\nx \u003c- matrix(rnorm(10000 * 100), ncol = 100)\ny \u003c- (exp(0.25 * x[,1] - 0.25 * x[,3] + 0.5 * x[,4] - 0.5 * x[,5] + rnorm(10000))) + 0.1\n\ngfit1 \u003c- glm(y ~ x, family = Gamma(link = \"sqrt\"), method = fastglm_fit)\ngfit2 \u003c- glm(y ~ x, family = Gamma(link = \"sqrt\"))\n\n## fastglm converges with a higher likelihood\nc(fastglm_converged = gfit1$converged, glm_converged = gfit2$converged)\nc(fastglm_logLik = logLik(gfit1), glm_logLik = logLik(gfit2))\n```\n\nSee `vignette(\"fastglm\", package = \"fastglm\")` for the full comparison, including `glm2::glm2()` and `speedglm`.\n\n## Native C++ families\n\nFor the most commonly used `family`/`link` combinations, *fastglm* dispatches `variance()`, `mu.eta()`, `linkinv()`, and `dev.resids()` to inline C++ implementations rather than calling back into R once per IRLS iteration. The covered combinations are:\n\n- gaussian (identity, log, inverse)\n- binomial (logit, probit, cloglog, log)\n- poisson (log, identity, sqrt)\n- Gamma (log, inverse, identity)\n- inverse.gaussian (1/mu^2, log, identity, inverse)\n\nDetection is automatic: if the `family` object matches one of the above, the native path is used; otherwise *fastglm* falls back to the R-callback path. The C++ native approach is meaningfully faster on large `n` because it eliminates the per-iteration calls to R for each of the four family functions.\n\n## Sparse, big.matrix, and streaming designs\n\nFor designs that are sparse, that live on disc, or that have to be built from a parquet / *arrow* / *DuckDB* source, *fastglm* provides three large-data paths that share a common streaming kernel and produce identical results:\n\n- **`Matrix::dgCMatrix`**: pass directly to `fastglm()`. Useful for one-hot encoded categoricals and high-dimensional sparse designs.\n- **`bigmemory::big.matrix`**: pass directly to `fastglm()`. The matrix is read in row-blocks and never fully materialized in memory.\n- **`fastglm_streaming(chunk_callback, n_chunks, family)`**: a user-supplied closure yields one row-block per call. The right path for fitting on a parquet dataset, *DuckDB* query, or any external columnar store.\n\nA short example of the streaming computation approach:\n\n```{r}\nn \u003c- 4000\nX \u003c- cbind(1, matrix(rnorm(n * 3), n, 3))\ny \u003c- rbinom(n, 1, plogis(X %*% c(0.2, 0.4, -0.2, 0.3)))\n\nchunk_size \u003c- 1000\nchunks \u003c- function(k) {\n    idx \u003c- ((k - 1) * chunk_size + 1):(k * chunk_size)\n    list(X = X[idx, , drop = FALSE], y = y[idx])\n}\n\nfit_stream \u003c- fastglm_streaming(chunks, n_chunks = 4, family = binomial())\nfit_full   \u003c- fastglm(X, y, family = binomial(), method = 2)\n\nmax(abs(coef(fit_stream) - coef(fit_full)))\n```\n\nSee `vignette(\"large-data-fastglm\", package = \"fastglm\")` for a detailed walk-through of all three paths.\n\n## Extended models\n\n### Negative-binomial regression\n\n`fastglm_nb()` fits negative-binomial regression with the dispersion `theta` estimated jointly with the regression coefficients, in the spirit of `MASS::glm.nb()`. The joint `(beta, theta)` MLE runs entirely in C++; IRLS for `beta`, Brent's method for `theta`:\n\n```{r}\nset.seed(123)\nn \u003c- 5000\nX \u003c- cbind(1, matrix(rnorm(n * 3), n, 3))\nmu \u003c- exp(X %*% c(0.5, 0.4, -0.2, 0.3))\ny \u003c- MASS::rnegbin(n, mu = mu, theta = 2)\n\nf_nb \u003c- fastglm_nb(X, y)\nc(coef = coef(f_nb), theta = f_nb$theta)\n```\n\n### Hurdle models\n\n`fastglm_hurdle()` fits a two-part count model: a binary regression for whether `y \u003e 0`, plus a zero-truncated Poisson or NB regression on the positive subset. The two parts factorize and both are fit by the same C++ IRLS solver. This is the same model as `pscl::hurdle()` (Zeileis, Kleiber, and Jackman, 2008). Different designs for the count and zero parts are specified via the `Formula` package's two-RHS syntax:\n\n```{r}\nset.seed(123)\nn \u003c- 5000\nx1 \u003c- rnorm(n);  x2 \u003c- rnorm(n)\nlam \u003c- exp(0.7 + 0.4 * x1 - 0.3 * x2)\nis_pos \u003c- rbinom(n, 1, plogis(-0.4 + 0.5 * x1 + 0.2 * x2))\nyt \u003c- integer(n)\nfor (i in seq_len(n)) {\n    repeat { v \u003c- rpois(1, lam[i]); if (v \u003e 0) { yt[i] \u003c- v; break } }\n}\ny \u003c- ifelse(is_pos == 1, yt, 0L)\n\nf_h \u003c- fastglm_hurdle(y ~ x1 + x2, data = data.frame(y, x1, x2), dist = \"poisson\")\ncoef(f_h)\n```\n\n### Zero-inflated models\n\n`fastglm_zi()` fits a zero-inflated Poisson or NB regression, a binary inflation component overlaid on the original count distribution, fit by an EM algorithm in C++ with closed-form posterior responsibilities and an analytical observed-information `vcov`. This is the same model as `pscl::zeroinfl()`:\n\n```{r}\nset.seed(123)\nn \u003c- 5000\nx1 \u003c- rnorm(n);  x2 \u003c- rnorm(n)\neta_c \u003c- 0.7 + 0.4 * x1 - 0.3 * x2\neta_z \u003c- -0.4 + 0.5 * x1 + 0.2 * x2\nz \u003c- rbinom(n, 1, plogis(eta_z))\ny \u003c- ifelse(z == 1, 0L, rpois(n, exp(eta_c)))\n\nf_zi \u003c- fastglm_zi(y ~ x1 + x2, data = data.frame(y, x1, x2), dist = \"poisson\")\ncoef(f_zi)\n```\n\n### Firth bias-reduced GLMs\n\nSetting `firth = TRUE` activates the general mean-bias reduction of Kosmidis and Firth (2009, 2021). This extends Firth's (1993) original logistic penalty to arbitrary GLM families, producing finite estimates even under separation and removing the leading $O(1/n)$ bias from maximum likelihood estimates:\n\n```{r}\ndata(sex2, package = \"logistf\")\nX \u003c- model.matrix(~ age + oc + vic + vicl + vis + dia, data = sex2)\ny \u003c- sex2$case\n\nf_firth \u003c- fastglm(X, y, family = binomial(), firth = TRUE)\ncoef(f_firth)\n```\n\nFirth bias reduction works with all six decomposition methods, all standard R families and link functions, and all three large-data paths (sparse, big.matrix, streaming). See `vignette(\"firth-fastglm\", package = \"fastglm\")` for verification against `logistf::logistf()` and `brglm2::brglmFit()`.\n\n## Inference\n\nThe fitted object stores the unscaled covariance directly, so `vcov()` and `summary()` work as expected. Heteroskedasticity-consistent and cluster-robust covariance matrices are available via `sandwich::vcovHC()` and `sandwich::vcovCL()`, *fastglm* registers methods on those generics, so loading *sandwich* is all that is required:\n\n```{r, eval = FALSE}\nlibrary(sandwich)\nV_hc \u003c- vcovHC(fit, type = \"HC0\")\nV_cl \u003c- vcovCL(fit, cluster = cluster, type = \"HC1\")\n```\n\nResults are numerically identical to `sandwich` applied to a `glm` fit to floating-point precision. `predict()` supports `se.fit = TRUE`:\n\n```{r, eval = FALSE}\npredict(fit, newdata = xnew, type = \"response\", se.fit = TRUE)\n```\n\n## Benchmarks\n\nA comprehensive benchmarking study is available in `vignette(\"benchmarks-fastglm\", package = \"fastglm\")`, comparing *fastglm* against the canonical reference implementations across standard GLMs (`glm.fit`, `glm2`, `speedglm`), negative-binomial regression (`MASS::glm.nb`), Firth bias-reduced GLMs (`brglm2`, `logistf`), and hurdle / zero-inflated count regressions (`pscl::hurdle`, `pscl::zeroinfl`).\n\nThe following summary plot shows the speedup *fastglm* delivers over the canonical reference for each model class, as a function of sample size. The reference for the standard GLMs is the fastest among `glm.fit`, `glm2`, and `speedglm`, so the comparison is conservative. Larger is better:\n\n![Speedup of fastglm vs canonical reference implementations across model classes](vignettes/benchmarks-figs/unnamed-chunk-18-1.png)\n\nAcross all model classes the same picture holds: *fastglm* matches the canonical reference implementation to floating-point precision, and the runtime gap grows with sample size. By $n = 10^5$ the speedup is generally an order of magnitude or more. For models with an outer iteration (NB joint MLE, hurdle/ZI with NB), the gap is widest, since the entire outer loop is in C++ in *fastglm* and entirely in R in the reference implementations.\n\n## References\n\n- Firth, D. (1993). Bias reduction of maximum likelihood estimates. *Biometrika*, 80(1), 27--38.\n\n- Kosmidis, I. and Firth, D. (2009). Bias reduction in exponential family nonlinear models. *Biometrika*, 96(4), 793--804.\n\n- Kosmidis, I. and Firth, D. (2021). Jeffreys-prior penalty, finiteness and shrinkage in binomial-response generalized linear models. *Biometrika*, 108(1), 71--82.\n\n- Marschner, I. C. (2011). glm2: Fitting generalized linear models with convergence problems. *The R Journal*, 3(2), 12--15.\n\n- Bates, D. and Eddelbuettel, D. (2013). Fast and elegant numerical linear algebra using the RcppEigen package. *Journal of Statistical Software*, 52(5), 1--24.\n\n- Zeileis, A., Kleiber, C., and Jackman, S. (2008). Regression models for count data in R. *Journal of Statistical Software*, 27(8), 1--25.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaredhuling%2Ffastglm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaredhuling%2Ffastglm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaredhuling%2Ffastglm/lists"}