ML, AI, and R: from Algo to Deployment
  1. Models
  2. Random Forests for Classification
  • Setup
  • Preprocessing
    • Data Preprocessing for ML / AI
    • content/course/Modules/10-Preprocessing/eda.qmd
    • content/course/Modules/10-Preprocessing/missingdata.qmd
    • content/course/Modules/10-Preprocessing/outliers.qmd
    • content/course/Modules/10-Preprocessing/scaling.qmd
    • content/course/Modules/10-Preprocessing/dimensional
  • Sampling
  • Recipes
  • Models
    • Linear Regression with tidymodels
    • Logistic Regression
    • Random Forests for Classification
    • Boosting
    • Support Vector Machines
    • K-means Clustering
    • Ensembles as Emergent Systems: From Simple Trees to Collective Intelligence
  • Workflow
  • Model Tuning
  • Model Evaluation
  • Neural Nets and DL
  • Model Deployment

On this page

  • Random Forest Classification with tidymodels — hotel_rates Dataset
  • 1. Load and prepare the data
  • 2. Split the data: train / test
  • 3. Preprocessing recipe
  • 4. Model specification (with tunable hyperparameters)
  • 5. Workflow: bundle recipe + model
  • 6. Tune mtry and min_n via cross-validation
  • 7. Select the best hyperparameters and finalize the workflow
  • 8. Final fit on full training set + evaluation on the held-out test set
  • 9. Confusion matrix and ROC curve
  • 10. Variable importance
  • 11. (Optional) Extract the final fitted workflow for reuse / deployment
  1. Models
  2. Random Forests for Classification

Random Forests for Classification

Random Forest Classification with tidymodels — hotel_rates Dataset

NOTE: modeldata::hotel_rates ships as a regression dataset (predicting avg_price_per_room). To demonstrate classification, we derive a binary outcome: has_children = TRUE if the booking included one or more children, else FALSE. The raw children and babies counts are dropped from the predictors afterward, since keeping them would leak the answer directly.

Packages needed: tidymodels (includes ranger via the “ranger” engine), modeldata (for hotel_rates), vip (for variable importance plots)

# install.packages("tidymodels")
# install.packages("ranger")
# pak::pak("bgreenwell/vip@main")  # latest stable release

library(tidymodels)
library(vip)
tidymodels_prefer()

1. Load and prepare the data

data(hotel_rates, package = "modeldata")

hotels <- hotel_rates |>
  as_tibble() |>
  mutate(
    has_children = factor(
      if_else(children > 0, "yes", "no"),
      levels = c("yes", "no") # first level = the "event" of interest
    )
  ) |>
  select(-children, -babies, -avg_price_per_room)

glimpse(hotels)
Rows: 15,402
Columns: 26
$ lead_time                      <dbl> 241, 273, 248, 236, 243, 267, 94, 10, 1…
$ stays_in_weekend_nights        <dbl> 0, 2, 2, 2, 4, 2, 4, 0, 0, 0, 0, 0, 0, …
$ stays_in_week_nights           <dbl> 1, 5, 5, 5, 10, 5, 7, 1, 1, 1, 1, 1, 1,…
$ adults                         <dbl> 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, …
$ meal                           <fct> bed_and_breakfast, bed_and_breakfast, b…
$ country                        <fct> prt, aus, gbr, prt, gbr, null, prt, esp…
$ market_segment                 <fct> online_travel_agent, offline_travel_age…
$ distribution_channel           <fct> ta_to, ta_to, ta_to, ta_to, ta_to, ta_t…
$ is_repeated_guest              <dbl> 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, …
$ previous_cancellations         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ previous_bookings_not_canceled <dbl> 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, …
$ reserved_room_type             <fct> a, a, a, a, a, a, f, e, h, a, a, g, a, …
$ assigned_room_type             <fct> c, a, c, a, a, a, f, f, h, e, e, g, e, …
$ booking_changes                <dbl> 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, …
$ agent                          <fct> devin_rivera_borrego, lia_nauth, jawhar…
$ company                        <fct> not_applicable, not_applicable, not_app…
$ days_in_waiting_list           <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ customer_type                  <fct> transient, transient_party, transient, …
$ required_car_parking_spaces    <dbl> 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, …
$ total_of_special_requests      <dbl> 1, 0, 0, 2, 0, 0, 1, 1, 0, 2, 2, 0, 2, …
$ arrival_date                   <date> 2016-07-02, 2016-07-02, 2016-07-02, 20…
$ arrival_date_num               <dbl> 2016.5, 2016.5, 2016.5, 2016.5, 2016.5,…
$ near_christmas                 <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ near_new_years                 <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ historical_adr                 <dbl> 104.9811, 104.9811, 104.9811, 104.9811,…
$ has_children                   <fct> yes, no, no, no, no, no, no, no, no, no…
count(hotels, has_children) # check class balance

