Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

In this post, I walk through the code I used to make a nice diagram illustrating
the parameters in a logistic growth curve. I made this figure for a conference
submission. I had a tight word limit (600 words) and a complicated
statistical method (Bayesian nonlinear mixed effects beta regression), so I
wanted to use a diagram to carry some of the expository load. Also, figures
didn’t count towards the word limit, so that was a bonus 😀.

Here I will cover a few different topics:

• The pieces of the three-parameter logistic curve
• What the murky “scale” parameter does in the curve
• How to use plotmath to add mathematical copy to a plot

## Growth towards a ceiling

Children can be hard to understand; they are learning to talk after all. You
“pwetty pwease”. This understandability problem is compounded for children with
cerebral palsy, because these kids will often have speech-motor impairments on
top of the usual developmental patterns. My current project is a statistical
model of how intelligibility—the probability that an unfamiliar listener
understands what a child says—develops from age 3 to age 8 in children with
cerebral palsy.

As an example, the R code plots some (simulated) data that represents a single
child. They visited our lab 6 times, so we have intelligibility measures for
each of those visits.

<span class="n">library</span><span class="p">(</span><span class="n">tidyverse</span><span class="p">)</span><span class="w">
</span><span class="c1">#> -- Attaching packages ----------------------------- tidyverse 1.2.1 --</span><span class="w">
</span><span class="c1">#> √ ggplot2 3.1.0     √ readr   1.3.1</span><span class="w">
</span><span class="c1">#> √ tibble  2.0.1     √ purrr   0.3.0</span><span class="w">
</span><span class="c1">#> √ tidyr   0.8.2     √ stringr 1.4.0</span><span class="w">
</span><span class="c1">#> √ ggplot2 3.1.0     √ forcats 0.3.0</span><span class="w">
</span><span class="c1">#> -- Conflicts -------------------------------- tidyverse_conflicts() --</span><span class="w">
</span><span class="c1">#> x dplyr::filter() masks stats::filter()</span><span class="w">
</span><span class="c1">#> x dplyr::lag()    masks stats::lag()</span><span class="w">
</span><span class="n">theme_set</span><span class="p">(</span><span class="n">theme_minimal</span><span class="p">())</span><span class="w">

</span><span class="n">points</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">tibble</span><span class="p">(</span><span class="w">
</span><span class="n">age</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">38</span><span class="p">,</span><span class="w"> </span><span class="m">45</span><span class="p">,</span><span class="w"> </span><span class="m">52</span><span class="p">,</span><span class="w"> </span><span class="m">61</span><span class="p">,</span><span class="w"> </span><span class="m">80</span><span class="p">,</span><span class="w"> </span><span class="m">74</span><span class="p">),</span><span class="w">
</span><span class="n">prop</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">0.146</span><span class="p">,</span><span class="w"> </span><span class="m">0.241</span><span class="p">,</span><span class="w"> </span><span class="m">0.571</span><span class="p">,</span><span class="w"> </span><span class="m">0.745</span><span class="p">,</span><span class="w"> </span><span class="m">0.843</span><span class="p">,</span><span class="w"> </span><span class="m">0.738</span><span class="p">))</span><span class="w">

</span><span class="n">colors</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="nf">list</span><span class="p">(</span><span class="w">
</span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"#41414550"</span><span class="p">,</span><span class="w">
</span><span class="c1"># data = "grey80",</span><span class="w">
</span><span class="n">fit</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"#414145"</span><span class="p">)</span><span class="w">

