<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/writing-better-r-functions-part-one-april-6-2018/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="http://ibecav.github.io/betterfunctions/"> Chuck Powell</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>One of the nicest things about working with <code>R</code> is that with very little<br>
effort you can customize and automate activities to produce the output<br>
you want – just the way you want it. You can contrast that with more<br>
monolithic packages that may allow you to do a bit of scripting, but for<br>
the most part, the price of a GUI or packaging everything in one package<br>
is that you lose the ability to have things just your way. Since<br>
everything in <code>R</code> is pretty much a function already, you may as well<br>
invest a little time and energy in making functions… your way, and to<br>
exactly your tastes and needs. This post is not meant to be an<br>
exhaustive or complete treatment of writing a function. For that you<br>
probably want a book, or at least a <a rel="nofollow" href="https://adv-r.hadley.nz/functions.html" target="_blank">Chapter like the one Hadley has in<br>
Advanced R</a>. This post will<br>
focus on a very practical, and hopefully useful, single example.</p>
<p>In my last three posts I have been writing about automating activities<br>
in R. You can <a rel="nofollow" href="https://ibecav.github.io/Functionalize/" target="_blank">review everything that happened in the first<br>
post</a>, <a rel="nofollow" href="https://ibecav.github.io/RtoExcel/" target="_blank">as well as the<br>
second</a>, <a rel="nofollow" href="https://ibecav.github.io/customize/" target="_blank">or the<br>
third</a> (<strong>which I strongly<br>
recommend</strong>), or you can start on this page. There is no need to save<br>
the same dataset or go through the process of building that dataset if<br>
you don’t want to. For our purposes in this post we’re going to make use<br>
of the built in dataframe known as <code>mtcars</code>. We’re doing that to make<br>
sure that whatever we do in this post today, it works on a known start<br>
point so we can compare and contrast. One of the most painful learning<br>
experiences you can have in <code>R</code> is to discover that you have written<br>
something so specific it won’t generalize to other data or other<br>
situations.</p>
<h2>Background and catch-up</h2>
<p>In our earlier postings we dealt with our desire to have some automated<br>
tools (functions) that took pairs of variables from a dataset and<br>
produced some nice useful <code>ggplot</code> plots from them. We started with the<br>
simplest case like plotting counts of how two variables cross-tabulate<br>
and then worked our way up to being able to automate the process of<br>
plotting lots of pairings of variables from the same dataframe. Today<br>
we’ll improve our functions even more and add some <strong>features</strong>.</p>
<p>First some basic setup.</p>
<div>
<div>
<pre><code><span>knitr</span><span>::</span><span>opts_chunk</span><span>$</span><span>set</span><span>(</span><span>echo</span><span> </span><span>=</span><span> </span><span>TRUE</span><span>,</span><span> </span><span>warning</span><span> </span><span>=</span><span> </span><span>FALSE</span><span>)</span><span>
</span><span>library</span><span>(</span><span>dplyr</span><span>)</span><span>
</span></code></pre>
</div>
</div>
<div>
<div>
<pre><code>## 
## Attaching package: 'dplyr'

## The following objects are masked from 'package:stats':
## 
##     filter, lag

## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
</code></pre>
</div>
</div>
<div>
<div>
<pre><code><span>library</span><span>(</span><span>ggplot2</span><span>)</span><span>
</span><span>theme_set</span><span>(</span><span>theme_bw</span><span>())</span><span> </span><span># set theme to my personal preference</span><span>
</span></code></pre>
</div>
</div>
<h2>Where we left off</h2>
<p>At the end of the last post we had accomplished two important feats:</p>
<ol>
<li>We had a function called <code>PlotMe</code> that took the name of a dataframe<br>
and two variables, cross-tabulated their counts, and created a nice<br>
plot for us.</li>
<li>We had some lines of code (not yet a function) that took the name of<br>
a dataframe and the numbers of the columns we were interested in,<br>
and created two lists that we could feed to <code>mapply</code> so that we<br>
could make lots of plots with little additional effort</li>
</ol>
<p>Along the way we learned the <strong>“tricks”</strong> of working with <code>dplyr</code> and<br>
<code>ggplot2</code> inside of functions ^[1]^. So using the <code>mtcars</code> dataset as<br>
our example data we started here with something that works in the<br>
console:</p>
<div>
<div>
<pre><code><span>### with dplyr and ggplot manually</span><span>
</span><span>mtcars</span><span> </span><span>%>%</span><span>
</span><span>filter</span><span>(</span><span>!</span><span>is.na</span><span>(</span><span>am</span><span>),</span><span> </span><span>!</span><span>is.na</span><span>(</span><span>cyl</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>group_by</span><span>(</span><span>am</span><span>,</span><span>cyl</span><span>)</span><span> </span><span>%>%</span><span>
</span><span>count</span><span>()</span><span> </span><span>%>%</span><span>
</span><span>ggplot</span><span>(</span><span>aes</span><span>(</span><span>fill</span><span>=</span><span>am</span><span>,</span><span> </span><span>y</span><span>=</span><span>n</span><span>,</span><span> </span><span>x</span><span>=</span><span>cyl</span><span>))</span><span> </span><span>+</span><span>
</span><span>geom_bar</span><span>(</span><span>position</span><span>=</span><span>&quot;dodge&quot;</span><span>,</span><span> </span><span>stat</span><span>=</span><span>&quot;identity&quot;</span><span>)</span><span>
</span></code></pre>
</div>
</div>
<p><img alt="" src="https://i0.wp.com/ibecav.github.io/images/betterfunctions1-1.png?w=456" data-recalc-dims="1"></p>
<p>then turned it into a function after we learned about <code>NSE</code>:</p>
<div>
<div>
<pre><code><span>PlotMe</span><span> </span><span>&lt;-</span><span> </span><span>function</span><span>(</span><span>dataframe</span><span>,</span><span>x</span><span>,</span><span>y</span><span>){</span><span>
</span><span>aaa</span><span> </span><span>&lt;-</span><span> </span><span>enquo</span><span>(</span><span>x</span><span>)</span><span>
</span><span>bbb</span><span> </span><span>&lt;-</span><span> </span><span>enquo</span><span>(</span><span>y</span><span>)</span><span>
</span><span>dataframe</span><span> </span><span>%>%</span><span>
</span><span>filter</span><span>(</span><span>!</span><span>is.na</span><span>(</span><span>!!</span><span> </span><span>aaa</span><span>),</span><span> </span><span>!</span><span>is.na</span><span>(</span><span>!!</span><span> </span><span>bbb</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>group_by</span><span>(</span><span>!!</span><span> </span><span>aaa</span><span>,</span><span>!!</span><span> </span><span>bbb</span><span>)</span><span> </span><span>%>%</span><span>
</span><span>count</span><span>()</span><span> </span><span>%>%</span><span>
</span><span>ggplot</span><span>(</span><span>aes_</span><span>(</span><span>fill</span><span>=</span><span>aaa</span><span>,</span><span> </span><span>y</span><span>=~</span><span>n</span><span>,</span><span> </span><span>x</span><span>=</span><span>bbb</span><span>))</span><span> </span><span>+</span><span>
</span><span>geom_bar</span><span>(</span><span>position</span><span>=</span><span>&quot;dodge&quot;</span><span>,</span><span> </span><span>stat</span><span>=</span><span>&quot;identity&quot;</span><span>)</span><span> </span><span>-></span><span>p</span><span>
</span><span>plot</span><span>(</span><span>p</span><span>)</span><span>
</span><span>}</span><span>
</span><span>PlotMe</span><span>(</span><span>mtcars</span><span>,</span><span>am</span><span>,</span><span>cyl</span><span>)</span><span>
</span></code></pre>
</div>
</div>
<p><img alt="" src="https://i0.wp.com/ibecav.github.io/images/betterfunctions2-1.png?w=456" data-recalc-dims="1"></p>
<blockquote>
<p><strong>Note that with <code>dplyr</code> if we don’t <code>filter</code> out <code>NA</code>’s we will see<br>
them plotted which may or may not be what you want substantively!</strong></p>
</blockquote>
<blockquote>
<p><strong>From this point forward I’m going to print the plots in a smaller<br>
size. I’m doing that via RMarkdown and it won’t happen automatically<br>
for you if you download and use the code.</strong></p>
</blockquote>
<p>We also wrote some code that allows us to be more efficient if we want<br>
to print multiple pairings in the same data set. The <code>cat</code> statement is<br>
unnecessary I simply inserted it so you can see how the loops provide<br>
what we need. We’ll remove it in the final version of the function most<br>
likely.</p>
<div>
<div>
<pre><code><span># Build two vectors</span><span>
</span><span>xwhich</span><span> </span><span>&lt;-</span><span> </span><span>c</span><span>(</span><span>2</span><span>,</span><span>10</span><span>:</span><span>11</span><span>)</span><span> </span><span># let's put cyl, gear, and carb in here</span><span>
</span><span>ywhich</span><span> </span><span>&lt;-</span><span> </span><span>c</span><span>(</span><span>8</span><span>:</span><span>9</span><span>)</span><span> </span><span># let's put vs and am in here</span><span>
</span><span>indvars</span><span>&lt;-</span><span>list</span><span>()</span><span> </span><span># create empty list to add to</span><span>
</span><span>depvars</span><span>&lt;-</span><span>list</span><span>()</span><span> </span><span># create empty list to add to</span><span>
</span><span>totalcombos</span><span> </span><span>&lt;-</span><span> </span><span>1</span><span> </span><span># keep track of where we are</span><span>
</span><span># loop through the vectors and build our lists</span><span>
</span><span>for</span><span> </span><span>(</span><span>j</span><span> </span><span>in</span><span> </span><span>seq_along</span><span>(</span><span>xwhich</span><span>))</span><span> </span><span>{</span><span>
</span><span>for</span><span> </span><span>(</span><span>k</span><span> </span><span>in</span><span> </span><span>seq_along</span><span>(</span><span>ywhich</span><span>))</span><span> </span><span>{</span><span>
</span><span>depvars</span><span>[[</span><span>totalcombos</span><span>]]</span><span> </span><span>&lt;-</span><span> </span><span>as.name</span><span>(</span><span>colnames</span><span>(</span><span>mtcars</span><span>[</span><span>xwhich</span><span>[[</span><span>j</span><span>]]]))</span><span>
</span><span>indvars</span><span>[[</span><span>totalcombos</span><span>]]</span><span> </span><span>&lt;-</span><span> </span><span>as.name</span><span>(</span><span>colnames</span><span>(</span><span>mtcars</span><span>[</span><span>ywhich</span><span>[[</span><span>k</span><span>]]]))</span><span>
</span><span>cat</span><span>(</span><span>&quot;iteration #&quot;</span><span>,</span><span> </span><span>totalcombos</span><span>,</span><span> </span><span>&quot; xwhich=&quot;</span><span>,</span><span> </span><span>xwhich</span><span>[[</span><span>j</span><span>]],</span><span> </span><span>&quot; depvars = &quot;</span><span>,</span><span> </span><span>as.name</span><span>(</span><span>colnames</span><span>(</span><span>mtcars</span><span>[</span><span>xwhich</span><span>[[</span><span>j</span><span>]]])),</span><span>
</span><span>&quot; ywhich=&quot;</span><span>,</span><span> </span><span>ywhich</span><span>[[</span><span>k</span><span>]],</span><span> </span><span>&quot; indvars = &quot;</span><span>,</span><span> </span><span>as.name</span><span>(</span><span>colnames</span><span>(</span><span>mtcars</span><span>[</span><span>ywhich</span><span>[[</span><span>k</span><span>]]])),</span><span>
</span><span>&quot;\n&quot;</span><span>,</span><span> </span><span>sep</span><span> </span><span>=</span><span> </span><span>&quot;&quot;</span><span>)</span><span>
</span><span>totalcombos</span><span> </span><span>&lt;-</span><span> </span><span>totalcombos</span><span> </span><span>+1</span><span>
</span><span>}</span><span>
</span><span>}</span><span>
</span></code></pre>
</div>
</div>
<div>
<div>
<pre><code>## iteration #1 xwhich=2 depvars = cyl ywhich=8 indvars = vs
## iteration #2 xwhich=2 depvars = cyl ywhich=9 indvars = am
## iteration #3 xwhich=10 depvars = gear ywhich=8 indvars = vs
## iteration #4 xwhich=10 depvars = gear ywhich=9 indvars = am
## iteration #5 xwhich=11 depvars = carb ywhich=8 indvars = vs
## iteration #6 xwhich=11 depvars = carb ywhich=9 indvars = am
</code></pre>
</div>
</div>
<p>This code produces two lists with the column names varying in the way we<br>
want them. Then we can pass it to our <code>PlotMe</code> function to get our 6<br>
plots back as desired. So <code>mapply(PlotMe, x=indvars, y=depvars, MoreArgs<br>
= list(dataframe=mtcars))</code>.</p>
<h2>Making our function better</h2>
<p>Other things we’d like to accomplish:</p>
<ol>
<li>Do a better job of labeling the plot properly.</li>
<li>Add some basic error checking and simple fixes</li>
<li>Let the user choose from different options for which graph type<br>
communicates their points about the data.</li>
<li>Convert the second block of code into a proper function.</li>
</ol>
<p>Let’s start with the first item. As a minimum we can add a title with<br>
<code>ggtitle(&quot;Crosstabulation of mtcars variables&quot;)</code> and <code>ylab(&quot;Count&quot;)</code></p>
<div>
<div>
<pre><code><span>PlotMe</span><span> </span><span>&lt;-</span><span> </span><span>function</span><span>(</span><span>dataframe</span><span>,</span><span>x</span><span>,</span><span>y</span><span>){</span><span>
</span><span>aaa</span><span> </span><span>&lt;-</span><span> </span><span>enquo</span><span>(</span><span>x</span><span>)</span><span>
</span><span>bbb</span><span> </span><span>&lt;-</span><span> </span><span>enquo</span><span>(</span><span>y</span><span>)</span><span>
</span><span>dataframe</span><span> </span><span>%>%</span><span>
</span><span>filter</span><span>(</span><span>!</span><span>is.na</span><span>(</span><span>!!</span><span> </span><span>aaa</span><span>),</span><span> </span><span>!</span><span>is.na</span><span>(</span><span>!!</span><span> </span><span>bbb</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>group_by</span><span>(</span><span>!!</span><span> </span><span>aaa</span><span>,</span><span>!!</span><span> </span><span>bbb</span><span>)</span><span> </span><span>%>%</span><span>
</span><span>count</span><span>()</span><span> </span><span>%>%</span><span>
</span><span>ggplot</span><span>(</span><span>aes_</span><span>(</span><span>fill</span><span>=</span><span>aaa</span><span>,</span><span> </span><span>y</span><span>=~</span><span>n</span><span>,</span><span> </span><span>x</span><span>=</span><span>bbb</span><span>))</span><span> </span><span>+</span><span>
</span><span>geom_bar</span><span>(</span><span>position</span><span>=</span><span>&quot;dodge&quot;</span><span>,</span><span> </span><span>stat</span><span>=</span><span>&quot;identity&quot;</span><span>)</span><span> </span><span>+</span><span>
</span><span>ggtitle</span><span>(</span><span>&quot;Crosstabulation of mtcars variables&quot;</span><span>)</span><span> </span><span>+</span><span>
</span><span>ylab</span><span>(</span><span>&quot;Count&quot;</span><span>)</span><span> </span><span>-></span><span>p</span><span>
</span><span>plot</span><span>(</span><span>p</span><span>)</span><span>
</span><span>}</span><span>
</span><span>PlotMe</span><span>(</span><span>mtcars</span><span>,</span><span>am</span><span>,</span><span>cyl</span><span>)</span><span>
</span></code></pre>
</div>
</div>
<p><img alt="" src="https://i0.wp.com/ibecav.github.io/images/betterfunctions4-1.png?w=456" data-recalc-dims="1"></p>
<p>Totally uninspired but serviceable. Better yet is to use <code>bquote</code> and<br>
the <code>.()</code> notation to make it more pertinent and portable. Notice that<br>
we had to create a new object called <code>dfname</code> to hold the name of the<br>
dataframe and that the name is quoted. So that means that inside our<br>
function <code>dataframe</code> actually refers to the whole dataset <code>mtcars</code> all<br>
the rows and columns and data itself. <code>dfname</code> on the other hand, is<br>
just a way for us to print out the word <strong>mtcars</strong> without having to<br>
hard code it in. No matter what dataframe we pass in to the function the<br>
right name gets printed. <strong>NB</strong> friendly reminder that if you try and<br>
cheat and pass <code>x</code> or <code>y</code> to bquote they will fail miserably the <code>enquo</code><br>
is essential.</p>
<div>
<div>
<pre><code><span>PlotMe</span><span> </span><span>&lt;-</span><span> </span><span>function</span><span>(</span><span>dataframe</span><span>,</span><span>x</span><span>,</span><span>y</span><span>){</span><span>
</span><span>aaa</span><span> </span><span>&lt;-</span><span> </span><span>enquo</span><span>(</span><span>x</span><span>)</span><span>
</span><span>bbb</span><span> </span><span>&lt;-</span><span> </span><span>enquo</span><span>(</span><span>y</span><span>)</span><span>
</span><span>dfname</span><span> </span><span>&lt;-</span><span> </span><span>enquo</span><span>(</span><span>dataframe</span><span>)</span><span>
</span><span>dataframe</span><span> </span><span>%>%</span><span>
</span><span>filter</span><span>(</span><span>!</span><span>is.na</span><span>(</span><span>!!</span><span> </span><span>aaa</span><span>),</span><span> </span><span>!</span><span>is.na</span><span>(</span><span>!!</span><span> </span><span>bbb</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>group_by</span><span>(</span><span>!!</span><span> </span><span>aaa</span><span>,</span><span>!!</span><span> </span><span>bbb</span><span>)</span><span> </span><span>%>%</span><span>
</span><span>count</span><span>()</span><span> </span><span>%>%</span><span>
</span><span>ggplot</span><span>(</span><span>aes_</span><span>(</span><span>fill</span><span>=</span><span>aaa</span><span>,</span><span> </span><span>y</span><span>=~</span><span>n</span><span>,</span><span> </span><span>x</span><span>=</span><span>bbb</span><span>))</span><span> </span><span>+</span><span>
</span><span>geom_bar</span><span>(</span><span>position</span><span>=</span><span>&quot;dodge&quot;</span><span>,</span><span> </span><span>stat</span><span>=</span><span>&quot;identity&quot;</span><span>)</span><span> </span><span>+</span><span>
</span><span>ggtitle</span><span>(</span><span>bquote</span><span>(</span><span>&quot;Crosstabs&quot;</span><span>~</span><span>.</span><span>(</span><span>dfname</span><span>)</span><span>*</span><span>.</span><span>(</span><span>aaa</span><span>)</span><span>~</span><span>&quot;by&quot;</span><span>*</span><span>.</span><span>(</span><span>bbb</span><span>)))</span><span> </span><span>+</span><span>
</span><span>ylab</span><span>(</span><span>&quot;Count&quot;</span><span>)</span><span> </span><span>-></span><span>p</span><span>
</span><span>plot</span><span>(</span><span>p</span><span>)</span><span>
</span><span>}</span><span>
</span><span>PlotMe</span><span>(</span><span>mtcars</span><span>,</span><span>am</span><span>,</span><span>cyl</span><span>)</span><span>
</span></code></pre>
</div>
</div>
<p><img alt="" src="https://i1.wp.com/ibecav.github.io/images/betterfunctions5-1.png?w=456" data-recalc-dims="1"></p>
<p>Okay enough for now. Maybe later we’ll do something about <em>am</em> and <em>cyl</em><br>
as labels. If the <code>mtcars</code> dataframe used better column names we<br>
wouldn’t have this problem but we’re good enough for now. We have a<br>
much “bigger” problem in my mind. Because <code>am</code> is an integer our<br>
<code>ggplot</code> is sort of ugly. Remember we built it for a different data set<br>
where the variables of interest were already <code>factors</code> not <code>integers</code>.<br>
<code>am</code> is a factor (whether or not the car has an automatic transmission)<br>
posing as an integer but <code>ggplot</code> doesn’t know that and tries to<br>
helpfully give us a display suitable for a number not a factor.</p>
<p>That’s the fun of building and testing a function, it’s looking for all<br>
the ways you can go wrong. So let’s fix this because that funny shaded<br>
bar for <code>am</code> is driving me crazy.</p>
<p>Now if we were doing this with <code>dplyr</code> outside of a function it would be<br>
simple. What we want is just…</p>
<div>
<div>
<pre><code><span>mtcars</span><span> </span><span>%>%</span><span>
</span><span>filter</span><span>(</span><span>!</span><span>is.na</span><span>(</span><span>am</span><span>),</span><span> </span><span>!</span><span>is.na</span><span>(</span><span>cyl</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>mutate</span><span>(</span><span>am</span><span> </span><span>=</span><span> </span><span>factor</span><span>(</span><span>am</span><span>),</span><span> </span><span>cyl</span><span> </span><span>=</span><span> </span><span>factor</span><span>(</span><span>cyl</span><span>))</span><span> </span><span>%>%</span><span>
</span><span>group_by</span><span>(</span><span>am</span><span>,</span><span>cyl</span><span>)</span><span> </span><span>%>%</span><span>
</span><span>count</span><span>()</span><span>
</span></code></pre>
</div>
</div>
<div>
<div>
<pre><code>## # A tibble: 6 x 3
## # Groups:   am, cyl [6]
##   am    cyl       n
##   </code></pre></div></div>