fixest offers a tool, the function etable, to view estimation tables in R or export them to Latex.

The main advantage of this function is its simplicity: it is completely integrated with other fixest functions, making it exceedingly easy to export multiple estimation results with, say, different types of standard-errors. On the other hand, its main limitations are that i) only fixest objects can be exported, and ii) only Latex is supported.

It also offers a fair deal of customization, and since you can seamlessly change its default values, you can completely transform the style of your tables without modifying a single line of code.

Note that there exists excellent alternatives to export tables, like for instance modelsummary or texreg; but they are, necessarily, less integrated with fixest objects, often necessiting more lines of code to export the same results.

This document does not describe etable’s arguments in details (the help page provides many examples). Rather, it illustrates some features that may be hidden at first sight.

This document applies to fixest version 0.8.0 or higher.

Preliminaries

Throughout this document, we will use data from the airquality data base. We also set a dictionary that will be used to rename the variables used in etable. This dictionary is set once and for all.

library(fixest)
data(airquality)

# Setting a dictionary 
setFixest_dict(c(Ozone = "Ozone (ppb)", Solar.R = "Solar Radiation (Langleys)",
                 Wind = "Wind Speed (mph)", Temp = "Temperature"))

Exporting multiple estimations to data.frames

Let’s estimate the following four models and cluster the standard-errors by Day:

# On multiple estimations: see the dedicated vignette
est = feols(Ozone ~ Solar.R + sw0(Wind + Temp) | csw(Month, Day), 
            airquality, cluster = ~Day)

By default, when the argument file is missing, the function etable returns a data.frame. Let’s see the ouput of the previous estimations:

etable(est)
#>                                     model 1          model 2         model 3
#> Dependent Var.:                 Ozone (ppb)      Ozone (ppb)     Ozone (ppb)
#>                                                                             
#> Solar Radiation (Langleys) 0.115*** (0.023)   0.052* (0.020) 0.108** (0.033)
#> Wind Speed (mph)                            -3.11*** (0.799)                
#> Temperature                                  1.88*** (0.367)                
#> Fixed-Effects:             ---------------- ---------------- ---------------
#> Month                                   Yes              Yes             Yes
#> Day                                      No               No             Yes
#> __________________________ ________________ ________________ _______________
#> S.E.: Clustered                     by: Day          by: Day         by: Day
#> Observations                            111              111             111
#> R2                                  0.31974          0.63686         0.58018
#> Within R2                           0.12245          0.53154         0.12074
#>                                     model 4
#> Dependent Var.:                 Ozone (ppb)
#>                                            
#> Solar Radiation (Langleys)   0.051* (0.024)
#> Wind Speed (mph)           -3.29*** (0.778)
#> Temperature                 2.05*** (0.242)
#> Fixed-Effects:             ----------------
#> Month                                   Yes
#> Day                                     Yes
#> __________________________ ________________
#> S.E.: Clustered                     by: Day
#> Observations                            111
#> R2                                  0.81604
#> Within R2                           0.61471

What can we notice? First, the variables are properly labeled. Second, the fixed-effects section details which fixed-effects is included in which model. Third, the type of standard-error is reminded in a dedicated row.

Starting from this table, two elements are detailed: a) how to change the look of the table with the style.df argument, b) how to leverage tools from other packages with the postprocess.df argument.

Changing the look of the data.frame with style.df

You can change many elements of the data.frame with the argument style.df whose input must come from the function style.df. The style monitors many elements of the table, in particular the titles of the sections. Let’s have an example:

etable(est, style.df = style.df(depvar.title = "", fixef.title = "", 
                                 fixef.suffix = " fixed effect", yesNo = "yes"))
#>                                     model 1          model 2         model 3
#>                                 Ozone (ppb)      Ozone (ppb)     Ozone (ppb)
#>                                                                             
#> Solar Radiation (Langleys) 0.115*** (0.023)   0.052* (0.020) 0.108** (0.033)
#> Wind Speed (mph)                            -3.11*** (0.799)                
#> Temperature                                  1.88*** (0.367)                
#> Month fixed effect                      yes              yes             yes
#> Day fixed effect                                                         yes
#> __________________________ ________________ ________________ _______________
#> S.E.: Clustered                     by: Day          by: Day         by: Day
#> Observations                            111              111             111
#> R2                                  0.31974          0.63686         0.58018
#> Within R2                           0.12245          0.53154         0.12074
#>                                     model 4
#>                                 Ozone (ppb)
#>                                            
#> Solar Radiation (Langleys)   0.051* (0.024)
#> Wind Speed (mph)           -3.29*** (0.778)
#> Temperature                 2.05*** (0.242)
#> Month fixed effect                      yes
#> Day fixed effect                        yes
#> __________________________ ________________
#> S.E.: Clustered                     by: Day
#> Observations                            111
#> R2                                  0.81604
#> Within R2                           0.61471

In the previous example, the dependent variable and fixed-effects (FE) headers have been removed, and this is achieved with the (explicit) arguments depvar.title and fixef.title. Furthermore the suffix "fixed effect" is added to each fixed-effect variable, and the indicator of which FE is included in which model is slightly changed. There are more options that are described in the style.df documentation.

Postprocessing with external functions

Since the output of etable is a data.frame, any formatting function handling data.frames can be leveraged. It is then very easy to integrate it into etable. Let’s have an example with the package pander:

library(pander)

