<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/sharing-modeling-pipelines-in-r/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://www.win-vector.com/blog/2018/12/sharing-modeling-pipelines-in-r/"> R – Win-Vector Blog</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>Reusable modeling pipelines are a practical idea that gets re-developed many times in many contexts. <a rel="nofollow" href="https://github.com/WinVector/wrapr" target="_blank"><code>wrapr</code></a> supplies a particularly powerful pipeline notation, and a pipe-stage re-use system (notes <a rel="nofollow" href="https://winvector.github.io/wrapr/articles/Function_Objects.html" target="_blank">here</a>). We will demonstrate this with the <a rel="nofollow" href="https://github.com/WinVector/vtreat" target="_blank"><code>vtreat</code></a> data preparation system.</p>
<p><span></span></p>
<p>
<p>Our example task is to fit a model on some arbitrary data. Our model will try to predict <code>y</code> as a function of the other columns.</p>
<p>Our example data is 10,000 rows of 210 variables. Ten of the variables are related to the outcome to predict (<code>y</code>), and 200 of them are irrelevant pure noise. Since this is a synthetic example we know which is which (and deliberately encode this information in the column names).</p>
<p>The data looks like the following:</p>
<div>
<pre><code><span>str</span>(d)</code></pre>
</div>
<pre><code>## 'data.frame':    10000 obs. of  211 variables:
##  $ y        : num  -1.086 -1.246 -0.173 -1.087 1.661 ...
##  $ var_001  : num  0.0108 NA -0.0149 -0.2192 -0.1605 ...
##  $ var_002  : num  -3043966 NA -2151778 -4942298 455479 ...
##  $ var_003  : num  -60627 -34317 64535 -1940 NA ...
##  $ var_004  : num  -373812 NA -232051 21812 74992 ...
##  $ var_005  : num  29370 -296583 -57431 -52728 94272 ...
##  $ var_006  : num  -108112 NA -387325 -803572 -60534 ...
##  $ var_007  : num  19294 -85200 498536 -52584 -14377 ...
##  $ var_008  : num  -253 -1296 -163 -568 -862 ...
##  $ var_009  : num  -2.13e+09 3.96e+10 -4.20e+10 -1.11e+10 -6.93e+10 ...
##  $ var_010  : num  148.15 997.9 5.92 -552.88 NA ...
##  $ noise_001: num  1.61e-07 -5.21e-07 NA -8.80e-08 9.76e-07 ...
##  $ noise_002: num  854212 -221108 -654245 -1095501 NA ...
##  $ noise_003: num  -53443 -28367 NA 8445 21881 ...
##  $ noise_004: num  3422424 NA 2538108 -2222437 NA ...
##  $ noise_005: num  -1.08e+11 -2.91e+10 -2.16e+08 -1.18e+10 NA ...
 ...
##   [list output truncated]</code></pre>
<p>Let’s start our example analysis.</p>
<p>We load our packages.</p>
<div>
<pre><code><span>library</span>(<span>&quot;wrapr&quot;</span>)
<span>library</span>(<span>&quot;vtreat&quot;</span>)
<span>library</span>(<span>&quot;glmnet&quot;</span>)
<span>library</span>(<span>&quot;ggplot2&quot;</span>)
<span>library</span>(<span>&quot;WVPlots&quot;</span>)
<span>library</span>(<span>&quot;doParallel&quot;</span>)
<span>library</span>(<span>&quot;rqdatatable&quot;</span>)</code></pre>
</div>
<p>We will also need a couple of simple functions that are part of the upcoming <a rel="nofollow" href="https://github.com/WinVector/vtreat" target="_blank"><code>vtreat 1.3.3</code></a>.</p>
<div>
<pre><code><span># Always return the same cross-validation plan.</span>

<span># will be released as:</span>
<span># https://github.com/WinVector/vtreat/blob/master/R/pre_comp_xval.R</span>
pre_comp_xval &lt;-<span> </span><span>function</span>(nRows, nSplits, splitplan) {
  <span>force</span>(splitplan)
  <span>attr</span>(splitplan, <span>'splitmethod'</span>) &lt;-<span> </span><span>paste</span>(<span>attr</span>(splitplan, <span>'splitmethod'</span>),
                                          <span>&quot;( pre-computed&quot;</span>, nRows, nSplits, <span>&quot;)&quot;</span>)
  f &lt;-<span> </span><span>function</span>(nRows, nSplits, dframe, y) {
    <span>return</span>(splitplan)
  }
  f
}

