# Import necessary libraries
import numpy as np
import pandas as pd
import pyfixest as pf
import statsmodels.formula.api as smf
import maketables as mt
# Load sample dataset
df = pd.read_csv("../data/salaries.csv")
# Estimate regressions for examples
est1 = pf.feols("logwage ~ age", data=df)
est2 = pf.feols("logwage ~ age + female", data=df)
est3 = pf.feols("logwage ~ age + female | worker_type", data=df)
est4 = pf.feols("logwage ~ age * female | worker_type + occupation", data=df)Regression tables with ETable
Regression tables are generated with ETable(). ETable inherits from the MTable base class, which provides all the core output functionality. This means that ETable can generate tables in multiple formats (HTML/GT, docx, LaTeX).
Let us load a sample dataset and run some regressions (here using PyFixest but you can also use other packages such as statsmodels):
Basic Usage
The simplest way to create to simply call ETable() with a list of models:
mt.ETable([est1, est2, est3, est4])| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| age × female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| occupation | - | - | - | x |
| worker_type | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. 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:
mt.ETable(pf.feols("logwage+wage~csw(age, female, age:female)", data=df))| logwage | wage | |||||
|---|---|---|---|---|---|---|
| (1) | (2) | (3) | (4) | (5) | (6) | |
| coef | ||||||
| age | 0.005*** (0.001) |
0.005*** (0.001) |
0.007*** (0.001) |
327.776*** (59.676) |
340.031*** (59.661) |
422.053*** (83.182) |
| female | -0.057** (0.023) |
0.051 (0.086) |
-4128.632*** (1323.781) |
2759.371 (5045.686) |
||
| age × female | -0.003 (0.002) |
-168.821 (119.337) |
||||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
10.697*** (0.059) |
49378.541*** (2521.411) |
50913.384*** (2563.005) |
47628.477*** (3457.930) |
| stats | ||||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.019 | 0.017 | 0.022 | 0.023 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||||
Change order of coefficients
You can change the order in which coefficients are displayed using the order argument. This argument takes a list of strings that specify the order of the coefficients. Any coefficients not included in the list will be displayed after the specified ones, in their original order.
mt.ETable([est1, est2, est3, est4], order=["age:female","female", "age"])| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| age × female | -0.003 (0.001) |
|||
| female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| occupation | - | - | - | x |
| worker_type | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||
Keep and drop variables
For example, we can only keep the variables that we’d like, which keeps all variables that fit the provided regex match.
mt.ETable([est1, est2, est3, est4], keep="age")| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| age × female | -0.003 (0.001) |
|||
| fe | ||||
| occupation | - | - | - | x |
| worker_type | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||
We can use the exact_match argument to select a specific set of variables:
mt.ETable([est1, est2, est3, est4], keep=["age", "female"], exact_match=True)| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| fe | ||||
| occupation | - | - | - | x |
| worker_type | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||
We can also easily drop variables via the drop argument:
mt.ETable([est1, est2, est3, est4], drop=["age"])| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| occupation | - | - | - | x |
| worker_type | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||
Specify displayed model statistics
If model_stats is not specified, ETable automatically selects statistics based on model type. This is handled by model extractors - specialized classes that know how to extract information from different types of regression models (statsmodels, PyFixest, linearmodels, etc.). Each extractor can define which statistics are most relevant for its model type. For example, logit and probit models from statsmodels automatically show Observations, Pseudo R², and Log-likelihood instead of regular R². When you mix different model types in one table (e.g., OLS and probit), ETable displays the union of all relevant defaults, so you see both R² (for OLS) and Pseudo R² + Log-likelihood (for probit).
You can override this automatic selection by passing a list of strings to model_stats. Names of the statistics must match the model’s respective attribute names such as “r2”, “adj_r2”, “N”, “r2_within” (see the respective function reference for attributes and omit the leading “_“). The type of standard error estimated can be shown by adding”se_type” to the list. When passing an empty list no model statistics are displayed.
mt.ETable([est1, est2, est3, est4], model_stats=['N','r2','r2_within'])| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| age × female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| occupation | - | - | - | x |
| worker_type | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Within R2 | - | - | 0.017 | 0.016 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||
Model statistics can also be relabeled by passing a dictionary model_stats_labels.
mt.ETable([est1, est2, est3, est4], model_stats=['N','r2'], model_stats_labels={"N": "Number of observations", "r2": "R squared"})| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| age × female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| occupation | - | - | - | x |
| worker_type | - | - | x | x |
| stats | ||||
| Number of observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R squared | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||
Hide fixed effects
We can hide the rows showing the relevant fixed effects by setting show_fe=False (for instance when the set of fixed effects is the same for all models and you want to describe this in the text or table notes rather than displaying it in the table).
mt.ETable([est1, est2, est3, est4], model_stats=['r2','N'], show_fe=False)| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| age × female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| stats | ||||
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||
Formatting coefficients and standard errors
By default coefficients and standard errors are reported. But we can also ask to output p-values or t-values via the coef_fmt function argument. This parameter accepts a template string that defines the layout and formatting of each coefficient cell. The coef_fmt parameter uses special tokens to represent different elements:
b- The coefficient estimate (beta)se- The standard error
t- The t-statisticp- The p-valueci95l&ci95u- The lower and upper limits of the confidence band\n- Line break (creates a new line in the cell)- Literal text - Any other characters (like parentheses, brackets, spaces) appear as-is
For example, "b* \n (se)" creates a two-line cell with the coefficient on the first line and the standard error in parentheses on the second line. You can control the format of numbers in two ways: The digits parameter provides a simple way to set decimal places for all elements when you’re using basic tokens without format specifiers. But you can also add :format after a token (e.g., b:.3f, se:.2e) to specify the exact formatting you want. Here are some common format specifiers:
.3f- 3 decimal places (e.g., 1.234).2e- Scientific notation with 2 decimals (e.g., 1.23e-02)
,.0f- Comma separators, no decimals (e.g., 1,234),.2f- Comma separators with 2 decimals (e.g., 1,234.56):d- Integer formatting (e.g., 1234)
You can customize whether and where significance stars (*, **, ***) appear by appending * after a token in the coef_fmt string (Stars are shown based on the signif_code thresholds that you can pass as a list to ETable or change in the defaults as explained below.):
b*- Stars after the coefficient (default)t*- Stars after the t-statisticse*- Stars after the standard errorp*- Stars after the p-valueb:.3f*- Stars after a formatted coefficient
For example: - "b* \n (se)" - Default behavior, stars appear after coefficient - "b \n (t*)" - Stars appear after the t-statistic instead - "b:.3f \n (se:.3f)" - No stars (default format applied but * not specified)
# Show standard errors and p-values
mt.ETable([est1, est2, est3, est4], coef_fmt="b* \n (se) \n [p]", digits=4)| logwage | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| age | 0.0052*** (0.0010) [0.0000] |
0.0053*** (0.0010) [0.0000] |
0.0051 (0.0009) [0.1170] |
0.0065** (0.0005) [0.0469] |
| female | -0.0572** (0.0225) [0.0112] |
-0.0570 (0.0409) [0.3967] |
0.0939* (0.0127) [0.0853] |
|
| age × female | -0.0028 (0.0007) [0.1559] |
|||
| Intercept | 10.7272*** (0.0429) [0.0000] |
10.7485*** (0.0436) [0.0000] |
||
| fe | ||||
| occupation | - | - | - | x |
| worker_type | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) [p-value] | ||||
# Example: Show coefficients with 2 and standard errors with 3 decimal places and no stars
mt.ETable([est1, est2, est3], coef_fmt="b:.2f \n (se:.3f)")| logwage | |||
|---|---|---|---|
| (1) | (2) | (3) | |
| coef | |||
| age | 0.01 (0.001) |
0.01 (0.001) |
0.01 (0.001) |
| female | -0.06 (0.023) |
-0.06 (0.041) |
|
| Intercept | 10.73 (0.043) |
10.75 (0.044) |
|
| fe | |||
| worker_type | - | - | x |
| stats | |||
| Observations | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 |
| Format of coefficient cell: Coefficient (Std. Error) | |||
# Scientific notation for small coefficients
mt.ETable([est1, est2, est3], coef_fmt="b:.2e* \n (se:.2e)")| logwage | |||
|---|---|---|---|
| (1) | (2) | (3) | |
| coef | |||
| age | 5.16e-03*** (1.01e-03) |
5.33e-03*** (1.02e-03) |
5.08e-03 (9.44e-04) |
| female | -5.72e-02** (2.25e-02) |
-5.70e-02 (4.09e-02) |
|
| Intercept | 1.07e+01*** (4.29e-02) |
1.07e+01*** (4.36e-02) |
|
| fe | |||
| worker_type | - | - | x |
| stats | |||
| Observations | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | |||
# Show confidence intervals instead of standard errors & have no stars
mt.ETable([est1, est2, est3], coef_fmt="b \n [ci95l, ci95u]", digits=3)| logwage | |||
|---|---|---|---|
| (1) | (2) | (3) | |
| coef | |||
| age | 0.005 [0.003, 0.007] |
0.005 [0.003, 0.007] |
0.005 [-0.007, 0.017] |
| female | -0.057 [-0.101, -0.013] |
-0.057 [-0.577, 0.463] |
|
| Intercept | 10.727 [10.643, 10.811] |
10.748 [10.663, 10.834] |
|
| fe | |||
| worker_type | - | - | x |
| stats | |||
| Observations | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 |
| Format of coefficient cell: Coefficient [95% CI Lower, 95% CI Upper] | |||
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).
labels = {
"logwage": "ln(Wage)",
"wage": "Wage",
"age": "Age",
"female": "Female",
"tenure": "Years of Tenure",
"occupation": "Occupation",
"worker_type": "Worker Type",
"education": "Education Level"
}
mt.ETable([est1, est2, est3, est4], labels=labels)| ln(Wage) | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| Age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| Female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| Age × Female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| Occupation | - | - | - | x |
| Worker Type | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. 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.
mt.ETable(
[est1, est2, est3, est4],
labels=labels,
felabels={"occupation": "Occupation Fixed Effects", "worker_type": "Worker Type Fixed Effects"},
)| ln(Wage) | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| Age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006** (0.000) |
| Female | -0.057** (0.023) |
-0.057 (0.041) |
0.094* (0.013) |
|
| Age × Female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| Occupation Fixed Effects | - | - | - | x |
| Worker Type Fixed Effects | - | - | x | x |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Significance levels: * p < 0.1, ** p < 0.05, *** p < 0.01. Format of coefficient cell: Coefficient (Std. Error) | ||||
Setting defaults
You can set default options for ETable by changing the class attributes. For example, to change the default model statistics, always use the same labels, digits, and significance levels, you can do the following:
# Set default options for all future ETable calls
mt.ETable.DEFAULT_MODEL_STATS = ["N","r2","r2_within"]
mt.ETable.DEFAULT_COEF_FMT = "b:.3f* \n (se:.3f)"
mt.ETable.DEFAULT_SIGNIF_CODE = [0.001, 0.01, 0.05]Note that you can also change the defaults for the fixed effects markers:
mt.ETable.DEFAULT_FE_MARKER = ("Yes","No")You can also set default labels. Importantly, default labels are a class attribute of MTable. The reason is that they then also apply to other table types such as (so far) DTable for descriptive statistics.
# Note: Default labels are set as class attribute of MTable
mt.MTable.DEFAULT_LABELS = labelsmt.ETable([est1, est2, est3, est4])| ln(Wage) | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| Age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006* (0.000) |
| Female | -0.057* (0.023) |
-0.057 (0.041) |
0.094 (0.013) |
|
| Age × Female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| Occupation | No | No | No | Yes |
| Worker Type | No | No | Yes | Yes |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Within R2 | - | - | 0.017 | 0.016 |
| Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error) | ||||
Rename categorical variables
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. ´
est5 = pf.feols("logwage ~ age + female + education", data = df)
mt.ETable([est5], cat_template = "{variable}: {value}")| ln(Wage) | |
|---|---|
| (1) | |
| coef | |
| Age | 0.005*** (0.001) |
| Female | -0.042* (0.020) |
| Education Level: High School | -0.302*** (0.027) |
| Education Level: Master's | 0.219*** (0.031) |
| Education Level: PhD | 0.373*** (0.055) |
| Education Level: Some College | -0.199*** (0.028) |
| Intercept | 10.815*** (0.042) |
| stats | |
| Observations | 1,800 |
| R2 | 0.191 |
| Within R2 | - |
| 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:
est6 = pf.feols("logwage ~ age + female + education", data = df)
est7 = pf.feols("logwage ~ age + female + education*age", data = df)
mt.ETable([est6, est7], cat_template="{value}")| ln(Wage) | ||
|---|---|---|
| (1) | (2) | |
| coef | ||
| Age | 0.005*** (0.001) |
0.006*** (0.002) |
| Female | -0.042* (0.020) |
-0.041* (0.021) |
| High School | -0.302*** (0.027) |
-0.348*** (0.103) |
| Master's | 0.219*** (0.031) |
0.285* (0.117) |
| PhD | 0.373*** (0.055) |
0.413 (0.218) |
| Some College | -0.199*** (0.028) |
-0.128 (0.108) |
| High School × Age | 0.001 (0.002) |
|
| Master's × Age | -0.002 (0.003) |
|
| PhD × Age | -0.001 (0.005) |
|
| Some College × Age | -0.002 (0.003) |
|
| Intercept | 10.815*** (0.042) |
10.799*** (0.067) |
| stats | ||
| Observations | 1,800 | 1,800 |
| R2 | 0.191 | 0.191 |
| Within R2 | - | - |
| Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error) | ||
Custom model headlines
You can also add custom headers for each model by passing a list of strings to the model_headers argument.
mt.ETable(
[est1, est2, est3, est4],
model_heads=["OLS", "OLS", "Fixed Effects", "Fixed Effects"],
)| ln(Wage) | ||||
|---|---|---|---|---|
| OLS | Fixed Effects | |||
| (1) | (2) | (3) | (4) | |
| coef | ||||
| Age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006* (0.000) |
| Female | -0.057* (0.023) |
-0.057 (0.041) |
0.094 (0.013) |
|
| Age × Female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| Occupation | No | No | No | Yes |
| Worker Type | No | No | Yes | Yes |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Within R2 | - | - | 0.017 | 0.016 |
| 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.
mod1 = pf.feols("logwage ~ age + female", data=df)
mod2 = pf.feols("wage ~ age + female", data=df)
mod3 = pf.feols("logwage ~ age + female | worker_type + occupation", data=df)
mod4 = pf.feols("wage ~ age + female | worker_type + occupation", data=df)
mt.ETable([mod1, mod2, mod3, mod4],
model_heads=["OLS", "OLS", "Fixed Effects", "Fixed Effects"],
head_order="hd")
| OLS | Fixed Effects | |||
|---|---|---|---|---|
| ln(Wage) | Wage | ln(Wage) | Wage | |
| (1) | (2) | (3) | (4) | |
| coef | ||||
| Age | 0.005*** (0.001) |
340.031*** (59.661) |
0.005 (0.001) |
329.914* (12.715) |
| Female | -0.057* (0.023) |
-4128.632** (1323.781) |
-0.018 (0.041) |
-1763.573 (2133.279) |
| Intercept | 10.748*** (0.044) |
50913.384*** (2563.005) |
||
| fe | ||||
| Occupation | No | No | Yes | Yes |
| Worker Type | No | No | Yes | Yes |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.018 | 0.022 | 0.095 | 0.1 |
| Within R2 | - | - | 0.015 | 0.019 |
| Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error) | ||||
Remove the dependent variables from the headers:
mt.ETable(
[est1, est2, est3, est4],
head_order="",
)| (1) | (2) | (3) | (4) | |
|---|---|---|---|---|
| coef | ||||
| Age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006* (0.000) |
| Female | -0.057* (0.023) |
-0.057 (0.041) |
0.094 (0.013) |
|
| Age × Female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| Occupation | No | No | No | Yes |
| Worker Type | No | No | Yes | Yes |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Within R2 | - | - | 0.017 | 0.016 |
| 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.
mt.ETable(
[est1, est2, est3, est4],
show_fe=False,
custom_model_stats={
"Fixed Effects": ["No", "No", "Worker Type", "Worker Type & Occupation"],
"Data Type": ["Cross-section", "Cross-section", "Cross-section", "Cross-section"],
},
)| ln(Wage) | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| Age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006* (0.000) |
| Female | -0.057* (0.023) |
-0.057 (0.041) |
0.094 (0.013) |
|
| Age × Female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| stats | ||||
| Fixed Effects | No | No | Worker Type | Worker Type & Occupation |
| Data Type | Cross-section | Cross-section | Cross-section | Cross-section |
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Within R2 | - | - | 0.017 | 0.016 |
| 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 = "This table shows wage regressions using a simulated dataset of 1,800 workers. The dependent variable is log wage. Models progressively add controls and fixed effects."
mt.ETable(
[est1, est2, est3, est4],
notes=mynotes,
)| ln(Wage) | ||||
|---|---|---|---|---|
| (1) | (2) | (3) | (4) | |
| coef | ||||
| Age | 0.005*** (0.001) |
0.005*** (0.001) |
0.005 (0.001) |
0.006* (0.000) |
| Female | -0.057* (0.023) |
-0.057 (0.041) |
0.094 (0.013) |
|
| Age × Female | -0.003 (0.001) |
|||
| Intercept | 10.727*** (0.043) |
10.748*** (0.044) |
||
| fe | ||||
| Occupation | No | No | No | Yes |
| Worker Type | No | No | Yes | Yes |
| stats | ||||
| Observations | 1,800 | 1,800 | 1,800 | 1,800 |
| R2 | 0.014 | 0.018 | 0.082 | 0.096 |
| Within R2 | - | - | 0.017 | 0.016 |
| This table shows wage regressions using a simulated dataset of 1,800 workers. The dependent variable is log wage. Models progressively add controls and fixed effects. | ||||