Categorical Arrays

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Written against MATLAB R2026b. categorical has been in MATLAB since R2013b and is part of base MATLAB; no toolbox is needed. It is not available in GNU Octave 11.3.0, so none of the code on this page was run here: every snippet is checked against the R2026b documentation, and a result is shown only where the documentation shows the same call (labelled as such).

A categorical array stores values from a finite set of discrete, nonnumeric categories, such as the sensor IDs of the heatflow thermocouples, the kind of a boundary condition (Dirichlet, Neumann, Periodic) or a size class. It keeps meaningful names for the values, as a string array does, while using memory efficiently, and it supports a mathematical order of the categories when one exists. It is the type that table variables holding labels should usually have (see Tables).

Creating a categorical array

C = categorical(A) creates a categorical array from a numeric, logical, string or cell-array-of-character-vectors input. The unique values of A become the categories. For string and cell inputs, leading and trailing spaces are removed, and for text the categories are listed alphabetically when they have no mathematical order.

C = categorical(A)
C = categorical(A,valueset)
C = categorical(A,valueset,catnames)
C = categorical(___,Ordinal=true)
C = categorical(___,Protected=true)
  • valueset lists the values that become categories, in the order you want them. Values of A that are not in valueset become undefined, and categories in valueset that do not occur in A still exist (with zero elements).

  • catnames gives the category names to use for the values of valueset, one per value, which is also how several values can be given readable names: categorical([1 2 3 2],[1 2 3],["low" "mid" "high"]).

  • Ordinal=true makes the categories ordered; Protected=true protects them against being extended by assignment (see below). The Name=Value form needs R2021a or later; earlier releases write 'Ordinal',true.

% Not run here (no categorical in Octave).
ids = ["S2" "S1" " S3" "S1" "S2 " "S4" "S3"];   % leading/trailing spaces
c = categorical(ids)          % 1x7 categorical: S2 S1 S3 S1 S2 S4 S3
categories(c)                 % 4x1 cell: 'S1' 'S2' 'S3' 'S4' (alphabetical)
numel(categories(c))          % 4
class(c)                      % 'categorical'

For the documentation’s own example, a string array of New England state abbreviations with stray spaces is converted into a categorical array with six categories, CT, MA, ME, NH, RI and VT, in alphabetical order, with the spaces removed. The categories function returns the names as a column cell array of character vectors.

The functions iscategorical(x), isordinal(x), iscategory(C,names) and isprotected© test the type, whether the order is meaningful, whether text is the name of a category, and whether the categories are protected. string©, cellstr© and char© convert back to text.

Elements, assignment and the undefined value

Reading and indexing work like any other array: c(2), c(end), c([true false true ...]). Assigning one element can use text that names a category; assigning several elements requires a categorical array on the right-hand side (or, for the same text repeated, a scalar).

% Not run here.
c(8)   = "S2";                         % one element, by category name
c(1:3) = categorical(["S4" "S4" "S1"]);% several elements need a categorical
c(2)   = missing;                      % undefined
c(3)   = "";                           % empty text also becomes undefined
isundefined(c)                         % logical array, true where undefined

An undefined element belongs to no category, like NaN in a numeric array, and displays as <undefined>. Missing strings and empty strings become undefined when a string array is converted, and NaN values become undefined when a numeric array is converted. Assigning missing, "", '' or NaN to an element has the same effect. Use isundefined© or ismissing© to find them; see Missing Data for filling and removing them.

Assigning text that is not a category name to an element depends on protection. In a categorical array that is not protected (the default for a non-ordinal array), the text is added as a new category, appended at the end of the category list. In a protected or ordinal array the assignment is an error (not run here). Add the category first with addcats, or start from categorical(A,valueset) with every category you expect, when the category set should be explicit.

Preallocation follows from this. To make an empty categorical array of a given size, convert an array of NaN, then add the categories:

% Not run here; the documentation shows this sequence.
A = categorical(NaN(2,4));            % all undefined, no categories yet
A = addcats(A,["small" "medium" "large"]);
A(1) = "medium"; A(8) = "small"; A(3:5) = "large"

Working with the categories

The categories are part of the array, not just labels derived from its values, so they can be changed independently of the elements:

Function Effect

categories©

The category names as a column cell array

addcats(C,new)

Adds categories (appended at the end of the order unless Before= or After= is given); no element changes

removecats©, removecats(C,old)

Without old, removes every unused category. With old, removes those categories and makes the elements that belonged to them undefined

mergecats(C,oldcats), mergecats(C,oldcats,newcat)

Merges the listed categories into one, named after oldcats(1) by default, or newcat

renamecats(C,old,new), renamecats(C,newnames)

Renames categories without changing the elements

reordercats(C,neworder)

Changes the order of the categories. For an ordinal array this changes what < means

setcats(C,newcats)

Sets the categories to exactly the listed ones, adding, removing and reordering as needed

% Not run here.
c = categorical(["S1" "S2" "S3" "S1" "S2" "S4"]);
c = addcats(c,"S5");                       % a channel with no readings yet
countcats(c)                               % 2 2 1 1 0
c = removecats(c);                         % drops the unused S5
c = mergecats(c,["S3" "S4"],"far");        % categories S1 S2 far
c = renamecats(c,"far","FAR");             % rename without touching the elements

countcats returns the number of elements in each category, in the same order as categories: for a vector it is one count per category, for a matrix it works down each column, and countcats(A,2) counts along rows. summary© prints the category names with their counts and the number of undefined elements, and for an ordinal array also the minimum, median and maximum.