<span># Center and scale some variables in a data.frame.</span>
<span>#</span>
<span># will be released as:</span>
<span># https://github.com/WinVector/vtreat/blob/master/R/center_scale.R</span>
center_scale &lt;-<span> </span><span>function</span>(d, 
                         center,
                         scale) {
  <span>for</span>(ni <span>in</span> <span>names</span>(center)) {
    d[[ni]] &lt;-<span> </span>d[[ni]] <span>-</span><span> </span>center[[ni]]
  }
  <span>for</span>(ni <span>in</span> <span>names</span>(scale)) {
    d[[ni]] &lt;-<span> </span>d[[ni]]<span>/</span>scale[[ni]]
  }
  d
}</code></pre>
</div>
<p>We set up a parallel cluster to speed up some calculations.</p>
<div>
<pre><code>ncore &lt;-<span> </span>parallel<span>::</span><span>detectCores</span>()
cl &lt;-<span> </span>parallel<span>::</span><span>makeCluster</span>(ncore)
<span>registerDoParallel</span>(cl)</code></pre>
</div>
<p>We split our data into training and a test evaluation set.</p>
<div>
<pre><code>is_train &lt;-<span> </span><span>runif</span>(<span>nrow</span>(d))<span>&lt;=</span><span>0.5</span>
dTrain &lt;-<span> </span>d[is_train, , drop =<span> </span><span>FALSE</span>]
dTest &lt;-<span> </span>d[<span>!</span>is_train, , drop =<span> </span><span>FALSE</span>]
outcome_name &lt;-<span> &quot;y&quot;</span>
vars &lt;-<span> </span><span>setdiff</span>(<span>colnames</span>(dTrain), outcome_name)</code></pre>
</div>
<p>Suppose our analysis plan is the following:</p>
<ul>
<li>Fix missing values with <code>vtreat</code>.</li>
<li>Scale and center the original variables (but not the new indicator variables).</li>
<li>Model <code>y</code> as a function of the other columns using <code>glmnet</code>.</li>
</ul>
<p>Now both <code>vtreat</code> and <code>glmnet</code> can scale, but we are going to keep the scaling as a separate step to control which variables are scaled, and to show how composite data preparation pipelines work.</p>
<p>We fit a model with cross-validated data treatment and hyper-parameters as follows. The process described is intentionally long and involved, simulating a number of steps (possibly some requiring domain knowledge) taken by a data scientist to build a good model.</p>
<div>
<pre><code><span># design a cross-validation plan</span>
ncross &lt;-<span> </span><span>5</span>
cplan &lt;-<span> </span>vtreat<span>::</span><span>kWayStratifiedY</span>(
  <span>nrow</span>(dTrain), ncross, dTrain, dTrain[[outcome_name]])

<span># design a treatment plan using cross-validation methods</span>
cp &lt;-<span> </span>vtreat<span>::</span><span>mkCrossFrameNExperiment</span>(
  dTrain, vars, outcome_name,
  <span>splitFunction =</span> <span>pre_comp_xval</span>(<span>nrow</span>(dTrain), ncross, cplan),
  <span>ncross =</span> ncross,
  <span>parallelCluster =</span> cl)</code></pre>
