Adding Rows to a panelsummary Table

This vignette shows how to add rows to panelsummary tables. Since panelsummary::panelsummary returns kableExtra objects, adding rows is impossible using the base function panelsummary::panelsummary. This can be a nuisance when you want to include additional statistics to your table. However, the workaround is to use the panelsummary::panelsummary_raw function.

panelsummary::panelsummary_raw

The panelsummary::panelsummary_raw function returns a dataframe that can be manipulated. The trade-off is that the table will need to be customized using the kableExtra package, although some of this customization can be completed using the panelsummary::clean_raw function (see example).

Workflow

If you want to create a table with panels that has additional statistical rows, the workflow will be the following:

  • Estimate your models (preferably with the fixest package).
  • Pass your models into panelsummary::panelsummary_raw to get a manipulable dataframe.
  • Manipulate the dataframe (preferably with dplyr::add_row).
  • Use panelsummary::clean_raw to return a kableExtra object.
  • Use kableExtra::pack_rows to create panel labels.

Example: Adding Additional Rows to Each Panel

To demonstrate how to add a row to each panel of a panelsummary::panelsummary_raw table, I will assume that each panel needs one additional row for the wild-cluster-bootstrap p-values of the cyl column from the mtcars data. While I will not explicitly calculate these (in fact, they will be completely made up), the following steps will show how to complete such task.

Consider the following models, estimated using the fixest package:

library(panelsummary)

## Panel A: mpg dependent variable
mpg_1 <- fixest::feols(mpg ~cyl + disp, data = mtcars)
mpg_2 <- fixest::feols(mpg ~ cyl + am, data = mtcars)

## Panel B: wt dependent variable
wt_1 <- fixest::feols(wt ~ cyl + disp, data = mtcars)
wt_2 <- fixest::feols(wt ~ cyl + am, data = mtcars)

Recall that putting each of these two models in a list will result in multiple specifications per-panel.

## putting each panel's models into a list—one for each panel
panel_a_mpg <- list(mpg_1, mpg_2)
panel_b_wt <- list(wt_1, wt_2)

Now, I will pass these two lists into panelsummary::panelsummary_raw. Note that this function shares many of the same arguments as panelsummary::panelsummary. In the following, I will also use the arguments stars (significance stars), mean_dependent (for dependent variable mean), and gof_omit (to omit R2 statistics):

## creating the dataframe
panelsummary_raw(panel_a_mpg, panel_b_wt,
                 stars = "econ",
                 mean_dependent = T,
                 gof_omit = "^R")
#>                          term   Model 1   Model 2
#> 1                 (Intercept) 34.661*** 34.522***
#> 2                               (2.547)   (2.603)
#> 3                         cyl  -1.587** -2.501***
#> 4                               (0.712)   (0.361)
#> 5                        disp   -0.021*          
#> 6                               (0.010)          
#> 7                          am              2.567*
#> 8                                         (1.291)
#> 9  Mean of Dependent Variable    20.091    20.091
#> 10                   Num.Obs.        32        32
#> 11                        AIC     165.1     165.2
#> 12                        BIC     169.5     169.6
#> 13                 Std.Errors       IID       IID
#> 14                (Intercept)  1.773***  1.566***
#> 15                              (0.386)   (0.453)
#> 16                        cyl    -0.054  0.317***
#> 17                              (0.108)   (0.063)
#> 18                       disp  0.008***          
#> 19                              (0.002)          
#> 20                         am           -0.765***
#> 21                                        (0.225)
#> 22 Mean of Dependent Variable     3.217     3.217
#> 23                   Num.Obs.        32        32
#> 24                        AIC      44.4      53.3
#> 25                        BIC      48.8      57.7
#> 26                 Std.Errors       IID       IID

The return value of this output is a dataframe without panel labels. Given that it is a dataframe, you can use dplyr functions to easily manipulate. For instance, I will use the dplyr::add_rows function to add the pseudo wild-cluster-bootstrap p-values to the table:

## adding rows using dplyr::add_row
bootstrap_table <- panelsummary_raw(panel_a_mpg, panel_b_wt,
                 stars = "econ",
                 mean_dependent = T,
                 gof_omit = "R") |> 
  dplyr::add_row(term = "Wild Cluster Boot P-value (cyl)",
                 `Model 1` = ".001",
                 `Model 2` = ".002",
                .before = 10) |> 
  dplyr::add_row(term = "Wild Cluster Boot P-value (cyl)",
                 `Model 1` = ".001",
                 `Model 2` = ".005",
                 .before = 22)

