Grid Integration
How parameters work with grid generation and tune workflows
This guide covers how dials parameters integrate with grid generation functions and tune workflows for hyperparameter tuning.
Overview
dials parameters are designed to work seamlessly with:
- Grid generation:
grid_regular(),grid_random(),grid_space_filling() - Value utilities:
value_sample(),value_seq() - Parameter sets:
parameters()for combining multiple parameters - Workflow extraction:
extract_parameter_set_dials()from workflows - Tuning functions:
tune_grid(),tune_bayes()from tune package
Grid Generation Functions
grid_regular()
Create factorial designs with regular spacing
grid_regular(param1, param2, ..., levels = 3, filter = NULL)For single parameter:
penalty_param <- penalty()
grid <- dials::grid_regular(penalty_param, levels = 5)
grid
#> # A tibble: 5 × 1
#> penalty
#> <dbl>
#> 1 0.0000000001 # 10^-10
#> 2 0.00001 # 10^-7.5
#> 3 0.001 # 10^-5
#> 4 0.01 # 10^-2.5
#> 5 1 # 10^0For multiple parameters:
params <- dials::parameters(
penalty = penalty(),
mixture = mixture()
)
grid <- dials::grid_regular(params, levels = 3)
grid
#> # A tibble: 9 × 2
#> penalty mixture
#> <dbl> <dbl>
#> 1 1.00e-10 0
#> 2 1.00e-10 0.5
#> 3 1.00e-10 1
#> 4 1.00e-05 0
#> 5 1.00e-05 0.5
#> 6 1.00e-05 1
#> 7 1.00e+00 0
#> 8 1.00e+00 0.5
#> 9 1.00e+00 1Different levels per parameter:
grid <- dials::grid_regular(params, levels = c(5, 3))
# 5 levels for penalty, 3 levels for mixture
# Total grid size: 5 × 3 = 15grid_random()
Create random grid with uniform sampling
grid_random(param1, param2, ..., size = 5, filter = NULL)For single parameter:
set.seed(123)
penalty_param <- penalty()
grid <- dials::grid_random(penalty_param, size = 5)
grid
#> # A tibble: 5 × 1
#> penalty
#> <dbl>
#> 1 0.000204
#> 2 0.130329
#> 3 0.000000100
#> 4 0.524148
#> 5 0.939941For multiple parameters:
set.seed(123)
params <- dials::parameters(
penalty = penalty(),
mixture = mixture()
)
grid <- dials::grid_random(params, size = 10)
grid
#> # A tibble: 10 × 2
#> penalty mixture
#> <dbl> <dbl>
#> 1 0.000204 0.788
#> 2 0.130329 0.409
#> 3 0.000000100 0.883
#> # ... with 7 more rowsgrid_space_filling()
Create space-filling designs (Latin hypercube, max entropy)
grid_space_filling(param1, param2, ..., size = 5, type = "latin_hypercube")Options for type:
"latin_hypercube": Latin hypercube sampling (default)"max_entropy": Maximum entropy design"audze_eglais": Audze-Eglais criterion"uniform": Uniform design
Example:
set.seed(123)
params <- dials::parameters(
penalty = penalty(),
mixture = mixture()
)
grid <- dials::grid_space_filling(params, size = 10, type = "latin_hypercube")
grid
#> # A tibble: 10 × 2
#> penalty mixture
#> <dbl> <dbl>
#> 1 0.00000272 0.550
#> 2 0.0000000344 0.989
#> 3 0.391 0.156
#> # ... with 7 more rowsBenefits:
- Better coverage of parameter space than random
- Avoids clustering of grid points
- Good for small grid sizes
Parameter Value Utilities
value_sample()
Generate random values from a parameter
value_sample(object, n, original = TRUE)For quantitative parameters:
penalty_param <- penalty()
set.seed(123)
samples <- dials::value_sample(penalty_param, n = 5)
samples
#> [1] 2.042e-04 1.303e-01 1.000e-10 5.241e-01 9.400e-01
# Uniformly distributed in transformed (log10) spaceFor qualitative parameters:
activation_param <- activation()
set.seed(456)
samples <- dials::value_sample(activation_param, n = 3)
samples
#> [1] "tanh" "relu" "sigmoid"
# Randomly samples from available valuesvalue_seq()
Generate regular sequence of values from a parameter
value_seq(object, n, original = TRUE)For quantitative parameters:
penalty_param <- penalty()
seq_vals <- dials::value_seq(penalty_param, n = 5)
seq_vals
#> [1] 1e-10 1e-07 1e-04 1e-01 1e+00
# Regular spacing in transformed spaceFor qualitative parameters:
activation_param <- activation()
seq_vals <- dials::value_seq(activation_param, n = 3)
seq_vals
#> [1] "relu" "sigmoid" "tanh"
# Returns first n values (or cycles if n > length)How Parameter Properties Affect Grids
range: Determines Sampling Bounds
# Wide range
penalty_wide <- penalty(range = c(-10, 0))
grid <- dials::grid_regular(penalty_wide, levels = 5)
range(grid$penalty)
#> [1] 1e-10 1e+00
# Narrow range
penalty_narrow <- penalty(range = c(-5, -2))
grid <- dials::grid_regular(penalty_narrow, levels = 5)
range(grid$penalty)
#> [1] 1e-05 1e-02trans: Affects Value Distribution
# With transformation (log scale)
penalty_log <- penalty(range = c(-10, 0), trans = scales::transform_log10())
grid <- dials::grid_regular(penalty_log, levels = 5)
grid$penalty
#> [1] 1e-10 1e-07 1e-04 1e-01 1e+00
#> Even spacing on log scale
# Without transformation (linear scale)
penalty_linear <- penalty(range = c(0.0000000001, 1), trans = NULL)
grid <- dials::grid_regular(penalty_linear, levels = 5)
grid$penalty
#> [1] 0.0000000001 0.2500000000 0.5000000000 0.7500000000 1.0000000000
#> Linear spacing (poor coverage of small values)type: Determines Granularity
# Double: continuous values
threshold_double <- threshold()
grid <- dials::grid_regular(threshold_double, levels = 5)
grid$threshold
#> [1] 0.00 0.25 0.50 0.75 1.00
# Integer: discrete values
num_trees_int <- num_trees(range = c(1L, 100L))
grid <- dials::grid_regular(num_trees_int, levels = 5)
grid$num_trees
#> [1] 1 25 50 75 100values: Enumerates Options (Qualitative)
# All values
activation_param <- activation() # 8 options
grid <- dials::grid_regular(activation_param)
nrow(grid)
#> [1] 8
# Subset of values
activation_subset <- activation(values = c("relu", "sigmoid", "tanh"))
grid <- dials::grid_regular(activation_subset)
nrow(grid)
#> [1] 3inclusive: Controls Endpoint Inclusion
# Both endpoints included
param_inclusive <- threshold(range = c(0, 1)) # inclusive = c(TRUE, TRUE)
seq_vals <- dials::value_seq(param_inclusive, n = 5)
seq_vals
#> [1] 0.00 0.25 0.50 0.75 1.00
#> Includes 0 and 1
# Both endpoints excluded
param_exclusive <- new_quant_param(
type = "double",
range = c(0, 1),
inclusive = c(FALSE, FALSE),
...
)
seq_vals <- dials::value_seq(param_exclusive, n = 5)
seq_vals
#> [1] 0.1 0.3 0.5 0.7 0.9
#> Excludes 0 and 1Parameter Sets
Creating Parameter Sets with parameters()
Combine multiple parameters into a set:
param_set <- dials::parameters(
penalty = penalty(),
mixture = mixture(),
num_trees = num_trees(range = c(100L, 1000L))
)
param_set
#> Collection of 3 parameters for tuning
#> id parameter type object class
#> penalty penalty nparam[+] quant_param
#> mixture mixture nparam[+] quant_param
#> num_trees num_trees nparam[+] quant_paramGenerating Grids from Parameter Sets
# Regular grid (factorial design)
grid <- dials::grid_regular(param_set, levels = 3)
nrow(grid)
#> [1] 27 # 3 × 3 × 3
# Random grid
grid <- dials::grid_random(param_set, size = 20)
nrow(grid)
#> [1] 20
# Space-filling grid
grid <- dials::grid_space_filling(param_set, size = 15)
nrow(grid)
#> [1] 15Updating Parameter Ranges
param_set <- dials::parameters(
penalty = penalty(),
mixture = mixture()
)
# Update penalty range
param_set_updated <- param_set |>
recipes::update(penalty = penalty(range = c(-5, -1)))
param_set_updated
#> Collection of 2 parameters for tuning
#> id parameter type object class
#> penalty penalty nparam[+] quant_param # Updated range
#> mixture mixture nparam[+] quant_paramExtracting Parameters from Workflows
From Model Specifications
# Define model with tunable parameters
rf_spec <- parsnip::rand_forest(
mtry = tune::tune(),
trees = tune::tune(),
min_n = tune::tune()
) |>
parsnip::set_engine("ranger") |>
parsnip::set_mode("regression")
# Extract parameter set
params <- parsnip::extract_parameter_set_dials(rf_spec)
params
#> Collection of 3 parameters for tuning
#> id parameter type object class
#> mtry mtry nparam[?] quant_param # Needs finalization
#> trees trees nparam[+] quant_param
#> min_n min_n nparam[+] quant_paramFrom Workflows
# Create workflow
wf <- workflows::workflow() |>
workflows::add_model(rf_spec) |>
workflows::add_formula(mpg ~ .)
# Extract parameters from entire workflow
params <- workflows::extract_parameter_set_dials(wf)
params
#> Collection of 3 parameters for tuning
#> id parameter type object class
#> mtry mtry nparam[?] quant_param
#> trees trees nparam[+] quant_param
#> min_n min_n nparam[+] quant_paramFinalizing Extracted Parameters
# Finalize data-dependent parameters
params_finalized <- dials::finalize(params, mtcars[, -1])
# Update ranges
params_updated <- params_finalized |>
recipes::update(
mtry = mtry(range = c(1, 5)),
trees = trees(range = c(500, 1500))
)
# Generate grid
grid <- dials::grid_regular(params_updated, levels = 3)Complete Workflow Examples
Example 1: Single Parameter Grid Search
# Define parameter
penalty_param <- penalty()
# Generate grid
grid <- dials::grid_regular(penalty_param, levels = 10)
# Define model
model_spec <- parsnip::linear_reg(penalty = tune::tune()) |>
parsnip::set_engine("glmnet")
# Create workflow
wf <- workflows::workflow() |>
workflows::add_model(model_spec) |>
workflows::add_formula(mpg ~ .)
# Tune
library(tune)
library(rsample)
cv_folds <- vfold_cv(mtcars, v = 5)
results <- tune_grid(wf, resamples = cv_folds, grid = grid)
# Best parameters
show_best(results, metric = "rmse")Example 2: Multi-Parameter Random Search
# Define parameters
param_set <- dials::parameters(
penalty = penalty(),
mixture = mixture()
)
# Generate random grid
set.seed(123)
grid <- dials::grid_random(param_set, size = 25)
# Define model
model_spec <- parsnip::linear_reg(
penalty = tune::tune(),
mixture = tune::tune()
) |>
parsnip::set_engine("glmnet")
# Workflow and tune
wf <- workflows::workflow() |>
workflows::add_model(model_spec) |>
workflows::add_formula(mpg ~ .)
results <- tune_grid(wf, resamples = cv_folds, grid = grid)Example 3: Automatic Grid with Finalization
# Model with data-dependent parameter
rf_spec <- parsnip::rand_forest(
mtry = tune::tune(),
trees = tune::tune()
) |>
parsnip::set_engine("ranger") |>
parsnip::set_mode("regression")
# Workflow
wf <- workflows::workflow() |>
workflows::add_model(rf_spec) |>
workflows::add_formula(mpg ~ .)
# tune_grid automatically finalizes mtry
results <- tune_grid(
wf,
resamples = cv_folds,
grid = 20 # Generates random grid, finalizes mtry
)Example 4: Custom Parameters in Grid
# Extract default parameters
params <- workflows::extract_parameter_set_dials(wf)
# Finalize and customize
params_custom <- dials::finalize(params, mtcars[, -1]) |>
recipes::update(
mtry = mtry(range = c(2, 8)),
trees = trees(range = c(100, 500))
)
# Generate space-filling grid
grid <- dials::grid_space_filling(params_custom, size = 30)
# Tune with custom grid
results <- tune_grid(wf, resamples = cv_folds, grid = grid)Example 5: Recipe + Model Parameters
# Recipe with tunable step
rec <- recipes::recipe(mpg ~ ., data = mtcars) |>
recipes::step_pca(all_numeric_predictors(), num_comp = tune::tune())
# Model with tunable parameters
model_spec <- parsnip::rand_forest(mtry = tune::tune()) |>
parsnip::set_engine("ranger") |>
parsnip::set_mode("regression")
# Workflow
wf <- workflows::workflow() |>
workflows::add_recipe(rec) |>
workflows::add_model(model_spec)
# Extract all parameters (from recipe + model)
params <- workflows::extract_parameter_set_dials(wf)
params
#> Collection of 2 parameters for tuning
#> id parameter type object class
#> num_comp num_comp nparam[?] quant_param
#> mtry mtry nparam[?] quant_param
# Finalize and tune
params_finalized <- dials::finalize(params, mtcars[, -1])
grid <- dials::grid_regular(params_finalized, levels = 3)
results <- tune_grid(wf, resamples = cv_folds, grid = grid)Extension vs Source Patterns
Extension Development
Use dials:: prefix for all functions:
# Create parameters
penalty_param <- penalty()
mixture_param <- mixture()
# Parameter set
param_set <- dials::parameters(
penalty = penalty_param,
mixture = mixture_param
)
# Generate grids
grid1 <- dials::grid_regular(param_set, levels = 3)
grid2 <- dials::grid_random(param_set, size = 20)
grid3 <- dials::grid_space_filling(param_set, size = 15)
# Value utilities
samples <- dials::value_sample(penalty_param, n = 10)
seq_vals <- dials::value_seq(penalty_param, n = 5)
# Finalize
finalized <- dials::finalize(param_set, train_data)
# Update
updated <- finalized |>
recipes::update(penalty = penalty(range = c(-5, -1)))Source Development
No dials:: prefix needed:
# Create parameters
penalty_param <- penalty()
mixture_param <- mixture()
# Parameter set
param_set <- parameters(
penalty = penalty_param,
mixture = mixture_param
)
# Generate grids
grid1 <- grid_regular(param_set, levels = 3)
grid2 <- grid_random(param_set, size = 20)
grid3 <- grid_space_filling(param_set, size = 15)
# Value utilities
samples <- value_sample(penalty_param, n = 10)
seq_vals <- value_seq(penalty_param, n = 5)
# Finalize
finalized <- finalize(param_set, train_data)
# Update (recipes:: still needed)
updated <- finalized |>
recipes::update(penalty = penalty(range = c(-5, -1)))Best Practices
Grid Size Selection
Regular grids: Grows exponentially with parameters - 2 parameters × 5 levels = 25 combinations - 3 parameters × 5 levels = 125 combinations - 4 parameters × 5 levels = 625 combinations
Recommendation: - Use fewer levels for many parameters - Consider random or space-filling for 4+ parameters
Grid Type Selection
Use grid_regular() when: - Few parameters (1-3) - Want complete factorial coverage - Interpretable grid structure
Use grid_random() when: - Many parameters (4+) - Want to control total grid size - Computational budget is fixed
Use grid_space_filling() when: - Need better coverage than random - Small grid sizes - Quality over quantity
Parameter Combinations
Avoid unnecessary combinations:
# Use filter to exclude illogical combinations
grid <- dials::grid_regular(param_set, levels = 5, filter = penalty > 0.001)Transformation Awareness
Verify grid coverage:
param <- penalty()
grid <- dials::grid_regular(param, levels = 5)
# Check transformed values
log10(grid$penalty)
#> Should be evenly spacedTroubleshooting
“Can’t generate grid with unknown() bound”
# Problem: Parameter not finalized
param <- mtry()
grid <- dials::grid_regular(param, levels = 5)
#> Error: Can't generate grid with unknown values
# Solution: Finalize first
param_finalized <- dials::finalize(param, train_data)
grid <- dials::grid_regular(param_finalized, levels = 5)“Grid size is too large”
# Problem: Too many parameter combinations
params <- dials::parameters(p1, p2, p3, p4, p5)
grid <- dials::grid_regular(params, levels = 5)
#> 5^5 = 3125 combinations!
# Solution: Use random or space-filling
grid <- dials::grid_random(params, size = 50)
grid <- dials::grid_space_filling(params, size = 50)“Integer parameter produces no values”
# Problem: Exclusive bounds on small integer range
param <- new_quant_param(
type = "integer",
range = c(1L, 3L),
inclusive = c(FALSE, FALSE), # Only value 2 is valid!
...
)
# Solution: Use inclusive bounds or wider range
param <- new_quant_param(
type = "integer",
range = c(1L, 3L),
inclusive = c(TRUE, TRUE), # 1, 2, 3 all valid
...
)Next Steps
Learn More
- Quantitative parameters: Quantitative Parameters Guide
- Qualitative parameters: Qualitative Parameters Guide
- Data-dependent parameters: Data-Dependent Parameters Guide
- Transformations: Transformations Guide
Implementation Guides
- Extension development: Extension Development Guide
- Source development: Source Development Guide
External Resources
Last Updated: 2026-03-31