</div>
<pre><code>## [1] &quot;vtreat 1.3.2 start initial treatment design Tue Dec 11 18:39:57 2018&quot;
## [1] &quot; start cross frame work Tue Dec 11 18:40:00 2018&quot;
## [1] &quot; vtreat::mkCrossFrameNExperiment done Tue Dec 11 18:40:08 2018&quot;</code></pre>
<div>
<pre><code><span>print</span>(cp<span>$</span>method)</code></pre>
</div>
<pre><code>## [1] &quot;kwaycrossystratified ( pre-computed 5053 5 )&quot;</code></pre>
<div>
<pre><code><span># get the list of new variables</span>
sf &lt;-<span> </span>cp<span>$</span>treatments<span>$</span>scoreFrame
newvars &lt;-<span> </span>sf<span>$</span>varName[sf<span>$</span>sig <span>&lt;=</span><span> </span><span>1</span><span>/</span><span>nrow</span>(sf)]
<span>print</span>(newvars)</code></pre>
</div>
<pre><code>##  [1] &quot;var_001_clean&quot;   &quot;var_001_isBAD&quot;   &quot;var_002_clean&quot;  
##  [4] &quot;var_002_isBAD&quot;   &quot;var_003_clean&quot;   &quot;var_003_isBAD&quot;  
##  [7] &quot;var_004_clean&quot;   &quot;var_004_isBAD&quot;   &quot;var_005_clean&quot;  
## [10] &quot;var_005_isBAD&quot;   &quot;var_006_clean&quot;   &quot;var_006_isBAD&quot;  
## [13] &quot;var_007_clean&quot;   &quot;var_007_isBAD&quot;   &quot;var_008_clean&quot;  
## [16] &quot;var_008_isBAD&quot;   &quot;var_009_clean&quot;   &quot;var_009_isBAD&quot;  
## [19] &quot;var_010_clean&quot;   &quot;var_010_isBAD&quot;   &quot;noise_156_isBAD&quot;</code></pre>
<div>
<pre><code><span># learn a centering and scaling of the cross-validated </span>
<span># training frame</span>
vars_to_scale =<span> </span><span>intersect</span>(newvars, sf<span>$</span>varName[sf<span>$</span>code<span>==</span><span>&quot;clean&quot;</span>])
<span>print</span>(vars_to_scale)</code></pre>
</div>
<pre><code>##  [1] &quot;var_001_clean&quot; &quot;var_002_clean&quot; &quot;var_003_clean&quot; &quot;var_004_clean&quot;
##  [5] &quot;var_005_clean&quot; &quot;var_006_clean&quot; &quot;var_007_clean&quot; &quot;var_008_clean&quot;
##  [9] &quot;var_009_clean&quot; &quot;var_010_clean&quot;</code></pre>
<div>
<pre><code><span># learn the centering and scalling on the &quot;cross-frame&quot;</span>
<span># training data.</span>
tfs &lt;-<span> </span><span>scale</span>(cp<span>$</span>crossFrame[, vars_to_scale, <span>drop =</span> <span>FALSE</span>], 
             <span>center =</span> <span>TRUE</span>, <span>scale =</span> <span>TRUE</span>)
centering &lt;-<span> </span><span>attr</span>(tfs, <span>&quot;scaled:center&quot;</span>)
scaling &lt;-<span> </span><span>attr</span>(tfs, <span>&quot;scaled:scale&quot;</span>)

<span># apply the centering and scaling to the cross-validated </span>
<span># training frame</span>
tfs &lt;-<span> </span><span>center_scale</span>(cp<span>$</span>crossFrame[, newvars, <span>drop =</span> <span>FALSE</span>],
                    <span>center =</span> centering,
                    <span>scale =</span> scaling)

<span># convert the cross-validation plan to cv.glmnet group notation</span>
foldid &lt;-<span> </span><span>numeric</span>(<span>nrow</span>(dTrain))
<span>for</span>(i <span>in</span> <span>seq_len</span>(<span>length</span>(cplan))) {
  cpi &lt;-<span> </span>cplan[[i]]
  foldid[cpi<span>$</span>app] &lt;-<span> </span>i
}

<span># search for best cross-validated alpha for cv.glmnet</span>
alphas &lt;-<span> </span><span>seq</span>(<span>0</span>, <span>1</span>, <span>by=</span><span>0.05</span>)
cross_scores &lt;-<span> </span><span>lapply</span>(
  alphas,
  <span>function</span>(alpha) {
    model &lt;-<span> </span><span>cv.glmnet</span>(<span>as.matrix</span>(tfs), 
                       cp<span>$</span>crossFrame[[outcome_name]],
                       <span>alpha =</span> alpha,
                       <span>family =</span> <span>&quot;gaussian&quot;</span>, 
                       <span>standardize =</span> <span>FALSE</span>,
                       <span>foldid =</span> foldid, 
                       <span>parallel =</span> <span>TRUE</span>)
    index &lt;-<span> </span><span>which</span>(model<span>$</span>lambda <span>==</span><span> </span>model<span>$</span>lambda.min)[[<span>1</span>]]
    score &lt;-<span> </span>model<span>$</span>cvm[[index]]
    res &lt;-<span> </span><span>data.frame</span>(<span>score =</span> score, <span>best_lambda =</span> model<span>$</span>lambda.min)
    res<span>$</span>lambdas &lt;-<span> </span><span>list</span>(model<span>$</span>lambda)
    res<span>$</span>cvm &lt;-<span> </span><span>list</span>(model<span>$</span>cvm)
    res
  })
