Getting Started with tidylearn

Introduction

Every machine learning package in R has its own API and its own output format. tidylearn puts one signature over 20 of them: tl_model() picks the underlying package from the method you name, and whatever comes back is a tibble or a ggplot2 object.

The algorithms are untouched. glmnet, randomForest, xgboost, e1071, cluster and dbscan do the fitting, and model$fit hands you the object they returned — model$fit$model for an unsupervised method, whose $fit is the list of tidied components — so nothing here closes off the package underneath.

This vignette covers the shape of a workflow end to end. The articles listed at the bottom go deeper on each step.

Installation

# From CRAN
install.packages("tidylearn")

# Development version
# devtools::install_github("ces0491/tidylearn") # nolint
library(tidylearn)
library(dplyr)

The Unified Interface

The core of tidylearn is the tl_model() function, which dispatches to the appropriate underlying package based on the method you specify. The wrapped packages include stats, glmnet, randomForest, xgboost, gbm, e1071, nnet, rpart, cluster, and dbscan.

Supervised Learning

Classification

Logistic regression handles two-class problems, so we take a binary subset of iris here. For three or more classes use "tree", "forest", "svm" or "nn".

# versicolor and virginica overlap, so this is a real classification
# problem -- setosa is linearly separable from the other two, which makes
# logistic regression fail to converge
iris_binary <- iris %>%
  filter(Species %in% c("versicolor", "virginica")) %>%
  mutate(Species = droplevels(Species))

model_logistic <- tl_model(iris_binary, Species ~ ., method = "logistic")
print(model_logistic)
#> tidylearn Model
#> ===============
#> Paradigm: supervised 
#> Method: logistic 
#> Task: Classification 
#> Formula: Species ~ . 
#> 
#> Training observations: 100

Predictions come back as a tibble with a .pred column. What .pred contains depends on type: "class" gives the predicted label, "prob" gives one column per class.

# Predicted class labels
predictions <- predict(model_logistic, type = "class")
head(predictions)
#> # A tibble: 6 × 1
#>   .pred     
#>   <fct>     
#> 1 versicolor
#> 2 versicolor
#> 3 versicolor
#> 4 versicolor
#> 5 versicolor
#> 6 versicolor
# Class probabilities
head(predict(model_logistic, type = "prob"))
#> # A tibble: 6 × 2
#>   versicolor virginica
#>        <dbl>     <dbl>
#> 1      1.000 0.0000117
#> 2      1.000 0.0000486
#> 3      0.999 0.00120  
#> 4      1.000 0.0000422
#> 5      0.999 0.00141  
#> 6      1.000 0.000102

Note that the default type = "response" means different things across methods — probabilities for logistic regression, class labels for trees and forests. Ask for type = "class" explicitly when you want labels, or let tl_evaluate() handle it:

tl_evaluate(model_logistic, metrics = c("accuracy", "f1"))
#> # A tibble: 2 × 2
#>   metric   value
#>   <chr>    <dbl>
#> 1 accuracy  0.98
#> 2 f1        0.98

Regression

# Regression with linear model
model_linear <- tl_model(mtcars, mpg ~ wt + hp, method = "linear")
print(model_linear)
#> tidylearn Model
#> ===============
#> Paradigm: supervised 
#> Method: linear 
#> Task: Regression 
#> Formula: mpg ~ wt + hp 
#> 
#> Training observations: 32
# Predictions
predictions_reg <- predict(model_linear)
head(predictions_reg)
#> # A tibble: 6 × 1
#>   .pred
#>   <dbl>
#> 1  23.6
#> 2  22.6
#> 3  25.3
#> 4  21.3
#> 5  18.3
#> 6  20.5

Unsupervised Learning

Dimensionality Reduction

# Principal Component Analysis
model_pca <- tl_model(iris[, 1:4], method = "pca")
print(model_pca)
#> tidylearn Model
#> ===============
#> Paradigm: unsupervised 
#> Method: pca 
#> Technique: pca 
#> 
#> Training observations: 150
# Transform data
transformed <- predict(model_pca)
head(transformed)
#> # A tibble: 6 × 5
#>   .obs_id   PC1    PC2     PC3      PC4
#>   <chr>   <dbl>  <dbl>   <dbl>    <dbl>
#> 1 1       -2.26 -0.478  0.127   0.0241 
#> 2 2       -2.07  0.672  0.234   0.103  
#> 3 3       -2.36  0.341 -0.0441  0.0283 
#> 4 4       -2.29  0.595 -0.0910 -0.0657 
#> 5 5       -2.38 -0.645 -0.0157 -0.0358 
#> 6 6       -2.07 -1.48  -0.0269  0.00659

Clustering

