---
title: 'Content Inventory and Grouping'
source: 'https://academia.sh/en/courses/user-experience/content-inventory-and-grouping'
course: 'User Experience and Behavior Design'
language: en
updated: '2026-08-19T05:19:58+00:00'
license: 'CC BY-SA 4.0'
---

# Content Inventory and Grouping

Deriving structure from content; the inventory's columns, building a similarity matrix from card-sorting data, threshold-based grouping, and detecting cards without consensus.

The problem is defined and the solution space is clear. The first question is not the
screens themselves but the structure between them. The catalog interface carries thousands
of records, dozens of subjects, and a set of operations; which headings these fall under is
not a matter of style. A wrong grouping leads the user to search for what they want in the
wrong place, a loss no search box can rescue.

**Information architecture** is the grouping and naming of content and the establishment
of the relationships between its pieces. This lesson shows where the structure is derived
from: first an inventory that counts what exists, then a study that measures how users
group these items.

## What the Inventory Counts

**Content inventory** is the list of every item the interface carries. Grouping is
impossible without the list; what is being grouped is unknown otherwise. The inventory has
five columns.

- **Item.** The name of the content. Not the screen name, but the content itself:
  "borrowing terms" is an item, "help page" is a container.
- **Type.** Is it a record, an explanatory text, an operation, or an external link. Type
  determines not how the item is displayed, but how it can be grouped.
- **Task link.** Which task the item serves. It connects to the task inventory built in
  the third lesson. An item that cannot be linked to any task stays in the inventory as a
  question mark.
- **Owner.** Who updates the content. Content without an owner grows stale, and stale
  content is wrong information.
- **Last updated.** How long the item has gone unchanged.

The last two columns do not directly affect the grouping decision, but they are the
inventory's most useful part: content that should be deleted before the structure is built
shows up here. In the catalog interface, an item that cannot be linked to any task and has
not been updated in two years is not an item to group but one to remove.

## User Grouping Is Measured

Having the design team do the grouping results in the organization copying its own
structure onto the interface. In the library's internal organization, the "circulation
unit" and the "reference unit" may be separate; the user does not know this distinction and
does not search for the "interlibrary loan" item by which unit runs it.

**Card sorting** is giving participants the inventory's items as cards and asking them to
group them their own way. If the participant assigns the group names, it is open sorting;
if the names are given in advance, it is closed sorting. Open sorting measures structure
and naming together; closed sorting serves to test an existing structure.

The result is a stack of patterns and cannot be read one by one. Ten participants' ten
separate sortings are reduced to how often the cards were grouped together.