cross_scores &lt;-<span> </span><span>do.call</span>(rbind, cross_scores)
cross_scores<span>$</span>alpha =<span> </span>alphas
best_i &lt;-<span> </span><span>which</span>(cross_scores<span>$</span>score<span>==</span><span>min</span>(cross_scores<span>$</span>score))[[<span>1</span>]]
alpha &lt;-<span> </span>alphas[[best_i]]
s &lt;-<span> </span>cross_scores<span>$</span>best_lambda[[best_i]]
lambdas &lt;-<span> </span>cross_scores<span>$</span>lambdas[[best_i]]
lambdas &lt;-<span> </span>lambdas[lambdas<span>>=</span>s]

<span>#print chosen hyper-params</span>
<span>print</span>(alpha)</code></pre>
</div>
<pre><code>## [1] 0.05</code></pre>
<div>
<pre><code><span>print</span>(s)</code></pre>
</div>
<pre><code>## [1] 0.002446381</code></pre>
<div>
<pre><code><span># show cross-val results</span>
<span>ggplot</span>(<span>data =</span> cross_scores,
       <span>aes</span>(<span>x =</span> alpha, <span>y =</span> score)) <span>+</span>
<span> </span><span>geom_point</span>() <span>+</span>
<span> </span><span>geom_line</span>() <span>+</span>
<span> </span><span>ggtitle</span>(<span>&quot;best cross validated mean loss as function of alpha&quot;</span>)</code></pre>
</div>
<p><img border="0" alt="Model1 1" title="model1-1.png" src="https://i0.wp.com/www.win-vector.com/blog/wp-content/uploads/2018/12/model1-1.png?w=660" data-recalc-dims="1"></p>
<div>
<pre><code>pf &lt;-<span> </span><span>data.frame</span>(<span>s =</span> cross_scores<span>$</span>lambdas[[best_i]],
                 <span>cvm =</span> cross_scores<span>$</span>cvm[[best_i]])
<span>ggplot</span>(<span>data =</span> pf,
       <span>aes</span>(<span>x =</span> s, <span>y =</span> cvm)) <span>+</span>
<span> </span><span>geom_point</span>() <span>+</span>
<span> </span><span>geom_line</span>() <span>+</span>
<span> </span><span>scale_x_log10</span>() <span>+</span>
<span> </span><span>ggtitle</span>(<span>&quot;cross validated mean loss as function of lambda/s&quot;</span>,
          <span>subtitle =</span> <span>paste</span>(<span>&quot;alpha =&quot;</span>, alpha))</code></pre>
</div>
<p><img border="0" alt="Model1 2" title="model1-2.png" src="https://i0.wp.com/www.win-vector.com/blog/wp-content/uploads/2018/12/model1-2.png?w=660" data-recalc-dims="1"></p>
<div>
<pre><code><span># re-fit model with chosen alpha</span>
model &lt;-<span> </span><span>glmnet</span>(<span>as.matrix</span>(tfs), 
                cp<span>$</span>crossFrame[[outcome_name]],
                <span>alpha =</span> alpha,
                <span>family =</span> <span>&quot;gaussian&quot;</span>, 
                <span>standardize =</span> <span>FALSE</span>,
                <span>lambda =</span> lambdas)</code></pre>
