Getting Started

Tables

Painless creation of nice-looking tables of data for Python.

Starting simple

from ansitable import ANSITable

table = ANSITable("Name", "Age", "Admission score")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
# →          Name   Age   Admission score  
# →         Alice    25              95.1  
# →           Bob    30              87.3  
# →         Carol    28              92.1  
# →  Michelangelo    35              88.0

Note

The lines prefixed by # → indicate the output of the preceding code block. They are commented to distinguish them from lines of executable code. This also means that if you copy and paste this code, using the icon in the top-right corner of the code block, these output lines will not be executed.

The result is a table with column widths automatically chosen, headings and column data all right-justified (default).

By default output is written to the console (stdout), but you can also:

  • write to a specific file by passing the file option to .print()

  • obtain the table as a multi-line string using str(table)

Borders

You can add borders made up of regular ASCII characters:

from ansitable import ANSITable

table = ANSITable("Name", "Age", "Admission score", border="ascii")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
# → +--------------+-----+-----------------+
# → |         Name | Age | Admission score |
# → +--------------+-----+-----------------+
# → |        Alice |  25 |            95.1 |
# → |          Bob |  30 |            87.3 |
# → |        Carol |  28 |            92.1 |
# → | Michelangelo |  35 |            88.0 |
# → +--------------+-----+-----------------+

Or use ANSI box-drawing characters (supported by most terminal emulators):

from ansitable import ANSITable

table = ANSITable("Name", "Age", "Admission score", border="thick")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
# → ┏━━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━━━━┓
# → ┃         Name ┃ Age ┃ Admission score ┃
# → ┣━━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━━━━┫
# → ┃        Alice ┃  25 ┃            95.1 ┃
# → ┃          Bob ┃  30 ┃            87.3 ┃
# → ┃        Carol ┃  28 ┃            92.1 ┃
# → ┃ Michelangelo ┃  35 ┃            88.0 ┃
# → ┗━━━━━━━━━━━━━━┻━━━━━┻━━━━━━━━━━━━━━━━━┛

Other border options: "thin", "round" (thin with rounded corners), and "double".

Column options

To gain additional control, you can create a table with Column objects, which allow you to specify formatting, alignment, and width constraints for each column:

from ansitable import ANSITable, Column

table = ANSITable(
    Column("Name", headalign="^", colalign="<"),
    Column("Age", headalign="^", colalign="<"),
    Column("Admission score", headalign="^", colalign=">"),
    border="thin")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
# → ┌──────────────┬─────┬─────────────────┐
# → │     Name     │ Age │ Admission score │
# → ├──────────────┼─────┼─────────────────┤
# → │ Alice        │ 25  │            95.1 │
# → │ Bob          │ 30  │            87.3 │
# → │ Carol        │ 28  │            92.1 │
# → │ Michelangelo │ 35  │            88.0 │
# → └──────────────┴─────┴─────────────────┘

Control alignment with colalign (data) and headalign (heading):

  • "<" - left

  • ">" - right (default)

  • "^" - center

There is also a shorthand way to control header and column alignment using a format string in the column headers.

from ansitable import ANSITable, Column

table = ANSITable("{<}Name", "{^<}Age", "{^>}Admission score", border="thin")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
# → ┌──────────────┬─────┬─────────────────┐
# → │ Name         │ Age │ Admission score │
# → ├──────────────┼─────┼─────────────────┤
# → │ Alice        │ 25  │            95.1 │
# → │ Bob          │ 30  │            87.3 │
# → │ Carol        │ 28  │            92.1 │
# → │ Michelangelo │ 35  │            88.0 │
# → └──────────────┴─────┴─────────────────┘

If the header string starts with {XY} where X and Y are alignment characters, these apply to the header and column alignment respectively. If the header string starts with {X} then X is used as the alignment character for both the header and the column.

Width constraints

Column width can be limited using the width argument:

from ansitable import ANSITable, Column

table = ANSITable(
    Column("Name", headalign="^", colalign="<", width=10),
    Column("Age", headalign="^", colalign="<"),
    Column("Admission score", headalign="^", colalign=">"),
    border="thin")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
# → ┌────────────┬─────┬─────────────────┐
# → │    Name    │ Age │ Admission score │
# → ├────────────┼─────┼─────────────────┤
# → │ Alice      │ 25  │            95.1 │
# → │ Bob        │ 30  │            87.3 │
# → │ Carol      │ 28  │            92.1 │
# → │ Michelang… │ 35  │            88.0 │
# → └────────────┴─────┴─────────────────┘

Excess text is truncated with an ellipsis (U+2026). Disable with ellipsis=False which simply truncates the field.

Field formatting

For numeric columns we can specify Python format strings that control how the cell values are rendered. Here we display age in hexadecimal and the score with 2 digits of precision.

from ansitable import ANSITable, Column

table = ANSITable(
    Column("Name", headalign="^", colalign="<", width=10),
    Column("Age", "0x{:x}", headalign="^", colalign="<"),
    Column("Admission score", "{:.3f}", headalign="^", colalign=">"),
    border="thin")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
# → ┌────────────┬──────┬─────────────────┐
# → │    Name    │ Age  │ Admission score │
# → ├────────────┼──────┼─────────────────┤
# → │ Alice      │ 0x19 │          95.100 │
# → │ Bob        │ 0x1e │          87.300 │
# → │ Carol      │ 0x1c │          92.100 │
# → │ Michelang… │ 0x23 │          88.000 │
# → └────────────┴──────┴─────────────────┘

Dividing lines

A dividing line, spanning the entire table, can be added between rows using .rule().

from ansitable import ANSITable, Column

table = ANSITable(
    Column("Name", headalign="^", colalign="<"),
    Column("Age", headalign="^", colalign="<"),
    Column("Admission score", headalign="^", colalign=">"),
    border="thin")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.rule() # dividing line
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
# → ┌──────────────┬─────┬─────────────────┐
# → │     Name     │ Age │ Admission score │
# → ├──────────────┼─────┼─────────────────┤
# → │ Alice        │ 25  │            95.1 │
# → │ Bob          │ 30  │            87.3 │
# → ├──────────────┼─────┼─────────────────┤
# → │ Carol        │ 28  │            92.1 │
# → │ Michelangelo │ 35  │            88.0 │
# → └──────────────┴─────┴─────────────────┘

Sorting

A table can be sorted on any heading string, and the result is a new table with its rows sorted.

from ansitable import ANSITable, Column

table = ANSITable(
    Column("Name", headalign="^", colalign="<"),
    Column("Age", headalign="^", colalign="<"),
    Column("Admission score", headalign="^", colalign=">"),
    border="thin")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
newtable = table.sort("Age", key=int)
newtable.print()
# → ┌──────────────┬─────┬─────────────────┐
# → │     Name     │ Age │ Admission score │
# → ├──────────────┼─────┼─────────────────┤
# → │ Alice        │ 25  │            95.1 │
# → │ Carol        │ 28  │            92.1 │
# → │ Bob          │ 30  │            87.3 │
# → │ Michelangelo │ 35  │            88.0 │
# → └──────────────┴─────┴─────────────────┘

Parameters to the .sort() method include:

  • column - column name (str) or index (int)

  • key - optional function to transform values before comparison

  • reverse - sort in descending order (default: False)

Horizontal rules (added with .rule()) are silently dropped from sorted output.

Color and styling

If the colored package is installed, you can set foreground/background colors and text styles (bold, reverse, underlined, dim) for header cells, data rows or data cells.

See Color reference for a searchable, swatch-illustrated reference of all 256 color names this package accepts (or colored’s own list).

Header cell color and format

Here we use a dict to set the style for the header cells: bold white text on a dark grey background.

from ansitable import ANSITable, Column

heading = dict(headstyle="bold", headcolor="white", headbgcolor="grey_53")
table = ANSITable(
    Column("Name", headalign="^", colalign="<", **heading),
    Column("Age", headalign="^", colalign="<", **heading),
    Column("Admission score", headalign="^", colalign=">", **heading),
    border="thin")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()

The color escape characters can’t be displayed from an inline documentation code block, they are separately rendered to HTML and included here:

Name Age Admission score
Alice 25 95.1
Bob 30 87.3
Carol 28 92.1
Michelangelo 35 88.0

Column format

Extending the above example so that the Name column has its background color set to light blue

from ansitable import ANSITable, Column

heading = dict(headstyle="bold", headcolor="white", headbgcolor="grey_53")
table = ANSITable(
    Column("Name", headalign="^", colalign="<", colbgcolor="sky_blue_3", **heading),
    Column("Age", headalign="^", colalign="<", **heading),
    Column("Admission score", headalign="^", colalign=">", **heading),
    border="thin")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
table.print()
Name Age Admission score
Alice 25 95.1
Bob 30 87.3
Carol 28 92.1
Michelangelo 35 88.0

Row format

We can apply the format flags to an entire row. Here we set rows where the score is greater than 90 to red background with white text.

from ansitable import ANSITable, Column