# K-means clustering
model_kmeans <- tl_model(iris[, 1:4], method = "kmeans", k = 3)
print(model_kmeans)
#> tidylearn Model
#> ===============
#> Paradigm: unsupervised 
#> Method: kmeans 
#> Technique: kmeans 
#> 
#> Training observations: 150
# Get cluster assignments
clusters <- model_kmeans$fit$clusters
head(clusters)
#> # A tibble: 6 × 2
#>   .obs_id cluster
#>   <chr>     <int>
#> 1 1             3
#> 2 2             3
#> 3 3             3
#> 4 4             3
#> 5 5             3
#> 6 6             3
# Compare with actual species
table(clusters$cluster, iris$Species)
#>    
#>     setosa versicolor virginica
#>   1      0          2        36
#>   2      0         48        14
#>   3     50          0         0

Data Preprocessing

tl_prepare_data() handles imputation, scaling and encoding in one call, and records what it did so the same transformation can be replayed on new data:

# Prepare data with multiple preprocessing steps
processed <- tl_prepare_data(
  iris,
  Species ~ .,
  impute_method = "mean",
  scale_method = "standardize",
  encode_categorical = FALSE
)
#> Scaling numeric features using method: standardize
# Check preprocessing steps applied
names(processed$preprocessing_steps)
#> [1] "scaling"
# Use processed data for modeling
model_processed <- tl_model(processed$data, Species ~ ., method = "forest")

Train-Test Splitting

# Simple random split
split <- tl_split(iris, prop = 0.7, seed = 123)

# Train model (three species, so a multiclass-capable method)
model_train <- tl_model(split$train, Species ~ ., method = "forest")

# Test predictions
predictions_test <- predict(model_train, new_data = split$test)
head(predictions_test)
#> # A tibble: 6 × 1
#>   .pred 
#>   <fct> 
#> 1 setosa
#> 2 setosa
#> 3 setosa
#> 4 setosa
#> 5 setosa
#> 6 setosa
# Stratified split (maintains class proportions)
split_strat <- tl_split(iris, prop = 0.7, stratify = "Species", seed = 123)

# Check proportions are maintained
prop.table(table(split_strat$train$Species))
#> 
#>     setosa versicolor  virginica 
#>  0.3333333  0.3333333  0.3333333
prop.table(table(split_strat$test$Species))
#> 
#>     setosa versicolor  virginica 
#>  0.3333333  0.3333333  0.3333333
prop.table(table(iris$Species))
#> 
#>     setosa versicolor  virginica 
#>  0.3333333  0.3333333  0.3333333

Wrapped Packages

tidylearn provides a unified interface to these established R packages:

Supervised Methods

Method Underlying Package Function Called
"linear" stats lm()
"polynomial" stats lm() with poly()
"logistic" stats glm(..., family = binomial)
"ridge", "lasso", "elastic_net" glmnet glmnet()
"tree" rpart rpart()
"forest" randomForest randomForest()
"boost" gbm gbm()
"xgboost" xgboost xgb.train()
"svm" e1071 svm()
"nn" nnet nnet()
"deep" keras keras_model_sequential()

Unsupervised Methods

Method Underlying Package Function Called
"pca" stats prcomp()
"mds" stats, MASS, smacof cmdscale(), isoMDS(), etc.
"kmeans" stats kmeans()
"pam" cluster pam()
"clara" cluster clara()
"hclust" stats hclust()
"dbscan" dbscan dbscan()

Accessing the Underlying Model

The raw model from the underlying package is reachable through $fit:

# Example: Access the raw randomForest object
model_forest <- tl_model(iris, Species ~ ., method = "forest")
class(model_forest$fit)  # This is the randomForest object
#> [1] "randomForest.formula" "randomForest"

# Use package-specific functions if needed
# randomForest::varImpPlot(model_forest$fit) # nolint

An unsupervised method returns tidied components as well, so its $fit is the list holding them and the wrapped object sits at $fit$model:

model_pca <- tl_model(iris, ~ ., method = "pca")
names(model_pca$fit)
#> [1] "scores"             "loadings"           "variance_explained"
#> [4] "model"              "settings"
class(model_pca$fit$model)  # This is the prcomp object
#> [1] "prcomp"

The Whole Workflow

Split, fit, predict, score — the four steps this vignette covered, in the order they run:

# Quick example combining everything
data_split <- tl_split(iris, prop = 0.7, stratify = "Species", seed = 42)

# Random forests are scale-invariant, so no scaling is needed here. When a
# method does need scaled inputs, the same transformation has to be applied
# to the test set -- see the Supervised Learning vignette.
model_final <- tl_model(data_split$train, Species ~ ., method = "forest")
test_preds <- predict(model_final, new_data = data_split$test)

accuracy <- mean(test_preds$.pred == data_split$test$Species)
cat("Test accuracy:", round(accuracy * 100, 1), "%\n")
#> Test accuracy: 95.6 %

Next Steps