</div>
<p>At this point we have model that works on prepared data (data that has gone through the <code>vtreat</code> and scaling steps). The point to remember: it was a lot of steps to transform data and build the model, so it may also be a fair number of steps to apply the model.</p>
<p>The question then is: how do we share such a model? Roughly we need to share the model, any fit parameters (such as centering and scaling choices), <em>and</em> the code sequence to apply all of these steps in the proper order. In this case the modeling pipeline consists of the following pieces:</p>
<ul>
<li>The treatment plan <code>cp$treatments</code>.</li>
<li>The list of chosen variables <code>newvars</code>.</li>
<li>The centering and scaling vectors <code>centering</code> and <code>scaling</code>.</li>
<li>The <code>glmnet</code> model <code>model</code> and final chosen lambda/s value <code>s</code>.</li>
</ul>
<p>These values are needed to run any news data through the sequence of operations:</p>
<ul>
<li>Using <code>vtreat</code> to prepare the data.</li>
<li>Re-scaling and centering the chosen variables.</li>
<li>Converting from a <code>data.frame</code> to a matrix of only input-variable columns.</li>
<li>Applying the <code>glmnet</code> model.</li>
<li>Converting the matrix of predictions into a vector of predictions.</li>
</ul>
<p>These are all steps we did in an ad-hoc manner while building the model. Having worked hard to build the model (taking a lot of steps and optimizing parameters/hyperparemeters) has left us with a lot of items and steps we need to share to have the full prediction process.</p>
<p>A really neat way to simply share of these things is the following.</p>
<p>Use <code>wrapr</code>’s <a rel="nofollow" href="https://winvector.github.io/wrapr/articles/Function_Objects.html" target="_blank">“function object” abstraction</a>, which treats names of functions, plus arguments as an efficient notation for partial evaluation. We can use this system to encode our model prediction pipeline as follows.</p>
<div>
<pre><code>pipeline &lt;-
<span> </span><span>pkgfn</span>(<span>&quot;vtreat::prepare&quot;</span>,
        <span>arg_name =</span> <span>&quot;dframe&quot;</span>, 
        <span>args =</span> <span>list</span>(<span>treatmentplan =</span> cp<span>$</span>treatments,
                    <span>varRestriction =</span> newvars)) <span>%.>%</span>
<span> </span><span>wrapfn</span>(center_scale,
        <span>arg_name =</span> <span>&quot;d&quot;</span>,
        <span>args =</span> <span>list</span>(<span>center =</span> centering,
                    <span>scale =</span> scaling))  <span>%.>%</span>
<span> </span><span>srcfn</span>(<span>qe</span>(<span>as.matrix</span>(.[, newvars, <span>drop =</span> <span>FALSE</span>])),
        <span>args =</span> <span>list</span>(<span>newvars =</span> newvars)) <span>%.>%</span>
<span> </span><span>pkgfn</span>(<span>&quot;glmnet::predict.glmnet&quot;</span>,
        <span>arg_name =</span> <span>&quot;newx&quot;</span>,
        <span>args =</span> <span>list</span>(<span>object =</span> model,
                    <span>s =</span> s))  <span>%.>%</span>
<span> </span><span>srcfn</span>(<span>qe</span>(.[, cname, <span>drop =</span> <span>TRUE</span>]),
        <span>args =</span> <span>list</span>(<span>cname =</span> <span>&quot;1&quot;</span>))