```js
// card-sorting.mjs — similarity matrix and grouping from card-sorting data

// The 15 content items in the inventory and each item's expected cluster (a hypothesis to verify)
const CARDS = [
  ["search tips", "discover"], ["subject headings", "discover"], ["new arrivals", "discover"],
  ["databases", "discover"], ["borrowing terms", "borrow"], ["late fees", "borrow"],
  ["renewal", "borrow"], ["reservation", "borrow"], ["interlibrary loan", "borrow"],
  ["shelf plan", "on-site"], ["study rooms", "on-site"], ["opening hours", "on-site"],
  ["membership signup", "account"], ["password reset", "account"], ["contact", "unclear"],
];
const CATEGORIES = ["discover", "borrow", "on-site", "account"];

// Deterministic pseudo-random generator (mulberry32)
function makeRng(seed) {
  let a = seed >>> 0;
  return () => {
    a = (a + 0x6d2b79f5) >>> 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
const rng = makeRng(20260101);

// How the 10 participants group the cards: 80% match to the expected category, the "unclear" card is free
const PARTICIPANTS = 10;
const sorts = [];
for (let k = 0; k < PARTICIPANTS; k++) {
  sorts.push(
    CARDS.map(([, expected]) =>
      expected === "unclear" || rng() > 0.8
        ? CATEGORIES[Math.floor(rng() * CATEGORIES.length)]
        : expected
    )
  );
}

// Similarity matrix: how many participants put i and j in the same group
const n = CARDS.length;
const M = Array.from({ length: n }, () => new Array(n).fill(0));
for (const s of sorts)
  for (let i = 0; i < n; i++)
    for (let j = 0; j < n; j++) if (i !== j && s[i] === s[j]) M[i][j]++;

console.log("similarity matrix (how many of the 10 participants grouped them together)");
console.log(" ".repeat(24) + CARDS.map((_, i) => String(i + 1).padStart(3)).join(""));
CARDS.forEach(([name], i) => {
  console.log(
    `${String(i + 1).padStart(2)} ${name.padEnd(21)}` +
      M[i].map((v, j) => (i === j ? "  ." : String(v).padStart(3))).join("")
  );
});

// Distance = 1 - similarity; merge with average linkage, stop at threshold 0.5
const dist = (i, j) => 1 - M[i][j] / PARTICIPANTS;
let groups = CARDS.map((_, i) => [i]);
const linkageDistance = (a, b) => {
  let t = 0;
  for (const i of a) for (const j of b) t += dist(i, j);
  return t / (a.length * b.length);
};
const THRESHOLD = 0.5;
while (groups.length > 1) {
  let best = Infinity, pair = null;
  for (let i = 0; i < groups.length; i++)
    for (let j = i + 1; j < groups.length; j++) {
      const d = linkageDistance(groups[i], groups[j]);
      if (d < best) { best = d; pair = [i, j]; }
    }
  if (best > THRESHOLD) break;
  const [i, j] = pair;
  groups = groups.filter((_, x) => x !== i && x !== j).concat([groups[i].concat(groups[j])]);
}
groups = groups.map((g) => g.sort((a, b) => a - b)).sort((a, b) => a[0] - b[0]);

console.log(`\ngroups formed at threshold ${THRESHOLD} (${groups.length} groups)`);
for (const g of groups) console.log(`  ${g.map((i) => CARDS[i][0]).join(", ")}`);

// Each card's affinity gap between its own group and the nearest second group
console.log("\ncard                 own group  nearest other  gap");
const avgSimilarity = (i, g) => {
  const d = g.filter((x) => x !== i);
  return d.length ? d.reduce((t, j) => t + M[i][j], 0) / d.length / PARTICIPANTS : 0;
};
for (let i = 0; i < n; i++) {
  const own = groups.find((g) => g.includes(i));
  const a = avgSimilarity(i, own);
  const others = groups.filter((g) => g !== own).map((g) => avgSimilarity(i, g));
  const b = others.length ? Math.max(...others) : 0;
  console.log(
    `${CARDS[i][0].padEnd(20)} ${a.toFixed(2).padStart(9)}  ${b.toFixed(2).padStart(13)}  ${(a - b).toFixed(2)}${a - b < 0.2 ? "  UNCLEAR" : ""}`
  );
}
```

```
similarity matrix (how many of the 10 participants grouped them together)
                          1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
 1 search tips            .  9  8 10  1  1  0  0  1  0  1  1  2  0  2
 2 subject headings       9  .  7  9  1  1  0  0  1  0  2  2  2  0  2
 3 new arrivals           8  7  .  8  0  2  1  1  2  0  0  1  2  1  3
 4 databases             10  9  8  .  1  1  0  0  1  0  1  1  2  0  2
 5 borrowing terms        1  1  0  1  .  8  8  9  7  0  1  0  1  0  2
 6 late fees              1  1  2  1  8  .  8  9  7  0  0  1  0  0  3
 7 renewal                0  0  1  0  8  8  .  9  7  0  1  0  1  1  3
 8 reservation            0  0  1  0  9  9  9  .  8  0  1  0  0  0  3
 9 interlibrary loan      1  1  2  1  7  7  7  8  .  0  1  0  1  1  3
10 shelf plan             0  0  0  0  0  0  0  0  0  .  6  8  2  1  5
11 study rooms            1  2  0  1  1  0  1  1  1  6  .  7  0  1  3
12 opening hours          1  2  1  1  0  1  0  0  0  8  7  .  1  0  3
13 membership signup      2  2  2  2  1  0  1  0  1  2  0  1  .  7  2
14 password reset         0  0  1  0  0  0  1  0  1  1  1  0  7  .  1
15 contact                2  2  3  2  2  3  3  3  3  5  3  3  2  1  .

groups formed at threshold 0.5 (5 groups)
  search tips, subject headings, new arrivals, databases
  borrowing terms, late fees, renewal, reservation, interlibrary loan
  shelf plan, study rooms, opening hours
  membership signup, password reset
  contact

card                 own group  nearest other  gap
search tips               0.90           0.20  0.70
subject headings          0.83           0.20  0.63
new arrivals              0.77           0.30  0.47
databases                 0.90           0.20  0.70
borrowing terms           0.80           0.20  0.60
late fees                 0.80           0.30  0.50
renewal                   0.80           0.30  0.50
reservation               0.88           0.30  0.57
interlibrary loan         0.72           0.30  0.42
shelf plan                0.70           0.50  0.20  UNCLEAR
study rooms               0.65           0.30  0.35
opening hours             0.75           0.30  0.45
membership signup         0.70           0.20  0.50
password reset            0.70           0.10  0.60
contact                   0.00           0.37  -0.37  UNCLEAR
```

