0

I'm trying to use modelsummary for the following process:

  1. Estimate a multivariate regression ( j explanatory variables) per group (k groups) so that you have k estimates per each one of j variables.
  2. Average those k coefficients per each variable and calculate the corresponding t stat.
  3. Display those averages and t stats in one column as if they were estimates in a normal regression.

Statistically it is not the best approach but that's necessary in this case. It's basically averaged coefficients of k regressions. So my question is for the steps 2 and 3. Here is a reprex using mtcars just to calc averages and tstats:

fit<-mtcars %>% feols(c(mpg,hp )~1)
modelsummary(fit, estimate="{estimate}{stars}", stars = c('*' = .1, '**' = .05, '***'=0.01), output="flextable")

So this is the original ouptut

Original output

And I would like to stack those averages and tstats in one column like in the screenshot below.

Desired column structure

There are several ways to do this but the best in this case imho would be to say within fixest+modelsummary+flextable workflow and I haven't been able to find the best approach for this. Would really appreciate any insight!

2 Answers2

1

Usually, I let the flextable package add footnotes to the results (with flextable::add_footer_lines) calculated conditionally from another column of p.value. However, since you are asking the modelSummary package to add "***", you can create the table you want with this code =>

    require(tidyverse)

    fit<-mtcars %>% fixest::feols(c(mpg,hp )~1)

    modelsummary::modelsummary(fit, estimate="{estimate}{stars}",
                           
                           stars = c('*' = .1, '**' = .05, '***'=0.01), 
                           output="dataframe") %>%
                         # ↑  change the desired 'output' to "dataframe"
  
      pivot_longer(cols = c(mpg, hp))  %>% 
        # ↑ transpose the dataframe

      filter(part == "estimates" | term == "Num.Obs.")  %>% 
      # ↑ keep only the "estimates" row (from "part"), and the "Num.Obs." row (from var' "term")
  
      arrange(desc(name) ) %>% arrange(part) %>% 
     # ↑ set the order you want, with Num.Obs in last row

     mutate(name = if_else(term == "Num.Obs.", "Num.Obs.", name)) %>%
     # ↑ adding a "Num.Obs." label to the variable "name"

     select(name, value) %>%
     # select only 2 cols
  
     flextable::flextable(cwidth = c(1.5, 2)  ) %>%
     # ↑ create your table (and indicate the width of your 2 col')
  
     flextable::set_header_labels(values = c("name" = "", 
                                          
                                          "value" = "(1)")) %>%
    # changing the labels, according to your desired output

     flextable::merge_v(j = 1:2) %>%
     # merge the similar values 

   flextable::add_footer_lines(values = "*** < .001; ** < .05; * < .1")
   # ↑ OPTIONAL: indicate a footnote line, since the modelsummary package add some stars to the results
Clément LVD
  • 648
  • 5
  • 12
  • Thank you for the reply! A very interesting approach! Very useful on how to manage the output during the flextable step. Is there an easy way to number columns (1), (2) and so on when fitting multiple models as columns in the same table? – astrae_research Oct 23 '22 at 21:33
  • You're welcome. If you have several columns, you should indicate (a) the width during the table creation `flextable::flextable(cwidth = c(1,.5,2,2))` which is usually mandatory (e.g., in the case of a Word output); and (b) set the labels with `flextable::set_header_labels()` but it is not mandatory: you may want to specify the column names earlier in the code / before creating the flextable. Personally, I have a preference for the `set_header_labels` method. – Clément LVD Oct 24 '22 at 07:23
1

You can use the shape argument for this. The ?modelsummary documentation for this argument is very detailed, and there are also many examples in the vignette here: https://vincentarelbundock.github.io/modelsummary/articles/modelsummary.html#shape

library(fixest)
library(modelsummary)
fit <- mtcars |> feols(c(mpg, hp) ~ 1)

modelsummary(
    fit,
    shape = model + statistic ~ term,
    statistic = "statistic",
    stars = c("*" = .1, "**" = .05, "***" = 0.01))
(Intercept)
mpg 20.091***
(18.857)
hp 146.687***
(12.103)

Note: ^^ * p < 0.1, ** p < 0.05, *** p < 0.01

Note that, by default, you won't get the number of observations or other goodness-of-fit statistics at the bottom of the table. This is because it doesn't usually make sense to report a single R2 or number of observations below the results of multiple models.

However, you can easily add those manually using the add_rows argument.

Vincent
  • 15,809
  • 7
  • 37
  • 39
  • Thank you so much! Can't believe i missed the `shape` argument. I'm trying to adapt this to a multi model setup though with numbered columns in a (1), (2) form and not having much success. Here is what i'm trying to run: ` fit<-mtcars %>% feols(c(mpg,hp )~1) fit_1 <- mtcars %>% feols(c(mpg,hp,wt )~1) models <- list( "(1)" =fit, "(2)" =fit_1) ` then `modelsummary(models, shape = model + statistic ~ term, estimate="{estimate}{stars}", statistic = "statistic", output="flextable" ) ` . I get an error though about not extracting info. – astrae_research Oct 23 '22 at 21:28
  • Since the table is reshaped, what you now have as a column is actually the `(Intercept)`, that is, a term name. I would try the `coef_rename` or `coef_map` argument. Also, with that `shape` formula, you will get all models in a single column, which is what you requested in the original question. If you want even more flexibility, you may want to extract your results with `get_estimates()`, combine them in a `modelsummary_list` object, and then feed that back to `modelsummary`. See the vignettes. – Vincent Oct 24 '22 at 10:54