Ordinal categorical arrays

By default the categories have no order, and MATLAB lists them alphabetically. In an ordinal categorical array the order of the categories defines a mathematical order, the first category listed is the smallest and the last the largest, and the relational operators <, >, <=, >= between elements work. In a categorical array that is not ordinal, only == and ~= are valid; comparing with < is an error.

% Not run here; the call is from the documentation.
AllSizes = ["medium" "large" "small" "small" "medium" "large" "medium" "small"];
valueset = ["small" "medium" "large"];
sizeOrd = categorical(AllSizes,valueset,Ordinal=true);
categories(sizeOrd)            % 'small' 'medium' 'large', in that order
sizeOrd >= "medium"            % logical: medium and large
max(sizeOrd)                   % large

With Ordinal=true, the text on the right of a comparison is read as a category name, so sizeOrd >= "medium" needs no conversion. An ordinal array also supports min, max, sort (by the category order, not alphabetically) and median. The documentation’s second ordinal example bins continuous values with discretize:

% Not run here; from the documentation.
x = rand(100,1)*50;
catnames = ["small" "medium" "large"];
binned = discretize(x,[0 15 35 50],"categorical",catnames);
summary(binned)

Each bin includes its left edge but not its right edge, except the last bin, so the first bin is 0 to 15 (including 0) and the last includes 50. binned is an ordinal categorical array with small < medium < large, and summary shows the count per bin. Convert an existing non-ordinal array with categorical(C,categories©,Ordinal=true) (after reordercats has put the categories in the intended order).

Comparison, membership and text functions

For a categorical array, == and ~= compare with text directly and need no strcmp. Two categorical arrays can be compared if they have the same category set (for an ordinal array, also the same order). The text functions matches, startsWith, endsWith and contains accept categorical arrays as input since R2026b and test the category names; before that release, convert with string© first. ismember(C,["S1" "S2"]) tests membership.

% Not run here.
c = categorical(["S1" "S2" "S3" "S1" "S2" "S4"]);
c == "S1"                         % 1 0 0 1 0 0
sel = ismember(c,["S1" "S2"]);    % logical mask of two channels
idx = startsWith(c,"S");          % names that start with S (R2026b)
n = nnz(c == "S3")                % number of S3 readings

For a table, T(T.SensorID == "S2",:) selects the rows of one sensor, and groupsummary(T,"SensorID","mean","Temperature") summarises a variable per category (see Tables).

Protected categoricals and other limits

Assigning text that is not a category to a non-protected categorical array adds it as a new category. A protected categorical array (Protected=true, or any ordinal array) cannot have new categories added that way: the assignment is an error instead of an automatic extension. Ordinal arrays are always protected. isprotected© tests it. Use protection for a set that should never change by accident, such as the allowed boundary kinds.

The documentation has a topic on combining categorical arrays: concatenating arrays with different categories is allowed for non-ordinal, non-protected arrays, while ordinal arrays must have the same ordered categories (not run here). combinations(A,B) (R2023a) produces every pair of the elements of two arrays, which is the standard way to enumerate the categories of two variables.

Why use categorical arrays for labels

The documentation lists three advantages: a natural representation of data, the mathematical ordering of text, and efficient memory use. In practice for the heatflow sensor log:

  • Fixed vocabulary. A string column holds a typo ("S1 ", "s1") without complaint. A categorical column at least makes it visible, because a typo becomes a new category that categories and countcats list, and a protected categorical column (Protected=true) rejects any assignment outside the set unless the array is extended deliberately with addcats.

  • Memory. The documentation describes categorical arrays as using memory efficiently: the category names are kept once rather than once per element, so a long log with a handful of sensor IDs takes much less memory than the same log as strings (measure with whos on your own data).

  • Speed and clarity of grouping. countcats, groupsummary and plots such as bar charts of counts use the category set, including empty categories, so a sensor with no readings still appears with a count of zero.

  • Order. An ordinal category (low < medium < high) makes sort, max and >= meaningful where a string array has only the alphabetical order.

When reading a CSV into a table, convert the label columns once, with T.SensorID = categorical(T.SensorID) or through setvartype(opts,"SensorID","categorical") on the import options, and keep numbers as numbers; do not convert a variable to categorical just because it has few distinct values if you later compute with it.

Pitfalls

  • Assigning a new name to an element of a protected or ordinal array is an error; add the category first with addcats.

  • Unused categories stay until removecats; they appear in categories, countcats and summary with a count of zero.

  • < on a non-ordinal array is an error; use ==, or make the array ordinal with an explicit order.

  • Alphabetical default order: categorical(["small" "medium" "large"]) lists large, medium, small, not the size order. Pass valueset.

  • Undefined is not a category: c == "<undefined>" does not find it; use isundefined.

  • Renaming versus merging: renamecats changes names and keeps the categories apart; use mergecats when you mean to combine categories.

Where older books differ

Books written for MATLAB before R2013b (such as the MATLAB Primer edition written for R2010a) have no categorical type and store labels as cell arrays of character vectors or as integer codes plus a lookup cell array, grouped with grp2idx/unique. That pattern still works, but categorical replaces it, and the statistics-toolbox nominal/ordinal classes that served this purpose before R2013b were replaced by categorical in base MATLAB. The Name=Value syntax and the string-array inputs are newer than the books (R2021a and R2016b).