title: How to format a table with counts, percents and marginal totals
body: <p>I often need to create a formatted table with counts, percents and marginal totals. For example I might have data with three classes and two categories. I want to make a table with a row for each class, containing within-class counts and percents for each category and the total count for the category. Finally, a total row at the bottom totals categories, shows categories as percent of total, and an overall total. </p>
  
  <p>The code is always ugly :-) and I would love to find a better way. Here is a very simple example; often it is much more complex than this.</p>
  
  <pre><code>=====  ===  =======  ===  ======  =====
  Class  Yes  Yes pct   No  No pct  Total
  =====  ===  =======  ===  ======  =====
  one     35      65%   19     35%     54
  two     21      70%    9     30%     30
  three    9      56%    7     44%     16
  Total   65      65%   35     35%    100
  =====  ===  =======  ===  ======  =====
  </code></pre>
  
  <p>I know about <code>addmargins</code> (doesn't work on data.frame), <code>prop.table</code> (gives a separate table of proportions), <code>descr::CrossTable</code> (puts values in cells, not spread across the row). Any suggestions for how to clean this up are welcome.</p>
  
  <p>Here is the code to create the above table:</p>
  
  <pre><code>library(formattable) # For nice percents
  library(tidyverse)
  
  # Make up some data. Three classes with two categories within each class
  # Order of cls is important so it is a factor
  d = tibble(cls=sample(c('one', 'two', 'three'), 100, 
                        replace=TRUE, prob=c(0.5, 0.3, 0.2)),
             conf=sample(c('yes', 'no'), 100, 
                         replace=TRUE, prob=c(0.6, 0.4))) %&gt;% 
    mutate(cls = factor(cls, levels=c('one', 'two', 'three', 'Total')))
  
  # Tabulate by cls and conf
  d2 = d %&gt;% group_by(cls, conf) %&gt;% 
    summarise(n=n()) %&gt;% # Total per cls x conf
    spread(conf, n)  # Spread to one row per cls
  
  # Add a total row. Do this before calculating percents so we don't total
  # the within-row percents. This is really ugly
  d2 = d2 %&gt;% bind_rows(as_data_frame(t(c(cls=NA, colSums(d2[,-1])))))
  d2$cls[nrow(d2)] = 'Total'
  
  d2 = d2 %&gt;% mutate(total=no+yes, # Make percents and row totals
           no_pct=percent(no/total, 0),
           yes_pct=percent(yes/total, 0)) %&gt;% 
    select(Class=cls, Yes=yes, `Yes pct`=yes_pct, # Reorder and rename columns
           No=no, `No pct`=no_pct, Total=total)
  
  formattable(d2) # Yay! Nice table.
  knitr::kable(d2, format='rst') # For pasting above
  </code></pre>