Skip to content

TPLm

A single declarative language to describe table layout and data requirements, for efficiently querying and rendering arbitrarily complex or nested crosstabulations.

Background ​

The original TPL was a language developed by the U.S. Bureau of Labor Statistics in the early 1970s for producing complex statistical tables from survey data on IBM mainframes. It was freely shared with other federal agencies and research institutions, with significant adoption. The language later influenced two commercial products: SAS's PROC TABULATE (1982), which adopted TPL's syntax and concepts, and TPL Tables by QQQ Software (1987).

TPLm is intended as an opinionated, lean reimplementation, with an adjusted syntax that compiles to Malloy for efficient querying against DuckDB or BigQuery, and renders to well-structured HTML tables.

See What's Possible ​

One line describes the table structure. TPL handles the rest - generating efficient queries, fetching data, and rendering HTML.

Top Occupations by Income
TABLE
  ROWS (occupation DESC@income.sum) * income.mean:currency
  COLS (education 'education level' * gender) | ALL
;
Dataset:samplesoccupation, education, gender, income

More Examples - Click to Run ​

Row Percentages
TABLE
  ROWS occupation DESC@income.sum * (income.sum ACROSS COLS)
  COLS education
;
Dataset:samplesoccupation, education, gender, income
Median vs 95th Percentile Income
TABLE
  ROWS occupation * income.(p50 | p95):currency
  COLS gender
;
Dataset:samplesoccupation, education, gender, income

How It Works ​

TPL is a unified language that describes both:

  1. Table Layout - rows, columns, nesting, totals
  2. Data Requirements - dimensions, aggregations, filters

You write one declaration. TPL compiles it to efficient database queries, executes them, and renders the result.

Syntax Overview ​

OperatorMeaningExample
*Cross/Nestoccupation * gender (gender nested within occupation)
|Concatenateoccupation | gender (separate sections)
ALLTotals(occupation | ALL) (add total row)
[-N@field]Top Noccupation[-5@income.sum] (top 5 by income)
.sum .meanAggregateincome.sum, income.(sum | mean)
.p50 .p95Percentilesincome.p50 (median), income.(p25 | p75)
ACROSSPercentages(income.sum ACROSS COLS) (row percentages)

Common Patterns ​

sql
-- Basic crosstab: occupation rows, education columns
TABLE ROWS occupation * income.sum COLS education;

-- Top 5 with totals
TABLE ROWS occupation[-5@income.sum] * income.sum COLS education | ALL;

-- Statistical summary with percentiles
TABLE ROWS occupation * income.(p25 | p50 | p75 | p95) COLS gender;

-- Row percentages (each row sums to 100%)
TABLE ROWS occupation * (income.sum ACROSS COLS) COLS education;

-- Multiple aggregates with currency formatting
TABLE ROWS occupation * income.(sum | mean):currency COLS gender;

Next Steps ​

Powered by Malloy and DuckDB