
# funcml

[![R-CMD-check](https://github.com/ielbadisy/funcml/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/ielbadisy/funcml/actions/workflows/R-CMD-check.yaml)
[![CRAN
status](https://www.r-pkg.org/badges/version/funcml)](https://CRAN.R-project.org/package=funcml)
[![CRAN
checks](https://badges.cranchecks.info/worst/funcml.svg)](https://cran.r-project.org/web/checks/check_results_funcml.html)
[![CRAN
downloads](https://cranlogs.r-pkg.org/badges/grand-total/funcml)](https://cran.r-project.org/package=funcml)
[![License:
GPL-3](https://img.shields.io/badge/license-GPL--3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![CRAN Task View: Machine
Learning](https://img.shields.io/badge/CRAN%20Task%20View-Machine%20Learning-2c7fb8)](https://CRAN.R-project.org/view=MachineLearning)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.20707605.svg)](https://doi.org/10.5281/zenodo.20707605)

`funcml` is a functional machine learning framework for tabular data in
R.

`funcml` is listed in the [CRAN Task View: Machine Learning &
Statistical Learning](https://CRAN.R-project.org/view=MachineLearning).

It provides one explicit interface for the core modeling workflow:

- fit models with `fit()`
- generate predictions with `predict()`
- validate performance with `evaluate()`
- tune hyperparameters with `tune()`
- compare learners with `compare()`
- interpret fitted models with `interpret()`
- estimate causal effects with `estimate()`

The package is intentionally compact and opinionated: preprocessing
happens before modeling, inputs stay explicit, and the API stays small
instead of expanding into a large orchestration layer.

A companion paper for `funcml` is submitted to JMLR.

## Installation

``` r
install.packages("funcml")

install.packages("remotes")
remotes::install_github("ielbadisy/funcml")
```

## Core API

The design of `funcml` centers on a small set of functions:

``` r
fit()
predict()
evaluate()
tune()
compare()
interpret()
estimate()
```

## Explore the registry

`funcml` exposes a session-aware registry of learners, metrics, and
interpretation methods.

``` r
list_learners()
#>         learner   fit   predict   tune has_fit has_predict has_tune available
#> 17     adaboost fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 24         bart fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 11          C50 fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 20      cforest fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 19        ctree fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 7      densemlp fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 8     e1071_svm fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 13        earth fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 16          fda fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 14          gam fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 10          gbm fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 1           glm fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 3        glmnet fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 12         kknn fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 21          lda fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 23     lightgbm fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 6           mlp fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 15   naivebayes fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 5          nnet fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 18          pls fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 22          qda fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 9  randomForest fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 4        ranger fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 2         rpart fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 26     stacking fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 27 superlearner fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 25      xgboost fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
```

``` r
list_tunable_learners()
#>         learner   fit   predict   tune has_fit has_predict has_tune available
#> 17     adaboost fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 24         bart fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 11          C50 fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 20      cforest fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 19        ctree fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 7      densemlp fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 8     e1071_svm fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 13        earth fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 16          fda fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 14          gam fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 10          gbm fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 1           glm fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 3        glmnet fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 12         kknn fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 21          lda fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 23     lightgbm fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 6           mlp fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 15   naivebayes fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 5          nnet fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 18          pls fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 22          qda fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 9  randomForest fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 4        ranger fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 2         rpart fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 26     stacking fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 27 superlearner fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
#> 25      xgboost fit() predict() tune()    TRUE        TRUE     TRUE      TRUE
```

``` r
list_metrics()
#>               metric direction
#> 1               rmse  minimize
#> 2                mae  minimize
#> 3                mse  minimize
#> 4              medae  minimize
#> 5               mape  minimize
#> 6                rsq  maximize
#> 7           accuracy  maximize
#> 8          precision  maximize
#> 9             recall  maximize
#> 10       specificity  maximize
#> 11                f1  maximize
#> 12 balanced_accuracy  maximize
#> 13           logloss  minimize
#> 14             brier  minimize
#> 15               auc  maximize
#> 16      auc_weighted  maximize
#> 17               ece  minimize
#> 18               mce  minimize
#>                                                       summary     range
#> 1         Root mean squared error for regression predictions.  [0, Inf)
#> 2             Mean absolute error for regression predictions.  [0, Inf)
#> 3              Mean squared error for regression predictions.  [0, Inf)
#> 4           Median absolute error for regression predictions.  [0, Inf)
#> 5  Mean absolute percentage error for regression predictions.  [0, Inf)
#> 6    Coefficient of determination for regression predictions. (-Inf, 1]
#> 7                                    Classification accuracy.    [0, 1]
#> 8                    Macro-averaged classification precision.    [0, 1]
#> 9                       Macro-averaged classification recall.    [0, 1]
#> 10                 Macro-averaged classification specificity.    [0, 1]
#> 11                                   Macro-averaged F1 score.    [0, 1]
#> 12                          Macro-averaged balanced accuracy.    [0, 1]
#> 13  Negative log-likelihood for classification probabilities.  [0, Inf)
#> 14              Brier score for classification probabilities.    [0, 2]
#> 15                                  Area under the ROC curve.    [0, 1]
#> 16              Weighted multiclass area under the ROC curve.    [0, 1]
#> 17      Expected calibration error for binary classification.    [0, 1]
#> 18       Maximum calibration error for binary classification.    [0, 1]
```

``` r
list_interpretability_methods()
#>                                  compute   plot has_compute has_plot
#> 1              interpret(method = "vip") plot()        TRUE     TRUE
#> 2          interpret(method = "permute") plot()        TRUE     TRUE
#> 3              interpret(method = "pdp") plot()        TRUE     TRUE
#> 4              interpret(method = "ice") plot()        TRUE     TRUE
#> 5              interpret(method = "ale") plot()        TRUE     TRUE
#> 6            interpret(method = "local") plot()        TRUE     TRUE
#> 7             interpret(method = "lime") plot()        TRUE     TRUE
#> 8             interpret(method = "shap") plot()        TRUE     TRUE
#> 9      interpret(method = "local_model") plot()        TRUE     TRUE
#> 10     interpret(method = "interaction") plot()        TRUE     TRUE
#> 11       interpret(method = "surrogate") plot()        TRUE     TRUE
#> 12         interpret(method = "profile") plot()        TRUE     TRUE
#> 13 interpret(method = "ceteris_paribus") plot()        TRUE     TRUE
#> 14     interpret(method = "calibration") plot()        TRUE     TRUE
```

## Example data

This README uses `funcml::arthritis` as the main running example.

Here, `status` is the outcome for a binary classification task.

``` r
demo_dat <- funcml::arthritis
demo_dat$status <- as.factor(demo_dat$status)
levels(demo_dat$status)
#> [1] "No"  "Yes"
```

## Fit a classification model

`fit()` trains a model and returns a `funcml_fit` object.

``` r
xgb_spec <- list(
  nrounds = 30,
  max_depth = 3,
  eta = 0.1,
  subsample = 1,
  colsample_bytree = 1
)

fit_obj <- fit(
  status ~ age + gender + bmi + diabetes + smoke + covered_health,
  data = demo_dat,
  model = "xgboost",
  spec = xgb_spec,
  seed = 42
)

fit_obj
#> <funcml_fit> classification model: xgboost
#> Formula: status ~ age + gender + bmi + diabetes + smoke + covered_health
#> Features: 6 | Obs: 4856
```

## Generate predictions

The same fitted object can produce class predictions or class
probabilities.

``` r
predict(fit_obj, demo_dat[1:6, ])
#> [1] Yes No  No  No  No  No 
#> Levels: No Yes
```

``` r
pred_prob <- predict(
  fit_obj,
  demo_dat[1:6, ],
  type = "prob"
)

pred_prob
#>             No        Yes
#> [1,] 0.4382392 0.56176078
#> [2,] 0.5414010 0.45859897
#> [3,] 0.8288925 0.17110750
#> [4,] 0.9638367 0.03616334
#> [5,] 0.5076765 0.49232352
#> [6,] 0.5064105 0.49358952
```

## Evaluate predictive performance

`evaluate()` applies the same learner under a resampling plan and
returns fold-level results with summary statistics.

``` r
eval_obj <- evaluate(
  data = demo_dat,
  formula = status ~ age + gender + bmi + diabetes + smoke + covered_health,
  model = "xgboost",
  spec = xgb_spec,
  resampling = cv(v = 4, seed = 42)
)

eval_obj
#> <funcml_eval> model: xgboost | task: classification
#>               metric   mean     sd n std_error conf_level conf_low conf_high
#> 1           accuracy 0.7473 0.0146 4    0.0073       0.95   0.7241    0.7706
#> 2          precision 0.6932 0.0234 4    0.0117       0.95   0.6559    0.7304
#> 3             recall 0.6405 0.0178 4    0.0089       0.95   0.6121    0.6689
#> 4        specificity 0.6405 0.0178 4    0.0089       0.95   0.6121    0.6689
#> 5                 f1 0.6657 0.0195 4    0.0098       0.95   0.6347    0.6968
#> 6  balanced_accuracy 0.6405 0.0178 4    0.0089       0.95   0.6121    0.6689
#> 7            logloss 0.4925 0.0088 4    0.0044       0.95   0.4785    0.5065
#> 8              brier 0.3278 0.0076 4    0.0038       0.95   0.3158    0.3398
#> 9                auc 0.7869 0.0118 4    0.0059       0.95   0.7681    0.8058
#> 10               ece 0.0359 0.0042 4    0.0021       0.95   0.0292    0.0426
#> 11               mce 0.0800 0.0137 4    0.0068       0.95   0.0583    0.1018
```

``` r
plot(eval_obj)
```

<img src="man/figures/README-evaluate-plot-1.png" alt="" width="100%" />

`funcml` also supports grouped cross-validation, time-based resampling,
and holdout validation through `group_cv()`, `time_cv()`, and
`holdout()`.

## Tune hyperparameters

`tune()` searches candidate hyperparameter settings using the same
evaluation framework.

``` r
tune_grid <- expand.grid(
  max_depth = c(2, 3),
  eta = c(0.05, 0.1),
  nrounds = c(20, 30)
)

tune_obj <- tune(
  data = demo_dat,
  formula = status ~ age + gender + bmi + diabetes + smoke + covered_health,
  model = "xgboost",
  grid = tune_grid,
  resampling = cv(v = 3, seed = 42),
  metric = "logloss",
  subsample = 1,
  colsample_bytree = 1,
  seed = 42
)

tune_obj
#> <funcml_tune> metric=logloss direction=min search=grid
#> Best:
#>   max_depth eta nrounds   mean     sd n std_error conf_level conf_low conf_high
#> 8         3 0.1      30 0.4933 0.0119 3    0.0069       0.95   0.4636    0.5229
```

``` r
plot(tune_obj)
```

<img src="man/figures/README-tune-plot-1.png" alt="" width="100%" />

## Compare learners

`compare()` benchmarks multiple learners under a common resampling
design.

``` r
compare_obj <- compare(
  data = demo_dat,
  formula = status ~ age + gender + bmi + diabetes + smoke + covered_health,
  models = c("glm", "rpart", "xgboost"),
  metrics = c("accuracy", "logloss"),
  resampling = cv(v = 4, seed = 42),
  specs = list(xgboost = xgb_spec)
)

compare_obj
#> <funcml_compare> task: classification | tuned: FALSE
#>     model   metric   mean     sd n std_error conf_level conf_low conf_high
#> 1     glm accuracy 0.7451 0.0170 4    0.0085       0.95   0.7180    0.7722
#> 2     glm  logloss 0.4897 0.0131 4    0.0066       0.95   0.4688    0.5106
#> 3   rpart accuracy 0.7337 0.0167 4    0.0084       0.95   0.7071    0.7603
#> 4   rpart  logloss 0.5311 0.0130 4    0.0065       0.95   0.5104    0.5517
#> 5 xgboost accuracy 0.7473 0.0146 4    0.0073       0.95   0.7241    0.7706
#> 6 xgboost  logloss 0.4925 0.0088 4    0.0044       0.95   0.4785    0.5065
#>   tuned rank
#> 1 FALSE    2
#> 2 FALSE    1
#> 3 FALSE    3
#> 4 FALSE    3
#> 5 FALSE    1
#> 6 FALSE    2
```

``` r
plot(compare_obj)
```

<img src="man/figures/README-compare-plot-1.png" alt="" width="100%" />

## Interpret fitted models

`interpret()` operates directly on fitted `funcml_fit` objects.

``` r
permute_obj <- interpret(
  fit = fit_obj,
  data = demo_dat,
  method = "permute",
  nsim = 20,
  seed = 42
)

summary(permute_obj)
#>          feature   importance      std_dev
#> 1            age 0.0768121911 0.0039101175
#> 2         gender 0.0192030478 0.0032380942
#> 3            bmi 0.0175658979 0.0038212586
#> 4 covered_health 0.0056013180 0.0021176594
#> 5          smoke 0.0053953871 0.0017159311
#> 6       diabetes 0.0005663097 0.0003398597
```

``` r
plot(permute_obj)
```

<img src="man/figures/README-interpret-permute-plot-1.png" alt="" width="100%" />

A second example shows accumulated local effects for one feature from
the same fitted model.

``` r
ale_obj <- interpret(
  fit = fit_obj,
  data = demo_dat,
  method = "ale",
  features = c("age"),
  type = "prob"
)

plot(ale_obj)
```

<img src="man/figures/README-interpret-ale-1.png" alt="" width="100%" />

Partial dependence shows the model-implied effect of one feature,
averaged over the rest of the data.

``` r
pdp_obj <- interpret(
  fit = fit_obj,
  data = demo_dat,
  method = "pdp",
  features = c("age"),
  type = "prob"
)

plot(pdp_obj)
```

<img src="man/figures/README-interpret-pdp-1.png" alt="" width="100%" />

SHAP values explain an individual prediction.
`interpret(method = "shap")` computes them with funcml’s own Monte Carlo
permutation estimate (no external SHAP dependency); `plot()` supports
waterfall, force, beeswarm/summary, importance, dependence,
dependence2d, and interaction views.

``` r
shap_obj <- interpret(
  fit = fit_obj,
  data = demo_dat,
  method = "shap",
  newdata = demo_dat[1, , drop = FALSE],
  nsim = 30,
  type = "prob",
  seed = 42
)

plot(shap_obj, kind = "waterfall")
```

<img src="man/figures/README-interpret-shap-1.png" alt="" width="100%" />

The beeswarm/summary view explains the model across many observations at
once, ordered by mean absolute SHAP value and colored by feature value.

``` r
shap_many_obj <- interpret(
  fit = fit_obj,
  data = demo_dat,
  method = "shap",
  newdata = demo_dat[1:80, , drop = FALSE],
  nsim = 15,
  type = "prob",
  seed = 42
)

plot(shap_many_obj, kind = "beeswarm")
```

<img src="man/figures/README-interpret-shap-beeswarm-1.png" alt="" width="100%" />

Other supported methods include ICE, local explanations, surrogate
models, and interaction diagnostics.

## Inspect calibration

For classification, the same interface also supports calibration
diagnostics.

``` r
calibration_obj <- interpret(
  fit = fit_obj,
  data = demo_dat,
  method = "calibration",
  type = "prob",
  bins = 10,
  strategy = "quantile"
)

plot(calibration_obj)
```

<img src="man/figures/README-calibration-1.png" alt="" width="100%" />

## Decision curve analysis

`interpret(method = "dca")` reports net benefit across risk thresholds
against the “treat all” and “treat none” strategies (Vickers and Elkin,
2006).

``` r
dca_obj <- interpret(
  fit = fit_obj,
  data = demo_dat,
  method = "dca"
)

plot(dca_obj)
```

<img src="man/figures/README-interpret-dca-1.png" alt="" width="100%" />

## ROC curve and AUC confidence interval

`roc_curve()` and `auc_ci()` are standalone metric functions (backed by
`pROC`) for a full ROC curve and a DeLong or bootstrap confidence
interval on AUC.

``` r
truth_vec <- demo_dat$status
prob_vec <- predict(fit_obj, demo_dat, type = "prob")[, "Yes"]

roc_obj <- roc_curve(truth_vec, prob_vec)
plot(roc_obj)
```

<img src="man/figures/README-roc-curve-1.png" alt="" width="100%" />

``` r
auc_ci(truth_vec, prob_vec)
#>      auc conf_low conf_high conf_level method
#> 1 0.8056   0.7929    0.8183       0.95 delong
```

## Estimate causal effects

`estimate()` extends the same framework to plug-in g-computation
estimands such as the ATE.

The example below treats `smoke` as the treatment variable and `status`
as the outcome, adjusting for the remaining covariates.

``` r
est_obj <- estimate(
  data = demo_dat,
  formula = status ~ smoke + diabetes + age + gender + bmi + covered_health,
  model = "glm",
  estimand = "ATE",
  treatment = "smoke",
  interval = "normal",
  seed = 42
)

est_obj
#> <funcml_estimand> ATE via g-computation
#> Treatment: smoke (Yes vs No)
#> Estimate: 0.0897 | SE: 0.0006 | 95% normal CI [0.0886, 0.0908]
```

By default, `plot()` shows the model-implied potential outcome
distributions under treatment and control, with dashed lines marking the
corresponding means. The unit-level effect histogram remains available
with `plot(est_obj, style = "effects")`.

``` r
plot(est_obj)
```

<img src="man/figures/README-estimate-plot-1.png" alt="" width="100%" />

The same interface also supports `ATT`, `CATE`, and `IATE`.

## Ensembles as first-class learners

Ensembles live in the same learner registry as base models.

``` r
stack_fit <- fit(
  status ~ age + gender + bmi + diabetes + smoke + covered_health,
  data = demo_dat,
  model = "superlearner", # or "stacking"
  spec = list(
    learners = c("glm", "rpart", "xgboost", "nnet"),
    learner_specs = list(xgboost = xgb_spec),
    meta_model = "glmnet"
  ),
  seed = 42
)

predict(stack_fit, demo_dat[1:5, ], type = "prob")
#>             No        Yes
#> [1,] 0.3184784 0.68152164
#> [2,] 0.5011472 0.49885283
#> [3,] 0.8758539 0.12414610
#> [4,] 0.9258312 0.07416884
#> [5,] 0.4985016 0.50149842
```

## Summary

`funcml` provides a compact interface for tabular machine learning in R.

Use it to:

- train models
- generate predictions
- validate performance
- tune hyperparameters
- compare learners
- interpret fitted models
- estimate causal effects

The package is designed to keep the main analysis workflow explicit.

## Contributing

Contributions are welcome.

For development setup, coding standards, and pull request guidelines,
see `CONTRIBUTING.md`.

## Citation

If you use `funcml` in your work, please cite it.

``` r
pkgload::unload("funcml")
library(funcml)
citation("funcml")
#> To cite package 'funcml' in publications use:
#> 
#>   El Badisy I (2026). _funcml: Functional Machine Learning Framework_.
#>   R package version 0.8.9, <https://github.com/ielbadisy/funcml>.
#> 
#> A BibTeX entry for LaTeX users is
#> 
#>   @Manual{,
#>     title = {funcml: Functional Machine Learning Framework},
#>     author = {Imad {El Badisy}},
#>     year = {2026},
#>     note = {R package version 0.8.9},
#>     url = {https://github.com/ielbadisy/funcml},
#>   }
```