etable(est, postprocess.df = pandoc.table.return, style = "rmarkdown")
#> 
#> 
#> |                           |     model 1      |     model 2      |
#> |:------------------------------:|:----------------:|:----------------:|
#> |      **Dependent Var.:**       |   Ozone (ppb)    |   Ozone (ppb)    |
#> |                                |                  |                  |
#> | **Solar Radiation (Langleys)** | 0.115*** (0.023) |  0.052* (0.020)  |
#> |      **Wind Speed (mph)**      |                  | -3.11*** (0.799) |
#> |        **Temperature**         |                  | 1.88*** (0.367)  |
#> |       **Fixed-Effects:**       | ---------------- | ---------------- |
#> |           **Month**            |       Yes        |       Yes        |
#> |            **Day**             |        No        |        No        |
#> | **__________________________** | ________________ | ________________ |
#> |      **S.E.: Clustered**       |     by: Day      |     by: Day      |
#> |        **Observations**        |       111        |       111        |
#> |             **R2**             |     0.31974      |     0.63686      |
#> |         **Within R2**          |     0.12245      |     0.53154      |
#> 
#> Table: Table continues below
#> 
#>  
#> 
#> |                           |     model 3     |     model 4      |
#> |:------------------------------:|:---------------:|:----------------:|
#> |      **Dependent Var.:**       |   Ozone (ppb)   |   Ozone (ppb)    |
#> |                                |                 |                  |
#> | **Solar Radiation (Langleys)** | 0.108** (0.033) |  0.051* (0.024)  |
#> |      **Wind Speed (mph)**      |                 | -3.29*** (0.778) |
#> |        **Temperature**         |                 | 2.05*** (0.242)  |
#> |       **Fixed-Effects:**       | --------------- | ---------------- |
#> |           **Month**            |       Yes       |       Yes        |
#> |            **Day**             |       Yes       |       Yes        |
#> | **__________________________** | _______________ | ________________ |
#> |      **S.E.: Clustered**       |     by: Day     |     by: Day      |
#> |        **Observations**        |       111       |       111        |
#> |             **R2**             |     0.58018     |     0.81604      |
#> |         **Within R2**          |     0.12074     |     0.61471      |

What did it do? First, it called the function pandoc.table.return from within etable. Second, the argument style is not from etable but is from pander’s function. Indeed, all the arguments to the postprocessing function are caught and passed to it. So far so good. But you could say: why bother using the posprocessing function when we could just use piping? You’re right, but wait a second for the next section.

Setting etable default values

One important feature of etable is that you can set the default values of almost all its arguments. This includes the postprocessing function. Let’s change the default values of style.df and postprocess.df:

my_style = style.df(depvar.title = "", fixef.title = "", 
                    fixef.suffix = " fixed effect", yesNo = "yes")
setFixest_etable(style.df = my_style, postprocess.df = pandoc.table.return)

Since now pandoc.table.return is the default postprocessing, all its arguments are added to etable. So calls like that are valid even though style or caption are not arguments from etable:

etable(est[rhs = 2], style = "rmarkdown", caption = "New default values")
#> 
#> 
#> |                           |     model 1      |     model 2      |
#> |:------------------------------:|:----------------:|:----------------:|
#> |                                |   Ozone (ppb)    |   Ozone (ppb)    |
#> |                                |                  |                  |
#> | **Solar Radiation (Langleys)** |  0.052* (0.020)  |  0.051* (0.024)  |
#> |      **Wind Speed (mph)**      | -3.11*** (0.799) | -3.29*** (0.778) |
#> |        **Temperature**         | 1.88*** (0.367)  | 2.05*** (0.242)  |
#> |     **Month fixed effect**     |       yes        |       yes        |
#> |      **Day fixed effect**      |                  |       yes        |
#> | **__________________________** | ________________ | ________________ |
#> |      **S.E.: Clustered**       |     by: Day      |     by: Day      |
#> |        **Observations**        |       111        |       111        |
#> |             **R2**             |     0.63686      |     0.81604      |
#> |         **Within R2**          |     0.53154      |     0.61471      |
#> 
#> Table: New default values

Exporting multiple estimations to Latex

We now illustrate the exports to Latex. First, to include all the sections of a table, let’s add a fifth estimation to the previous example; this new estimation includes variables with varying slopes:

est_slopes = feols(Ozone ~ Solar.R + Wind | Day + Month[Temp], airquality)

To export to Latex, use the argument tex = TRUE (note that this argument is on when the argument file is not missing):

etable(est, est_slopes, tex = TRUE)

The previous code produces the following table:

The style of the table is rather sober, but no worries: most of it can be customized. We now illustrate: a) how to change the look of the table with the argument style.tex, and how to include custom features with the argument postproces.tex.

Changing the look of the Latex table with style.tex

The argument style.tex defines how the table looks. It allows an in-depth customization of the table. The table is split into several components, each allowing some customization. The components of a table and some of its associated keywords are described by the following figure:

The argument style.tex only accepts outputs from the function style.tex. That function is documented and describes the different components that can be found in the previous illustration.

The function style.tex has two starting points (in the argument main), either the style of the first table displayed, either a much more compact style named “aer”. Let’s show the same table with the aer style, without stars beside the coefficients, and different fit statistics:

etable(est, est_slopes, style.tex = style.tex("aer"), 
       signifCode = NA, fitstat = ~ r2 + n, tex = TRUE)

Which yields the following table: