# Tables

A [`Table`](../reference/Table.md) holds data in columns, and renders it as text or HTML.


# Creating a table

Pass a dictionary that maps each column name to a list of values. All the columns must have the same length:


``` python
from tinytab import Table

menu = Table(
    {
        "item": ["coffee", "bagel", "croissant"],
        "price": [3.5, 2.25, 4.0],
        "sold": [120, 45, None],
    }
)

menu.columns, menu.n_rows
```


    (['item', 'price', 'sold'], 3)


In a notebook, or on a page like this one, a table displays as HTML. Missing values (`None`) show as empty cells:


``` python
menu
```


| item      | price | sold |
|-----------|-------|------|
| coffee    | 3.5   | 120  |
| bagel     | 2.25  | 45   |
| croissant | 4.0   |      |


# Formatting columns

[`Table.fmt()`](../reference/Table.fmt.md) attaches a formatting function to a column. Any function that takes a value and returns a string works, including tinytab's own formatters:


``` python
from tinytab import fmt_currency

menu.fmt("price", fmt_currency)
```


| item      | price  | sold |
|-----------|--------|------|
| coffee    | \$3.50 | 120  |
| bagel     | \$2.25 | 45   |
| croissant | \$4.00 |      |


[fmt()](../reference/Table.fmt.md#tinytab.Table.fmt) returns a *new* table and leaves the original unchanged, so calls can be chained, one per column:


``` python
from tinytab import fmt_number

menu.fmt("price", fmt_currency).fmt("sold", lambda x: fmt_number(x, decimals=0))
```


| item      | price  | sold |
|-----------|--------|------|
| coffee    | \$3.50 | 120  |
| bagel     | \$2.25 | 45   |
| croissant | \$4.00 |      |


Formatters are never given missing values, so a formatter doesn't need to handle `None`.


# Rendering as text

[`Table.as_text()`](../reference/Table.as_text.md) renders a Markdown-style text table, ready to print or paste into a Markdown document:


``` python
print(menu.fmt("price", fmt_currency).as_text())
```


    | item      | price | sold |
    |-----------|-------|------|
    | coffee    | $3.50 | 120  |
    | bagel     | $2.25 | 45   |
    | croissant | $4.00 |      |


# Rendering as HTML

[`Table.as_html()`](../reference/Table.as_html.md) returns the HTML as a string, for use in a web page or report:


``` python
print(menu.fmt("price", fmt_currency).as_html())
```


    <table class="tinytab">
      <thead>
        <tr><th>item</th><th>price</th><th>sold</th></tr>
      </thead>
      <tbody>
        <tr><td>coffee</td><td>$3.50</td><td>120</td></tr>
        <tr><td>bagel</td><td>$2.25</td><td>45</td></tr>
        <tr><td>croissant</td><td>$4.00</td><td></td></tr>
      </tbody>
    </table>


Every value is HTML-escaped, so text in the data can never be mistaken for markup:


``` python
Table({"name": ["<b>bold</b>", "Tom & Jerry"]})
```


| name            |
|-----------------|
| \<b\>bold\</b\> |
| Tom & Jerry     |


# Saving and loading

[`Table.to_csv()`](../reference/Table.to_csv.md) writes the table's values to a CSV file, and [`Table.from_csv()`](../reference/Table.from_csv.md) reads one back:


``` python
import tempfile
from pathlib import Path

path = Path(tempfile.mkdtemp()) / "menu.csv"
menu.to_csv(path)

Table.from_csv(path)
```


| item      | price | sold |
|-----------|-------|------|
| coffee    | 3.5   | 120  |
| bagel     | 2.25  | 45   |
| croissant | 4.0   |      |


Two things to know about CSV files:

- Formatting isn't saved. [to_csv()](../reference/Table.to_csv.md#tinytab.Table.to_csv) writes the original values, not the formatted ones.
- CSV files don't record types, so [from_csv()](../reference/Table.from_csv.md#tinytab.Table.from_csv) reads every value as a string.