2. Split the data: train / test

set.seed(123)

hotels_split <- initial_split(hotels, prop = 0.80, strata = has_children)
hotels_train <- training(hotels_split)
hotels_test <- testing(hotels_split)

3. Preprocessing recipe

Random forests don’t need dummy variables, centering, or scaling — the ranger engine handles factors and raw numeric scales natively. The main preprocessing worth doing here is collapsing rare factor levels (e.g. in agent, company, country) so the forest isn’t swamped with near-unique categories, and removing any zero-variance columns.

hotels_rec <-
  recipe(has_children ~ ., data = hotels_train) |>
  step_other(agent, company, country, threshold = 0.01) |>
  step_zv(all_predictors())

4. Model specification (with tunable hyperparameters)

rf_spec <-
  rand_forest(
    mtry  = tune(),
    trees = 500,
    min_n = tune()
  ) |>
  set_engine("ranger", importance = "impurity") |>
  set_mode("classification")

5. Workflow: bundle recipe + model

hotels_wflow <-
  workflow() |>
  add_recipe(hotels_rec) |>
  add_model(rf_spec)

6. Tune mtry and min_n via cross-validation

set.seed(456)
hotels_folds <- vfold_cv(hotels_train, v = 5, strata = has_children)

class_metrics <- metric_set(roc_auc, accuracy, sensitivity, specificity)

set.seed(789)
rf_grid <- grid_regular(
  mtry(range = c(2, 8)),
  min_n(range = c(2, 20)),
  levels = 4
)
rf_tune_results <-
  hotels_wflow |>
  tune_grid(
    resamples = hotels_folds,
    grid      = rf_grid,
    metrics   = class_metrics
  )
# Inspect the top-performing hyperparameter combinations by ROC AUC
show_best(rf_tune_results, metric = "roc_auc", n = 5)
autoplot(rf_tune_results)

7. Select the best hyperparameters and finalize the workflow

best_rf <- select_best(rf_tune_results, metric = "roc_auc")

final_wflow <- finalize_workflow(hotels_wflow, best_rf)

8. Final fit on full training set + evaluation on the held-out test set

final_fit <- last_fit(
  final_wflow,
  split   = hotels_split,
  metrics = class_metrics
)

# Test-set performance
collect_metrics(final_fit)
# Predictions (class + probabilities) on the test set
test_predictions <- collect_predictions(final_fit)
head(test_predictions)

9. Confusion matrix and ROC curve

test_predictions |>
  conf_mat(truth = has_children, estimate = .pred_class)
          Truth
Prediction  yes   no
       yes  126   18
       no   138 2799
test_predictions |>
  roc_curve(truth = has_children, .pred_yes) |>
  autoplot() +
  labs(title = "ROC Curve — Predicting Whether a Hotel Stay Includes Children")

10. Variable importance

final_fit |>
  extract_fit_parsnip() |>
  vip(num_features = 15) +
  labs(title = "Random Forest Variable Importance")

NULL

11. (Optional) Extract the final fitted workflow for reuse / deployment

final_wflow_fit <- extract_workflow(final_fit)
# saveRDS(final_wflow_fit, "hotels_rf_workflow.rds")

# Example: predict on new/unseen data using the finalized workflow
# predict(final_wflow_fit, new_data = some_new_data, type = "prob")
Back to top
Logistic Regression
Boosting

License: CC BY-SA 2.0

Website made with ❤️ and Quarto, by Arvind V.

Hosted by Netlify .