heading = dict(headstyle="bold", headcolor="white", headbgcolor="grey_53")
table = ANSITable(
    Column("Name", headalign="^", colalign="<", colbgcolor="sky_blue_3", **heading),
    Column("Age", headalign="^", colalign="<", **heading),
    Column("Admission score", headalign="^", colalign=">", **heading),
    border="thin")
table.row("Alice", 25, 95.1, bgcolor="red_3b", fgcolor="white")
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1, bgcolor="red_3b", fgcolor="white")
table.row("Michelangelo", 35, 88.0)
table.print()
Name Age Admission score
Alice 25 95.1
Bob 30 87.3
Carol 28 92.1
Michelangelo 35 88.0

These row formats take priority over formats set for a column.

Cell format

We can override the styling of a particular cell using Cell instances. Extending the example above, we highlight score cells over 90 with a red background and white text.

from ansitable import ANSITable, Column, Cell

heading = dict(headstyle="bold", headcolor="white", headbgcolor="grey_53")
table = ANSITable(
    Column("Name", headalign="^", colalign="<", colbgcolor="sky_blue_3", **heading),
    Column("Age", headalign="^", colalign="<", **heading),
    Column("Admission score", headalign="^", colalign=">", **heading),
    border="thin")
table.row("Alice", 25, Cell(95.1, bgcolor="red_3b", fgcolor="white"))
table.row("Bob", 30, 87.3)
table.row("Carol", 28, Cell(92.1, bgcolor="red_3b", fgcolor="white"))
table.row("Michelangelo", 35, 88.0)
table.print()
Name Age Admission score
Alice 25 95.1
Bob 30 87.3
Carol 28 92.1
Michelangelo 35 88.0

Cell formats will take priority over formats set for a row or a column.

Export formats

Tables can be exported, as a string, in a number of common markup languages for use in documents. Capabilities for alignment, text formatting and color vary across these markup formats.

Markdown

from ansitable import ANSITable
table = ANSITable("Name", "Age", "Admission score")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
print(table.markdown())
# → |          Name  |  Age  |  Admission score  |
# → | -----------: | --: | --------------: |
# → |         Alice  |   25  |             95.1  |
# → |           Bob  |   30  |             87.3  |
# → |         Carol  |   28  |             92.1  |
# → |  Michelangelo  |   35  |             88.0  |

HTML

from ansitable import ANSITable
table = ANSITable("Name", "Age", "Admission score")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
print(table.html())
# → <table>
# →   <tr style=''>
# →     <th style='text-align:right;'>Name</th>
# →     <th style='text-align:right;'>Age</th>
# →     <th style='text-align:right;'>Admission score</th>
# →   </tr>
# →   <tr style=''>
# →     <td style='text-align:right;'>Alice</td>
# →     <td style='text-align:right;'>25</td>
# →     <td style='text-align:right;'>95.1</td>
# →   </tr>
# →   <tr style=''>
# →     <td style='text-align:right;'>Bob</td>
# →     <td style='text-align:right;'>30</td>
# →     <td style='text-align:right;'>87.3</td>
# →   </tr>
# →   <tr style=''>
# →     <td style='text-align:right;'>Carol</td>
# →     <td style='text-align:right;'>28</td>
# →     <td style='text-align:right;'>92.1</td>
# →   </tr>
# →   <tr style=''>
# →     <td style='text-align:right;'>Michelangelo</td>
# →     <td style='text-align:right;'>35</td>
# →     <td style='text-align:right;'>88.0</td>
# →   </tr>
# → </table>

The table will be rendered according to the document’s CSS settings. We can override them for this table by passing additional parameters to html()

from ansitable import ANSITable
table = ANSITable("Name", "Age", "Admission score")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
html = table.html(
    table="border-collapse: collapse; margin: 10px 0; border: 3px solid blue;",
    th="padding: 8px; border: 1px solid #ddd; font-weight: bold;",
    td="padding: 8px; border: 1px solid #ddd;",
)
print(html)
# → <table style="border-collapse: collapse; margin: 10px 0; border: 3px solid blue;">
# →   <tr style=''>
# →     <th style='text-align:right;padding: 8px; border: 1px solid #ddd; font-weight: bold;'>Name</th>
# →     <th style='text-align:right;padding: 8px; border: 1px solid #ddd; font-weight: bold;'>Age</th>
# →     <th style='text-align:right;padding: 8px; border: 1px solid #ddd; font-weight: bold;'>Admission score</th>
# →   </tr>
# →   <tr style=''>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>Alice</td>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>25</td>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>95.1</td>
# →   </tr>
# →   <tr style=''>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>Bob</td>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>30</td>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>87.3</td>
# →   </tr>
# →   <tr style=''>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>Carol</td>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>28</td>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>92.1</td>
# →   </tr>
# →   <tr style=''>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>Michelangelo</td>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>35</td>
# →     <td style='text-align:right;padding: 8px; border: 1px solid #ddd;'>88.0</td>
# →   </tr>
# → </table>

