glmtree builds decision trees with a logistic regression at each leaf: the tree cuts the population into segments, and each segment gets its own, interpretable, scoring function. The segmentation is searched jointly with the regressions by a stochastic EM algorithm, as described in Formalization and study of statistical problems in Credit Scoring, Ehrhardt A. (see the manuscript or the web article).
Status. The package was archived on CRAN on 2025-10-03 (a vignette needed a suggested package that was missing on one check flavor) and a re-submission is in preparation. Meanwhile, install it from GitHub.
Installation
# install.packages("pak")
pak::pak("adimajo/glmtree")Example
library(glmtree)
set.seed(1)
data <- generateData(n = 2000, scenario = "tree") # 3 segments, 3 different regressions
features <- c("x1", "x2", "x3")
fit <- glmtree(data[, features], data$y, K = 5, iterations = 100,
ctree_controls = partykit::ctree_control(alpha = 0.1, minbucket = 100, maxdepth = 4))
fit # number of segments, criterion, the tree
predict(fit, data[1:5, features]) # probabilities
predict(fit, data[1:5, features], type = "segment") # segments
fit@best.tree$glms # the logistic regression of each segmentpartykit also exports a function called glmtree() (model-based recursive partitioning). If you attach both packages, call ours as glmtree::glmtree(). See vignette("glmtree") for the interface (validation, test, criterion, burn_in, patience, …).
What to expect: measured results
The numbers below come from inst/benchmarks/real_data.R (20 random stratified 70/30 splits per dataset, 100 SEM iterations, K = 5; the full table and the raw results are in inst/benchmarks/results/). They are test-set Gini coefficients (2 AUC - 1, higher is better), means over the splits. Tree-based methods use leaves of at least 5% of the training data and depth 4. glmtree selects the tree with the BIC on the training data, glmtree (valid.) uses 25% of the training data as a validation set and the Gini. MOB is partykit::glmtree(), the model-based recursive partitioning, with all features both as regressors and as partitioning variables.
| Dataset | logit | ctree |
MOB | random forest | glmtree |
glmtree (valid.) |
|---|---|---|---|---|---|---|
| Simulated, 3 segments (n = 3000) | 0.248 | 0.568 | 0.698 | 0.657 | 0.650 | 0.644 |
| Simulated, 1 regression (n = 3000) | 0.672 | 0.619 | 0.672 | 0.606 | 0.672 | 0.670 |
| German credit (1000 × 20) | 0.552 | 0.439 | 0.436 | 0.579 | 0.514 | 0.393 |
| Pima diabetes (532 × 7) | 0.704 | 0.591 | 0.704 | 0.665 | 0.697 | 0.633 |
ISLR Default (10000 × 3) |
0.896 | 0.867 | 0.896 | 0.849 | 0.894 | 0.893 |
AER CreditCard (1319 × 10) |
0.670 | 0.608 | 0.654 | 0.692 | 0.667 | 0.668 |
ISLR Caravan (5822 × 85) |
0.439 | 0.400 | 0.439 | 0.426 | 0.413 | 0.379 |
What this says, honestly:
-
glmtreeis not a general-purpose accuracy booster. It helps when the population really is made of segments with different regressions: on the simulation with three segments it goes from 0.248 (one logistic regression) to 0.650, using 4.1 segments on average (three are true). On the five real datasets no single-tree method (ctree, MOB,glmtree) beats a plain logistic regression:glmtreeis between 0.002 and 0.038 below it, because the data are close to a single regression and the chain mostly keeps one or two segments (1.2 to 1.7 on average). Only the random forest is ahead, on German credit (+0.027 ± 0.007, paired mean ± standard error) andCreditCard(+0.022 ± 0.006). -
partykit::glmtree()(MOB) is the stronger and faster alternative on these data. On the simulation with segments MOB scores higher thanglmtree(0.698 against 0.650) and takes 1.4 s per fit against 11 s. On the real data sets MOB either does not split (it is then identical to the logit: Pima,Default,Caravan) or loses (German credit 0.436,CreditCard0.654), whereglmtreestays close to the logit.glmtreeis neither faster nor more accurate than MOB in this benchmark; what it offers is a different, likelihood-based search (a latent segment variable), a choice of criterion (BIC, AIC, held-out Gini) andctreefor the segmentation. - The validation set version is the worse choice on small samples (German credit 0.393, Pima 0.633): holding out data makes both the fit and the selection noisy. Prefer the BIC on the training data unless the sample is large.
-
Cost. A fit of 100 iterations takes 3 to 18 s on the data sets above (5 s for 1319 observations, 18 s for 7000 training observations; 160 s for the 85 features of
Caravan), measured with six fits running in parallel. About 70% of the time is spent inpartykit::ctree, which is refitted at every iteration. Compared with this package’s own version 0.3.1, the rewrite of version 0.4.0 made an iteration about twice as fast for the same accuracy (2.4×, 2.2× and 2.0× for 1000, 3000 and 10000 observations, 50 iterations;inst/benchmarks/speed.R). -
Convergence is slow in iterations. On the simulation with segments, the test Gini rises with the iteration budget (BIC on the training data, mean over 12 repetitions): 0.639 (25 iterations), 0.645 (50), 0.652 (100), 0.658 (200), 0.664 (400), 0.676 (800), and the best iteration is still far into the chain (349 on average for 800). With
patience = 25the chain stops after about 51 iterations in about 5 s at a Gini of 0.646, against 75 s for 800 iterations: usepatiencefor a quick answer, and a largeiterationsif the extra 0.03 Gini matters (inst/benchmarks/convergence.R).
Reproduce with Rscript inst/benchmarks/real_data.R 20 100 and Rscript inst/benchmarks/convergence.R (they need partykit, ranger, scorecard, ISLR, AER; about an hour).
Documentation
- Help pages and vignette:
?glmtree,vignette("glmtree"). - Online: https://adimajo.github.io/glmtree.
- Questions and bug reports: https://github.com/adimajo/glmtree/issues.