Publication-ready tables with Great Tables or LaTeX booktabs — customize labels, significance stars, and output formats.
Table Layout with PyFixest
NoteMigration Notice
Starting with pyfixest 0.41.0 (currently in development), the table functionality is powered by maketables. The pf.etable() API remains unchanged. pf.dtable() is deprecated (use DTable() directly) and pf.make_table() has been removed (use maketables.MTable() directly).
Pyfixest comes with functions to generate publication-ready tables. Regression tables are generated with pf.etable(), which can output different formats, for instance using the Great Tables package or generating formatted LaTex Tables using booktabs. Descriptive statistics tables can be created with DTable() and custom tables with maketables.MTable().
To begin, we load some libraries and fit a set of regression models.
import numpy as npimport pandas as pdimport pylatex as pl # for the latex table; note: not a dependency of pyfixest - needs manual installationfrom maketables import DTablefrom great_tables import loc, style # great_tables is used by maketables internallyfrom IPython.display import FileLink, displayimport pyfixest as pf%load_ext autoreload%autoreload 2data = pf.get_data()fit1 = pf.feols("Y ~ X1 + X2 | f1", data=data)fit2 = pf.feols("Y ~ X1 + X2 | f1 + f2", data=data)fit3 = pf.feols("Y ~ X1 *X2 | f1 + f2", data=data)fit4 = pf.feols("Y2 ~ X1 + X2 | f1", data=data)fit5 = pf.feols("Y2 ~ X1 + X2 | f1 + f2", data=data)fit6 = pf.feols("Y2 ~ X1 *X2 | f1 + f2", data=data)
OMP: Info #276: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.
Regression Tables via pf.etable()
Basic Usage
We can compare all regression models via the pyfixest-internal pf.etable() function:
pf.etable([fit1, fit2, fit3, fit4, fit5, fit6])
Y
Y2
(1)
(2)
(3)
(4)
(5)
(6)
coef
X1
-0.95*** (0.066)
-0.924*** (0.056)
-0.924*** (0.056)
-1.267*** (0.211)
-1.232*** (0.211)
-1.231*** (0.211)
X2
-0.174*** (0.018)
-0.174*** (0.015)
-0.185*** (0.025)
-0.131* (0.056)
-0.118* (0.056)
-0.074 (0.094)
X1 × X2
0.011 (0.019)
-0.041 (0.071)
fe
f1
x
x
x
x
x
x
f2
-
x
x
-
x
x
stats
Observations
997
997
997
998
998
998
R2
0.489
0.659
0.659
0.12
0.172
0.172
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
You can also estimate and display multiple regressions with one line of code using the (py)fixest stepwise notation:
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Keep and drop variables
etable allows us to do a few things out of the box. For example, we can only keep the variables that we’d like, which keeps all variables that fit the provided regex match.
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error) [p-value]
Significance levels and rounding
Additionally, we can also overwrite the defaults for the reported significance levels and control the rounding of results via the signif_code and digits function arguments:
Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error)
Other output formats
By default, pf.etable() returns a GT object (see the Great Tables package), but you can also opt to dataframe, markdown, or latex output via the type argument.
To obtain latex output use type = "tex". If you want to save the table as a tex file, you can use the file_name= argument to specify the respective path where it should be saved. Etable will use latex packages booktabs, threeparttable, makecell, and tabularx for the table layout, so don’t forget to include these packages in your latex document.
# LaTex output (include latex packages booktabs, threeparttable, makecell, and tabularx in your document):tab = pf.etable( [fit1, fit2, fit3, fit4, fit5, fit6], signif_code=[0.01, 0.05, 0.1], digits=2,type="tex",)
The following code generates a pdf including the regression table which you can display clicking on the link below the cell:
## Use pylatex to create a tex file with the tabledef make_pdf(tab, file):"Create a PDF document with tex table." doc = pl.Document() doc.packages.append(pl.Package("booktabs")) doc.packages.append(pl.Package("threeparttable")) doc.packages.append(pl.Package("makecell")) doc.packages.append(pl.Package("tabularx"))with ( doc.create(pl.Section("A PyFixest LateX Table")), doc.create(pl.Table(position="htbp")) as table, ): table.append(pl.NoEscape(tab)) doc.generate_pdf(file, clean_tex=False)# Compile latex to pdf & display a button with the hyperlink to the pdf# requires tex installationrun =Falseif run: make_pdf(tab, "latexdocs/SampleTableDoc")display(FileLink("latexdocs/SampleTableDoc.pdf"))
Path (latexdocs/SampleTableDoc.pdf) doesn't exist. It may still be in the process of being generated, or you may have the incorrect path.
Rename variables
You can also rename variables if you want to have a more readable output. Just pass a dictionary to the labels argument. Note that interaction terms will also be relabeled using the specified labels for the interacted variables (if you want to manually relabel an interaction term differently, add it to the dictionary).
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
If you want to label the rows indicating the inclusion of fixed effects not with the variable label but with a custom label, you can pass on a separate dictionary to the felabels argument.
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Rename categorical variables
By default, categorical variables are returned using the formulaic “C(variable)[T.value]” notation. Via the cat_template argument, you can rename categorical variables via a specified template {variable}={value}. This works when either the variable is categorial in the DataFrame, or the C() or i() operators are used in the regresson formula. ´
# Add a categorical variabledata['job'] = np.random.choice(["Managerial", "Admin", "Blue collar"], size=len(data), p=[1/3, 1/3, 1/3])# Add a label for this variable to the dictionarylabels['job']="Job Family"fit7 = pf.feols("Y ~ X1 + X2 + job", data = data)pf.etable([fit7], labels=labels, cat_template ="{variable}::{value}")
Wage
(1)
coef
Age
-0.994*** (0.082)
Years of Schooling
-0.177*** (0.022)
Job Family::Blue collar
-0.082 (0.164)
Job Family::Managerial
0.048 (0.164)
Intercept
0.901*** (0.143)
stats
Observations
998
R2
0.178
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
But you can also remove the variable name and only keep the levels (categories) by specifying cat_template=“{value}”. Note that the labeling of categories also works in interaction terms:
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Change reference category
You can also change the reference category of a categorical variable using the ref argument in the interaction i() operator. For example, repeating the last estimation but changing the reference category to “Managerial” instead of “Admin”:
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Or change the ordering of headlines having headlines first and then dependent variables using the head_order argument. “hd” stands for headlines then dependent variables, “dh” for dependent variables then headlines. Assigning “d” or “h” can be used to only show dependent variables or only headlines. When head_order=“” only model numbers are shown.
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Further custom model information
You can add further custom model statistics/information to the bottom of the table by using the custom_stats argument to which you pass a dictionary with the name of the row and lists of values. The length of the lists must be equal to the number of models.
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Custom table notes
You can replace the default table notes with your own notes using the notes argument.
mynotes ="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."pf.etable( [fit1, fit4, fit2, fit5, fit3, fit6], labels=labels, model_heads=["US", "US", "China", "China", "EU", "EU"], head_order="hd", notes=mynotes,)
US
China
EU
Wage
Wealth
Wage
Wealth
Wage
Wealth
(1)
(2)
(3)
(4)
(5)
(6)
coef
Age
-0.95*** (0.066)
-1.267*** (0.211)
-0.924*** (0.056)
-1.232*** (0.211)
-0.924*** (0.056)
-1.231*** (0.211)
Years of Schooling
-0.174*** (0.018)
-0.131* (0.056)
-0.174*** (0.015)
-0.118* (0.056)
-0.185*** (0.025)
-0.074 (0.094)
Age × Years of Schooling
0.011 (0.019)
-0.041 (0.071)
fe
Industry
x
x
x
x
x
x
Year
-
-
x
x
x
x
stats
Observations
997
998
997
998
997
998
R2
0.489
0.12
0.659
0.172
0.659
0.172
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Publication-ready LaTex tables
With few lines of code you thus obtain a publication-ready latex table:
tab = pf.etable( [fit1, fit4, fit2, fit5, fit3, fit6], labels=labels, model_heads=["US", "US", "China", "China", "EU", "EU"], head_order="hd",type="tex", notes=mynotes, show_fe=True, show_se_type=False, custom_model_stats={"Number of Clusters": [42, 42, 42, 37, 37, 37], },)# Compile latex to pdf & display a button with the hyperlink to the pdfrun =Falseif run: make_pdf(tab, "latexdocs/SampleTableDoc2")display(FileLink("latexdocs/SampleTableDoc2.pdf"))
Path (latexdocs/SampleTableDoc2.pdf) doesn't exist. It may still be in the process of being generated, or you may have the incorrect path.
Rendering Tables in Quarto
When you use quarto you can include latex tables generated by pyfixest when rendering the qmd file as pdf. Just specify output: asis in the code block options of the respective chunk and print the LaTex string returned by etable. Don’t forget to include the \usepackage commands for necessary latex packages in the YAML block. Here you find a sample qmd file.
When you render either a jupyter notebook or qmd file to html it is advisable to turn html-table-processing off in quarto as otherwise quarto adds further formatting which alters how your tables look like. You can do this in a raw cell at the top of your document.
---
format:
html:
html-table-processing: none
---
Descriptive Statistics via DTable()
WarningDeprecation Notice
pf.dtable() will be deprecated in the future. Please use DTable from the maketables package.
The function DTable() allows to display descriptive statistics for a set of variables in the same layout.
Basic Usage of DTable
Specify the variables you want to display the descriptive statistics for. You can also use a dictionary to rename the variables and add a caption.
You can summarize by characteristics using the bycol argument when groups are to be displayed in columns. When the number of observations is the same for all variables in a group, you can also opt to display the number of observations only once for each group byin a separate line at the bottom of the table with counts_row_below==True.
# Generate some categorial datadata["country"] = np.random.choice(["US", "EU"], data.shape[0])data["occupation"] = np.random.choice(["Blue collar", "White collar"], data.shape[0])# Drop nan values to have balanced datadata.dropna(inplace=True)DTable( data,vars=["Y", "Y2", "X1", "X2"], labels=labels, bycol=["country", "occupation"], stats=["count", "mean", "std"], caption="Descriptive statistics", stats_labels={"count": "Number of observations"}, counts_row_below=True,)
Descriptive statistics
EU
US
Blue collar
White collar
Blue collar
White collar
Mean
Std. Dev.
Mean
Std. Dev.
Mean
Std. Dev.
Mean
Std. Dev.
stats
Wage
-0.20
2.25
-0.19
2.40
0.01
2.28
-0.13
2.30
Wealth
-0.84
5.43
0.24
5.71
-0.50
5.48
-0.20
5.69
Age
1.07
0.79
1.02
0.79
1.05
0.82
1.03
0.83
Years of Schooling
-0.31
3.22
0.15
2.98
-0.13
2.96
-0.22
3.04
nobs
Number of observations
233.00
247.00
254.00
263.00
You can also use custom aggregation functions to compute further statistics or affect how statistics are presented. Pyfixest provides two such functions mean_std and mean_newline_std which compute the mean and standard deviation and display both the same cell (either with line break between them or not). This allows to have more compact tables when you want to show statistics for many characteristcs in the columns.
You can also hide the display of the statistics labels in the header with hide_stats_labels=True. In that case a table note will be added naming the statistics displayed using its label (if you have not provided a custom note).
You can also split by characteristics in both columns and rows. Note that you can only use one grouping variable in rows, but several in columns (as shown above).
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Defining Table Styles: Some Examples
You can easily define table styles that you can apply to all tables in your project. Just define a dictionary with the respective values for the tab options (see the Great Tables documentation) and use the style with .tab_options(**style_dict).