API reference
Everything in __all__ is importable from the top-level package:
from greater_tables import (
GT, TableSpec, HouseStyle, build,
TableDoc, Column, Row, Cell, HeadCell, FormatSpec, IR_VERSION,
render_html, render_text, render_tikz, Etcher,
canonical_dict, canonical_json, doc_hash, stamp,
Fabricator, quick_fab, load_spec, load_style, __version__,
)
Package
greater_tables: semantic table engine with a versioned table-document IR.
Python owns semantics (dtypes, format inference, hierarchy, spans); renderers own geometry and style. No import side effects — importing this package does not touch pandas options, warnings filters, or anything else global.
- class greater_tables.Etcher(tex: str, *, file_name: str = '', base_path: str | Path | None = None, tex_engine: str = 'tectonic', pdf2svg: str = 'pdf2svg', font_size: int = 11)[source]
Bases:
objectCompile a TeX blob containing a tikzpicture to PDF/SVG files.
- Parameters:
tex – LaTeX source containing one
tikzpicture(e.g.render_tikzoutput; any surrounding table environment is stripped — standalone cannot host floats).file_name – Output stem; content hash when omitted (deterministic caching).
base_path – Working directory; outputs land in
base_path/'tikz'.tex_engine –
'tectonic'(default) or'pdflatex'— name or full path.pdf2svg – Converter executable for
compile(svg=True).font_size – Standalone document point size.
- compile(*, svg: bool = False) Path[source]
Compile to PDF (and SVG when requested); return the output path.
- display()[source]
Show the compiled SVG inline (Jupyter; lazy IPython import).
- class greater_tables.GT(data=None, caption=None, label=None, aligners=None, formatters=None, unbreakable=None, ratio_cols=None, year_cols=None, date_cols=None, raw_cols=None, show_index=None, config_path=None, style=None, mimes=None, **overrides)[source]
Bases:
objectTable facade: data + spec in, renderings out (all lazy, all cached).
Parameters mirror the greater_tables constructor where they still make sense;
formattersaccepts the 5.x shapes (callable,'{x:...}'format string, or int = decimal digits, now grouped).config_pathloads YAML TableSpec defaults which explicit arguments override.styletakes aHouseStyleand is forwarded to every renderer.mimespins what_repr_mimebundle_publishes — None (default) detects the target, an explicit tuple such as('text/plain',)publishes exactly that. It is a whole-document policy, so the intended form isrender = partial(GT, mimes=('text/plain',)). Additional keyword arguments naming TableSpec fields pass through; unknown ones warn and are ignored (migration aid), and a HouseStyle field name warns with the fix.
- class greater_tables.Cell(*, text: str, raw: float | int | str | bool | None = None, rowspan: Annotated[int, Ge(ge=1)] = 1, colspan: Annotated[int, Ge(ge=1)] = 1, flags: tuple[Literal['neg', 'emphasis', 'muted'], ...] = (), math: bool = False, html: bool = False)[source]
Bases:
BaseModelOne body/foot cell.
textis plain unicode — each renderer applies its own escaping.rawis only present when the owning column advertisesraw: true; an explicit JSONnullthere means a missing value, which is distinct from the field being absent (tracked via pydantic’s fields-set).- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- text: str
- rowspan: int
- colspan: int
- flags: tuple[CellFlag, ...]
- math: bool
- html: bool
- class greater_tables.Column(*, key: str, name: tuple[str, ...], dtype: Literal['int', 'float', 'date', 'datetime', 'bool', 'string', 'object'], align: Literal['l', 'r', 'c'], role: Literal['stub', 'data'] = 'data', level: Annotated[int | None, Ge(ge=0)] = None, tag: Literal['ratio', 'year', 'date', 'raw'] | None = None, wrap: bool = False, break_depth: Annotated[int | None, Ge(ge=0)] = None, format: FormatSpec | None = None, raw: bool = False)[source]
Bases:
BaseModelDescriptor for one visual column, stubs first.
- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- key: str
- dtype: Dtype
- align: Alignment
- role: Role
- tag: Tag | None
- wrap: bool
- format: FormatSpec | None
- raw: bool
- class greater_tables.Fabricator(decorate: bool = False, pyarrow: bool = False, seed: int | None = None, now: Timestamp | str = Timestamp('2026-01-01 00:00:00'))[source]
Bases:
objectFabricate small synthetic DataFrames covering GT2’s fixture space.
- Parameters:
decorate (bool) – Append type hints (” year”, “ ratio”, “ date”, “ time”) to generated metric names.
pyarrow (bool) – Convert fabricated frames to pyarrow-backed dtypes.
seed (int, optional) – RNG seed; one is drawn from entropy when omitted. Always pass a seed for fixtures.
now (pandas.Timestamp or str) – Reference timestamp for date/time columns. Fixed default keeps seeded output identical across runs.
Notes
Column type codes for
data_spec:d date r ratio (small floats) f float sN string, N words h hash t time (minute grid) i integer v extreme-range float l log float (wide range) wN string, random length ~N words p path (filename) x TeX expression y year
- metric_roots = ['absorption', 'acceleration', 'account', 'activation', 'adjustment', 'allocation', 'amplitude', 'approval', 'asset', 'atom', 'attrition', 'balance', 'band', 'binding', 'cancellation', 'capacitance', 'capital', 'cashflow', 'category', 'cell', 'charge', 'claim', 'commission', 'compound', 'concentration', 'conductivity', 'constraint', 'consumption', 'conversion', 'correlation', 'cost', 'count', 'coverage', 'credit', 'current', 'debt', 'decay', 'decibel', 'deductible', 'deficit', 'deflator', 'demand', 'density', 'development', 'diffusion', 'discount', 'distribution', 'dividend', 'dose', 'duration', 'earnings', 'efficiency', 'elasticity', 'employment', 'energy', 'entropy', 'enzyme', 'estimate', 'excess', 'exhaustion', 'expense', 'exposure', 'failure', 'field', 'flux', 'force', 'frequency', 'funding', 'gdp', 'gene', 'gradient', 'growth', 'half_life', 'incidence', 'income', 'index', 'indicator', 'inequality', 'inflation', 'inhibition', 'input', 'intensity', 'investment', 'kurtosis', 'lapse', 'layer', 'leverage', 'liability', 'limit', 'loss', 'luminosity', 'margin', 'mass', 'molecule', 'momentum', 'mortality', 'neutron', 'noise', 'operating', 'output', 'penalty', 'photon', 'policy', 'portfolio', 'potential', 'power', 'preference', 'premium', 'pressure', 'price', 'productivity', 'profit', 'protein', 'proton', 'provision', 'radiation', 'rate', 'ratio', 'reaction', 'recovery', 'reflection', 'refraction', 'renewal', 'reserve', 'residual', 'resistance', 'return', 'revenue', 'risk', 'sample', 'savings', 'scenario', 'score', 'sector', 'settlement', 'severity', 'shock', 'shortfall', 'signal', 'skewness', 'spread', 'strain', 'stress', 'subsidy', 'supply', 'tail', 'tariff', 'tax', 'temperature', 'tension', 'term', 'threshold', 'trade', 'trend', 'turbulence', 'unemployment', 'uptake', 'utility', 'utilization', 'valuation', 'variance', 'velocity', 'viscosity', 'volatility', 'voltage', 'volume', 'wage', 'wavelength', 'wealth', 'weight', 'yield']
- static roll_columns(df: DataFrame, levels: int = -1) DataFrame[source]
Rotate the column MultiIndex levels by
levelsand re-sort.
- static drop_singleton_levels(df: DataFrame) DataFrame[source]
Drop index/column levels holding a single value (keep at least one).
- make(rows: int, data_spec, *, index_levels: int = 1, index_names=None, column_groups: int = 1, column_levels: int = 1, column_names=None, metric_name_spec='', missing: float = 0.0, simplify: bool = True, oversample: int = 1) DataFrame[source]
Fabricate a DataFrame to a specification.
- Parameters:
rows – Number of body rows.
data_spec – Per-metric type codes (see class notes): a string like
's3sfid', a list of codes, or an int meaning that many floats.index_levels – Row MultiIndex depth.
index_names – Level names; generated when omitted.
column_names – Level names; generated when omitted.
column_groups – Column MultiIndex shape: total columns = metrics × groups.
column_levels – Column MultiIndex shape: total columns = metrics × groups.
metric_name_spec –
''for generated names, a list of names, or a type-code spec whose generated values become the names.missing – Proportion of values per column replaced with NaN.
simplify – Drop index/column levels that hold a single value. Note this can reduce the requested level count on small frames.
oversample – Fabricate
oversample × rowsthen keep the firstrows— more distinct index values at small sizes.
- Return type:
- random(rows: int = 0, columns: int = 0, index_levels: int = 0, omit: str = '') DataFrame[source]
Fabricate a DataFrame with randomly chosen shape and types.
- Parameters:
rows – 0 means choose randomly.
columns – 0 means choose randomly.
index_levels – 0 means choose randomly.
omit – Type codes to exclude.
- Return type:
- class greater_tables.FormatSpec(*, kind: Literal['int', 'dec', 'pct', 'sci', 'gen', 'si', 'eng', 'year', 'date', 'str'], digits: Annotated[int | None, Ge(ge=0)] = None, group: bool = False, scale: float = 1.0, prefix: str | None = None, suffix: str | None = None, negative: Literal['minus', 'paren'] = 'minus', null: str = '—', zero: str | None = None, pattern: str | None = None)[source]
Bases:
BaseModelResolved display format for one column.
Applicable by Python and by the JS walker (Intl.NumberFormat). The formatted
textin cells is normative; this spec is informative — it lets a client re-render at different precision without redoing the engine’s column-scanning inference.- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- kind: FormatKind
- group: bool
- scale: float
- negative: Negative
- null: str
- class greater_tables.HeadCell(*, text: str = '', colspan: Annotated[int, Ge(ge=1)] = 1, align: Literal['l', 'r', 'c'] | None = None, flags: tuple[Literal['neg', 'emphasis', 'muted'], ...] = (), math: bool = False, html: bool = False)[source]
Bases:
BaseModelOne header cell; level is implicit in its row position within
head.- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- text: str
- colspan: int
- align: Alignment | None
- flags: tuple[CellFlag, ...]
- math: bool
- html: bool
- class greater_tables.HouseStyle(max_rule_depth: int | Literal['auto'] | None = 'auto', max_vrule_depth: int | None = 1, max_prose_width_em: float = 18.0, max_table_width_em: float | None = None, text_ascii_rules: bool = False, tikz_scale: float = 1.0, tikz_column_sep: float = 1.0, tikz_row_sep: float = 0.25, tikz_container_env: str = 'table', tikz_latex: str | None = None, tikz_post_process: str = '', frame_rule: float = 1.0, head_rule: float = 1.0, stub_rule: float = 1.0, total_rule: float = 0.75, spanner_rule: float = 0.5, hrule_widths: tuple[float, float, float] = (0.5, 0.25, 0.25), vrule_widths: tuple[float, float, float] = (0.25, 0.25, 0.25))[source]
Bases:
objectRenderer opinions — never serialized into the IR.
Rule weights are the greater_tables float scale, mapped to TikZ line styles via
render.tikz.W2TIKZ(0 = don’t draw, 0.25 = ultra thin … 3 = ultra thick). HTML equivalents live as CSS custom properties inrender.css.VARIABLES.- Parameters:
max_rule_depth (int | Literal['auto'] | None) – How deep the hierarchy is ruled.
'auto'(default) rules every break except the innermost stub level — a change there is just “next row”, so ruling it draws a line between every row; that gives no rules on a single-level index, depth 0 only on two levels, depths 0 and 1 on three. An int is literal:0rules only outermost changes, and on a single-level index it rules every row whose stub changed. None draws no internal rules at all.max_vrule_depth (int | None) – The same for column-group verticals (None = none). Only
render.policy.N_VRULE_SLOTSweights exist, so deeper breaks reuse the lightest.max_prose_width_em (float) – How wide a single wrapping column may get. TikZ reads it directly; HTML emits it as
--gt-prose-max. One column, not the table — the table ismax_table_width_em.max_table_width_em (float | None) – Total table width to aim for, in ems, or None (default) for no target. A target, not a guarantee: columns that must not wrap — numbers, dates, anything tagged or listed in
TableSpec.unbreakable— hold their natural width whatever the budget says, so a table of nothing but numbers can exceed it. What the budget can do is narrow the prose columns, which it does with one shared quantile so every one of them carries the same chance of wrapping (render.widths). When even the floors do not fit, TikZ stepstikz_scaledown a fixed ladder and says so throughlogging; HTML hands the number to the browser as a CSSmax-widthand lets it measure. Ems because every other geometry field here is ems: at 11pt, 8 inches is about 52em.text_ascii_rules (bool) – Draw the text rendering with ASCII only —
=and-for heavy and light rules,|for the stub divider,<<flag>>for the row gutter. Default False (box-drawing characters). Worth setting for a LaTeX verbatim block, where Pygments lexes U+2501 as an error token and styles it red, and for a console whose encoding or mono font cannot carry box drawing. Covers what the renderer draws, not cell text: values arrive already formatted, soFormatSpec.null(an em dash) and the SI suffix ladder (µ) still come through. Change those on the spec side if a build must be pure ASCII end to end.
- max_prose_width_em: float = 18.0
- text_ascii_rules: bool = False
- tikz_scale: float = 1.0
- tikz_column_sep: float = 1.0
- tikz_row_sep: float = 0.25
- tikz_container_env: str = 'table'
- tikz_post_process: str = ''
- frame_rule: float = 1.0
- head_rule: float = 1.0
- stub_rule: float = 1.0
- total_rule: float = 0.75
- spanner_rule: float = 0.5
- class greater_tables.Row(*, cells: tuple[Cell | str, ...], break_depth: Annotated[int | None, Ge(ge=0)] = None, flags: tuple[Literal['total', 'subtotal', 'emphasis', 'muted'], ...] = ())[source]
Bases:
BaseModelOne body/foot row.
cellsholds only the uncovered columns — a stub cell withrowspanin an earlier row covers its column here, exactly as HTML<td rowspan>semantics. A cell that is plain text with no other attributes may be a bare string (the shorthand; canonicalized on dump).break_depthis the shallowest stub level whose value changed versus the previous row (0 = outermost). House style maps depth to rule weight; the IR never names a weight.- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- flags: tuple[RowFlag, ...]
- class greater_tables.TableDoc(*, ir_version: int = 1, generator: str | None = None, hash: Annotated[str | None, _PydanticGeneralMetadata(pattern='^[0-9a-f]{12}$')] = None, caption: str | Cell | None = None, label: str | None = None, notes: tuple[str, ...] = (), n_stub_levels: Annotated[int, Ge(ge=0)], n_head_levels: Annotated[int, Ge(ge=0)], columns: tuple[Column, ...], head: tuple[tuple[HeadCell, ...], ...], body: tuple[Row, ...], foot: tuple[Row, ...] = ())[source]
Bases:
BaseModelThe table document — the versioned IR a semantic engine emits.
hashis stamped bygreater_tables.engine.hashing.stamp()(sha256 over canonical bytes,hashandgeneratorexcluded).- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- ir_version: int
- n_stub_levels: int
- n_head_levels: int
- classmethod read(source: str | bytes | dict[str, Any]) TableDoc[source]
Validate a foreign document, ignoring unknown fields.
- Parameters:
source – JSON text/bytes or an already-parsed dict.
- Returns:
The validated document.
- Return type:
- Raises:
ValueError – If
ir_versionis unsupported or the document is malformed.
- class greater_tables.TableSpec(caption: str | None = None, label: str | None = None, notes: ~collections.abc.Sequence[str] = (), show_index: bool | None = None, header_row: bool = False, ratio_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, year_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, date_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, raw_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, html_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, unbreakable: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, aligners: str | ~collections.abc.Mapping[~typing.Hashable, str] | None = None, formatters: ~collections.abc.Mapping[~typing.Hashable, ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | ~collections.abc.Callable[[~typing.Any], str]] = <factory>, float_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, int_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, ratio_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, date_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, table_float_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, row_flags: ~collections.abc.Mapping[int, ~collections.abc.Sequence[str]] | ~collections.abc.Callable[[int, ~typing.Any], ~collections.abc.Sequence[str]] | None = None, cell_flags: ~collections.abc.Callable[[int, ~typing.Hashable, ~typing.Any], ~collections.abc.Sequence[str]] | None = None, math: str = 'auto', include_raw: bool | str | ~collections.abc.Sequence[~typing.Hashable] = False, max_rows: int | None = 200, sparsify: bool = True, sparsify_columns: bool = True, pef_lower: int = -3, pef_upper: int = 6, eng_digits: int = 3, eng_style: ~typing.Literal['si', 'exp'] = 'si')[source]
Bases:
objectSemantic specification for one table.
- Parameters:
caption – Table metadata;
labelis the Quarto cross-reference id.label – Table metadata;
labelis the Quarto cross-reference id.notes – Table metadata;
labelis the Quarto cross-reference id.show_index (bool | None) – Show the row index as stub columns. None (default) auto-hides a trivial unnamed RangeIndex.
header_row (bool) – For list input: promote the first row to column headers.
ratio_cols – Semantic column tags: a list of labels,
'all', or a regex matched against column names.year_cols – Semantic column tags: a list of labels,
'all', or a regex matched against column names.date_cols – Semantic column tags: a list of labels,
'all', or a regex matched against column names.raw_cols – Semantic column tags: a list of labels,
'all', or a regex matched against column names.html_cols (str | collections.abc.Sequence[Hashable] | None) – String columns whose cells carry raw HTML (sets the cell
htmlflag; renderers still gate on their own allow-html option).unbreakable (str | collections.abc.Sequence[Hashable] | None) – Columns forced to
wrap=Falseregardless of dtype.aligners (str | collections.abc.Mapping[Hashable, str] | None) –
'lrc'-style string (positional) or mapping label → ‘l’/’r’/’c’.formatters (collections.abc.Mapping[Hashable, greater_tables.ir.FormatSpec | collections.abc.Mapping | str | int | collections.abc.Callable[[Any], str]]) – Per-column format override, keyed by column label (a tuple label also matches on its leaf name). Values: a FormatSpec, a mapping of FormatSpec fields (the YAML form), a sugar string (csv-grid
',.1f'or GT'{x:.1%}'style), an int (decimal digits, with grouping), or a callable taking the value and returning a string. A callable owns its own NaN handling and does not reach the IR, so a client cannot re-render that column.float_format – House defaults, one per kind — the 5.x
default_*_strsettings. Each overrides only its own kind’s built-in default and loses to an explicitformattersentry.date_formatcovers the date tag and both date and datetime dtypes. Settingfloat_formatturns the float inference off entirely, including the automatic engineering/SI switch, sopef_*andeng_*stop applying. Callables are rejected: these are house rules that must be YAML-expressible.int_format – House defaults, one per kind — the 5.x
default_*_strsettings. Each overrides only its own kind’s built-in default and loses to an explicitformattersentry.date_formatcovers the date tag and both date and datetime dtypes. Settingfloat_formatturns the float inference off entirely, including the automatic engineering/SI switch, sopef_*andeng_*stop applying. Callables are rejected: these are house rules that must be YAML-expressible.ratio_format – House defaults, one per kind — the 5.x
default_*_strsettings. Each overrides only its own kind’s built-in default and loses to an explicitformattersentry.date_formatcovers the date tag and both date and datetime dtypes. Settingfloat_formatturns the float inference off entirely, including the automatic engineering/SI switch, sopef_*andeng_*stop applying. Callables are rejected: these are house rules that must be YAML-expressible.date_format – House defaults, one per kind — the 5.x
default_*_strsettings. Each overrides only its own kind’s built-in default and loses to an explicitformattersentry.date_formatcovers the date tag and both date and datetime dtypes. Settingfloat_formatturns the float inference off entirely, including the automatic engineering/SI switch, sopef_*andeng_*stop applying. Callables are rejected: these are house rules that must be YAML-expressible.table_float_format (greater_tables.ir.FormatSpec | collections.abc.Mapping | str | int | None) – The sledgehammer: every untagged float column, overriding even an explicit
formattersentry. Ratio, year and date columns have a kind, so it never reaches them. Default None, and deliberately absent fromconfig.write_template— set it consciously at a call site, never in a config file where it would silently defeat per-column formats months later.row_flags (collections.abc.Mapping[int, collections.abc.Sequence[str]] | collections.abc.Callable[[int, Any], collections.abc.Sequence[str]] | None) – Mapping row position (negative allowed) → flags, or a callable
(position, row_series) -> flags. Vocabulary: total, subtotal, emphasis, muted.cell_flags (collections.abc.Callable[[int, Hashable, Any], collections.abc.Sequence[str]] | None) – Callable
(position, column_label, value) -> flagsfor per-cell emphasis/muted. Thenegflag is stamped automatically.math (str) –
'auto'detects$...$-wrapped string cells as math;'off'disables.include_raw (bool | str | collections.abc.Sequence[Hashable]) – Carry raw values in body cells: False (default), True/’data’ (numeric/date/bool data columns), or an explicit column list. Data columns only in every form — stub (index) columns never carry raw values, so a client that must sort on an index level wants
df.reset_index()beforebuild.max_rows (int | None) – Truncate longer tables, appending a note. None = unlimited.
sparsify – Merge repeated stub values into rowspans / repeated header prefixes into colspans.
sparsify_columns – Merge repeated stub values into rowspans / repeated header prefixes into colspans.
pef_lower – Engineering-format bounds: a float column whose mean magnitude falls outside
[10**pef_lower, 10**pef_upper]renders in engineering notation, as does one whose nonzero values span more than10**pef_upperbetween largest and smallest (column-level decisions — 5.x switched per cell, mixing formats within a column).pef_upper – Engineering-format bounds: a float column whose mean magnitude falls outside
[10**pef_lower, 10**pef_upper]renders in engineering notation, as does one whose nonzero values span more than10**pef_upperbetween largest and smallest (column-level decisions — 5.x switched per cell, mixing formats within a column).eng_digits – What engineering format looks like when it fires: decimals, and
'si'suffixes (µ m k M G …) or'exp'aligned exponents. Both are dead iffloat_formatis set — that turns inference off.eng_style – What engineering format looks like when it fires: decimals, and
'si'suffixes (µ m k M G …) or'exp'aligned exponents. Both are dead iffloat_formatis set — that turns inference off.
- header_row: bool = False
- float_format: FormatSpec | Mapping | str | int | None = None
- int_format: FormatSpec | Mapping | str | int | None = None
- ratio_format: FormatSpec | Mapping | str | int | None = None
- date_format: FormatSpec | Mapping | str | int | None = None
- table_float_format: FormatSpec | Mapping | str | int | None = None
- math: str = 'auto'
- sparsify: bool = True
- sparsify_columns: bool = True
- pef_lower: int = -3
- pef_upper: int = 6
- eng_digits: int = 3
- eng_style: Literal['si', 'exp'] = 'si'
- greater_tables.build(data, spec: TableSpec | None = None) TableDoc[source]
Build a stamped table document from data plus a semantic spec.
- Parameters:
data – DataFrame, Series, list of row-lists (
spec.header_rowpromotes the first), or a markdown pipe-table string (caption/label/alignment extracted and used where the spec leaves them unset).spec – Semantic specification; defaults to
TableSpec().
- Returns:
Validated, hash-stamped document.
- Return type:
Notes
A DataFrame with default RangeIndex columns is treated as headerless; an unnamed RangeIndex row index is auto-hidden unless
spec.show_indexsays otherwise.
- greater_tables.canonical_dict(doc: TableDoc, *, include_hash: bool = True) dict[str, Any][source]
Return the canonical plain-dict form of a document.
Deterministic field presence: structural fields always appear; optional fields appear only when they differ from their defaults; trivial cells collapse to bare strings; all strings are NFC-normalized. Key ordering is left to the JSON writer (sorted there).
- Parameters:
doc – The document to canonicalize.
include_hash – When False,
hashandgeneratorare omitted — the form that content hashing operates on.
- greater_tables.canonical_json(doc: TableDoc, *, include_hash: bool = True) bytes[source]
Serialize a document to canonical UTF-8 JSON bytes.
Sorted keys, compact separators, NFC strings (applied by
greater_tables.ir.canonical_dict()), no NaN/Infinity — a raw value that is not JSON-representable is an engine bug, surfaced here.- Parameters:
doc – The document to serialize.
include_hash – When False, omit
hashandgenerator(the hashed form).
- greater_tables.doc_hash(doc: TableDoc) str[source]
Return the 12-hex-char sha256 content hash of a document.
- greater_tables.load_spec(path: str | Path) TableSpec[source]
Load a TableSpec from a YAML mapping file.
notesarrives from YAML as a list and is converted to the tuple the dataclass holds, so a loaded spec compares equal to the Python-built one (andwrite_template(path, spec)round-trips).
- greater_tables.load_style(path: str | Path) HouseStyle[source]
Load a HouseStyle from a YAML mapping file.
Tuple-valued fields (
hrule_widths,vrule_widths) arrive from YAML as lists and are converted, sohrule_widths: [1, 0.5, 0]works.
- greater_tables.quick_fab(rows: int = 10, data_spec: str = 's3sfid', *, seed: int | None = None, pyarrow: bool = False, **kwargs) DataFrame[source]
One-stop fabrication of a DataFrame (see
Fabricator.make()).
- greater_tables.render_html(doc: TableDoc, *, css: str = 'embed', inline_css: bool = False, allow_html: bool = True, style: HouseStyle | None = None) str[source]
Render a table document to an HTML fragment.
- Parameters:
doc – The document.
css –
'embed'(default),'once', or'none'.inline_css – Flatten the stylesheet onto
styleattributes (HTMX/email); impliescss='none'.allow_html – Honor cell
html: true. Off ⇒ such cells render as escaped source.style – House style.
max_rule_depthandmax_vrule_depthare the only fields this renderer reads — they decide which breaks get agt-break-{slot}/gt-vbreak-{slot}class. Rule weights are CSS custom properties (--gt-rule-{slot},--gt-vrule-{slot}), set in a stylesheet, not here.
- greater_tables.render_text(doc: TableDoc, style: HouseStyle | None = None) str[source]
Render a table document to monospace text.
Two
HouseStylefields reach here.max_rule_depthsets how deep breaks are ruled — text has no way to express a rule weight, so every drawn break looks the same.text_ascii_rulesswapsASCII_CHARSfor the defaultUNICODE_CHARS.
- greater_tables.render_tikz(doc: TableDoc, style: HouseStyle | None = None) str[source]
Render a table document to a TikZ table (LaTeX source).
Specification
User-facing table specification (semantics) and house style (opinions).
TableSpec carries everything the engine needs to build a TableDoc —
semantic choices that ride with the table. HouseStyle carries renderer
opinions (rule weights, fonts, spacing) that must never enter the IR; it is
populated as renderers arrive.
- class greater_tables.spec.HouseStyle(max_rule_depth: int | Literal['auto'] | None = 'auto', max_vrule_depth: int | None = 1, max_prose_width_em: float = 18.0, max_table_width_em: float | None = None, text_ascii_rules: bool = False, tikz_scale: float = 1.0, tikz_column_sep: float = 1.0, tikz_row_sep: float = 0.25, tikz_container_env: str = 'table', tikz_latex: str | None = None, tikz_post_process: str = '', frame_rule: float = 1.0, head_rule: float = 1.0, stub_rule: float = 1.0, total_rule: float = 0.75, spanner_rule: float = 0.5, hrule_widths: tuple[float, float, float] = (0.5, 0.25, 0.25), vrule_widths: tuple[float, float, float] = (0.25, 0.25, 0.25))[source]
Bases:
objectRenderer opinions — never serialized into the IR.
Rule weights are the greater_tables float scale, mapped to TikZ line styles via
render.tikz.W2TIKZ(0 = don’t draw, 0.25 = ultra thin … 3 = ultra thick). HTML equivalents live as CSS custom properties inrender.css.VARIABLES.- Parameters:
max_rule_depth (int | Literal['auto'] | None) – How deep the hierarchy is ruled.
'auto'(default) rules every break except the innermost stub level — a change there is just “next row”, so ruling it draws a line between every row; that gives no rules on a single-level index, depth 0 only on two levels, depths 0 and 1 on three. An int is literal:0rules only outermost changes, and on a single-level index it rules every row whose stub changed. None draws no internal rules at all.max_vrule_depth (int | None) – The same for column-group verticals (None = none). Only
render.policy.N_VRULE_SLOTSweights exist, so deeper breaks reuse the lightest.max_prose_width_em (float) – How wide a single wrapping column may get. TikZ reads it directly; HTML emits it as
--gt-prose-max. One column, not the table — the table ismax_table_width_em.max_table_width_em (float | None) – Total table width to aim for, in ems, or None (default) for no target. A target, not a guarantee: columns that must not wrap — numbers, dates, anything tagged or listed in
TableSpec.unbreakable— hold their natural width whatever the budget says, so a table of nothing but numbers can exceed it. What the budget can do is narrow the prose columns, which it does with one shared quantile so every one of them carries the same chance of wrapping (render.widths). When even the floors do not fit, TikZ stepstikz_scaledown a fixed ladder and says so throughlogging; HTML hands the number to the browser as a CSSmax-widthand lets it measure. Ems because every other geometry field here is ems: at 11pt, 8 inches is about 52em.text_ascii_rules (bool) – Draw the text rendering with ASCII only —
=and-for heavy and light rules,|for the stub divider,<<flag>>for the row gutter. Default False (box-drawing characters). Worth setting for a LaTeX verbatim block, where Pygments lexes U+2501 as an error token and styles it red, and for a console whose encoding or mono font cannot carry box drawing. Covers what the renderer draws, not cell text: values arrive already formatted, soFormatSpec.null(an em dash) and the SI suffix ladder (µ) still come through. Change those on the spec side if a build must be pure ASCII end to end.
- class greater_tables.spec.TableSpec(caption: str | None = None, label: str | None = None, notes: ~collections.abc.Sequence[str] = (), show_index: bool | None = None, header_row: bool = False, ratio_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, year_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, date_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, raw_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, html_cols: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, unbreakable: str | ~collections.abc.Sequence[~typing.Hashable] | None = None, aligners: str | ~collections.abc.Mapping[~typing.Hashable, str] | None = None, formatters: ~collections.abc.Mapping[~typing.Hashable, ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | ~collections.abc.Callable[[~typing.Any], str]] = <factory>, float_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, int_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, ratio_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, date_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, table_float_format: ~greater_tables.ir.FormatSpec | ~collections.abc.Mapping | str | int | None = None, row_flags: ~collections.abc.Mapping[int, ~collections.abc.Sequence[str]] | ~collections.abc.Callable[[int, ~typing.Any], ~collections.abc.Sequence[str]] | None = None, cell_flags: ~collections.abc.Callable[[int, ~typing.Hashable, ~typing.Any], ~collections.abc.Sequence[str]] | None = None, math: str = 'auto', include_raw: bool | str | ~collections.abc.Sequence[~typing.Hashable] = False, max_rows: int | None = 200, sparsify: bool = True, sparsify_columns: bool = True, pef_lower: int = -3, pef_upper: int = 6, eng_digits: int = 3, eng_style: ~typing.Literal['si', 'exp'] = 'si')[source]
Bases:
objectSemantic specification for one table.
- Parameters:
caption – Table metadata;
labelis the Quarto cross-reference id.label – Table metadata;
labelis the Quarto cross-reference id.notes – Table metadata;
labelis the Quarto cross-reference id.show_index (bool | None) – Show the row index as stub columns. None (default) auto-hides a trivial unnamed RangeIndex.
header_row (bool) – For list input: promote the first row to column headers.
ratio_cols – Semantic column tags: a list of labels,
'all', or a regex matched against column names.year_cols – Semantic column tags: a list of labels,
'all', or a regex matched against column names.date_cols – Semantic column tags: a list of labels,
'all', or a regex matched against column names.raw_cols – Semantic column tags: a list of labels,
'all', or a regex matched against column names.html_cols (str | collections.abc.Sequence[Hashable] | None) – String columns whose cells carry raw HTML (sets the cell
htmlflag; renderers still gate on their own allow-html option).unbreakable (str | collections.abc.Sequence[Hashable] | None) – Columns forced to
wrap=Falseregardless of dtype.aligners (str | collections.abc.Mapping[Hashable, str] | None) –
'lrc'-style string (positional) or mapping label → ‘l’/’r’/’c’.formatters (collections.abc.Mapping[Hashable, greater_tables.ir.FormatSpec | collections.abc.Mapping | str | int | collections.abc.Callable[[Any], str]]) – Per-column format override, keyed by column label (a tuple label also matches on its leaf name). Values: a FormatSpec, a mapping of FormatSpec fields (the YAML form), a sugar string (csv-grid
',.1f'or GT'{x:.1%}'style), an int (decimal digits, with grouping), or a callable taking the value and returning a string. A callable owns its own NaN handling and does not reach the IR, so a client cannot re-render that column.float_format – House defaults, one per kind — the 5.x
default_*_strsettings. Each overrides only its own kind’s built-in default and loses to an explicitformattersentry.date_formatcovers the date tag and both date and datetime dtypes. Settingfloat_formatturns the float inference off entirely, including the automatic engineering/SI switch, sopef_*andeng_*stop applying. Callables are rejected: these are house rules that must be YAML-expressible.int_format – House defaults, one per kind — the 5.x
default_*_strsettings. Each overrides only its own kind’s built-in default and loses to an explicitformattersentry.date_formatcovers the date tag and both date and datetime dtypes. Settingfloat_formatturns the float inference off entirely, including the automatic engineering/SI switch, sopef_*andeng_*stop applying. Callables are rejected: these are house rules that must be YAML-expressible.ratio_format – House defaults, one per kind — the 5.x
default_*_strsettings. Each overrides only its own kind’s built-in default and loses to an explicitformattersentry.date_formatcovers the date tag and both date and datetime dtypes. Settingfloat_formatturns the float inference off entirely, including the automatic engineering/SI switch, sopef_*andeng_*stop applying. Callables are rejected: these are house rules that must be YAML-expressible.date_format – House defaults, one per kind — the 5.x
default_*_strsettings. Each overrides only its own kind’s built-in default and loses to an explicitformattersentry.date_formatcovers the date tag and both date and datetime dtypes. Settingfloat_formatturns the float inference off entirely, including the automatic engineering/SI switch, sopef_*andeng_*stop applying. Callables are rejected: these are house rules that must be YAML-expressible.table_float_format (greater_tables.ir.FormatSpec | collections.abc.Mapping | str | int | None) – The sledgehammer: every untagged float column, overriding even an explicit
formattersentry. Ratio, year and date columns have a kind, so it never reaches them. Default None, and deliberately absent fromconfig.write_template— set it consciously at a call site, never in a config file where it would silently defeat per-column formats months later.row_flags (collections.abc.Mapping[int, collections.abc.Sequence[str]] | collections.abc.Callable[[int, Any], collections.abc.Sequence[str]] | None) – Mapping row position (negative allowed) → flags, or a callable
(position, row_series) -> flags. Vocabulary: total, subtotal, emphasis, muted.cell_flags (collections.abc.Callable[[int, Hashable, Any], collections.abc.Sequence[str]] | None) – Callable
(position, column_label, value) -> flagsfor per-cell emphasis/muted. Thenegflag is stamped automatically.math (str) –
'auto'detects$...$-wrapped string cells as math;'off'disables.include_raw (bool | str | collections.abc.Sequence[Hashable]) – Carry raw values in body cells: False (default), True/’data’ (numeric/date/bool data columns), or an explicit column list. Data columns only in every form — stub (index) columns never carry raw values, so a client that must sort on an index level wants
df.reset_index()beforebuild.max_rows (int | None) – Truncate longer tables, appending a note. None = unlimited.
sparsify – Merge repeated stub values into rowspans / repeated header prefixes into colspans.
sparsify_columns – Merge repeated stub values into rowspans / repeated header prefixes into colspans.
pef_lower – Engineering-format bounds: a float column whose mean magnitude falls outside
[10**pef_lower, 10**pef_upper]renders in engineering notation, as does one whose nonzero values span more than10**pef_upperbetween largest and smallest (column-level decisions — 5.x switched per cell, mixing formats within a column).pef_upper – Engineering-format bounds: a float column whose mean magnitude falls outside
[10**pef_lower, 10**pef_upper]renders in engineering notation, as does one whose nonzero values span more than10**pef_upperbetween largest and smallest (column-level decisions — 5.x switched per cell, mixing formats within a column).eng_digits – What engineering format looks like when it fires: decimals, and
'si'suffixes (µ m k M G …) or'exp'aligned exponents. Both are dead iffloat_formatis set — that turns inference off.eng_style – What engineering format looks like when it fires: decimals, and
'si'suffixes (µ m k M G …) or'exp'aligned exponents. Both are dead iffloat_formatis set — that turns inference off.
YAML configuration: load a TableSpec (project defaults or per-table).
YAML is this project’s single config format (pyproject.toml aside). A file is a plain mapping of field names; unknown keys raise so typos surface. Callable fields (row_flags, cell_flags) are code, not config.
Two files, because the two objects mean different things: a spec file
holds semantics that travel with the table (formats, column tags), a style
file holds renderer opinions that do not (rule depth, TikZ geometry). Keeping
them apart is the same split as greater_tables.spec; a single file
with two sections would blur it.
- greater_tables.config.load_spec(path: str | Path) TableSpec[source]
Load a TableSpec from a YAML mapping file.
notesarrives from YAML as a list and is converted to the tuple the dataclass holds, so a loaded spec compares equal to the Python-built one (andwrite_template(path, spec)round-trips).
- greater_tables.config.load_style(path: str | Path) HouseStyle[source]
Load a HouseStyle from a YAML mapping file.
Tuple-valued fields (
hrule_widths,vrule_widths) arrive from YAML as lists and are converted, sohrule_widths: [1, 0.5, 0]works.
- greater_tables.config.spec_to_yaml(spec: TableSpec) str[source]
Serialize a TableSpec’s effective values to YAML.
- Raises:
ValueError – If
row_flagsorcell_flagsholds a callable — code has no YAML form, and emitting a function repr would produce a file that cannot be loaded back.
- greater_tables.config.write_template(path: str | Path, spec: TableSpec | None = None) Path[source]
Write a YAML TableSpec template, or dump one spec’s effective values.
With
spec=Noneevery field appears commented out with a one-line hint — a starting point to copy from. Pass a spec and its actual settings are written uncommented, so the round trip iswrite_template('house.yaml', gt.spec)→ edit →config_path=.table_float_formatis never written: it overrides even per-column formatters, which makes it right for a deliberate call site and wrong in a file that outlives the reason you set it.
Facade
GT — the greater_tables constructor-compatible facade.
Thin, not bug-for-bug: the common blog/Quarto call
GT(df, caption=..., ratio_cols=..., formatters=...) works and renders via
the GT2 engine; exotic 5.x config knobs are accepted with a warning and
ignored. Everything is lazy cached — constructing a GT computes nothing, and
TikZ is never computed on the HTML path (the 5.x eager-TikZ waste is gone by
construction).
formatters is passed straight through: it is a real TableSpec field,
not a translated shim. One deliberate difference from 5.x — a bare int now
groups ({'paid': 2} → 1,234.57, where 5.x meant 1234.57), because
GT2 groups whenever it chooses the format for you. Write '.2f' for the
ungrouped form.
- class greater_tables.compat.GT(data=None, caption=None, label=None, aligners=None, formatters=None, unbreakable=None, ratio_cols=None, year_cols=None, date_cols=None, raw_cols=None, show_index=None, config_path=None, style=None, mimes=None, **overrides)[source]
Bases:
objectTable facade: data + spec in, renderings out (all lazy, all cached).
Parameters mirror the greater_tables constructor where they still make sense;
formattersaccepts the 5.x shapes (callable,'{x:...}'format string, or int = decimal digits, now grouped).config_pathloads YAML TableSpec defaults which explicit arguments override.styletakes aHouseStyleand is forwarded to every renderer.mimespins what_repr_mimebundle_publishes — None (default) detects the target, an explicit tuple such as('text/plain',)publishes exactly that. It is a whole-document policy, so the intended form isrender = partial(GT, mimes=('text/plain',)). Additional keyword arguments naming TableSpec fields pass through; unknown ones warn and are ignored (migration aid), and a HouseStyle field name warns with the fix.
The IR
Table-document IR: frozen pydantic models, ir_version 1.
The IR is the contract between the semantic engine and every renderer (Python HTML, TikZ, text, and the JS walker). It carries semantics only: no widths, no rule weights, no CSS, no fonts.
Two access paths with different strictness:
Write (producer): construct models directly; unknown fields raise (
extra='forbid'), so producer typos surface immediately.Read (consumer):
TableDoc.read()strips unknown fields before validation, so documents from a newer producer with additive advisory fields remain readable (“must ignore” rule).
Canonical serialization lives in canonical_dict(): deterministic field
presence (defaults omitted), NFC-normalized strings, and the bare-string cell
shorthand. greater_tables.engine.hashing turns that dict into
canonical bytes and the content hash.
- class greater_tables.ir.Cell(*, text: str, raw: float | int | str | bool | None = None, rowspan: Annotated[int, Ge(ge=1)] = 1, colspan: Annotated[int, Ge(ge=1)] = 1, flags: tuple[Literal['neg', 'emphasis', 'muted'], ...] = (), math: bool = False, html: bool = False)[source]
Bases:
BaseModelOne body/foot cell.
textis plain unicode — each renderer applies its own escaping.rawis only present when the owning column advertisesraw: true; an explicit JSONnullthere means a missing value, which is distinct from the field being absent (tracked via pydantic’s fields-set).- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class greater_tables.ir.Column(*, key: str, name: tuple[str, ...], dtype: Literal['int', 'float', 'date', 'datetime', 'bool', 'string', 'object'], align: Literal['l', 'r', 'c'], role: Literal['stub', 'data'] = 'data', level: Annotated[int | None, Ge(ge=0)] = None, tag: Literal['ratio', 'year', 'date', 'raw'] | None = None, wrap: bool = False, break_depth: Annotated[int | None, Ge(ge=0)] = None, format: FormatSpec | None = None, raw: bool = False)[source]
Bases:
BaseModelDescriptor for one visual column, stubs first.
- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- dtype: Dtype
- align: Alignment
- role: Role
- format: FormatSpec | None
- class greater_tables.ir.FormatSpec(*, kind: Literal['int', 'dec', 'pct', 'sci', 'gen', 'si', 'eng', 'year', 'date', 'str'], digits: Annotated[int | None, Ge(ge=0)] = None, group: bool = False, scale: float = 1.0, prefix: str | None = None, suffix: str | None = None, negative: Literal['minus', 'paren'] = 'minus', null: str = '—', zero: str | None = None, pattern: str | None = None)[source]
Bases:
BaseModelResolved display format for one column.
Applicable by Python and by the JS walker (Intl.NumberFormat). The formatted
textin cells is normative; this spec is informative — it lets a client re-render at different precision without redoing the engine’s column-scanning inference.- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- kind: FormatKind
- negative: Negative
- class greater_tables.ir.HeadCell(*, text: str = '', colspan: Annotated[int, Ge(ge=1)] = 1, align: Literal['l', 'r', 'c'] | None = None, flags: tuple[Literal['neg', 'emphasis', 'muted'], ...] = (), math: bool = False, html: bool = False)[source]
Bases:
BaseModelOne header cell; level is implicit in its row position within
head.- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class greater_tables.ir.Row(*, cells: tuple[Cell | str, ...], break_depth: Annotated[int | None, Ge(ge=0)] = None, flags: tuple[Literal['total', 'subtotal', 'emphasis', 'muted'], ...] = ())[source]
Bases:
BaseModelOne body/foot row.
cellsholds only the uncovered columns — a stub cell withrowspanin an earlier row covers its column here, exactly as HTML<td rowspan>semantics. A cell that is plain text with no other attributes may be a bare string (the shorthand; canonicalized on dump).break_depthis the shallowest stub level whose value changed versus the previous row (0 = outermost). House style maps depth to rule weight; the IR never names a weight.- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class greater_tables.ir.TableDoc(*, ir_version: int = 1, generator: str | None = None, hash: Annotated[str | None, _PydanticGeneralMetadata(pattern='^[0-9a-f]{12}$')] = None, caption: str | Cell | None = None, label: str | None = None, notes: tuple[str, ...] = (), n_stub_levels: Annotated[int, Ge(ge=0)], n_head_levels: Annotated[int, Ge(ge=0)], columns: tuple[Column, ...], head: tuple[tuple[HeadCell, ...], ...], body: tuple[Row, ...], foot: tuple[Row, ...] = ())[source]
Bases:
BaseModelThe table document — the versioned IR a semantic engine emits.
hashis stamped bygreater_tables.engine.hashing.stamp()(sha256 over canonical bytes,hashandgeneratorexcluded).- model_config = {'extra': 'forbid', 'frozen': True}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- classmethod read(source: str | bytes | dict[str, Any]) TableDoc[source]
Validate a foreign document, ignoring unknown fields.
- Parameters:
source – JSON text/bytes or an already-parsed dict.
- Returns:
The validated document.
- Return type:
- Raises:
ValueError – If
ir_versionis unsupported or the document is malformed.
- greater_tables.ir.canonical_dict(doc: TableDoc, *, include_hash: bool = True) dict[str, Any][source]
Return the canonical plain-dict form of a document.
Deterministic field presence: structural fields always appear; optional fields appear only when they differ from their defaults; trivial cells collapse to bare strings; all strings are NFC-normalized. Key ordering is left to the JSON writer (sorted there).
- Parameters:
doc – The document to canonicalize.
include_hash – When False,
hashandgeneratorare omitted — the form that content hashing operates on.
Engine
build(data, spec) -> TableDoc — the semantic engine orchestrator.
Accepts a DataFrame, Series, list of rows, or a markdown pipe table. Never mutates its input. Everything semantic is decided here — dtypes, tags, formats, alignment, hierarchy, flags — and nothing stylistic.
- greater_tables.engine.build.build(data, spec: TableSpec | None = None) TableDoc[source]
Build a stamped table document from data plus a semantic spec.
- Parameters:
data – DataFrame, Series, list of row-lists (
spec.header_rowpromotes the first), or a markdown pipe-table string (caption/label/alignment extracted and used where the spec leaves them unset).spec – Semantic specification; defaults to
TableSpec().
- Returns:
Validated, hash-stamped document.
- Return type:
Notes
A DataFrame with default RangeIndex columns is treated as headerless; an unnamed RangeIndex row index is auto-hidden unless
spec.show_indexsays otherwise.
Column dtype discovery, semantic tags, alignment, and wrap hints.
Arrow-aware via pandas.api.types predicates — dtypes are trusted, never
coerced (the greater_tables float-coercion loop does not come along).
- greater_tables.engine.dtypes.alignment(col: Hashable, *, is_stub: bool, dtype: Literal['int', 'float', 'date', 'datetime', 'bool', 'string', 'object'], tag: Literal['ratio', 'year', 'date', 'raw'] | None, aligners: dict[Hashable, str]) Literal['l', 'r', 'c'][source]
Alignment ladder: user > stub-left > year-center > raw-left > date-center > numeric-right > left.
- greater_tables.engine.dtypes.column_dtype(ser: Series) Literal['int', 'float', 'date', 'datetime', 'bool', 'string', 'object'][source]
Classify a series into an IR dtype.
Datetime columns whose values all sit at midnight classify as
date; any time-of-day component makes themdatetime.
- greater_tables.engine.dtypes.resolve_cols(selector: str | Sequence[Hashable] | None, columns: list[Hashable]) set[Hashable][source]
Resolve a column selector to a set of labels from
columns.A list matches labels exactly (and leaf names for tuple labels);
'all'matches everything; any other string is a regex searched against the leaf name and the full label.
- greater_tables.engine.dtypes.semantic_tag(col: Hashable, tags: dict[str, set[Hashable]]) Literal['ratio', 'year', 'date', 'raw'] | None[source]
Return the winning semantic tag for a column (GT ladder order).
- greater_tables.engine.dtypes.wrap_hint(dtype: Literal['int', 'float', 'date', 'datetime', 'bool', 'string', 'object'], tag: Literal['ratio', 'year', 'date', 'raw'] | None, col: Hashable, unbreakable: set[Hashable]) bool[source]
Prose (string/object, untagged) columns may wrap; everything else not.
Format sugar parsing, column-statistics inference, and application.
The resolution ladder (user > semantic tag > dtype) produces
FormatSpec data objects — never closures, so
the greater_tables late-binding formatter bug cannot recur. Application is a
pure function of (value, spec).
Sugar accepted, both prior vocabularies:
csv-grid spec strings:
','?'.N'? then one off d % e g s, plus'year','eng','si', and strftime patterns for dates.GT f-strings:
'{x:,.1f}','{x:.1%}'— the python format mini-spec.An int means that many decimal digits.
g is the one kind whose digits counts significant figures rather
than decimal places, because that is what python’s g presentation type
means; '.3g' renders 1234.5 as 1.23e+03 and 0.0012345 as
0.00123. Uppercase G/E are still unparsed — see dev/pending.md.
- greater_tables.engine.formats.apply_format(value, fs: FormatSpec | None) str[source]
Format one value. Pure; NaN-safe; never raises on odd input.
- greater_tables.engine.formats.infer_numeric_format(ser: Series, *, pef_lower: int = -3, pef_upper: int = 6, digits: int = 3, style: str = 'si') FormatSpec[source]
Choose a float column’s format from its statistics (GT salvage).
Digits bucket by mean magnitude (<1 → 5, <10 → 3, <20000 → 2, else 0). Engineering notation on either trigger: mean magnitude outside
[10**pef_lower, 10**pef_upper](the GT rule), or nonzero values spanning more than10**pef_upperorders-of-magnitude ratio (the csv-grid rule — no single fixed precision serves such a column), rendered withdigitsdecimals instyle('si'suffixes or'exp'aligned exponents). Column-level decision — GT 5.x switched per cell, which mixed formats within a column.This is the guess, and it only runs when the caller has not stated a house rule:
TableSpec.float_format(ortable_float_format) short-circuits it ingreater_tables.engine.build._resolve_format(), which is what makes a document’s floats consistent across tables.
- greater_tables.engine.formats.parse_sugar(value: FormatSpec | Mapping | str | int) FormatSpec[source]
Normalize a user format override to a FormatSpec.
Accepts a FormatSpec, a mapping of its fields (the YAML form — the only way a config file can reach
negative='paren',scale,prefix,suffix,nullorzero), a csv-grid spec string (',.1f','.3g'), a GT f-string ('{x:.1%}','{x:.3g}'), a strftime pattern ('%d %b %Y'), the words'year'/'eng'/'si', or an int (decimal digits, grouped).gmaps tokind='gen', wheredigitsmeans significant figures.- Raises:
ValueError – If the sugar string is not recognized.
pydantic.ValidationError – If a mapping names a field FormatSpec does not have.
Table structure: row break depths, stub rowspans, head colspans.
The single implementation of the hierarchy logic (greater_tables derived it independently per backend). Everything here is pure functions over plain Python sequences — no pandas mutation, no rendering concerns.
- greater_tables.engine.structure.break_depths(tuples: Sequence[tuple]) list[int | None][source]
Per row, the shallowest level whose value changed vs. the row above.
Row 0 is None (top edge is structural). A row identical to its predecessor is also None — no boundary at all.
- greater_tables.engine.structure.column_break_depths(names: Sequence[tuple]) dict[int, int][source]
Data-column positions carrying a column-group boundary at their left edge.
Depth = shallowest head level that changes between column j-1 and j. Only genuine group boundaries are reported (depth ≤ n_levels − 2); leaf-level changes are just “next column”.
- greater_tables.engine.structure.head_groups(names: Sequence[tuple], level: int, merge: bool) list[tuple[str, int]][source]
Header cells for one level: (text, colspan) runs over data columns.
Adjacent columns group only while their full prefix through
levelmatches — the same leaf label under different parents never merges. The deepest level is never merged (distinct columns stay distinct); callers passmerge=Falsethere and when sparsify_columns is off.
- greater_tables.engine.structure.stub_layout(depths: Sequence[int | None], n_levels: int, sparsify: bool) list[dict[int, int]][source]
Which stub cells each row emits, and with what rowspan.
Returns one dict per row mapping stub level → rowspan for the levels that start a run at that row (HTML
<td rowspan>semantics: covered rows omit the cell entirely). Without sparsify every row emits every level with span 1.
Canonical bytes and content hashing for table documents.
Determinism contract: same document content ⇒ same bytes ⇒ same hash, on any
machine, any run. No time salt, no counters. hash and generator are
excluded from the hashed form so stamping does not perturb the digest.
- greater_tables.engine.hashing.canonical_json(doc: TableDoc, *, include_hash: bool = True) bytes[source]
Serialize a document to canonical UTF-8 JSON bytes.
Sorted keys, compact separators, NFC strings (applied by
greater_tables.ir.canonical_dict()), no NaN/Infinity — a raw value that is not JSON-representable is an engine bug, surfaced here.- Parameters:
doc – The document to serialize.
include_hash – When False, omit
hashandgenerator(the hashed form).
Renderers
Python HTML renderer: TableDoc → fragment.
Mirrors js/src/gt-render.js structure and class stamping EXACTLY — the
DOM-transcript conformance goldens (tests/golden/transcript) enforce the
agreement, so any change here must be paired with the walker and the goldens
regenerated. Class helpers below are direct ports of the walker’s.
Escaping: every text is HTML-escaped unless the cell carries
html: true and allow_html is on (the default here — the producer set
that flag deliberately via TableSpec.html_cols; the walker’s default is
the opposite because it consumes foreign documents).
Stylesheet policy (dev/design.md): css='embed' puts the ~2 KB sheet in
every fragment (idempotent — duplicates are no-ops, and a saved notebook
whose first cell was cleared keeps its styles); 'once' embeds only the
first time per process; 'none' for documents that include gt.css
themselves. inline_css=True flattens the same RULES dict onto style
attributes for HTMX-fragment/email use — no classes required by the host.
- greater_tables.render.html.render_html(doc: TableDoc, *, css: str = 'embed', inline_css: bool = False, allow_html: bool = True, style: HouseStyle | None = None) str[source]
Render a table document to an HTML fragment.
- Parameters:
doc – The document.
css –
'embed'(default),'once', or'none'.inline_css – Flatten the stylesheet onto
styleattributes (HTMX/email); impliescss='none'.allow_html – Honor cell
html: true. Off ⇒ such cells render as escaped source.style – House style.
max_rule_depthandmax_vrule_depthare the only fields this renderer reads — they decide which breaks get agt-break-{slot}/gt-vbreak-{slot}class. Rule weights are CSS custom properties (--gt-rule-{slot},--gt-vrule-{slot}), set in a stylesheet, not here.
- greater_tables.render.html.reset_css_once() None[source]
Reset the
css='once'latch (new kernel/document boundary).
Shared renderer policy: which structural breaks get a rule, and how heavy.
The IR states where the hierarchy breaks (Row.break_depth,
Column.break_depth); house style decides which of those breaks are drawn
and with what weight. This module is the one implementation, called by
html.py, tikz.py and text.py and ported line for line into
js/src/gt-render.js.
It exists because three independent copies of the same comparison had already
drifted: text.py drew a depth-3 rule the other three renderers suppressed,
at five or more stub levels — the conformance oracle disagreeing with the
renderers it exists to check.
Both functions return a weight slot, not a boolean, so the depth-to-weight
clamp lives in one place: there are only N_RULE_SLOTS weights defined
anywhere (HouseStyle.hrule_widths, --gt-rule-0/1/2, gt-break-0/1/2),
and a break deeper than the last slot reuses the lightest weight rather than
producing a class no stylesheet knows or an IndexError.
- greater_tables.render.policy.rule_slot(break_depth: int | None, n_stub_levels: int, max_rule_depth: int | str | None = 'auto') int | None[source]
Weight slot for one row break, or None when no rule is drawn.
- Parameters:
break_depth –
Row.break_depth— the shallowest stub level whose value changed from the row above, or None when nothing changed (or for the first row of a section). None never rules, under any setting.n_stub_levels –
TableDoc.n_stub_levels.max_rule_depth –
HouseStyle.max_rule_depth.'auto'(default) additionally caps atn_stub_levels - 2, which suppresses the innermost level — a change there is just “next row”, so ruling it draws a line between every row. An int is literal: it caps at that depth and nothing else, so0on a single-level index rules every row whose stub changed. None draws no internal rules at all.
- Returns:
Slot in
0 .. N_RULE_SLOTS - 1— the index intoHouseStyle.hrule_widths, thegt-break-{slot}class, and the--gt-rule-{slot}custom property — or None for no rule.- Return type:
int or None
- Raises:
ValueError – If
max_rule_depthis a string other than'auto'.
- greater_tables.render.policy.vrule_slot(break_depth: int | None, max_vrule_depth: int | None = 1) int | None[source]
Weight slot for one column-group break, or None for no rule.
Column break depths already have the leaf level filtered out at build time (
engine.structure.column_break_depths), so there is non_stub_levels-style term here — only the user cap and the slot clamp.
The CSS contract: one RULES dict → committed assets/gt.css.
Single source of truth for the class vocabulary shared by the Python HTML renderer and the JS walker (conformance transcripts enforce the agreement). The [notebook-html] inline flattener consumes the same dict, so the sheet and inline styles cannot diverge.
Principles (dev/design.md): selectors double the wrapper class (.gt.gt)
to beat JupyterLab/Quarto host rules without !important; per-table
scalars ride as custom properties with defaults declared on .gt; colors
default to currentColor/transparent so an embedded fragment has no color
opinion; no widths anywhere — gt-nowrap/gt-wrap express intent and
the browser measures.
Semantic classes:
alignment
gt-l gt-r gt-c(right implies tabular numerals)role
gt-stub gt-data;gt-stub-endmarks the last stub cell of a row (carries the stub/body vertical rule, spanning cells carry it for covered rows automatically)wrapping
gt-nowrap gt-wraprow breaks
gt-break-0/1/2— border-top on the row’s own cells only, so rowspan-covered stub columns are skipped and the rule starts exactly at the changed columncolumn-group breaks
gt-vbreak-0/1(border-left)flags
gt-total gt-subtotal gt-emph gt-muted gt-neg;gt-math
- greater_tables.render.css.css_text() str[source]
Serialize RULES to the stylesheet, deterministically.
- greater_tables.render.css.write_asset() Path[source]
Regenerate the committed
assets/gt.css(explicit recipe).
Notebook/Quarto integration helpers.
The mimebundle always carries text/plain: it is the one type every
measured consumer renders (JupyterLab, Quarto html/latex/gfm, Sphinx
html/latex), so a table degrades to a monospace rendering rather than to
silence or to <GT object at 0x…>. What joins it depends on the target —
text/html normally, text/latex (the TikZ rendering) under a Quarto
PDF render, never both. Keying on the target is what structurally fixes
greater_tables 5.x’s compute-TikZ-then-discard-it-per-repr waste: an HTML
render now computes no TikZ at all.
text/html is excluded on the PDF path deliberately. Pandoc prefers html
when it is offered, and its LaTeX writer then discards the raw html, so a
{html, latex, plain} bundle renders nothing; omitting html means a TikZ
failure degrades to text instead of to an empty page.
Detection reads QUARTO_FIG_FORMAT, the only channel Quarto offers. It is
pdf on the PDF path and png for html and gfm alike, so html and gfm
are indistinguishable from the environment. A gfm build that needs plain only
has to say so with mimes — see quarto_target().
- greater_tables.render.notebook.in_quarto() bool[source]
True inside a
quarto render(any QUARTO_* env variable set).
- greater_tables.render.notebook.mimebundle(doc: TableDoc, *, css: str = 'embed', tikz: bool | None = None, style: HouseStyle | None = None, mimes: Iterable[str] | None = None) dict[str, str][source]
MIME bundle for
_repr_mimebundle_.text/plainis always present unlessmimesexcludes it. With bothtikzandmimesleft at None the published keys are:target
keys
QUARTO_FIG_FORMAT == 'pdf'text/latex, text/plainany other Quarto render, or none
text/html, text/plain- Parameters:
doc – The document.
css – Stylesheet policy for the HTML entry.
tikz – Force
text/latexin or out, bypassing target detection; None (default) detects. Forcing it in keepstext/html— it means “also give me TikZ”, not “this is a PDF render”. Usemimesfor exact control.style – HouseStyle, forwarded to every renderer (HTML reads only the rule depth caps; TikZ reads everything).
mimes – Publish exactly these types, suppressing detection entirely and overriding
tikz. Members are validated againstKNOWN_MIMES. This is the escape hatch for a target GT cannot detect — notably Quarto--to gfm, where pandoc prefers html and the caller must ask for('text/plain',).
- greater_tables.render.notebook.quarto_target() str | None[source]
Which Quarto output is being built:
'pdf','other', or None.QUARTO_FIG_FORMATis the proxy:pdfon the PDF path,pngfor both html and gfm.'other'therefore means “a Quarto render that is not PDF” and deliberately not “html” — the environment cannot tell html from gfm, and naming it html would hide a real ambiguity. None means no Quarto render was detected at all.
- greater_tables.render.notebook.validate_mimes(mimes: Iterable[str]) tuple[str, ...][source]
Check an explicit MIME request, returning it as a tuple.
Raises
ValueErroron anything outsideKNOWN_MIMES: a typo would otherwise publish a bundle no consumer can use, and fail silently.
Text renderer — the conformance oracle.
Reads only the TableDoc. Anything this renderer cannot draw is data missing from the IR, which is the point: it keeps the IR honest. Output is deterministic monospace text with box-drawing rules.
Semantics made visible: break depths draw partial horizontal rules starting
at the changed stub column (which depths qualify is render.policy, shared
with every other renderer); rowspan/colspan cells span; row flags appear in a
right gutter; math cells render as $tex$; emphasis/muted cells as
*text*/~text~.
Every non-ASCII character the renderer emits lives in Charset, so
HouseStyle.text_ascii_rules can swap the whole set at once — for a LaTeX
verbatim block, or a console whose encoding or mono font cannot carry box
drawing. The ASCII set keeps the heavy/light distinction (= against
-) rather than collapsing both to one character: which rule is a frame
and which is a group break is information the oracle should not lose.
- class greater_tables.render.text.Charset(heavy: str, light: str, vrule: str, flag_open: str, flag_close: str)[source]
Bases:
objectThe characters the text renderer draws rules and markers with.
vrulemust be one character wide and the flag delimiters live in a right gutter, so swapping sets never moves a column: widths are solved from cell content, andSTUB_GAPis 3 either way.
- greater_tables.render.text.render_text(doc: TableDoc, style: HouseStyle | None = None) str[source]
Render a table document to monospace text.
Two
HouseStylefields reach here.max_rule_depthsets how deep breaks are ruled — text has no way to express a rule weight, so every drawn break looks the same.text_ascii_rulesswapsASCII_CHARSfor the defaultUNICODE_CHARS.
TikZ renderer: TableDoc → tikzpicture in a table environment.
The single implementation (greater_tables carried three, two dead), rebuilt
from the live make_tikz mechanics against the IR:
a
matrix of nodeswith a zero-height spacer row that fixes column widths, header rows in bold (stub header cells bold-italic), and a trailing phantom column used as a stable anchor rail;rules drawn as
\pathsegments anchored on node corners, shifted by half the row/column separation into the inter-cell gap — row break rules start exactly at the changed stub column, mirroring the HTML renderer;colspan support (new vs. 5.x): spanning cells sit as empty matrix cells and their label is overlaid centered on the span via
calcmidpoints — real spanners, not blanked repeats;character-based width estimation (the salvaged TextLength glyph table) lives here and only here — TeX genuinely needs geometry, HTML does not.
Cells flagged math re-wrap as $...$; everything else is TeX-escaped.
The output expects the \I strut macro (TIKZ_PREAMBLE) — Etcher’s
standalone template defines it; Quarto documents include it once in the
preamble.
- greater_tables.render.tikz.render_tikz(doc: TableDoc, style: HouseStyle | None = None) str[source]
Render a table document to a TikZ table (LaTeX source).
Character-based display-width estimation (greater_tables TextLength salvage).
Lives in the TikZ renderer’s orbit only — TeX genuinely needs geometry ahead
of rendering; the HTML paths let the browser measure. Widths are em
estimates from a serif glyph table; $...$ spans are token-counted TeX.
The table is Times metrics, which is what Etcher compiles against
(newtxtext): measured there, the estimate is exact — median actual /
estimate 1.000 over the fixture corpus, p99 1.007. A document set in another
face is a different measurement: Latin Modern, the LaTeX default that a
plain Quarto PDF uses, runs about 11% wider for text (digits only 1.6%), so
columns come out slightly tight and a long heading may hyphenate. Either
load a Times-metric serif alongside the tables, or accept that. Re-measure
any font with dev/tools/measure_glyphs.py.
Glyph widths are held as integer milli-ems and accumulated as integers,
converted to ems only at the end. Summing the float ems directly is not
reproducible across interpreters: CPython 3.12 gave sum() Neumaier
compensated summation, so ‘apples’ totalled 2.5549999999999997 on 3.11 and
2.555 on 3.13 — one ULP apart, but on opposite sides of the 2 dp rounding
the TikZ renderer applies, which moved a committed column width by 0.01em.
Integer accumulation is exact on every version, so the TeX goldens are
interpreter-independent. Callers doing width arithmetic (summing columns,
allocating a budget) should therefore work in milli-ems throughout —
display_milli() and token_milli() — and divide only to print.
Two measures, because a column has two kinds of floor: display_milli()
is the width the text wants on one line, token_milli() the width of its
widest unbreakable token. TeX will not hyphenate inside a word, so a wrapping
column narrower than its longest token gives an overfull hbox.
- greater_tables.render.textlength.display_milli(s: str) int[source]
Display width in milli-ems — the exact, integer accumulation.
- greater_tables.render.textlength.text_display_len(s: str) float[source]
Estimate display width in ems: plain text,
$math$, HTML entities.
- greater_tables.render.textlength.token_display_len(s: str) float[source]
Em width of the widest unbreakable token — a wrap column’s floor.
- greater_tables.render.textlength.token_milli(s: str) int[source]
Widest whitespace-delimited token in milli-ems.
A
$...$span counts as one token: math is set as a box and does not break at spaces the way surrounding prose does.
Column-width allocation under a total-width budget.
A port of the csv-grid solver the JS walker also carries (solveWidths in
js/src/gt-render.js, itself copied from csv-grid). The allocation math is
identical; what differs is entirely the caller’s business:
csv-grid narrows a column by truncating cells, so a column’s floor is a minimum readable width and narrowing destroys content;
a typeset table narrows a column by wrapping it, so the floor is the widest unbreakable token — TeX will not hyphenate inside a word — and narrowing costs height, not content.
Same solver, different floors. This module takes floors as given and never asks which world it is in.
Units are plain integers and the module does not care what they mean:
milli-ems for TikZ, characters for a text renderer. Integers in, integers
out, so an allocation is identical on every interpreter — the same
discipline, and the same reason, as render/textlength.py.
The budget is text width only. Column separations, padding and rules are the caller’s geometry: subtract them before calling, add them back after.
Two modes:
equal-risk(default)Find the one quantile q that every column is measured at, so each takes the width its q-th percentile cell needs. Every column then carries the same probability of a cell overflowing its width — nothing is singled out and made to look mangled. Monotone in q, so a bisection finds it.
coverageMaximize the number of cells that fit outright, by spending the budget where it buys the most cells per unit width (a water-fill along each column’s concave envelope). Equalizes marginal cost rather than probability: a column with a fat tail is left short so several tight columns can complete.
- class greater_tables.render.widths.Allocation(widths: tuple[int, ...], status: Literal['natural', 'solved', 'over'])[source]
Bases:
objectSolved column widths and how the budget worked out.
statusis'natural'when nothing had to be narrowed,'solved'when narrowing brought the table inside the budget, and'over'when the floors alone exceed it — no allocation can help and the caller must scale, rotate, or accept the overflow.
- greater_tables.render.widths.solve_widths(samples: Sequence[Sequence[int]], floors: Sequence[int], budget: int, mode: Literal['equal-risk', 'coverage'] = 'equal-risk') Allocation[source]
Allocate
budgetacross columns.- Parameters:
samples – Per column, the width of every cell in it, in any order. A column with no samples is held at its floor.
floors – Per column, the width below which it must not be narrowed. A column that must not be narrowed at all passes its natural width here, and then simply rides through the allocation as a constant.
budget – Total text width available.
mode –
'equal-risk'or'coverage'; see the module docstring.
- Returns:
Widths in column order, and the status of the fit.
- Return type:
Etcher: compile TikZ LaTeX to PDF (and optionally SVG).
greater_tables salvage, modernized: tectonic is the default engine (fast,
self-caching — the 5.x .fmt precompilation dance is gone entirely), with
classic pdflatex as a fallback. Success is judged by the PDF existing,
not the exit code — MiKTeX’s pdflatex exits 1 on recoverable issues while
still producing output. House rules honored: no hardcoded tool paths, no
.bat side files, no os.startfile, pathlib throughout, logging not
print. Outputs cache by content hash.
- class greater_tables.render.etcher.Etcher(tex: str, *, file_name: str = '', base_path: str | Path | None = None, tex_engine: str = 'tectonic', pdf2svg: str = 'pdf2svg', font_size: int = 11)[source]
Bases:
objectCompile a TeX blob containing a tikzpicture to PDF/SVG files.
- Parameters:
tex – LaTeX source containing one
tikzpicture(e.g.render_tikzoutput; any surrounding table environment is stripped — standalone cannot host floats).file_name – Output stem; content hash when omitted (deterministic caching).
base_path – Working directory; outputs land in
base_path/'tikz'.tex_engine –
'tectonic'(default) or'pdflatex'— name or full path.pdf2svg – Converter executable for
compile(svg=True).font_size – Standalone document point size.
Fixtures
Fabricate synthetic pandas DataFrames for tests and fixtures.
Port of greater_tables fabrications.py with fixes recorded in the 1.0.0
CHANGELOG entry. Fully deterministic under a seed: datetime columns derive
from a fixed now reference (default 2026-01-01), never the wall clock —
this is what makes golden-file fixtures possible.
- class greater_tables.fabricator.Fabricator(decorate: bool = False, pyarrow: bool = False, seed: int | None = None, now: Timestamp | str = Timestamp('2026-01-01 00:00:00'))[source]
Bases:
objectFabricate small synthetic DataFrames covering GT2’s fixture space.
- Parameters:
decorate (bool) – Append type hints (” year”, “ ratio”, “ date”, “ time”) to generated metric names.
pyarrow (bool) – Convert fabricated frames to pyarrow-backed dtypes.
seed (int, optional) – RNG seed; one is drawn from entropy when omitted. Always pass a seed for fixtures.
now (pandas.Timestamp or str) – Reference timestamp for date/time columns. Fixed default keeps seeded output identical across runs.
Notes
Column type codes for
data_spec:d date r ratio (small floats) f float sN string, N words h hash t time (minute grid) i integer v extreme-range float l log float (wide range) wN string, random length ~N words p path (filename) x TeX expression y year
- metric_roots = ['absorption', 'acceleration', 'account', 'activation', 'adjustment', 'allocation', 'amplitude', 'approval', 'asset', 'atom', 'attrition', 'balance', 'band', 'binding', 'cancellation', 'capacitance', 'capital', 'cashflow', 'category', 'cell', 'charge', 'claim', 'commission', 'compound', 'concentration', 'conductivity', 'constraint', 'consumption', 'conversion', 'correlation', 'cost', 'count', 'coverage', 'credit', 'current', 'debt', 'decay', 'decibel', 'deductible', 'deficit', 'deflator', 'demand', 'density', 'development', 'diffusion', 'discount', 'distribution', 'dividend', 'dose', 'duration', 'earnings', 'efficiency', 'elasticity', 'employment', 'energy', 'entropy', 'enzyme', 'estimate', 'excess', 'exhaustion', 'expense', 'exposure', 'failure', 'field', 'flux', 'force', 'frequency', 'funding', 'gdp', 'gene', 'gradient', 'growth', 'half_life', 'incidence', 'income', 'index', 'indicator', 'inequality', 'inflation', 'inhibition', 'input', 'intensity', 'investment', 'kurtosis', 'lapse', 'layer', 'leverage', 'liability', 'limit', 'loss', 'luminosity', 'margin', 'mass', 'molecule', 'momentum', 'mortality', 'neutron', 'noise', 'operating', 'output', 'penalty', 'photon', 'policy', 'portfolio', 'potential', 'power', 'preference', 'premium', 'pressure', 'price', 'productivity', 'profit', 'protein', 'proton', 'provision', 'radiation', 'rate', 'ratio', 'reaction', 'recovery', 'reflection', 'refraction', 'renewal', 'reserve', 'residual', 'resistance', 'return', 'revenue', 'risk', 'sample', 'savings', 'scenario', 'score', 'sector', 'settlement', 'severity', 'shock', 'shortfall', 'signal', 'skewness', 'spread', 'strain', 'stress', 'subsidy', 'supply', 'tail', 'tariff', 'tax', 'temperature', 'tension', 'term', 'threshold', 'trade', 'trend', 'turbulence', 'unemployment', 'uptake', 'utility', 'utilization', 'valuation', 'variance', 'velocity', 'viscosity', 'volatility', 'voltage', 'volume', 'wage', 'wavelength', 'wealth', 'weight', 'yield']
- static roll_columns(df: DataFrame, levels: int = -1) DataFrame[source]
Rotate the column MultiIndex levels by
levelsand re-sort.
- static drop_singleton_levels(df: DataFrame) DataFrame[source]
Drop index/column levels holding a single value (keep at least one).
- make(rows: int, data_spec, *, index_levels: int = 1, index_names=None, column_groups: int = 1, column_levels: int = 1, column_names=None, metric_name_spec='', missing: float = 0.0, simplify: bool = True, oversample: int = 1) DataFrame[source]
Fabricate a DataFrame to a specification.
- Parameters:
rows – Number of body rows.
data_spec – Per-metric type codes (see class notes): a string like
's3sfid', a list of codes, or an int meaning that many floats.index_levels – Row MultiIndex depth.
index_names – Level names; generated when omitted.
column_names – Level names; generated when omitted.
column_groups – Column MultiIndex shape: total columns = metrics × groups.
column_levels – Column MultiIndex shape: total columns = metrics × groups.
metric_name_spec –
''for generated names, a list of names, or a type-code spec whose generated values become the names.missing – Proportion of values per column replaced with NaN.
simplify – Drop index/column levels that hold a single value. Note this can reduce the requested level count on small frames.
oversample – Fabricate
oversample × rowsthen keep the firstrows— more distinct index values at small sizes.
- Return type:
- random(rows: int = 0, columns: int = 0, index_levels: int = 0, omit: str = '') DataFrame[source]
Fabricate a DataFrame with randomly chosen shape and types.
- Parameters:
rows – 0 means choose randomly.
columns – 0 means choose randomly.
index_levels – 0 means choose randomly.
omit – Type codes to exclude.
- Return type: