if ( requireNamespace("klaR", quietly = TRUE) && requireNamespace("naivebayes", quietly = TRUE) ) { library(tidypredict) library(dplyr) eval_code <- TRUE } else { eval_code <- FALSE } knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = eval_code )
| Function |Works|
|---------------------------------------------------------------|-----|
|tidypredict_fit(), tidypredict_sql(), parse_model() | ✔ |
|tidypredict_to_column() | ✗ |
|tidypredict_test() | ✗ |
|tidypredict_interval(), tidypredict_sql_interval() | ✗ |
|parsnip | ✔ |
klaR::NaiveBayes() and naivebayes::naive_bayes() fit naive Bayes
classifiers. Predicting with such a model
multiplies the prior probability of each class by one conditional density per
predictor, and then normalizes those products into posterior probabilities.
Working on the log scale turns the products into sums, which makes the posterior
probabilities the softmax of the summed log densities.
Numeric predictors contribute a normal log density, and categorical predictors
contribute a case_when() lookup of the conditional probability of the observed
level. naivebayes::naive_bayes() additionally supports Poisson densities for
integer predictors when fit with usepoisson = TRUE. Kernel density estimates
cannot be expressed this way, so models fit with usekernel = TRUE are not
supported.
Because these models predict one probability per outcome class,
tidypredict_fit() returns a named list of expressions, one for each class,
rather than a single expression. Since the output is a list,
tidypredict_to_column() and tidypredict_test() are not supported.
tidypredict_ functionsmodel <- klaR::NaiveBayes(Species ~ ., data = iris)
Create the R formulas, one per class
r
fit <- tidypredict_fit(model)
names(fit)
fit[["setosa"]]
Add the predictions to the original table ```r library(dplyr)
iris %>% mutate(!!!tidypredict_fit(model)) %>% glimpse() ```
predict() results
r
probs <- sapply(fit, \(f) rlang::eval_tidy(f, iris))
all.equal(unname(probs), unname(predict(model, iris)$posterior))There is one case where tidypredict_fit() does not reproduce predict(), and
it is deliberately left in place.
Both packages evaluate each normal density and then replace any density that
came out as exactly zero with their threshold argument, 0.001 by default. A
normal density only reaches zero by underflowing the smallest number a double
can hold, which takes a value roughly 38 standard deviations from that class's
mean. tidypredict works on the log scale throughout, where such a value is an
ordinary negative number and nothing underflows, so the substitution never
happens.
The substitution is drastic when it fires. Consider an iris with a
Sepal.Length of 20, well beyond the largest in the data:
outlier <- iris[1, ] outlier$Sepal.Length <- 20 predict(model, outlier)$posterior
setosa has the narrowest spread of the three classes, so its density is the
first to underflow. Replacing it with 0.001 makes it enormous next to the
other two, which are around 1e-162 and 1e-97 but were computed honestly, so
the class that fits the value worst is the one that wins. This happens silently
unless the value is extreme enough for every class to underflow, which is the
only case either package warns about. tidypredict instead continues on the
log scale and reports the ordering the fitted model implies:
sapply(tidypredict_fit(model), \(f) rlang::eval_tidy(f, outlier))
Reproducing the substitution is not possible on the log scale in any case.
Testing exp(log_density) == 0 instead of density == 0 picks out a different
set of values: over 200,000 draws from the band where the underflow happens the
two tests disagree on about 1,000 of them, because the two routes round
differently in the denormal range. A SQL backend, working in its own floating
point, would disagree again in a third way.
parsnip fitted models are also supported by tidypredict:
library(parsnip) library(discrim) p_model <- naive_Bayes() %>% set_engine("klaR", usekernel = FALSE) %>% fit(Species ~ ., data = iris)
tidypredict_fit(p_model)[["virginica"]]
naivebayes::naive_bayes()The naivebayes package is supported in the same way:
nb_model <- naivebayes::naive_bayes(Species ~ ., data = iris) nb_fit <- tidypredict_fit(nb_model) nb_fit[["setosa"]]
nb_probs <- sapply(nb_fit, \(f) rlang::eval_tidy(f, iris)) all.equal( unname(nb_probs), unname(predict(nb_model, iris[names(nb_model$tables)], type = "prob")) )
The "naivebayes" parsnip engine works too, as long as usekernel = FALSE:
nb_p_model <- naive_Bayes() %>% set_engine("naivebayes", usekernel = FALSE) %>% fit(Species ~ ., data = iris) tidypredict_fit(nb_p_model)[["virginica"]]
Here is an example of the model spec:
pm <- parse_model(model) str(pm, 2)
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.