reStructuredText (ReST) “simple table”

from ansitable import ANSITable
table = ANSITable("Name", "Age", "Admission score")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
print(table.rest())
# → ============  ===  ===============  
# →         Name  Age  Admission score  
# → ============  ===  ===============  
# →        Alice   25             95.1  
# →          Bob   30             87.3  
# →        Carol   28             92.1  
# → Michelangelo   35             88.0  
# → ============  ===  ===============

LaTeX

from ansitable import ANSITable
table = ANSITable("Name", "Age", "Admission score")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
print(table.latex())
# → \begin{tabular}{ |r|r|r| }\hline
# → \multicolumn{1}{|r|}{Name} & \multicolumn{1}{|r|}{Age} & \multicolumn{1}{|r|}{Admission score}\\\hline\hline
# → Alice & 25 & 95.1 \\
# → Bob & 30 & 87.3 \\
# → Carol & 28 & 92.1 \\
# → Michelangelo & 35 & 88.0 \\
# → \hline
# → \end{tabular}

Alignment options are supported.

Wikitable (Wikipedia)

from ansitable import ANSITable
table = ANSITable("Name", "Age", "Admission score")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
print(table.wikitable())
# → {| class="wikitable" col1right col2right col3right
# → |-
# → !          Name  !!  Age  !!  Admission score  
# → |-
# → |         Alice  ||   25  ||             95.1  
# → |-
# → |           Bob  ||   30  ||             87.3  
# → |-
# → |         Carol  ||   28  ||             92.1  
# → |-
# → |  Michelangelo  ||   35  ||             88.0  
# → |}

CSV

from ansitable import ANSITable
table = ANSITable("Name", "Age", "Admission score")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)
print(table.csv())
# → Name,Age,Admission score
# → Alice,25,95.1
# → Bob,30,87.3
# → Carol,28,92.1
# → Michelangelo,35,88.0

Matrices

Display NumPy arrays as formatted matrices:

from ansitable import ANSIMatrix
import numpy as np

np.random.seed(42)
formatter = ANSIMatrix(style='thick')
m = np.random.rand(4, 4) - 0.5
formatter.print(m)
# → ┏                                           ┓
# → ┃-0.125      0.451      0.232      0.0987   ┃
# → ┃-0.344     -0.344     -0.442      0.366    ┃
# → ┃ 0.101      0.208     -0.479      0.47     ┃
# → ┃ 0.332     -0.288     -0.318     -0.317    ┃
# → ┗                                           ┛

Add superscript and subscript suffixes:

from ansitable import ANSIMatrix
import numpy as np

np.random.seed(42)
formatter = ANSIMatrix(style='thick')
m = np.random.rand(4, 4) - 0.5
formatter.print(m, suffix_super='T', suffix_sub='3')
# → ┏                                           ┓T
# → ┃-0.125      0.451      0.232      0.0987   ┃
# → ┃-0.344     -0.344     -0.442      0.366    ┃
# → ┃ 0.101      0.208     -0.479      0.47     ┃
# → ┃ 0.332     -0.288     -0.318     -0.317    ┃
# → ┗                                           ┛3

Pandas integration

Convert Pandas DataFrames to ANSITable:

import pandas as pd
from ansitable import ANSITable

df = pd.DataFrame({"calories": [420, 380, 390], "duration": [50, 40, 45]})
table = ANSITable.Pandas(df, border="thin")
table.print()
# → ┌──────────┬──────────┐
# → │ calories │ duration │
# → ├──────────┼──────────┤
# → │      420 │       50 │
# → │      380 │       40 │
# → │      390 │       45 │
# → └──────────┴──────────┘

Convert ANSITable back to DataFrame:

from ansitable import ANSITable
import pandas as pd

table = ANSITable("Name", "Age", "Admission score", border="ascii")
table.row("Alice", 25, 95.1)
table.row("Bob", 30, 87.3)
table.row("Carol", 28, 92.1)
table.row("Michelangelo", 35, 88.0)

df = table.pandas()
print(df)
# →            Name Age Admission_score
# → 0         Alice  25            95.1
# → 1           Bob  30            87.3
# → 2         Carol  28            92.1
# → 3  Michelangelo  35            88.0

Column names are converted to valid Python identifiers (spaces → underscores), allowing attribute access like df.Admission_score. Disable this with underscores=False.