mmtable2 can handle tables with multiple layers of header columns. This is paricularly useful for reporting regression results for multiple specifications of subsamples.

Modelling titanic survival

Below is an example using the titanic data set to predict survival using logit/LPM, two specifications and separate models for males and females.

The modeling and data preparation are not our primary concern here, but see the source rmd file of this vignette if you’d like to see the function completes these tasks.

Data preparation

We now produce a data frame that is ready for mmtable2. That is, a data frame with a row for each body cell and a column for each header.

cells <-
  bind_rows(
    get_model_data(sex_filter = "Female", specification_arg = "(II)", model = "LPM" ),
    get_model_data(sex_filter = "Female", specification_arg = "(I)", model = "LPM" ),
    get_model_data(sex_filter = "Female", specification_arg = "(II)", model = "Logit" ),
    get_model_data(sex_filter = "Female", specification_arg = "(I)", model = "Logit" ),
    get_model_data(sex_filter = "Male", specification_arg = "(II)", model = "LPM" ),
    get_model_data(sex_filter = "Male", specification_arg = "(I)", model = "LPM" ),
    get_model_data(sex_filter = "Male", specification_arg = "(II)", model = "Logit" ),
    get_model_data(sex_filter = "Male", specification_arg = "(I)", model = "Logit" )
  ) %>% filter(term != "Intercept")  %>% 
  mutate(value = str_trim(value))

glimpse(cells)
#> Rows: 76
#> Columns: 8
#> $ term          <fct> Age, Passenger class, Passenger class, Age, Passenger cl…
#> $ var           <chr> "estimate", "estimate", "estimate", "", "", "", "estimat…
#> $ value         <chr> "-0.003", "-0.063", "-0.542***", "(0.002)", "(0.058)", "…
#> $ stat_type     <chr> "Marginal effects", "Marginal effects", "Marginal effect…
#> $ var_level     <chr> "", "2", "3", "", "2", "3", "1", "1", "", "", "", "", ""…
#> $ model_type    <chr> "LPM", "LPM", "LPM", "LPM", "LPM", "LPM", "LPM", "LPM", …
#> $ gender        <chr> "Female", "Female", "Female", "Female", "Female", "Femal…
#> $ specification <chr> "(II)", "(II)", "(II)", "(II)", "(II)", "(II)", "(II)", …

Table construction

And here’s the code to create the final table. Notice the use of header_merged_cols(). This ensures top_left headers are not constrained to a single cell.

cells  %>%
  mmtable(cells =  value)  +
  header_left_top(var_level) +
  header_left_top(term) +
  header_left_top(stat_type) +
  header_top(specification) +
  header_top_left(model_type) +
  header_top_left(gender) +
  cells_format(cell_predicate = T, 
               style = list(cell_text(align = "left"))
               ) +
  header_format(header = "all_cols", 
                style = list(cell_text(align = "center"))
                ) +
  header_format(stat_type,
    scope = "table",
    style = list(
      cell_borders(sides = "top",
      weight = px(4),
      color = "lightgrey"))
    ) +
  header_merged_cols() +
  NULL
Female Male
Logit LPM Logit LPM
(I) (II) (I) (II) (I) (II) (I) (II)
Marginal effects Age 0.004 -0.003 0.004 -0.003 -0.003* -0.007*** -0.003* -0.007***
(0.002) (0.002) (0.002) (0.002) (0.001) (0.001) (0.001) (0.001)
Passenger class 1 - - - -
- - - -
2 -0.050 -0.063 -0.365*** -0.320***
(0.036) (0.058) (0.063) (0.056)
3 -0.545*** -0.542*** -0.384*** -0.351***
(0.055) (0.057) (0.057) (0.049)
Model statistics Observations 261 261 261 261 453 453 453 453
R2 0.01 0.31 0.01 0.12
Adjusted R2 0.01 0.30 0.01 0.11
Log likelihood -143.59 -102.62 -148.42 -101.90 -226.61 -202.93 -228.85 -203.08
Akaike Inf. Crit. 291.18 213.24 302.85 213.79 457.22 413.87 463.70 416.16
F statistic 3.54 38.37 6.55 20.47

.