--- title: "Collecting and filtering output" output: rmarkdown::html_vignette: toc: true toc_depth: 4 description: > Shows how to tag, filter, and collect pipeline output. vignette: > %\VignetteIndexEntry{Collecting and filtering output} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r knitr-setup, include = FALSE} knitr::opts_chunk$set( comment = "#", prompt = FALSE, tidy = FALSE, cache = FALSE, collapse = TRUE ) old <- options(width = 100L) ``` Generally speaking, one should keep pipeline steps as simple as possible, basically following the principle *"one step, one task"*. Splitting up analysis steps into multiple functions naturally can be hard to manage, but since {pipeflow} manages all function and parameter dependencies for you, this is not a problem. Following this principle, usually a lot of pipeline steps will carry intermediate results and only a few steps will contain the final output we are interested in. This vignette shows how to conveniently tag, collect, and filter pipeline outputs using tags and views. ### Tagging steps Tags let you label steps with meaningful keywords so you can later filter the pipeline by topic, stage, or output type. Tags can be set during pipeline creation or later using `pip_set_tags()`. ```{r pipeline-with-output} library(pipeflow) pip <- pip_new("my-pip") |> pip_add( "data", function(data = airquality) data ) |> pip_add("data_prep", function(data = ~data) { replace(data, "Temp.Celsius", (data[, "Temp"] - 32) * 5 / 9) }, tags = "data" # <-- set 'data' tag ) |> pip_add( "data_summary", function( data = ~data_prep, xVar = "Temp.Celsius", yVar = "Ozone" ) { format(summary(data[, c(xVar, yVar)])) |> as.data.frame(row.names = NA) }, tags = c("data", "summary") # <-- set 'data' and 'summary' tags ) |> pip_add( "data_plot", function( data = ~data_prep, xVar = "Temp.Celsius", yVar = "Ozone" ) { require(ggplot2, quietly = TRUE) ggplot(data) + geom_point(aes(.data[[xVar]], .data[[yVar]])) + labs(title = "Data") }, tags = c("data", "plot") # <-- set 'data' and 'plot' tags ) |> pip_add( "model_fit", function( data = ~data_prep, xVar = "Temp.Celsius", yVar = "Ozone" ) { lm(paste(yVar, "~", xVar), data = data) }, tags = c("model", "fit") # <-- set 'model' and 'fit' tags ) |> pip_add( "model_summary", function(fit = ~model_fit) { summary(fit) |> coefficients() |> as.data.frame() }, tags = c("model", "summary") # <-- set 'model' and 'summary' tags ) |> pip_add( "model_plot", function( model = ~model_fit, data_plot = ~data_plot, xVar = "Temp.Celsius", yVar = "Ozone" ) { coeffs <- coefficients(model) data_plot + geom_abline(intercept = coeffs[1], slope = coeffs[2]) + labs(title = "Linear model fit") }, tags = c("model", "plot") # <-- set 'model' and 'plot' tags ) ``` We used two families of tags: `"data"`/`"model"` to distinguish the topic, and `"summary"`/`"plot"`/`"fit"` for the output type. Whenever tags are defined, they are shown in the pipeline overview: ```{r} pip ``` Before showing how to make use of the tags, let's run the pipeline and inspect the output individually as we did in the previous vignettes. ```{r} pip_run(pip) pip ``` ```{r, message = FALSE, warning = FALSE} pip[["data_plot", "out"]] ``` ```{r, message = FALSE, warning = FALSE} pip[["model_plot", "out"]] ``` ### Flat output collection `pip_collect_out()` returns all step outputs as a flat named list. ```{r} out <- pip_collect_out(pip) names(out) ``` ### Filtered output using tags To collect only the output of steps with a specific tag, we use `pip_view()`, which is {pipeflow}'s general-purpose function for filtering pipelines, and then call `pip_collect_out()` on the filtered pipeline. To collect only the plots, for example, we can filter by the `plot` tag. ```{r} pip_view(pip, tags = "plot") ``` ```{r grouped-plots, message = FALSE, warning = FALSE} pip |> pip_view(tags = "plot") |> pip_collect_out() |> gridExtra::grid.arrange(grobs = _, nrow = 2) ``` ### Grouped output via views Grouped output is achieved by composing `pip_view()` with `pip_collect_out()`. For example, to collect outputs grouped by topic (`"data"` and `"model"`): ```{r} grouped <- list( data = pip_view(pip, tags = "data") |> pip_collect_out(), model = pip_view(pip, tags = "model") |> pip_collect_out() ) names(grouped[["data"]]) names(grouped[["model"]]) ``` ### More on views To make the example a bit more interesting, we first update some parameters. ```{r} pip_set_params(pip, params = list(xVar = "Solar.R", yVar = "Wind")) pip ``` {pipeflow} views provide a variety of filtering options. In the previous section, the filtering was done based on tags, but you can also filter based on other properties, for example, all steps that depend on the `model_fit` step: ```{r} pip |> pip_view(filter = list(depends = "model_fit", state = "outdated")) ``` or using regex-based filtering, for example, to filter all outdated steps starting with `data`: ```{r} pip |> pip_view(filter = list(step = "^data", state = "outdated"), fixed = FALSE) ``` Views can also be chained together: ```{r} v <- pip |> pip_view(filter = list(state = "outdated")) v v2 <- v |> pip_view(tags = "plot") v2 ``` Last but not least, views can be run as pipelines themselves, which allows to conveniently re-run only the filtered steps, while {pipeflow} ensures that any upstream dependencies are run first if needed. ```{r} v2 |> pip_run() ``` Having a closer look at the run log, you'll see which steps were re-run as part of the `[view]` and which were re-run as `[upstream]` dependencies. Since all views work by reference on the given pipeline, the original pipeline is now up-to-date for the filtered steps. ```{r} pip ``` ```{r, include = FALSE} options(old) ```