Executes the analysis pipeline for every row of an expanded decision grid,
returning tidied results for each. Execution is parallel-ready: if
mirai::daemons() have been provisioned, the rows are distributed across the
daemons; otherwise they run sequentially. analyze_grid() never sets up
daemons itself — you provision them, and the function uses them if present.
Arguments
- .grid
a
tibbleproduced byexpand_decisions.- show_progress
logical, whether to show a progress bar while running.
- ship_base_df
logical (default
TRUE). Controls how the base data frame reaches the workers when running in parallel. WhenTRUE, the base data frame named in the grid's"base_df"attribute is resolved in the calling environment, shipped to each worker, and assigned there by name — the simplest option, suited to small-to-moderate in-memory data. WhenFALSE, the base data frame is not shipped; you are responsible for making it available on the workers under the name the pipeline code references, typically by establishing it on the daemons beforehand withmirai::everywhere()(e.g. opening an Arrow dataset on each worker). UseFALSEfor large or externally-stored data you do not want copied to every worker. Has no effect when running sequentially.- libraries
a character vector naming packages to load on each worker. Internally this calls
library()dynamically so that functions from the packages your pipeline uses are available during execution on the workers. Relevant when running in parallel viamirai::daemons(); include here any package your pipeline code calls that is not attached by default.- ...
Custom functions your pipeline references (e.g. a custom post-processing step), passed as
name = functionpairs (for examplemy_step = my_step). Each is made available on the workers under the given name, so pipeline code that calls it by name resolves. The name must match the name used in the pipeline.
Value
a single tibble containing tidied results for the model and
any post-processing tests/tasks. For each unique test (e.g., an lm
or aov called on an lm), a list column with the function name
is created with parameters and
performance and any warnings or messages printed
while fitting the models. A timing_logs list column records the
start, end, and duration of each row's run. The grid's "pipeline"
attribute is carried through to the result.
Parallel execution
To run in parallel, provision daemons before calling, e.g.
mirai::daemons(6). Each worker loads multitool, dplyr,
and any packages named in libraries; receives any custom functions
passed through ...; and (when ship_base_df = TRUE) the base
data frame. With no daemons set, execution falls back to sequential and these
provisions still apply locally. For large data, prefer
ship_base_df = FALSE with the data established on the daemons via
mirai::everywhere(), or an Arrow partition path baked into the pipeline so
each worker reads its own slice from storage.
Establishing the data on workers yourself (ship_base_df = FALSE)
With ship_base_df = FALSE, analyze_grid() does not ship the base
data frame — you are responsible for making it available on each worker, under
the same name your pipeline references. This avoids copying large data to
every worker: instead each worker holds its own handle (for example an Arrow
dataset opened from storage), established once with mirai::everywhere().
Two rules make this work. First, the object must live in each worker's global
environment, because that is where the pipeline code is resolved; the reliable
way to put it there is evalq(..., envir = .GlobalEnv) inside
everywhere(). Second, the name must match the grid's "base_df"
attribute exactly — the pipeline code refers to the data by that name, so the
object you establish must carry it.
# name must match attr(.grid, "base_df"), e.g. "coffee_analysis_df"
mirai::daemons(6)
mirai::everywhere(
evalq(
{
library(arrow)
coffee_analysis_df <- open_dataset("/absolute/path/to/data/")
},
envir = .GlobalEnv
)
)
analyzed_grid <- analyze_grid(pipeline_grid, ship_base_df = FALSE)
mirai::daemons(0)If the object is missing or misnamed on the workers, the pipeline code fails
to find it; because the failure occurs inside worker evaluation it may surface
as an opaque error rather than a clear "object not found", so check the name
match first if a parallel run with ship_base_df = FALSE fails.
Examples
library(tidyverse)
library(multitool)
# Simulate some data
the_data <-
data.frame(
id = 1:500,
iv1 = rnorm(500),
iv2 = rnorm(500),
iv3 = rnorm(500),
mod1 = rnorm(500),
mod2 = rnorm(500),
mod3 = rnorm(500),
cov1 = rnorm(500),
cov2 = rnorm(500),
dv1 = rnorm(500),
dv2 = rnorm(500),
include1 = rbinom(500, size = 1, prob = .1),
include2 = sample(1:3, size = 500, replace = TRUE),
include3 = rnorm(500)
)
# Decision pipeline
full_pipeline <-
the_data |>
add_filters(include1 == 0,include2 != 3,include2 != 2,scale(include3) > -2.5) |>
add_variables("ivs", iv1, iv2, iv3) |>
add_variables("dvs", dv1, dv2) |>
add_variables("mods", starts_with("mod")) |>
add_preprocess(process_name = "scale_iv", 'mutate({ivs} = scale({ivs}))') |>
add_preprocess(process_name = "scale_mod", mutate({mods} := scale({mods}))) |>
add_model("no covariates",lm({dvs} ~ {ivs} * {mods})) |>
add_model("covariate", lm({dvs} ~ {ivs} * {mods} + cov1)) |>
add_postprocess("ptp", predict())
pipeline_grid <- expand_decisions(full_pipeline)
# analyze the grid (sequential)
analyzed_grid <- analyze_grid(pipeline_grid[1:10,])
if (FALSE) { # \dontrun{
# analyze in parallel: provision daemons first
mirai::daemons(6)
analyzed_grid <- analyze_grid(pipeline_grid)
mirai::daemons(0)
} # }