---
title: "Combining pipelines"
output:
  rmarkdown::html_vignette:
    toc: true
    toc_depth: 4
description: >
  Shows how to combine different pipelines to a single pipeline.
vignette: >
  %\VignetteIndexEntry{Combining pipelines}
  %\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)
```

The possibility to combine pipelines basically allows to modularize the
pipeline creation process. This is especially useful when you have a set of
pipelines that are used in different contexts and you want to avoid code
duplication.

### Two pipelines

Let's define one pipeline that is used for data preprocessing and one that does
the modeling.

Data preprocessing pipeline:
```{r define-prepocessing-pipeline}
library(pipeflow)

pip1 <- pip_new("preprocessing") |>
    pip_add(
        "data",
        function(data = airquality) data
    ) |>
    pip_add(
        "data_prep",
        function(data = ~data) {
            replace(data, "Temp.Celsius", (data[, "Temp"] - 32) * 5 / 9)
        }
    ) |>
    pip_add(
        "standardize",
        function(
            data = ~data_prep,
            yVar = "Ozone"
        ) {
            data[, yVar] <- scale(data[, yVar])
            data
        }
    )
```

```{r}
pip1
```

Modelling pipeline:
```{r define-modeling-pipeline}
pip2 <- pip_new("modeling") |>
    pip_add(
        "data",
        function(data = airquality) data
    ) |>
    pip_add(
        "fit",
        function(
            data = ~data,
            xVar = "Temp",
            yVar = "Ozone"
        ) {
            lm(paste(yVar, "~", xVar), data = data)
        }
    ) |>
    pip_add(
        "plot",
        function(
            model = ~fit,
            data = ~data,
            xVar = "Temp",
            yVar = "Ozone",
            title = "Linear model fit"
        ) {
            require(ggplot2, quietly = TRUE)
            coeffs <- coefficients(model)
            ggplot(data) +
                geom_point(aes(.data[[xVar]], .data[[yVar]])) +
                geom_abline(intercept = coeffs[1], slope = coeffs[2]) +
                labs(title = title)
        }
    )
```


```{r}
pip2
```


### Combined pipeline

Next we combine the two pipelines using `pip_bind()`.

```{r}
pip <- pip_bind(pip1, pip2)

pip
```

First of all, note that the `data` step of the second pipeline
has been renamed automatically to avoid name clashes. In particular, the first
step of the second pipeline has been renamed from `data` to `data2`
(line 4 in the `step` column) and likewise the data-dependencies of the
second pipeline have been updated (see lines 5-6 in the `depends` column).

That is, when binding two pipelines, {pipeflow} ensures that the
step names remain unique in the resulting combined pipeline and therefore
automatically renames duplicated step names if necessary.

Now, as can be also seen from the graphical representation of the pipeline,

```{r, eval = FALSE}
library(visNetwork)
do.call(visNetwork, args = pip_get_graph(pip)) |>
    visHierarchicalLayout(direction = "LR")
```

```{r, echo = FALSE}
library(visNetwork)
do.call(
    visNetwork,
    args = c(pip_get_graph(pip), list(height = 250, width = 500))
) |>
    visHierarchicalLayout(direction = "LR", sortMethod = "directed")
```

the two pipelines are not yet connected. To make actual use of the combined
pipeline, we have to use the output of the first pipeline as input of
the second pipeline, that is, we want to use the output of the
`standardize` step as the data parameter input in the `data2` step.
To achieve this, we apply the `replace` function as described earlier
in the vignette [modify the pipeline](v02-modify-pipeline.html):

```{r}
pip |> pip_replace("data2", function(data = ~standardize) data)

pip
```

```{r, echo = FALSE}
do.call(
    visNetwork,
    args = c(pip_get_graph(pip), list(height = 100, width = 700))
) |>
    visHierarchicalLayout(direction = "LR")
```

#### Relative indexing

Since the name of the re-routed step might not always be known^[A typical
example would be appending several pipelines in a programmatic context.],
the {pipeflow} package also provides a relative position indexing mechanism,
which allows to rewrite the above command as follows:

```{r}
pip |> pip_replace("data2", function(data = ~ -1) data)

pip
```

Generally speaking, the relative indexing mechanism allows to refer to steps positioned
above the current step. The index `~-1` can be interpreted as "go one step back", `~-2`
as "go two steps back", and so on.


### Combined pipeline results

Let's now run the combined pipeline and inspect the plot.

```{r}
pip_run(pip)
```

```{r, fig.alt = "model-plot"}
pip[["plot", "out"]]
```

As we can see, the plot shows the linear model fit of the standardized data.
We can now go ahead and for example change the x-variable of the model and rerun
the pipeline.

```{r}
pip_set_params(pip, params = list(xVar = "Temp.Celsius"))
```

```{r, echo = FALSE}
library(visNetwork)
do.call(
    visNetwork,
    args = c(pip_get_graph(pip), list(height = 100, width = 700))
) |>
    visHierarchicalLayout(direction = "LR", sortMethod = "directed")
```

```{r}
pip_run(pip)
```

```{r, fig.alt = "model-plot"}
pip[["plot", "out"]]
```

### Step cherry-picking

Another way to re-use steps from other pipelines is by cherry-picking,
which can be done via `pip_add_from`, for example:

```{r cherry-pick}
pip <- pip_new("cherry-picked-from-1-and-2") |>
    pip_add_from(pip1, "data") |>
    pip_add_from(pip1, "data_prep") |>
    pip_add_from(pip1, "standardize") |>
    pip_add_from(pip2, "fit") |>
    pip_add_from(pip2, "plot")

pip
```

Note that here the cherry-pick approach is not all that useful,
because in contrast to the `pip_bind` command,
which renames `data` to `data2`, the cherry-picked `fit` and `plot` steps
still refer to the initial `data` step.

```{r, echo = FALSE}
library(visNetwork)
do.call(
    visNetwork,
    args = c(pip_get_graph(pip), list(height = 250, width = 700))
) |>
    visHierarchicalLayout(direction = "LR", sortMethod = "directed")
```

In other scenarios, however, this might be exactly what you want.

Generally, when creating these pipelines, there will be a lot of steps calculating
intermediate results and only a few steps contain the final output we are
interested in (see e.g. the `plot` output in the above example).
To see how {pipeflow} allows to conveniently tag, collect and possibly group
those final outputs,
see the next vignette [Collecting and filtering output](v04-collect-output.html).

```{r, include = FALSE}
options(old)
```
