Table Layout with MTable

MTable is the base class for generating tables and outputting them to different formats. It takes a pandas (Multiindex) DataFrame as its core input and formats it. DTable and ETable are subclasses of MTable that inherit its functionality and add specific output capabilities for their purpose. You can use MTable to add your own custom table needs, either by calling it directly or by subclassing it.

The simplest way to create a table is to pass a pandas DataFrame to MTable():

# Import necessary libraries
import sys
import numpy as np
import pandas as pd
import maketables as mt

df = pd.DataFrame(np.random.randn(4, 4).round(2), columns=["A", "B", "C", "D"])

# Create table
mt.MTable(df)
A B C D
0 -1.91 -0.86 -0.41 1.89
1 0.56 -1.34 0.49 -1.55
2 1.08 -0.47 -0.09 1.33
3 -1.29 -1.4 -0.58 1.04

When the respective dataframe has a mutiindex for the columns, columns spanners are generated from the index. The row index can also be a multiindex (of at most two levels). In this case the first index level is used to generate group rows (for instance using the index name as headline and separating the groups by a horizontal line) and the second index level is used to generate the row labels.

# Create a multiindex dataframe with random data
row_index = pd.MultiIndex.from_tuples(
    [
        ("Group 1", "Variable 1"),
        ("Group 1", "Variable 2"),
        ("Group 1", "Variable 3"),
        ("Group 2", "Variable 4"),
        ("Group 2", "Variable 5"),
        ("Group 3", "Variable 6"),
    ]
)

col_index = pd.MultiIndex.from_product([["A", "B"], ["X", "Y"], ["High", "Low"]])
df = pd.DataFrame(np.random.randn(6, 8).round(3), index=row_index, columns=col_index)

mt.MTable(df=df, caption="This is a caption", notes="These are notes")
This is a caption
  A B
X Y X Y
High Low High Low High Low High Low
Group 1
Variable 1 -1.519 -2.832 -0.451 0.552 1.2 -0.463 -0.411 1.154
Variable 2 -1.87 -0.389 0.19 0.449 -0.51 0.034 -2.488 -0.658
Variable 3 0.454 -0.982 0.059 0.447 -0.343 0.17 -0.963 -0.207
Group 2
Variable 4 0.61 0.157 -0.587 0.224 0.715 -2.05 1.159 -0.336
Variable 5 0.425 1.197 -1.372 -0.709 -0.289 -0.784 1.735 -0.857
Group 3
Variable 6 -0.556 0.204 -1.202 -0.396 0.317 -0.333 -0.093 -0.529
These are notes

Example: Generating a Correlation Table

Here we for instance build a correlation table with MTable:

# Example: Correlation Table with Simulated Data and Significance Stars
from scipy.stats import pearsonr

# Simulate data for 8 variables
np.random.seed(42)
n = 500

data = {
    'Income': np.random.normal(50000, 15000, n),
    'Age': np.random.normal(40, 12, n),
    'Education': np.random.normal(14, 3, n),
    'Experience': np.random.normal(15, 8, n),
    'Hours': np.random.normal(40, 8, n),
    'Job satisfaction': np.random.normal(7, 2, n),
}

sim_df = pd.DataFrame(data)
variables = sim_df.columns
corr_matrix = pd.DataFrame(index=variables, columns=variables)
p_values = pd.DataFrame(index=variables, columns=variables)

for i, var1 in enumerate(variables):
    for j, var2 in enumerate(variables):
        if i <= j:  # Only calculate for upper triangle and diagonal
            if i == j:
                corr_matrix.loc[var1, var2] = 1.0
                p_values.loc[var1, var2] = 0.0
            else:
                corr, pval = pearsonr(sim_df[var1], sim_df[var2])
                corr_matrix.loc[var1, var2] = corr
                p_values.loc[var1, var2] = pval

# Format correlations with significance stars
def add_stars(corr_val, p_val):
    if pd.isna(corr_val):
        return ''
    corr_str = f'{float(corr_val):.2f}'
    if corr_val == 1.0:
        return corr_str
    if float(p_val) < 0.01:
        return corr_str + '***'
    elif float(p_val) < 0.05:
        return corr_str + '**'
    elif float(p_val) < 0.10:
        return corr_str + '*'
    else:
        return corr_str

# Apply formatting
corr_display = pd.DataFrame(index=corr_matrix.index, columns=corr_matrix.columns)
for i in corr_matrix.index:
    for j in corr_matrix.columns:
        corr_display.loc[i, j] = add_stars(corr_matrix.loc[i, j], p_values.loc[i, j])

# Create table with custom column widths
ct= mt.MTable(
    corr_display,
    caption="Correlation Matrix of Labor Market Variables",
    notes="Pearson correlation coefficients. * p<0.10, ** p<0.05, *** p<0.01",
    tab_label="tab:correlation",
    gt_style={"first_col_width": "150px", "table_width": "80%"},
)

ct
Correlation Matrix of Labor Market Variables
Income Age Education Experience Hours Job satisfaction
Income 1.00 -0.08* -0.06 0.06 -0.01 0.03
Age 1.00 0.08* -0.02 0.03 0.05
Education 1.00 -0.02 -0.04 0.01
Experience 1.00 -0.09** 0.02
Hours 1.00 -0.02
Job satisfaction 1.00
Pearson correlation coefficients. * p<0.10, ** p<0.05, *** p<0.01

Exporting Tables

Export your table to different formats. Currently maketables supports LaTeX, HTML (via great_tables), Word (docx), and Typst. Note that we can also add different output specific formatting settings:

# Save correlation table to LaTeX
ct.save(type='tex', file_name='../output/correlation_table.tex', replace=True, show=False,
        tex_style={"first_col_width": "3cm"})

# Save to Word document
ct.save(type='docx', file_name='../output/correlation_table.docx', 
        docx_style={"first_col_width": "2in", "arraystretch": 1.2})

# Save to HTML
ct.save(type='html', file_name='../output/correlation_table.html')

Updating Documents

For LaTex, Word, and Typst, you can also update existing documents with the respective update_tex, update_docx, and update_typst methods. For LaTex and Typst, the methods search for a table with the same label in the respective document and replace it with the rendered table object. For Word documents, you specify the table number and the respective table is replaced (see the respective documentation for the output formats). In both cases, if a table with the label (or number) is not found in the document, the rendered table is appended to the end of the document.