Polyglot
Languages MATLAB tables
MATLAB § tables

Tables and timetables

The table class is MATLAB’s column-oriented heterogeneous tabular container — analogous to a Pandas DataFrame or an R data.frame. A table has named variables (columns) of possibly different classes, row names (optional), and a row count common to all variables. The closely related timetable has a time-axis row label and is the canonical container for irregular or regular time series. Both were added in R2013b and R2016b respectively; they have since become the recommended container for any analytical workflow that processes records with heterogeneous fields.

Constructing a table

The most common forms are from columns and from files:

T = table([1; 2; 3], ...
          ["Ada"; "Grace"; "Donald"], ...
          [1815; 1906; 1938], ...
          'VariableNames', ["id", "name", "year"]);

% T =
%   3×3 table
%      id    name     year
%      ──    ─────    ────
%       1    "Ada"    1815
%       2    "Grace"  1906
%       3    "Donald" 1938

A table is read from a CSV, Excel, or Parquet file with readtable:

T = readtable("people.csv");

readtable infers the column types from the data, optionally informed by the import options object that detectImportOptions constructs. Modern alternatives — readmatrix for purely numeric data, readtimetable for time-axis files, parquetread for Parquet — round out the family.

Accessing data

Three principal access forms:

Dot access retrieves a single variable as a column:

T.name              % 3×1 string vector
T.year(2)           % 1906

Parenthesis indexing returns a sub-table:

T(1:2, :)           % first two rows, all variables (a table)
T(:, ["name", "year"])    % all rows, named variables (a table)

Brace indexing extracts a matrix of the underlying values (requires that the indexed variables be of compatible class):

T{1:2, "year"}      % 2×1 double
T{:, 1:3}           % all rows, first three vars; fails if the vars have different classes

The three forms mirror the cell-array distinction: () keeps the container, {} extracts the contents, dot names a single variable.

Row labels

A table may carry row names — a string-valued column that indexes rows by name rather than position:

T.Properties.RowNames = ["alpha", "beta", "gamma"];
T("beta", :)        % the row named "beta"

The row-names form is most useful for small look-up tables; for larger workflows the timetable (with a time axis as row label) and ordinary integer indexing are more common.

Filtering and selecting

Logical indexing works on tables, as it does on arrays:

T(T.year > 1900, :)       % rows where year > 1900
T(T.year > 1900, ["name", "year"])

The expression T.year > 1900 is a logical column; using it as the row index selects the matching rows. This is the canonical table-filter idiom.

Grouping and split-apply-combine

The split-apply-combine idiom — split a table into groups, apply a function to each group, combine the results — is supported by groupsummary, varfun, rowfun, and groupfilter:

T = readtable("sales.csv");
% Per-region totals:
groupsummary(T, "region", "sum", "amount")

% Per-region statistics on multiple variables:
groupsummary(T, "region", ["mean", "median", "std"], ["amount", "units"])

The function groupsummary is the modern replacement for the older grpstats (Statistics Toolbox) and accumarray (a lower-level building block). For arbitrary per-group computation, rowfun and varfun accept a function handle and return a table of results.

Joins

Tables join on a key column:

sales = readtable("sales.csv");
customers = readtable("customers.csv");

merged = innerjoin(sales, customers, 'Keys', "customerId");
left = outerjoin(sales, customers, 'Keys', "customerId", 'Type', "left");

The functions innerjoin, outerjoin, and join mirror the SQL operations. The 'Type' parameter selects inner, left, right, or full outer.

Pivoting and stacking

stack, unstack, pivot (R2023b), groupcounts, and groupfilter perform the reshaping operations familiar from R’s tidyverse and Python’s pandas:

% Wide → long
long = stack(T, ["q1", "q2", "q3", "q4"], ...
              'NewDataVariableName', "amount", ...
              'IndexVariableName', "quarter");

% Long → wide
wide = unstack(long, "amount", "quarter");

These reshaping operations are the most-cited reasons for choosing table over a plain matrix.

Timetables

A timetable is a table with a time axis: the first column is datetime or duration values, the remaining columns are data. Time-oriented operations are available on timetables that have no table analogue:

TT = timetable([datetime(2024,1,1); datetime(2024,1,2); datetime(2024,1,3)], ...
               [10; 12; 14], ...
               'VariableNames', "temp");

retime(TT, 'hourly')                  % upsample / downsample
synchronize(TT1, TT2)                 % align two timetables on a common time axis
TT(timerange("2024-01-01", "2024-02-01"), :)

The class is the canonical container for time-series data and is the input to the Econometrics, Predictive Maintenance, and Statistics toolboxes’ time-series functions.

table vs matrix vs struct

The three principal heterogeneous containers compare:

ContainerHeterogeneous columns?Named columns?Row labels?
Numeric matrixNo (one class)NoNo
structYes (per field)YesNo
struct arrayYes (per field, shared across elements)YesNo
cellYes (per cell)NoNo
tableYes (per variable)YesYes
timetableYesYesYes (time)

The decision: a table is the right choice for record-oriented data — rows are records, columns are fields, each column may be a different class. A struct array is the right choice for a small fixed number of records with a fixed schema. A numeric matrix is the right choice when all the data is homogeneous (signals, images, dense numerical arrays).

Properties

Every table carries a Properties field with metadata: variable names, units, descriptions, row names:

T.Properties.VariableNames        % cellstr/string row
T.Properties.VariableUnits = ["", "", "yr"];
T.Properties.Description = "Notable computer scientists";
T.Properties

The metadata travels with the table through joins, filters, and most reshape operations. It is the canonical way to attach units, descriptions, and provenance to data — features that are absent from plain numeric matrices.

Conversion

Tables convert to and from struct arrays, cell arrays, and numeric matrices:

T = struct2table(structArray);
S = table2struct(T);
A = table2array(T);                    % requires homogeneous classes
T2 = array2table(A, 'VariableNames', ["a", "b", "c"]);

The conversions are useful at function boundaries with code that predates table or that operates on matrices.