Python Tabulate: Format Tables from Lists and DataFrames
python tabulate format tables from lists and dataframes: Learn how to use Python tabulate to format tables from lists, dictionaries, and pandas DataFrames with headers...
python tabulate format tables from lists and dataframes requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
To format tables from lists and dataframes in Python, the tabulate library is one of the most direct options. It accepts lists, lists of lists, dictionaries, and pandas DataFrames, then renders them in a variety of text formats suitable for terminal output, Markdown documents, or plain-text reports. Instead of hand-building string alignment, tabulate handles column widths, separators, and alignment for you.
Installing tabulate
pip install tabulate
The library has no mandatory dependencies. If you pass a pandas DataFrame, pandas itself must be installed, but tabulate does not require it for list-based input. This makes it usable in small scripts where pulling in a full data-processing stack is unnecessary.
Formatting a List of Lists
The most common input is a list of lists, where each inner list represents one row:
from tabulate import tabulate data = [ ["nginx", 120, 2.4], ["postgres", 45, 1.1], ["redis", 89, 0.8], ] print(tabulate(data))
The default output is a simple grid with aligned columns:
--------- --- ---
nginx 120 2.4
postgres 45 1.1
redis 89 0.8
--------- --- ---
The first row is not treated as a header by default. If your first row contains column names, pass headers="firstrow":
print(tabulate(data, headers="firstrow"))
Adding Explicit Headers
When the data and headers are separate, pass a list of column names:
data = [ ["nginx", 120, 2.4], ["postgres", 45, 1.1], ] print(tabulate(data, headers=["Service", "Requests", "CPU"]))
You can also use headers="keys" when working with a list of dictionaries:
rows = [ {"service": "nginx", "requests": 120}, {"service": "postgres", "requests": 45}, ] print(tabulate(rows, headers="keys"))
This reads the dictionary keys as column headers and fills missing values with empty cells.
Choosing a Table Format
The tablefmt parameter controls the visual style. The most commonly used values are:
| tablefmt | Use case |
|---|---|
plain | No borders, minimal output |
simple | Default, single horizontal line |
grid | ASCII grid with full borders |
pipe | Markdown-compatible pipe table |
html | HTML <table> markup |
github | GitHub-flavored Markdown |
fancy_grid | Rounded borders with box-drawing characters |
print(tabulate(data, headers="firstrow", tablefmt="pipe"))
Output:
| service | requests |
|:----------|-----------:|
| nginx | 120 |
| postgres | 45 |
The pipe format is useful when you need to paste a table into a Markdown file or a GitHub issue.
Formatting a pandas DataFrame
Passing a DataFrame to tabulate works directly:
import pandas as pd from tabulate import tabulate df = pd.DataFrame({ "service": ["nginx", "postgres", "redis"], "requests": [120, 45, 89], "cpu": [2.4, 1.1, 0.8], }) print(tabulate(df, headers="keys", tablefmt="grid"))
The DataFrame index is not included by default. If you want the index column visible, pass showindex=True:
print(tabulate(df, headers="keys", showindex=True))
This is useful when the index carries meaningful information, such as a date or an ID, rather than a positional row number.
Controlling Number Formatting
tabulate renders numbers using their default string representation, which can produce long decimals. The floatfmt parameter applies a format string to floating-point columns:
print(tabulate(data, headers=["Service", "Requests", "CPU"], floatfmt=".2f"))
For per-column control, pass a list of format strings matching the column order:
print(tabulate(data, headers=["Service", "Requests", "CPU"], floatfmt=(".2f", ".1f")))
Integer columns are unaffected by floatfmt.
Missing Values and None Handling
None values are rendered as an empty string by default. This can make a table look like data is missing when it is simply absent. The missingval parameter replaces that placeholder:
data = [ ["nginx", 120, None], ["postgres", None, 1.1], ] print(tabulate(data, headers=["Service", "Requests", "CPU"], missingval="N/A"))
This matters when the output is consumed by another tool or read by a person who needs to distinguish a missing value from a zero.
Performance Considerations with Large Data
tabulate builds the entire output as a single string in memory. For a few hundred rows this is fine. For tens of thousands of rows, the string concatenation and column-width calculation become noticeable. The library computes the maximum width of every column by scanning all rows, so memory usage grows with the total size of the rendered text.
If you are formatting a very large DataFrame, consider whether you actually need the full table in the terminal. Printing a truncated preview with df.head(20) before passing it to tabulate keeps the output readable and avoids building a multi-megabyte string. For programmatic consumption, writing the DataFrame directly to CSV or Parquet is usually more appropriate than rendering it as text.
Alignment Control
Column alignment defaults to left for text and right for numbers. The stralign and numalign parameters override this:
print(tabulate(data, headers="firstrow", stralign="center", numalign="right"))
The disable_numparse parameter is worth knowing when a column contains values that look numeric but should stay as text, such as zip codes or product codes:
data = [ ["A100", "west"], ["B200", "east"], ] print(tabulate(data, headers=["Code", "Region"], disable_numparse=True))
Without this, tabulate may right-align the code column because it detects numeric-looking content.
When tabulate Is the Right Choice
tabulate is best suited for human-readable output: terminal logs, generated reports, Markdown documents, and quick debugging. It is not a data interchange format. If the table will be parsed by another system, prefer CSV, JSON, or the DataFrame's native to_csv and to_parquet methods.
For interactive exploration inside a Jupyter notebook, the DataFrame's own HTML rendering is usually more convenient than a text table. tabulate becomes the better option when the output must be plain text, such as in a CI log, a command-line tool, or an email body.
The html tablefmt can generate an HTML table without pulling in an HTML templating library, which is occasionally useful for simple report generation. For anything beyond a static table, a proper templating engine is a better fit.