title: ggplot2: Adding sample size information to x-axis tick labels
body: <p>This question is related to 
  <a href="https://stackoverflow.com/questions/39993117/create-custom-geom-to-compute-summary-statistics-and-display-them-outside-the">Create custom geom to compute summary statistics and display them *outside* the plotting region</a>
  (NOTE:  All functions have been simplified; no error checks for correct objects types, NAs, etc.)</p>
  
  <p>In base R, it is quite easy to create a function that produces a stripchart with the sample size indicated below each level of the grouping variable:  you can add the sample size information using the <code>mtext()</code> function:</p>
  
  <pre><code>stripchart_w_n_ver1 &lt;- function(data, x.var, y.var) {
      x &lt;- factor(data[, x.var])
      y &lt;- data[, y.var]
  # Need to call plot.default() instead of plot because 
  # plot() produces boxplots when x is a factor.
      plot.default(x, y, xaxt = "n",  xlab = x.var, ylab = y.var)
      levels.x &lt;- levels(x)
      x.ticks &lt;- 1:length(levels(x))
      axis(1, at = x.ticks, labels = levels.x)
      n &lt;- sapply(split(y, x), length)
      mtext(paste0("N=", n), side = 1, line = 2, at = x.ticks)
  }
  
  stripchart_w_n_ver1(mtcars, "cyl", "mpg")
  </code></pre>
  
  <p>or you can add the sample size information to the x-axis tick labels using the <code>axis()</code> function:</p>
  
  <pre><code>stripchart_w_n_ver2 &lt;- function(data, x.var, y.var) {
      x &lt;- factor(data[, x.var])
      y &lt;- data[, y.var]
  # Need to set the second element of mgp to 1.5 
  # to allow room for two lines for the x-axis tick labels.
      o.par &lt;- par(mgp = c(3, 1.5, 0))
      on.exit(par(o.par))
  # Need to call plot.default() instead of plot because 
  # plot() produces boxplots when x is a factor.
      plot.default(x, y, xaxt = "n", xlab = x.var, ylab = y.var)
      n &lt;- sapply(split(y, x), length)
      levels.x &lt;- levels(x)
      axis(1, at = 1:length(levels.x), labels = paste0(levels.x, "\nN=", n))
  }
  
  stripchart_w_n_ver2(mtcars, "cyl", "mpg")
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/woTqp.png" rel="noreferrer"><img src="https://i.stack.imgur.com/woTqp.png" alt="Example using axis()"></a></p>
  
  <p>While this is a very easy task in base R, it is maddingly complex in ggplot2 because it is very hard to get at the data being used to generate the plot, and while there are functions equivalent to <code>axis()</code> (e.g., <code>scale_x_discrete</code>, etc.) there is no equivalent to <code>mtext()</code> that lets you easily place text at specified coordinates within the margins.  </p>
  
  <p>I tried using the built in <code>stat_summary()</code> function to compute the sample sizes (i.e., <code>fun.y = "length"</code>) and then place that information on the x-axis tick labels, but as far as I can tell, you can't extract the sample sizes and then somehow add them to the x-axis tick labels using the function <code>scale_x_discrete()</code>, you have to tell <code>stat_summary()</code> what geom you want it to use.  You could set <code>geom="text"</code>, but then you have to supply the labels, and the point is that the labels should be the values of the sample sizes, which is what <code>stat_summary()</code> is computing but which you can't get at (and you would also have to specify where you want the text to be placed, and again, it is difficult to figure out where to place it so that it lies directly underneath the x-axis tick labels).</p>
  
  <p>The vignette "Extending ggplot2" (<a href="http://docs.ggplot2.org/dev/vignettes/extending-ggplot2.html" rel="noreferrer">http://docs.ggplot2.org/dev/vignettes/extending-ggplot2.html</a>) shows you how to create your own stat function that allows you to get directly at the data, but the problem is that you always have to define a geom to go with your stat function (i.e., <code>ggplot</code> thinks you want to plot this information within the plot, not in the margins); as far as I can tell, you can't take the information you compute in your custom stat function, not plot anything in the plot area, and instead pass the information to a scales function like <code>scale_x_discrete()</code>.  Here was my try at doing it this way; the best I could do was place the sample size information at the minimum value of y for each group:</p>
  
  <pre><code>StatN &lt;- ggproto("StatN", Stat,
      required_aes = c("x", "y"), 
      compute_group = function(data, scales) {
      y &lt;- data$y
      y &lt;- y[!is.na(y)]
      n &lt;- length(y)
      data.frame(x = data$x[1], y = min(y), label = paste0("n=", n))
      }
  )
  
  stat_n &lt;- function(mapping = NULL, data = NULL, geom = "text", 
      position = "identity", inherit.aes = TRUE, show.legend = NA, 
          na.rm = FALSE, ...) {
      ggplot2::layer(stat = StatN, mapping = mapping, data = data, geom = geom, 
          position = position, inherit.aes = inherit.aes, show.legend = show.legend, 
          params = list(na.rm = na.rm, ...))
  }
  
  ggplot(mtcars, aes(x = factor(cyl), y = mpg)) + geom_point() + stat_n()
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/TFz71.png" rel="noreferrer"><img src="https://i.stack.imgur.com/TFz71.png" alt="enter image description here"></a></p>
  
  <p>I thought I had solved the problem by simply creating a wrapper function to <code>ggplot</code>:</p>
  
  <pre><code>ggstripchart &lt;- function(data, x.name, y.name,  
      point.params = list(), 
      x.axis.params = list(labels = levels(x)), 
      y.axis.params = list(), ...) {
      if(!is.factor(data[, x.name]))
      data[, x.name] &lt;- factor(data[, x.name])
      x &lt;- data[, x.name]
      y &lt;- data[, y.name]
      params &lt;- list(...)
      point.params    &lt;- modifyList(params, point.params)
      x.axis.params   &lt;- modifyList(params, x.axis.params)
      y.axis.params   &lt;- modifyList(params, y.axis.params)
  
      point &lt;- do.call("geom_point", point.params)
  
      stripchart.list &lt;- list(
          point, 
          theme(legend.position = "none")
      )
  
      n &lt;- sapply(split(y, x), length)
      x.axis.params$labels &lt;- paste0(x.axis.params$labels, "\nN=", n)
      x.axis &lt;- do.call("scale_x_discrete", x.axis.params)
      y.axis &lt;- do.call("scale_y_continuous", y.axis.params)
      stripchart.list &lt;- c(stripchart.list, x.axis, y.axis)           
  
      ggplot(data = data, mapping = aes_string(x = x.name, y = y.name)) + stripchart.list
  }
  
  
  ggstripchart(mtcars, "cyl", "mpg")
  </code></pre>
  
  <p><a href="https://i.stack.imgur.com/rzd3h.png" rel="noreferrer"><img src="https://i.stack.imgur.com/rzd3h.png" alt="Example using ggstripchart()"></a></p>
  
  <p>However, this function does not work correctly with faceting.  For example:</p>
  
  <pre><code>ggstripchart(mtcars, "cyl", "mpg") + facet_wrap(~am)
  </code></pre>
  
  <p>shows the the sample sizes for both facets combined for each facet.  I would have to build faceting into the wrapper function, which defeats the point of trying to use everything <code>ggplot</code> has to offer.</p>
  
  <p><a href="https://i.stack.imgur.com/OL2rc.png" rel="noreferrer"><img src="https://i.stack.imgur.com/OL2rc.png" alt="Example using ggstripchart with facet_wrap"></a></p>
  
  <p>If anyone has any insights to this problem I would be grateful.  Thanks so much for your time!</p>