---
title: "RuleFit, using xrf"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{RuleFit, using xrf}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
if (rlang::is_installed("xrf")) {
  library(tidypredict)
  library(xrf)
  library(dplyr)
  eval_code <- TRUE
} else {
  eval_code <- FALSE
}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = eval_code
)
set.seed(7)
```

| Function                                                      |Works|
|---------------------------------------------------------------|-----|
|`tidypredict_fit()`, `tidypredict_sql()`, `parse_model()`      |  ✔  |
|`tidypredict_to_column()`                                      |  ✔  |
|`tidypredict_test()`                                           |  ✔  |
|`tidypredict_interval()`, `tidypredict_sql_interval()`         |  ✗  |
|`parsnip`                                                      |  ✔  |

## How it works

A RuleFit model is a regularized linear model over two kinds of terms: rules
extracted from a boosted tree ensemble, and the original predictors entered
linearly. Both kinds translate cleanly, so the whole model becomes a single
formula.

```{r}
library(xrf)
library(dplyr)
library(tidypredict)

df <- mtcars
df$cyl <- factor(df$cyl)

model <- xrf(
  mpg ~ wt + hp + cyl,
  df,
  family = "gaussian",
  xgb_control = list(nrounds = 5, max_depth = 3)
)
```

## Under the hood

The parser reads the fitted `glmnet` coefficients and the rules they belong to.
Each rule becomes a `dplyr::if_else()` indicator multiplied by its coefficient,
and the linear terms are added on top.

```{r}
pm <- parse_model(model)
str(pm, 2)
```

The parsed model is transformed into a `dplyr`, a.k.a. Tidy Eval, formula.

```{r}
tidypredict_fit(model)
```

From there, the Tidy Eval formula can be used anywhere it can be evaluated.
`tidypredict` provides three paths:

- Use directly inside `dplyr`, `mutate(df, !! tidypredict_fit(model))`
- Use `tidypredict_to_column(model)` to add it to a piped command set
- Use `tidypredict_sql(model, con)` to retrieve the SQL statement

```{r}
df %>%
  tidypredict_to_column(model) %>%
  glimpse()
```

## How it performs

```{r}
tidypredict_test(model, df)
```

## Classification

Binary classification models, `family = "binomial"`, are supported and return
the probability of the second outcome level through the logistic link.

```{r}
df_bin <- mtcars
df_bin$vs <- factor(df_bin$vs)

model_bin <- xrf(
  vs ~ wt + mpg,
  df_bin,
  family = "binomial",
  xgb_control = list(nrounds = 5, max_depth = 3)
)

tidypredict_test(model_bin, df_bin)
```

## parsnip

`tidypredict` also supports `xrf` model objects fitted via the `parsnip`
package, using `rule_fit()` from the `rules` package with the `"xrf"` engine.

```{r, eval = eval_code && rlang::is_installed(c("parsnip", "rules"))}
library(parsnip)
library(rules)

parsnip_model <- rule_fit(
  mode = "regression",
  trees = 5,
  tree_depth = 3,
  penalty = 0.1
) |>
  set_engine("xrf") |>
  fit(mpg ~ wt + hp + cyl, data = df)

tidypredict_test(parsnip_model, df)
```

## Limitations

- Multinomial models are not supported; only `family = "gaussian"` and
  `family = "binomial"` are.
- Prediction intervals are not supported.
- Functions and interactions written inside the model formula are not supported.
  Prepare those columns with `dplyr` before fitting.
- The underlying trees come from XGBoost, which stores split thresholds as
  32-bit floats. See the [float precision](float-precision.html) article.