bootstrap_table
#>                               term   Model 1   Model 2
#> 1                      (Intercept) 34.661*** 34.522***
#> 2                                    (2.547)   (2.603)
#> 3                              cyl  -1.587** -2.501***
#> 4                                    (0.712)   (0.361)
#> 5                             disp   -0.021*          
#> 6                                    (0.010)          
#> 7                               am              2.567*
#> 8                                              (1.291)
#> 9       Mean of Dependent Variable    20.091    20.091
#> 10 Wild Cluster Boot P-value (cyl)      .001      .002
#> 11                        Num.Obs.        32        32
#> 12                             AIC     165.1     165.2
#> 13                             BIC     169.5     169.6
#> 14                      Std.Errors       IID       IID
#> 15                     (Intercept)  1.773***  1.566***
#> 16                                   (0.386)   (0.453)
#> 17                             cyl    -0.054  0.317***
#> 18                                   (0.108)   (0.063)
#> 19                            disp  0.008***          
#> 20                                   (0.002)          
#> 21                              am           -0.765***
#> 22 Wild Cluster Boot P-value (cyl)      .001      .005
#> 23                                             (0.225)
#> 24      Mean of Dependent Variable     3.217     3.217
#> 25                        Num.Obs.        32        32
#> 26                             AIC      44.4      53.3
#> 27                             BIC      48.8      57.7
#> 28                      Std.Errors       IID       IID

Now the dataframe is ready to be passed into kableExtra::kbl to further customize it with panel headers/column names etc. Alternatively, you can use the panelsummary::clean_raw function to get a few desirable traits and speed up the process. In particular, the panelsummary::clean_raw function does the following by default:

  • Passes the dataframe into kableExtra::kbl
  • Changes the alignment to Left for the first column, and Center for all remaining columns.
  • Changes the names of the columns to a blank white space for the first column, and consecutive numbers in the remaining columns.

However, you can also pass in a caption using the caption argument:

## creating a kableExtra object with a few desirable defaults
bootstrap_table |> 
  clean_raw(caption = "A customized panelsummary table with added rows.")
A customized panelsummary table with added rows.
(Intercept) 34.661*** 34.522***
(2.547) (2.603)
cyl -1.587** -2.501***
(0.712) (0.361)
disp -0.021*
(0.010)
am 2.567*
(1.291)
Mean of Dependent Variable 20.091 20.091
Wild Cluster Boot P-value (cyl) .001 .002
Num.Obs. 32 32
AIC 165.1 165.2
BIC 169.5 169.6
Std.Errors IID IID
(Intercept) 1.773*** 1.566***
(0.386) (0.453)
cyl -0.054 0.317***
(0.108) (0.063)
disp 0.008***
(0.002)
am -0.765***
Wild Cluster Boot P-value (cyl) .001 .005
(0.225)
Mean of Dependent Variable 3.217 3.217
Num.Obs. 32 32
AIC 44.4 53.3
BIC 48.8 57.7
Std.Errors IID IID

Finally, to get the desired panel labels, you will need to use kableExtra::pack_rows:

## creating the final panels.
bootstrap_table |> 
  clean_raw(caption = "A customized panelsummary table with added rows.") |> 
  kableExtra::pack_rows("Panel A: MPG", 1, 14,
                        italic = T,
                        bold = F) |> 
  kableExtra::pack_rows("Panel B: Cyl", 15, 28, 
                        italic = T,
                        bold = F) |> 
  kableExtra::footnote(list("* p < 0.1, ** p < 0.05, *** p < 0.01")) |> 
  kableExtra::kable_classic(full_width = F, html_font = "Cambria") 
A customized panelsummary table with added rows.
Panel A: MPG
(Intercept) 34.661*** 34.522***
(2.547) (2.603)
cyl -1.587** -2.501***
(0.712) (0.361)
disp -0.021*
(0.010)
am 2.567*
(1.291)
Mean of Dependent Variable 20.091 20.091
Wild Cluster Boot P-value (cyl) .001 .002
Num.Obs. 32 32
AIC 165.1 165.2
BIC 169.5 169.6
Std.Errors IID IID
Panel B: Cyl
(Intercept) 1.773*** 1.566***
(0.386) (0.453)
cyl -0.054 0.317***
(0.108) (0.063)
disp 0.008***
(0.002)
am -0.765***
Wild Cluster Boot P-value (cyl) .001 .005
(0.225)
Mean of Dependent Variable 3.217 3.217
Num.Obs. 32 32
AIC 44.4 53.3
BIC 48.8 57.7
Std.Errors IID IID
Note:
* p < 0.1, ** p < 0.05, *** p < 0.01