---------------------------------------------------------------------- This is the API documentation for the tinytab library. ---------------------------------------------------------------------- ## Formatting values Turn single values into display strings. fmt_number(x: 'float', *, decimals: 'int' = 2, sep_mark: 'str' = ',') -> 'str' Format a number with a fixed number of decimal places and a thousands separator. Parameters ---------- x The number to format. decimals The number of decimal places. sep_mark The character used to separate groups of thousands. Returns ------- str The formatted number. Examples -------- By default, numbers get two decimal places and a comma between groups of thousands: ```{python} from tinytab import fmt_number fmt_number(1234567.891) ``` Use `decimals=` to change the precision, and `sep_mark=` for a different separator: ```{python} fmt_number(1234567.891, decimals=0, sep_mark=" ") ``` fmt_percent(x: 'float', *, decimals: 'int' = 1) -> 'str' Format a proportion as a percentage. Parameters ---------- x The proportion to format, where `1` is 100%. decimals The number of decimal places. Returns ------- str The formatted percentage. Examples -------- A proportion of `0.256` is 25.6%: ```{python} from tinytab import fmt_percent fmt_percent(0.256) ``` Use `decimals=` to change the precision: ```{python} fmt_percent(0.256, decimals=0) ``` fmt_currency(x: 'float', *, currency: 'str' = 'USD') -> 'str' Format a number as a currency value. Parameters ---------- x The amount to format. currency A three-letter currency code (case-insensitive). Yen values are shown without decimal places. Returns ------- str The formatted amount, with the currency symbol. Examples -------- Amounts are in US dollars by default: ```{python} from tinytab import fmt_currency fmt_currency(1234.5) ``` Choose another currency with its three-letter code, in any case. Negative amounts put the sign before the symbol: ```{python} fmt_currency(-5, currency="gbp") ``` Yen are shown without decimal places: ```{python} fmt_currency(1234.5, currency="JPY") ``` ## Tables Build a table, format its columns, and render it as text or HTML. Table(data: 'Mapping[str, Sequence[Any]]') A small, column-oriented table. Parameters ---------- data A dictionary mapping column names to sequences of values. All columns must have the same length. Examples -------- Create a table from a dictionary of columns. In a notebook, or on a page like this one, a table displays as HTML: ```{python} from tinytab import Table tbl = Table({"item": ["coffee", "bagel"], "price": [3.5, 2.25]}) tbl ``` fmt(self, column: 'str', fn: 'Callable[[Any], str]') -> 'Table' Attach a formatting function to a column. Parameters ---------- column The column to format. fn A function that takes a single value and returns a string. Missing values (`None`) are rendered as empty cells and are never passed to `fn`. Returns ------- Table A new table; the original is not modified. Examples -------- Format the `price` column as US dollars: ```{python} from tinytab import Table, fmt_currency tbl = Table({"item": ["coffee", "bagel"], "price": [3.5, 2.25]}) tbl.fmt("price", fmt_currency) ``` Any function that takes a value and returns a string works as a formatter: ```{python} tbl.fmt("price", lambda x: f"{x:.1f} USD") ``` as_text(self) -> 'str' Render the table as a Markdown-style text table. Returns ------- str The rendered table. Examples -------- ```{python} from tinytab import Table, fmt_currency tbl = Table({"item": ["coffee", "bagel"], "price": [3.5, 2.25]}) print(tbl.fmt("price", fmt_currency).as_text()) ``` as_html(self) -> 'str' Render the table as an HTML ``. Returns ------- str The rendered table, with all values HTML-escaped. Examples -------- ```{python} from tinytab import Table tbl = Table({"item": ["coffee", "bagel"], "price": [3.5, 2.25]}) print(tbl.as_html()) ``` to_csv(self, path: 'str | Path') -> 'None' Write the table's unformatted values to a CSV file. Parameters ---------- path The file to write. Examples -------- Save a table, then read it back with `Table.from_csv()`: ```{python} import tempfile from pathlib import Path from tinytab import Table path = Path(tempfile.mkdtemp()) / "menu.csv" Table({"item": ["coffee", "bagel"], "price": [3.5, 2.25]}).to_csv(path) print(path.read_text()) ``` from_csv(path: 'str | Path') -> 'Table' Read a table from a CSV file. All values are read as strings. Parameters ---------- path The file to read. Returns ------- Table The table read from the file. Examples -------- Save a table with `Table.to_csv()`, then read it back. CSV files don't record types, so every value comes back as a string: ```{python} import tempfile from pathlib import Path from tinytab import Table path = Path(tempfile.mkdtemp()) / "menu.csv" Table({"item": ["coffee", "bagel"], "price": [3.5, 2.25]}).to_csv(path) Table.from_csv(path) ``` ## Summary statistics Summarize a set of numbers. summarize(values: 'Sequence[float | None]') -> 'SummaryResult' Compute summary statistics for a sequence of numbers. Missing values (`None` and NaN) are dropped with a warning. Parameters ---------- values The numbers to summarize. Returns ------- SummaryResult The count, mean, standard deviation (with `n - 1` denominator), minimum and maximum. Examples -------- Summarize a list of numbers: ```{python} from tinytab import summarize result = summarize([3.5, 2.25, 4.0, 1.75]) result ``` Each statistic is an attribute of the result: ```{python} result.mean ``` SummaryResult(n: 'int', mean: 'float', sd: 'float', min: 'float', max: 'float') -> None Summary statistics for a set of numbers, as returned by `summarize()`. ---------------------------------------------------------------------- This is the User Guide documentation for the package. ---------------------------------------------------------------------- ## Getting Started ### Introduction tinytab is a small library for formatting values and rendering small tables, as text or as HTML. It has no dependencies beyond Python itself. It exists as an example: a small but complete package that shows how to structure, test, and document a Python library. It isn't published on PyPI. ## Installation Install tinytab from its GitHub repository: ```{.bash filename="Terminal"} pip install git+https://github.com/rich-iannone/example-python-package.git ``` ## A first look tinytab does three things. It formats single values as strings: ```{python} from tinytab import fmt_currency fmt_currency(1234.5) ``` It builds tables, with a formatter attached to any column: ```{python} from tinytab import Table menu = Table( { "item": ["coffee", "bagel", "croissant"], "price": [3.5, 2.25, 4.0], } ) menu.fmt("price", fmt_currency) ``` And it summarizes sets of numbers: ```{python} from tinytab import summarize summarize([3.5, 2.25, 4.0]) ``` The rest of this guide takes each of these in turn: - [Formatting Values](formatting-values.qmd): numbers, percentages, and currencies - [Tables](tables.qmd): building, formatting, rendering, and saving tables - [Summary Statistics](summary-statistics.qmd): `summarize()` and its result object ## Using tinytab ### Formatting Values tinytab has three functions for turning a single number into a display string. They share the same rules: the value is the first argument, and every option is a keyword argument. ## Numbers [`fmt_number()`](../reference/fmt_number.qmd) rounds to a fixed number of decimal places (2 by default) and groups thousands with a separator: ```{python} from tinytab import fmt_number fmt_number(1234567.891) ``` Use `decimals=` to change the precision: ```{python} fmt_number(1234567.891, decimals=0) ``` The thousands separator is a comma by default. Set another with `sep_mark=`: ```{python} fmt_number(1234567.891, sep_mark=" ") ``` ## Percentages [`fmt_percent()`](../reference/fmt_percent.qmd) takes a proportion, where `1` means 100%, and shows one decimal place by default: ```{python} from tinytab import fmt_percent fmt_percent(0.256) ``` ```{python} fmt_percent(0.256, decimals=0) ``` ## Currencies [`fmt_currency()`](../reference/fmt_currency.qmd) adds a currency symbol and uses the right number of decimal places for the currency. US dollars are the default: ```{python} from tinytab import fmt_currency fmt_currency(1234.5) ``` Choose another currency with a three-letter code (in any case). Yen are shown without decimal places, and negative amounts put the sign before the symbol: ```{python} [ fmt_currency(1234.5, currency="eur"), fmt_currency(1234.5, currency="JPY"), fmt_currency(-5, currency="GBP"), ] ``` The supported codes are USD, EUR, GBP, and JPY. Any other code is an error, and the message lists the valid ones: ```{python} #| error: true fmt_currency(10, currency="XYZ") ``` ## Only numbers All three functions accept only numbers (integers and floats). Anything else, including a number written as a string, raises a `TypeError`: ```{python} #| error: true fmt_number("10") ``` `True` and `False` are rejected too, even though Python treats them as the integers 1 and 0. Formatting a boolean as "1.00" is almost always a mistake. ### Tables A [`Table`](../reference/Table.qmd) 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 ``` In a notebook, or on a page like this one, a table displays as HTML. Missing values (`None`) show as empty cells: ```{python} menu ``` ## Formatting columns [`Table.fmt()`](../reference/Table.fmt.qmd) 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) ``` `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)) ``` 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.qmd) renders a Markdown-style text table, ready to print or paste into a Markdown document: ```{python} print(menu.fmt("price", fmt_currency).as_text()) ``` ## Rendering as HTML [`Table.as_html()`](../reference/Table.as_html.qmd) returns the HTML as a string, for use in a web page or report: ```{python} print(menu.fmt("price", fmt_currency).as_html()) ``` Every value is HTML-escaped, so text in the data can never be mistaken for markup: ```{python} Table({"name": ["bold", "Tom & Jerry"]}) ``` ## Saving and loading [`Table.to_csv()`](../reference/Table.to_csv.qmd) writes the table's values to a CSV file, and [`Table.from_csv()`](../reference/Table.from_csv.qmd) reads one back: ```{python} import tempfile from pathlib import Path path = Path(tempfile.mkdtemp()) / "menu.csv" menu.to_csv(path) Table.from_csv(path) ``` Two things to know about CSV files: - Formatting isn't saved. `to_csv()` writes the original values, not the formatted ones. - CSV files don't record types, so `from_csv()` reads every value as a string. ### Summary Statistics [`summarize()`](../reference/summarize.qmd) computes the basic summary statistics of a set of numbers: ```{python} from tinytab import summarize result = summarize([3.5, 2.25, 4.0, 1.75]) result ``` ## The result object `summarize()` returns a [`SummaryResult`](../reference/SummaryResult.qmd), with one attribute per statistic: ```{python} result.mean, result.sd ``` | Attribute | Meaning | |---|---| | `n` | the number of values summarized | | `mean` | the arithmetic mean | | `sd` | the sample standard deviation (dividing by `n - 1`) | | `min`, `max` | the smallest and largest values | The printed form rounds to four significant figures, to keep it readable. The attributes hold the full values. A result can't be changed after it's created. Assigning to an attribute is an error: ```{python} #| error: true result.mean = 0 ``` ## Missing values Missing values, written as `None` or NaN, are left out of the calculation. Since dropping data silently could hide a problem, `summarize()` issues a warning saying how many were dropped: ```{python} import math summarize([3.5, None, 4.0, math.nan]) ``` If there are no values left to summarize, `summarize()` raises an error: ```{python} #| error: true summarize([None, None]) ``` ## A single value With only one value, there's no spread to measure, so the standard deviation is 0: ```{python} summarize([23]) ```