<span>cat</span>(<span>format</span>(pipeline))</code></pre>
</div>
<pre><code>## UnaryFnList(
##    vtreat::prepare(dframe=., treatmentplan, varRestriction),
##    PartialFunction{center_scale}(d=., center, scale),
##    SrcFunction{ as.matrix(.[, newvars, drop = FALSE]) }(.=., newvars),
##    glmnet::predict.glmnet(newx=., object, s),
##    SrcFunction{ .[, cname, drop = TRUE] }(.=., cname))</code></pre>
<p>The above pipeline uses several <code>wrapr</code> abstractions:</p>
<ul>
<li><a rel="nofollow" href="https://winvector.github.io/wrapr/reference/pkgfn.html" target="_blank"><code>pkgfn()</code></a> which wraps a function specified by a package qualified name. When used the function is called with the pipeline argument as the first argument (and named <code>arg_name</code>), and extra arguments supplied from the list <code>args</code>.</li>
<li><a rel="nofollow" href="https://winvector.github.io/wrapr/reference/wrapfn.html" target="_blank"><code>wrapfn()</code></a> which wraps a function specified by value. When used the function is called with the pipeline argument as the first argument (and named <code>arg_name</code>), and extra arguments supplied from the list <code>args</code>.</li>
<li><a rel="nofollow" href="https://winvector.github.io/wrapr/reference/srcfn.html" target="_blank"><code>srcfn()</code></a> which wraps quoted code (here quoted by <a rel="nofollow" href="https://winvector.github.io/wrapr/reference/qe.html" target="_blank"><code>wrapr::qe()</code></a>, but quote marks will also work). When used the function is evaluated in an environment with the pipeline argument mapped to the name specified in <code>arg_name</code> (defaults to <code>.</code>), and the additional arguments from <code>args</code> available in the evaluation environment.</li>
</ul>
<p>Each of these captures the action and extra values needed to perform each step of the model application. The steps can be chained together by pipes (as shown above), or assembled directly as a list using <a rel="nofollow" href="https://winvector.github.io/wrapr/reference/fnlist.html" target="_blank"><code>fnlist()</code></a> or <a rel="nofollow" href="https://winvector.github.io/wrapr/reference/as_fnlist.html" target="_blank">as_fnlist()</a>. Function lists can be built all at once, or concatenated together from pieces. More details on <code>wrapr</code> function objects can be found <a rel="nofollow" href="https://winvector.github.io/wrapr/articles/Function_Objects.html" target="_blank">here</a>.</p>
<p>After all this you can then pipe data into the pipeline to get predictions.</p>
<div>
<pre><code>dTrain <span>%.>%</span><span> </span>pipeline <span>%.>%</span><span> </span><span>head</span>(.)</code></pre>
</div>
<pre><code>## [1] -0.60372843  0.46662315  0.15205810  0.39812493  0.44087441  0.09160836</code></pre>
<div>
<pre><code>dTest <span>%.>%</span><span> </span>pipeline <span>%.>%</span><span> </span><span>head</span>(.)</code></pre>
</div>
<pre><code>## [1]  0.532070422 -0.046165380 -1.347887772  0.007668392 -1.133345162
## [6]  0.662722678</code></pre>
<p>Or you can use a functional notation <a rel="nofollow" href="https://winvector.github.io/wrapr/reference/ApplyTo.html" target="_blank"><code>ApplyTo()</code></a>.</p>
<div>
<pre><code><span>head</span>(<span>ApplyTo</span>(pipeline, dTrain))</code></pre>
</div>
<pre><code>## [1] -0.60372843  0.46662315  0.15205810  0.39812493  0.44087441  0.09160836</code></pre>
<p>The pipeline itself is an <code>R</code> <code>S4</code> class containing a simple list of steps.</p>
<div>
<pre><code>pipeline<span>@</span>items</code></pre>
</div>
<pre><code>## [[1]]
## [1] &quot;vtreat::prepare(dframe=., treatmentplan, varRestriction)&quot;
## 
## [[2]]
## [1] &quot;PartialFunction{center_scale}(d=., center, scale)&quot;
## 
## [[3]]
## [1] &quot;SrcFunction{ as.matrix(.[, newvars, drop = FALSE]) }(.=., newvars)&quot;
## 
## [[4]]
## [1] &quot;glmnet::predict.glmnet(newx=., object, s)&quot;
## 
## [[5]]
## [1] &quot;SrcFunction{ .[, cname, drop = TRUE] }(.=., cname)&quot;</code></pre>
<div>
<pre><code><span>str</span>(pipeline<span>@</span>items[[<span>3</span>]])</code></pre>
</div>
<pre><code>## Formal class 'SrcFunction' [package &quot;wrapr&quot;] with 3 slots
##   ..@ expr_src: chr &quot;as.matrix(.[, newvars, drop = FALSE])&quot;
##   ..@ arg_name: chr &quot;.&quot;
##   ..@ args    :List of 1
##   .. ..$ newvars: chr [1:21] &quot;var_001_clean&quot; &quot;var_001_isBAD&quot; &quot;var_002_clean&quot; &quot;var_002_isBAD&quot; ...</code></pre>
<p>The pipeline can be saved, and contains the required parameters in simple lists.</p>
<div>
<pre><code><span>saveRDS</span>(dTrain, <span>&quot;dTrain.RDS&quot;</span>)
<span>saveRDS</span>(pipeline, <span>&quot;pipeline.RDS&quot;</span>)</code></pre>
</div>
<p>Now the processing pipeline can be read back and used as follows.</p>
<div>
<pre><code><span># Fresh R session , not part of this markdown</span>
<span>library</span>(<span>&quot;wrapr&quot;</span>)

