<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/one-recipe-step-to-rule-them-all/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://smaakage85.netlify.com/2018/12/03/one-recipe-step-to-rule-them-all/"> Posts on pRopaganda by smaakagen</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>In this post I will demonstrate, how my new R package <code>customsteps</code> can be used to create recipe steps, that apply custom transformations to a data set.</p>
<p>Note, you should already be fairly familiar with the <a rel="nofollow" href="https://cran.r-project.org/web/packages/recipes/recipes.pdf" target="_blank"><code>recipes</code></a> package before you continue reading this post or give <code>customsteps</code> a spin!</p>
<p>Recommended music for this reading session:</p>
<p><iframe src="https://open.spotify.com/embed/track/323v1HB9ugfmQyqZwFaZwz" width="300" height="80"><br>
</iframe></p>
<div>
<h2>Introducing the <code>customsteps</code> package</h2>
<p>Along with the <code>recipes</code> package distribution comes a number of pre-specified steps, that enables the user to manipulate data sets in various ways. The resulting data sets (/design matrices) can then be used as inputs into statistical or machine learning models.</p>
<p>If you want to apply a specific transformation to your data set, that is not supported by the pre-specified steps, you have two options. You can write an entire <a rel="nofollow" href="https://github.com/tidymodels/recipes/blob/master/vignettes/Custom_Steps.Rmd" target="_blank">custom recipe step <strong>from scratch</strong></a>. This however takes quite a bit of work and code. An alternative – and sometimes better – approach is to apply the <code>customsteps</code> package, that I have just released on CRAN.</p>
<pre><code># install.packages(&quot;customsteps&quot;)
library(customsteps)</code></pre>
<div>
<h3>Customizable Higher-Order Steps</h3>
<p><code>customsteps</code> contains a set of customizable higher-order recipe step functions, that create specifications of recipe steps, that will transform or filter the data in accordance with custom input functions.</p>
<p>Let me just remind you of the definition of <a rel="nofollow" href="https://en.wikipedia.org/wiki/Higher-order_function" target="_blank"><strong>higher-order functions</strong></a>:</p>
<blockquote>
<p><em>In mathematics and computer science, a higher-order function is a function that does at least one of the following: 1. takes one or more functions as arguments, 2. returns a function as its result.</em></p>
</blockquote>
<p>Next, I will present an example of how to use the <code>customsteps</code> package in order to create a recipe step, that will apply a custom transformation to a data set.</p>
</div>
</div>
<div>
<h2>Use Case: Centering and Scaling Numeric Data</h2>
<p>Assume, that I want to transform a variable <span>\({\mathbf{x}}\)</span> like this:</p>
<ol>
<li>Center <span>\({\mathbf{x}}\)</span> around an arbitrary number <span>\(\alpha\)</span>.</li>
<li>Scale the transformed variable, such that its standard deviation equals an arbitrary number <span>\(\beta\)</span>.</li>
</ol>
<p>The transformed variable <span>\(\hat{\mathbf{x}}\)</span> can then be derived as (try to do it yourself):</p>
<p><span>\(\hat{\mathbf{x}} = \alpha + (\mathbf{x} – \bar{\mathbf{x}})\frac{\beta}{s_\mathbf{x}}\)</span></p>
<p>where <span>\(\bar{\mathbf{x}}\)</span> is the mean of <span>\(\mathbf{x}\)</span>, and <span>\(s_\mathbf{x}\)</span> is the standard deviation of <span>\({\mathbf{x}}\)</span>.</p>
<p>Note that centering <span>\({\mathbf{x}}\)</span> around 0 and scaling it in order to arrive at a standard deviation of 1 is just a special case of the above transformation with parameters <span>\(\alpha = 0, \beta = 1\)</span>.</p>
<div>
<h3>Write the <code>prep</code> helper function</h3>
<p>First, I need to write a function, that estimates the relevant statistical parameters from an initial data set. I call this function the <code>prep</code> helper function.</p>
<p>Obviously, the above transformation requires the mean <span>\(\bar{\mathbf{x}}\)</span> and standard deviation <span>\(s_\mathbf{x}\)</span> to be learned from the initial data set. Therefore I define a function <code>compute_means_sd</code>, that estimates the two parameters for (any arbitrary number of) numeric variables.</p>
<p>By convention the <code>prep</code> helper function must take the argument <code>x</code>: the subset of selected variables from the initial data set.</p>
<pre><code>library(purrr)

compute_means_sd &lt;- function(x) {
  
  map(.x = x, ~ list(mean = mean(.x), sd = sd(.x)))

}</code></pre>
<p>Let us see the function in action. I will apply it to a subset of the famous <code>mtcars</code> data set.</p>
<pre><code>library(dplyr)

# divide 'mtcars' into two data sets.
cars_initial &lt;- mtcars[1:16, ]
cars_new &lt;- mtcars[17:nrow(mtcars), ]

# learn parameters from initial data set.
params &lt;- cars_initial %>%
  select(mpg, disp) %>%
  compute_means_sd(.)

# display parameters. 
as.data.frame(params)
#>   mpg.mean  mpg.sd disp.mean disp.sd
#> 1     18.2 4.14761  250.8187 113.372</code></pre>
<p>It works like a charm. Great, we are halfway there!</p>
</div>
<div>
<h3>Write the <code>bake</code> helper function</h3>
<p>Second, I have to specify a <code>bake</code> helper function, that defines how to apply the transformation to a new data set using the parameters estimated from the intial data set.</p>
<p>By convention the <code>bake</code> helper function must take the following arguments:</p>
<ul>
<li><code>x</code>: the new data set, that the step will be applied to.</li>
<li><code>prep_output</code>: the output from the <code>prep</code> helper function containing any parameters estimated from the initial data set.</li>
</ul>
<p>I define the function <code>center_scale</code>, that will serve as my <code>bake</code> helper function. It will center and scale variables of a new data set.</p>
<pre><code>center_scale &lt;- function(x, prep_output, alpha, beta) {

  # extract only the relevant variables from the new data set.
  new_data &lt;- select(x, names(prep_output))

  # apply transformation to each of these variables.
  # variables are centered around 'alpha' and scaled to have a standard 
  # deviation of 'beta'.
  map2(.x = new_data,
       .y = prep_output,
       ~ alpha + (.x - .y$mean) * beta / .y$sd)
  
}</code></pre>
<p>My first (sanity) check of the function is to apply it to the initial data set, that was used for estimation of the means and standard deviations.</p>
<pre><code>library(tibble)
# center and scale variables of new data set to have a mean of zero
# and a standard deviation of one.
cars_initial_transformed &lt;- center_scale(x = cars_initial, 
                                         prep_output = params,
                                         alpha = 0, 
                                         beta = 1)

# display transformed variables.
cars_initial_transformed %>%
  compute_means_sd(.) %>%
  as.data.frame(.)
#>       mpg.mean mpg.sd    disp.mean disp.sd
#> 1 1.731877e-16      1 7.199102e-17       1</code></pre>
<p>Results are correct within computational precision.</p>
<p>Also, I will just check the function out on the other subset of <code>mtcars</code>.</p>
<pre><code># center and scale variables of new data set to have a mean of zero
# and a standard deviation of one.
cars_new_transformed &lt;- center_scale(x = cars_new, 
                                     prep_output = params,
                                     alpha = 0, 
                                     beta = 1)

# display transformed variables.
cars_new_transformed %>%
  as.tibble(.) %>%
  head(.)
#> # A tibble: 6 x 2
#>      mpg   disp
#>    </code></pre></div></div>