dials grid_latin_hypercube() in R: Space-Filling Tuning Grid
The dials grid_latin_hypercube() function in R draws a space-filling Latin hypercube sample across one or more tuning parameters, returning a tibble of candidate combinations that cover the joint range more evenly than random search at the same row budget.
grid_latin_hypercube(penalty(), size = 25) # one parameter, 25 strata grid_latin_hypercube(penalty(), mixture(), size = 30) # two parameters grid_latin_hypercube(penalty(range = c(-4, 0)), size = 20) # custom log10 range grid_latin_hypercube(extract_parameter_set_dials(wf), size = 50) # from a workflow grid_latin_hypercube(mtry(c(1, 10)), trees(c(100, 500)), size = 25) # tree-model knobs grid_latin_hypercube(neighbors(), weight_func(), size = 15) # numeric and qualitative grid_latin_hypercube(pset, size = 40, original = FALSE) # transformed-scale draws set.seed(1); grid_latin_hypercube(penalty(), size = 10) # reproducible sample
Need explanation? Read on for examples and pitfalls.
What grid_latin_hypercube() does in one sentence
grid_latin_hypercube() divides each parameter's range into size equal strata and draws one sample from every stratum. You pass dials parameter objects (or a parameters() set), say how many rows you want, and the function returns a size-by-p tibble where every column is a stratified permutation. The same size value drives the grid regardless of parameter count.
The function lives in dials and is re-exported by tidymodels. Pair it with tune_grid() whenever a regular grid would explode but you still want even coverage. Latin hypercube reaches corners random search can miss without a full cartesian product.
How Latin hypercube stratification works
Each axis is split into size equal-probability bins; one draw per bin. For a single parameter and size = 10, you get one sample from each of 10 strata, so the column is guaranteed to span the full range. With multiple parameters, the stratum order is permuted independently per column, which prevents the diagonal alignment a naive grid would produce.
Even though rows are shuffled, the 10 values together cover all 10 log10 strata between 1e-4 and 1. A random grid of the same size could leave [-3, -2] empty or stack three draws in [-1, 0]. The stratification removes that lottery.
grid_max_entropy(), which post-processes the Latin hypercube to push candidates apart.grid_latin_hypercube() syntax and arguments
The signature mirrors the rest of the dials grid family.
| Argument | Description |
|---|---|
x, ... |
Parameter objects: penalty(), mixture(), mtry(), or a parameters() set. |
size |
How many candidates to draw, and equivalently the number of strata per axis. |
original |
If TRUE, returns natural-scale values; if FALSE, returns transformed-scale values useful for plotting the sample geometry. |
Latin hypercube has no filter argument because dropping rows would break the one-per-stratum guarantee. Constrain the design upfront with parameter ranges.
Examples by use case
Start with two parameters where the space-filling property shows up visually. A 2D Latin hypercube spreads candidates across both axes without the clustering that random search occasionally exhibits.
With 16 rows you get 16 distinct strata per axis, covering the whole [10^-4, 1] x [0, 1] rectangle. A grid_regular(levels = 4) also fits 16 cells, but only at four exact grid lines per axis.
Reading from a workflow keeps identifiers in sync with the model spec.
Hand the tibble to tune_grid() exactly as you would a random grid; only the geometry differs.
Higher-dimensional sweeps are where Latin hypercube earns its keep. With five parameters a levels = 5 regular grid hits 3,125 candidates; a 50-row LHS reaches every stratum on every axis with sixty times fewer fits.
grid_latin_hypercube() vs grid_random() vs grid_max_entropy()
Choose by how strict the even-coverage requirement is, not by what feels exotic.
| Function | Coverage style | When to reach for it |
|---|---|---|
grid_random() |
Independent uniform draws | Quick prototyping, very high dimensions, no concern about clustering. |
grid_latin_hypercube() |
Stratified (one sample per axis bin) | Most tuning runs with continuous parameters; sensible default for 3 to 6 knobs. |
grid_max_entropy() |
Post-processed LHS, maximally spread in joint space | Expensive fits where every candidate must be far from the others. |
grid_regular() |
Exhaustive cartesian product | Two-parameter sweeps you intend to plot as a heatmap. |
Latin hypercube is the modern default for 3 to 6 continuous parameters. Regular still wins for 2-parameter heatmaps; max-entropy pays its setup cost when fits are expensive; random remains cheapest for quick scans at large budgets.
Common pitfalls
Three traps account for most Latin hypercube surprises.
- Treating
sizeas candidate count without thinking about strata.size = 5means 5 strata per axis, leaving wide gaps. For 3 to 6 parameters, aim for 30 to 100. - Unfinalized data-dependent parameters.
mtry()has no default upper bound; pass an explicit range or callfinalize(mtry(), train)before sampling, otherwise the function errors with an unknown-range message. - Expecting LHS to beat max-entropy at low budgets. Latin hypercube stratifies each axis but does not enforce joint spread, so at
size = 10in 4 dimensions you can still get near-duplicate points. Reach forgrid_max_entropy()when wasted candidates hurt.
set.seed() with the grid call in the same chunk so the candidate set is reproducible for reviewers.grid_space_filling(pset, size = N, type = "latin_hypercube") as an umbrella that dispatches to LHS, random, or max-entropy. The direct grid_latin_hypercube() still works for backward compatibility and reads more clearly when the design is decided upfront.Try it yourself
Try it: Build a Latin hypercube grid for an elastic-net workflow with penalty on the log10 range [-3, 0] and mixture on [0, 1], drawing 12 candidates with set.seed(7). The result should have 12 rows and 2 columns.
Click to reveal solution
Explanation: grid_latin_hypercube() takes one size regardless of parameter count, so 12 rows of two columns means 12 strata on each axis with the stratum order permuted independently per column. The seed pins both the strata and the permutation so the tibble is identical across sessions.
Related dials functions
grid_latin_hypercube() sits inside a family; reach for the right neighbor when stratified sampling is wrong.
grid_random()when the budget is large and clustering is acceptable.grid_regular()for an exhaustive cartesian product on 2 or 3 parameters you plan to plot.grid_max_entropy()when expensive fits demand the maximum joint spread.grid_space_filling()as the new umbrella in dials 1.2+, with atypeargument that selects Latin hypercube, max entropy, or random.parameters()andextract_parameter_set_dials()to assemble a tuning specification from a workflow.finalize()to pin data-dependent ranges such asmtry()against a training set before grid construction.update()to override one parameter's range inside an existing parameter set without rebuilding it.
External reference: the official dials package documentation at dials.tidymodels.org/reference/grid_max_entropy.html.
FAQ
What does "Latin hypercube" mean in plain terms?
A Latin hypercube is a sampling design where each parameter's range is split into equal-probability strata and exactly one sample falls in every stratum. The "Latin square" analogy comes from the property that any 2D projection has at most one point per row and per column. Extended to p parameters it becomes a "hypercube" because the same one-per-stratum property holds on every axis.
How is grid_latin_hypercube() different from grid_random()?
Both return size rows with one column per parameter, but Latin hypercube stratifies each axis so marginal samples are uniform, while grid_random() draws independently and can cluster. For a 25-row grid on penalty(), LHS guarantees one sample in each of 25 log10 strata; random gives no such guarantee. Use LHS as the default for continuous parameters; reserve random for very high dimensions or quick scans.
How many candidates should I draw with grid_latin_hypercube()?
For 2 parameters, 20 to 30 candidates beat a 5x5 regular grid on coverage. For 3 to 6 parameters, draw 50 to 100. Above that, switch to tune_bayes() because the marginal value of each extra LHS candidate drops fast in high dimensions and model-based search uses prior runs to focus the budget.
Can I tune qualitative parameters with grid_latin_hypercube()?
Yes. Qualitative parameters like weight_func() are treated as factors whose levels are stratified across the size rows. grid_latin_hypercube(neighbors(), weight_func(), size = 15) returns 15 rows where the numeric column spans 15 strata and the qualitative column visits each level proportionally. The stratification logic adapts via the dials class system.
Should I prefer grid_latin_hypercube() or grid_space_filling()?
Both produce the same sample when grid_space_filling(type = "latin_hypercube") is used; the umbrella lets one call switch designs by changing an argument. New code that compares LHS against max-entropy benefits from the umbrella. Code that has already chosen LHS at design time reads more clearly with the direct call.