## Reading the Matrix

The matrix shows a structure that cannot be extracted from the individual sortings. The
first and fourth cards — search tips and databases — were placed in the same group by ten
of the ten participants. The fifth and eighth cards were together for nine participants.
These values are a measure of agreement: a high count shows that users regard the two
items as part of the same task.

The threshold was set at 0.5; that is, two items enter the same group if they were grouped
together by at least half the participants. The threshold is not a law of nature but an
auditable decision. Raising it produces more, smaller groups; lowering it merges groups.
What matters is that the threshold is written down: the sentence "these should stay
together" is debatable, while "co-grouping rate 0.7, threshold 0.5" is auditable.

Five groups emerged, and four of them are internally consistent. The fifth "group" carries
a single card.

## The Card Without Consensus Is the Structure's Most Important Finding

The affinity gap in the last table is the difference between each card's average affinity
to its own group and its affinity to the nearest second group. A large gap means the card
has found its place; a small gap means the participants did not reach consensus.

The **contact** card did not stay inside any group. Its highest affinity is 0.37, to the
on-site group; it does not clear any threshold. This is not a data flaw but a finding:
users do not agree on where to look for contact information. Three options exist for such
a card, and the choice is made from the data.

- **It is shown in multiple places.** If the information is cheap and short, repeating it
  is harmless; contact information can appear in both the on-site group and the account
  group.
- **It is taken out of the structure.** Some items belong not to groups but to an area
  reachable from every screen. The footer exists for this.
- **It is split.** "Contact" may not be a single item: asking a question about a shelf and
  reporting a membership problem are separate tasks and go to separate groups. The lack of
  consensus can be a sign that the item itself splits in two.

The **shelf plan** card was also flagged: its affinity to its own group is 0.70, but its
co-grouping rate with the contact card is 0.50. Half the participants treat asking where a
shelf is and looking at the shelf plan as the same task. This is the information-architecture
side trace of the "book cannot be found on the shelf" problem measured in the fourth lesson:
the user does not know where to look for shelf information.

## Naming the Groups

The groups now exist but have no names. In the open form of card sorting, the name is
derived from the names the participants gave themselves; among the twelve phrases naming
the same group, the most frequently used word is chosen. The organization's internal
vocabulary must not intrude here: if users do not say "circulation services," the heading
does not become "circulation services."

Two criteria apply to naming. The name must cover the group's **entirety**; the heading
"renewal" names only one of the five items in the borrowing group. The name must be
**distinct** from a neighboring group; when the headings "library information" and
"general information" stand side by side, the user cannot choose which to enter and,
unable to choose, tries both.

## The Ethical Framework of Card Sorting

Card sorting is a research session, and the rules from the previous lesson apply here as
well: the participant gives consent knowing what the sorting is for, the session record is
kept under a code name, and the raw recording is deleted within the retention limit after
analysis. No single participant's sorting appears in the matrix above; only the
co-grouping counts do.

Two more rules apply to the inventory itself. **Content carrying personal data is flagged
in the inventory**: borrowing history, membership information, and search history are tied
to a person, and their grouping decision is made together with the access decision.
**Every personal-data item in the inventory has its retention period written down**; an
item without a written period is being stored for an unspecified duration before it is
even placed anywhere in the interface.

## Summary

- The content inventory carries item, type, task-link, owner, and last-updated columns; an
  item that cannot be linked to any task and is not updated is one to remove, not to group.
- Card sorting ensures that structure is derived from the user's mental grouping rather
  than the organization's internal structure; the open form measures structure and naming
  together.
- Individual sortings cannot be read; the data is reduced to a similarity matrix giving the
  co-grouping count for each pair of cards and grouped by a written threshold.
- A card with a low affinity gap is a finding, not a flaw; in the sample data, one card
  could not be attached to any group, and the solution is repetition, moving it outside the
  structure, or splitting the item.
- Group names are chosen from the participants' own words; the name must cover the group's
  entirety and be distinct from a neighboring group.
- Content carrying personal data is flagged in the inventory and written down together with
  its retention period.

## Next Step

Five groups and group names have been obtained, but this is not yet a navigation. How many
of the groups are shown at once, how many levels the sub-breakdowns descend, and how many
clicks a user needs to reach an item are separate decisions. The next lesson compares
hierarchical, flat, and matrix navigation models, computes the trade-off between menu width
and depth, and finds the width that minimizes total scanning cost.