pipeline &lt;-<span> </span><span>readRDS</span>(<span>&quot;pipeline.RDS&quot;</span>)
dTrain &lt;-<span> </span><span>readRDS</span>(<span>&quot;dTrain.RDS&quot;</span>)
dTrain <span>%.>%</span><span> </span>pipeline <span>%.>%</span><span> </span><span>head</span>(.)</code></pre>
</div>
<pre><code>## [1] -0.60372843  0.46662315  0.15205810  0.39812493  0.44087441  0.09160836</code></pre>
<p>We can use this pipeline on different data, as we do to create performance plots below.</p>
<div>
<pre><code>dTrain<span>$</span>prediction &lt;-<span> </span>dTrain <span>%.>%</span><span> </span>pipeline

WVPlots<span>::</span><span>ScatterHist</span>(
  dTrain, <span>&quot;prediction&quot;</span>, <span>&quot;y&quot;</span>, <span>&quot;fit on training data&quot;</span>,
  <span>smoothmethod =</span> <span>&quot;identity&quot;</span>,
  <span>estimate_sig =</span> <span>TRUE</span>,
  <span>point_alpha =</span> <span>0.1</span>,
  <span>contour =</span> <span>TRUE</span>)</code></pre>
</div>
<p><img border="0" src="https://i2.wp.com/www.win-vector.com/blog/wp-content/uploads/2018/12/unnamed-chunk-10-1.png?resize=660%2C471" alt="Unnamed chunk 10 1" width="450" title="unnamed-chunk-10-1.png" data-recalc-dims="1"></p>
<div>
<pre><code>dTest<span>$</span>prediction &lt;-<span> </span>dTest <span>%.>%</span><span> </span>pipeline

WVPlots<span>::</span><span>ScatterHist</span>(
  dTest, <span>&quot;prediction&quot;</span>, <span>&quot;y&quot;</span>, <span>&quot;fit on test&quot;</span>,
  <span>smoothmethod =</span> <span>&quot;identity&quot;</span>,
  <span>estimate_sig =</span> <span>TRUE</span>,
  <span>point_alpha =</span> <span>0.1</span>,
  <span>contour =</span> <span>TRUE</span>)</code></pre>
</div>
<p><img border="0" src="https://i1.wp.com/www.win-vector.com/blog/wp-content/uploads/2018/12/unnamed-chunk-10-2.png?resize=660%2C471" alt="Unnamed chunk 10 2" width="450" title="unnamed-chunk-10-2.png" data-recalc-dims="1"><br>
</p>
<p>The idea is: the work was complicated, but sharing should not be complicated.</p>
<p>And that is how to effectively save, share, and deploy non-trivial modeling workflows.</p>
<p>(The source for this example can be found <a rel="nofollow" href="https://github.com/WinVector/vtreat/blob/master/extras/ModelingPipelines.Rmd" target="_blank">here</a>. More on <code>wrapr</code> function objects can be found <a rel="nofollow" href="https://winvector.github.io/wrapr/articles/Function_Objects.html" target="_blank">here</a>. We also have another run <a rel="nofollow" href="https://github.com/WinVector/vtreat/blob/master/extras/ModelingPipelinesH.md" target="_blank">here</a> showing why we do not recommend always using the number of variables as “just another hyper-parameter”, but instead using simple threshold based filtering. The coming version of <code>vtreat</code> also has a new non-linear variable filter function called <a rel="nofollow" href="https://winvector.github.io/vtreat/reference/value_variables_N.html" target="_blank">value_variables_*()</a>.)</p>
<div>
<pre><code><span># clean-up</span>
parallel<span>::</span><span>stopCluster</span>(cl)</code></pre>
</div>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="http://www.win-vector.com/blog/2018/12/sharing-modeling-pipelines-in-r/"> R – Win-Vector Blog</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/4ZW3KdE95sA" height="1"></a></div>