</span><span class="n">ggplot</span><span class="p">(</span><span class="n">points</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">aes</span><span class="p">(</span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">age</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">prop</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">geom_point</span><span class="p">(</span><span class="n">size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3.5</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">data</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">scale_x_continuous</span><span class="p">(</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Age in months"</span><span class="p">,</span><span class="w"> </span><span class="n">limits</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">96</span><span class="p">),</span><span class="w"> </span><span class="c1"># Because age is in months, I want breaks to land on multiples</span><span class="w"> </span><span class="c1"># of 12. The Q in extended_breaks() are "nice" numbers to use</span><span class="w"> </span><span class="c1"># for axis breaks.</span><span class="w"> </span><span class="n">breaks</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scales</span><span class="o">::</span><span class="n">extended_breaks</span><span class="p">(</span><span class="n">Q</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">24</span><span class="p">,</span><span class="w"> </span><span class="m">12</span><span class="p">)))</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">scale_y_continuous</span><span class="p">(</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Intelligibility"</span><span class="p">,</span><span class="w"> </span><span class="n">limits</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="kc">NA</span><span class="p">),</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scales</span><span class="o">::</span><span class="n">percent_format</span><span class="p">(</span><span class="n">accuracy</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="p">))</span><span class="w"> </span> One of the interesting features of speech development is that it finishes: Children stop making their usual developmental speech patterns and converge on a mature level of performance. They will, no doubt, continue grow and change through adolescence, but when it comes to making speech sounds accurately and reliably, most of the developmental change is done by age 8. For the statistical models, therefore, we expected children to follow a certain developmental trajectory towards a ceiling: Begin at zero intelligibility, show a period of accelerating then decelerating growth, and finally plateau at some mature level of ability. This pattern of growth can be modelled using a logistic growth curve using three parameters: an asymptote, a midpoint when growth is steepest, and a scale which sets the slope of the curve. Below is the equation of the logistic growth curve: But this equation doesn’t do us any good. If you are like me, you probably stopped paying attention when you saw exp() in the denominator. Here’s the logistic curve plotted for these data. <span class="n">xs</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">seq</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">96</span><span class="p">,</span><span class="w"> </span><span class="n">length.out</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">80</span><span class="p">)</span><span class="w"> </span><span class="c1"># Create the curve from the equation parameters</span><span class="w"> </span><span class="n">trend</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">tibble</span><span class="p">(</span><span class="w"> </span><span class="n">age</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">xs</span><span class="p">,</span><span class="w"> </span><span class="n">asymptote</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.8</span><span class="p">,</span><span class="w"> </span><span class="n">scale</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.2</span><span class="p">,</span><span class="w"> </span><span class="n">midpoint</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">48</span><span class="p">,</span><span class="w"> </span><span class="n">prop</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">asymptote</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="p">(</span><span class="m">1</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nf">exp</span><span class="p">((</span><span class="n">midpoint</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">age</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">scale</span><span class="p">)))</span><span class="w"> </span><span class="n">ggplot</span><span class="p">(</span><span class="n">points</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">aes</span><span class="p">(</span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">age</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">prop</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">geom_line</span><span class="p">(</span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">trend</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">fit</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">geom_point</span><span class="p">(</span><span class="n">size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3.5</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">data</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">scale_x_continuous</span><span class="p">(</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Age in months"</span><span class="p">,</span><span class="w"> </span><span class="n">limits</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">96</span><span class="p">),</span><span class="w"> </span><span class="n">breaks</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scales</span><span class="o">::</span><span class="n">extended_breaks</span><span class="p">(</span><span class="n">Q</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">24</span><span class="p">,</span><span class="w"> </span><span class="m">12</span><span class="p">)))</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">scale_y_continuous</span><span class="p">(</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Intelligibility"</span><span class="p">,</span><span class="w"> </span><span class="n">limits</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="kc">NA</span><span class="p">),</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scales</span><span class="o">::</span><span class="n">percent_format</span><span class="p">(</span><span class="n">accuracy</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="p">))</span><span class="w"> </span> Now, let’s add some labels to mark some key parts of the equation. One unfamiliar bit of ggplot technology here might be annotate(). Geometry functions like geom_point() or geom_text() are used to draw data that lives in a dataframe using aesthetic mappings defined in aes(), and these function draws some geometry (like a point or a label) for each row of the data. But we don’t have rows and rows of data to draw. annotate() is meant to handle these one-off annotations, and we set the aesthetics manually instead of pulling them from some data. The first argument of annotate() says what kind of geom to use for the annotation: for example, "text" calls on geom_text() and "segment" calls on geom_segment(). <span class="n">colors</span><span class="o">$</span><span class="n">asym</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="s2">"#E7552C"</span><span class="w">
</span><span class="n">colors</span><span class="o">$</span><span class="n">mid</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="s2">"#3B7B9E"</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">scale</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="s2">"#1FA35C"</span><span class="w">

</span><span class="n">p</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">ggplot</span><span class="p">(</span><span class="n">points</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">aes</span><span class="p">(</span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">age</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">prop</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">annotate</span><span class="p">(</span><span class="w">
</span><span class="s2">"segment"</span><span class="p">,</span><span class="w">
</span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">mid</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">48</span><span class="p">,</span><span class="w"> </span><span class="n">xend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">48</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="n">yend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.4</span><span class="p">,</span><span class="w"> </span><span class="n">linetype</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"dashed"</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">annotate</span><span class="p">(</span><span class="w"> </span><span class="s2">"segment"</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">asym</span><span class="p">,</span><span class="w">
</span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">20</span><span class="p">,</span><span class="w"> </span><span class="n">xend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">Inf</span><span class="p">,</span><span class="w">
</span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.8</span><span class="p">,</span><span class="w"> </span><span class="n">yend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.8</span><span class="p">,</span><span class="w">
</span><span class="n">linetype</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"dashed"</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">geom_line</span><span class="p">(</span><span class="n">data</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">trend</span><span class="p">,</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">fit</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">geom_point</span><span class="p">(</span><span class="n">size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">3.5</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">data</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">annotate</span><span class="p">(</span><span class="w">
</span><span class="s2">"text"</span><span class="p">,</span><span class="w">
</span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"growth plateaus at asymptote"</span><span class="p">,</span><span class="w">
</span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">20</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.84</span><span class="p">,</span><span class="w">
</span><span class="c1"># horizontal justification = 0 sets x position to left edge of text</span><span class="w">
</span><span class="n">hjust</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">,</span><span class="w">
</span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">asym</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">annotate</span><span class="p">(</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w"> </span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"growth steepest at midpoint"</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">49</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.05</span><span class="p">,</span><span class="w"> </span><span class="n">hjust</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">mid</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">scale_x_continuous</span><span class="p">(</span><span class="w">
</span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Age in months"</span><span class="p">,</span><span class="w">
</span><span class="n">limits</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">96</span><span class="p">),</span><span class="w">
</span><span class="n">breaks</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scales</span><span class="o">::</span><span class="n">extended_breaks</span><span class="p">(</span><span class="n">Q</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">24</span><span class="p">,</span><span class="w"> </span><span class="m">12</span><span class="p">)))</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">scale_y_continuous</span><span class="p">(</span><span class="w">
</span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Intelligibility"</span><span class="p">,</span><span class="w">
</span><span class="n">limits</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">c</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="kc">NA</span><span class="p">),</span><span class="w">
</span><span class="n">labels</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scales</span><span class="o">::</span><span class="n">percent_format</span><span class="p">(</span><span class="n">accuracy</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="p">))</span><span class="w">

</span><span class="n">p</span><span class="w">
</span>

By the way, some other ways to describe the asymptote besides “ceiling” or
“plateau” would be “saturation” which emphasizes how things only change a small
amount near the asymptote or as a “limiting” factor or “capacity” which
emphasizes how growth is no longer tenable after a certain point.

Okay, that just leaves the scale parameter.

## We need to talk about the scale parameter for a second

In a sentence, the scale parameter controls how steep the curve is. The logistic
curve is at its steepest at the midpoint. Growth accelerates, hits the midpoint,
then decelerates. The rate of change on the curve is changing constantly along
the course of the curve. Therefore, it doesn’t make sense to talk about the
scale as the growth rate or as the slope in any particular location. It’s better
to think of it as a growth factor, or umm, scale. I say that it “controls” the
slope of the curve, because changing the scale will affect the overall stepness
of the curve.

Here is the derivative of the logistic curve. (I had to ask Wolfram Alpha to do
the math for me.) This function tells you the rate of change in the curve at any
point.

Yeah, I don’t like it either, but I have to show you this mess to show how neat
things are at the midpoint of the curve. When t is the midpoint, algebraic
magic happens 🎆. All of the (mid − t) parts become 0, exp(0) is 1, so everything
simplifies a great deal. Check it out.

In our case, with a scale of .2 and asymptote of .8, the slope at 48 months is
(.2 / 4) * 8 which is .04. When the curve is at its steepest, for the data
illustrated here, intelligibility grows at a rate of 4 percentage points per
month. That’s an upper limit on growth rate: This child never gains more than 4
percentage points per month.

Now, we can add annotate the plot with an arrow with this slope at the midpoint.
That seems like a good representation because this point is where the scale is
most transparently related to the curve’s shape.

<span class="c1"># Compute endpoints for segment with given slope in middle</span><span class="w">
</span><span class="n">slope</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="p">(</span><span class="m">.2</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="m">4</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="m">.8</span><span class="w">
</span><span class="n">x_step</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="m">2.5</span><span class="w">
</span><span class="n">y</span><span class="m">1</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="m">.4</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">slope</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="o">-</span><span class="n">x_step</span><span class="w">
</span><span class="n">y</span><span class="m">2</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="m">.4</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">slope</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">x_step</span><span class="w">

</span><span class="n">p</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">geom_segment</span><span class="p">(</span><span class="w">
</span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">48</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">x_step</span><span class="p">,</span><span class="w"> </span><span class="n">xend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">48</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">x_step</span><span class="p">,</span><span class="w">
</span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">y</span><span class="m">1</span><span class="p">,</span><span class="w"> </span><span class="n">yend</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">y</span><span class="m">2</span><span class="p">,</span><span class="w">
</span><span class="n">size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1.2</span><span class="p">,</span><span class="w">
</span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">scale</span><span class="p">,</span><span class="w"> </span><span class="n">arrow</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">arrow</span><span class="p">(</span><span class="n">ends</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"both"</span><span class="p">,</span><span class="w"> </span><span class="n">length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">unit</span><span class="p">(</span><span class="m">.1</span><span class="p">,</span><span class="w"> </span><span class="s2">"in"</span><span class="p">)))</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">annotate</span><span class="p">(</span><span class="w"> </span><span class="s2">"text"</span><span class="p">,</span><span class="w"> </span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"scale controls slope of curve"</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">49</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.38</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">scale</span><span class="p">,</span><span class="w"> </span><span class="n">hjust</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">)</span><span class="w">
</span><span class="n">p</span><span class="w">
</span>

For my conference submission, I didn’t want to include the equation in the text.
It was just too low-level of a detail for the 600-word limit. So I added the
equation to the plot using
plotmath.
I’m not exactly sure what this feature should be called, but ?plotmath is
what you type to open the help page, so that’s what I call it. You can add math
to a plot by providing an expression() which is parsed into mathematical copy,
or by passing a string and setting parse = TRUE. Here is a demo of both
approaches.

<span class="n">ggplot</span><span class="p">(</span><span class="n">tibble</span><span class="p">(</span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="o">:</span><span class="m">3</span><span class="p">))</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">aes</span><span class="p">(</span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">geom_text</span><span class="p">(</span><span class="w">
</span><span class="n">aes</span><span class="p">(</span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">1</span><span class="p">),</span><span class="w">
</span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nf">expression</span><span class="p">(</span><span class="m">1</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="m">100</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nb">pi</span><span class="p">))</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">geom_text</span><span class="p">(</span><span class="w">
</span><span class="n">aes</span><span class="p">(</span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.5</span><span class="p">),</span><span class="w">
</span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"frac(mu, 100)"</span><span class="p">,</span><span class="w">
</span><span class="n">parse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">xlim</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">4</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">ylim</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="m">1.1</span><span class="p">)</span><span class="w">
</span><span class="c1">#> Warning in is.na(x): is.na() applied to non-(list or vector) of type</span><span class="w">
</span><span class="c1">#> 'expression'</span><span class="w">
</span>

<span class="w">
</span><span class="c1"># (I don't know what this warning is about.)</span><span class="w">
</span>

For this plot, we’re going to create a helper function that pre-sets parse to
TRUE and pre-sets the location for the equation.

<span class="c1"># Helper to plot an equation in a pre-set spot</span><span class="w">
</span><span class="n">annotate_eq</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">label</span><span class="p">,</span><span class="w"> </span><span class="n">...</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="n">annotate</span><span class="p">(</span><span class="s2">"text"</span><span class="p">,</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">.6</span><span class="p">,</span><span class="w"> </span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">label</span><span class="p">,</span><span class="w"> </span><span class="n">parse</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">,</span><span class="w">
</span><span class="n">hjust</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">0</span><span class="p">,</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="m">4</span><span class="p">,</span><span class="w"> </span><span class="n">...</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>

Then we just add the equation to the plot.

<span class="n">p</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">annotate_eq</span><span class="p">(</span><span class="w">
</span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"f(t)==frac(asymptote, 1 + exp((mid-t)%*%scale))"</span><span class="p">,</span><span class="w">
</span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">fit</span><span class="p">)</span><span class="w"> </span> This is a perfectly serviceable plot, but we can get fancier. I gave the parameter annotations different colors for a reason 😉. ### Phantom menaces Plotmath provides a function called phantom() for adding placeholders to an equation. phantom(x) will make space for x in the equation but it won’t draw it. Therefore, we can phantom() out all of the parameters to draw the non-parameter parts of the equation in black. <span class="n">p</span><span class="m">1</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">annotate_eq</span><span class="p">(</span><span class="w"> </span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">" f(t) == frac( phantom(asymptote), 1 + exp((phantom(mid) - t) %*% phantom(scale)) )"</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">fit</span><span class="p">)</span><span class="w">
</span><span class="n">p</span><span class="m">1</span><span class="w">
</span>

Then we layer on the other parts of the equation in different colors, using
phantom() as needed so we don’t overwrite the black parts. We also use
atop(); it does the same thing as frac() except it doesn’t draw a fraction
line. Here’s the addition of the asymptote.

<span class="n">p</span><span class="m">2</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">p</span><span class="m">1</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">annotate_eq</span><span class="p">(</span><span class="w">
</span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"
phantom(f(t) == symbol('')) ~ atop(
asymptote,
phantom(1 + exp((mid-t) %*% scale))
)"</span><span class="p">,</span><span class="w">
</span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">asym</span><span class="p">)</span><span class="w"> </span><span class="n">p</span><span class="m">2</span><span class="w"> </span> But the other parameters are not that simple. The plotmath help page states that “A mathematical expression must obey the normal rules of syntax for any R expression” so that means that we can’t do something like phantom(1 + ) x" because the 1 +  is not valid R syntax. So to blank out parts of expressions, we create expressions using paste() to put symbols next to each other and symbol() to refer to symbols/operators as characters. I have to be honest, however: it took a lot of fiddling to get this work right. Therefore, I have added the following disclaimer: 🚨 Don’t study this code. Just observe what is possible, but observe all the hacky code required. 🚨 <span class="n">p</span><span class="m">2</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">annotate_eq</span><span class="p">(</span><span class="w"> </span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">" phantom(f(t) == symbol('')) ~ atop( phantom(asymptote), phantom(1 + exp((mid-t) * symbol(''))) ~ scale )"</span><span class="p">,</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">$</span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w">
</span><span class="n">annotate_eq</span><span class="p">(</span><span class="w">
</span><span class="n">label</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"
phantom(f(t) == symbol('')) ~ atop(
phantom(asymptote),
paste(
phantom(paste(1 + exp, symbol(')'), symbol(')'))),
mid,
phantom(paste(symbol('-'), t, symbol(')') * scale))
)
)"</span><span class="p">,</span><span class="w">
</span><span class="n">color</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">colors</span><span class="o">\$</span><span class="n">mid</span><span class="p">)</span><span class="w">
</span>

There we have it—my wonderful, colorful diagram! Take that word count!

By the way, if you know a better way to plot partially colorized math equations
or how to blank out subexpressions in an easier way, I would love to hear it.