cld-reducer

cld-reducer reduces compact letter displays (CLDs) while preserving the pairwise statistical relationships they encode. It comes as an R package (cldreducer, at the root of this repository), a Python package (cld-reducer, in python/), and a JavaScript and TypeScript package (cld-reducer, in js/). All three follow one specification and return the same display for the same input. When the essential decisions alone cover every required relationship, a proof gives the unique optimum. Otherwise, the HiGHS solver finds the optimum.

Its optional CLD-sigma method implements the assignment-minimum clique covering problem introduced by Ennis, Fayle, and Ennis (2012): finding a CLD that uses the fewest possible individual letter-to-group assignments. The 2012 paper solves this problem with a backtracking algorithm (FIND-AM); this repository uses a binary mixed-integer model with HiGHS and an exact essential cover certificate. It is the implementation behind the CLD letter-reduction work presented at Sensometrics 2026.

See submission status for registry availability and release instructions for the release process. The commands below install from the repository.

Why reduce CLDs?

Compact Letter Displays are useful because two products that share at least one letter are not significantly different, while products with no shared letters are significantly different.

For large sensory studies, standard maximal-clique CLD algorithms often assign more letters than are needed to preserve those relationships. A sample labeled ABC, for example, may only need AC if the removed B does not change any pairwise significance relationship.

The default CLD-C method minimizes distinct letters. The CLD-sigma option minimizes group-letter assignments. Both methods check that the display reconstructs the input relationship matrix.

Installation

# install.packages("remotes")
remotes::install_github("aigorahub/cld-reducer")   # R
pip install "git+https://github.com/aigorahub/cld-reducer.git#subdirectory=python"   # Python 3.10 or later

The #subdirectory=python part is needed because the Python package lives in python/. The JavaScript package is built from js/; see js/README.md.

The examples below work with version 0.3.3, published on PyPI and npm on 2026-10-15. Both packages default to CLD-C. Use assignment_minimum for CLD-sigma. CRAN still has version 0.2.0 under review and does not yet list the package. See submission status for the release records.

Usage in R

Give reduce_letters() the complete pairwise results: one row for each pair of groups, with a logical column that tells whether the pair differs significantly. Missing pairs are rejected, so an accidental omission is not treated as a significant difference.

library(cldreducer)

result <- reduce_letters(simple_abc_pairs, simple_abc_means)
result
#> Reduced compact letter display (letter_minimum)
#>  group letters assignments
#>      1       A           A
#>      2      AB         A B
#>      3     ABC       A B C
#>      4      BC         B C
#>      5       C           C
#> Assignments: 9 -> 9 (0.0% fewer). Letters: 3 -> 3. Relationships preserved: TRUE.

result$letters
#>    1    2    3    4    5 
#>  "A" "AB" "ABC" "BC"  "C"

The default returns three distinct letters. Use method = "assignment_minimum" to reduce group 3 from ABC to AC. The means argument is optional; the means order the groups and the letters. If you already have the matrix of non-significant pairs, use reduce_from_adjacency().

The package also contains the wheat yield example of Piepho (2004), as tabulated in Table VIII of Ennis, Fayle, and Ennis (2012): 20 treatments and 190 comparisons.

wheat <- reduce_letters(piepho2004_wheat, method = "assignment_minimum")
wheat$stats[c("assignments_before", "assignments_after", "num_letters_after")]
#> $assignments_before
#> [1] 56
#> 
#> $assignments_after
#> [1] 44
#> 
#> $num_letters_after
#> [1] 4

The 56 assignments of the maximal display reduce to 44 (21.4% fewer) with the same 4 letters, as in the paper.

Usage in Python

import pandas as pd

from cld_reducer import reduce_letters

# One row for each pair. True means the groups differ significantly.
pairs = pd.DataFrame(
    [
        ("1", "2", False),
        ("1", "3", False),
        ("1", "4", True),
        ("1", "5", True),
        ("2", "3", False),
        ("2", "4", False),
        ("2", "5", True),
        ("3", "4", False),
        ("3", "5", False),
        ("4", "5", False),
    ],
    columns=["group1", "group2", "significant"],
)
means = pd.DataFrame(
    {"group": ["1", "2", "3", "4", "5"], "mean": [3.73, 3.57, 3.46, 3.33, 3.30]}
)

result = reduce_letters(pairs, means)
print(result.letters)
# {'1': 'A', '2': 'AB', '3': 'ABC', '4': 'BC', '5': 'C'}

This example works after package installation and needs no external files. The package also has a command line tool, cld-reduce. See python/README.md.

Usage in JavaScript

import { reduceFromAdjacency } from "cld-reducer";

const result = await reduceFromAdjacency(
  [[1, 1, 0], [1, 1, 1], [0, 1, 1]],
  { groups: ["low", "mid", "high"] },
);
console.log(result.letters);
// { low: 'A', mid: 'AB', high: 'B' }

See js/README.md for the pairwise input, the options, and loading the WebAssembly solver in a browser.

Browser page

dashboard/ builds one HTML file that runs the JavaScript package in a browser. Open the file; it needs no server and no network connection, and the data does not leave the computer. The page reads pairwise significance decisions as CSV, by paste or by file. It runs no significance tests on the user's data; Generate simulates decisions with a t test. It shows the letters before reduction, and Run puts the reduced letters next to them with the time of each solve. It solves with CLD-C or CLD-sigma, or with both for a comparison. A second stage then minimizes the other quantity, and the page shows that display under the name of the method; the R package has the same two orders as letter_then_assignment and assignment_then_letter. A second view follows the design of the simulation study of the 2012 paper in the browser. The results download as CSV and JSON.

cd js && npm ci
cd ../dashboard && npm ci && npm run build
open dist/cld-reducer-dashboard.html

The page is hosted at https://cld.aigora.ai and can also be built as an offline HTML file. The js.yaml workflow keeps the built file as the cld-reducer-dashboard artifact of each run. See dashboard/README.md for the input format, the limits, and the tests.

Method

The default method is letter_minimum (CLD-C). Use assignment_minimum for CLD-sigma.

Both methods use the same pipeline.

  1. Build the non-significance graph from the pairwise results.
  2. Merge identical closed neighborhoods with their original vertex counts as weights, then find all maximal cliques. At least one assignment-minimum covering is a subcovering of the maximal covering, which is the starting point used by the 2012 paper.
  3. Build ordered coverage data. C decisions select full maximal cliques. Sigma decisions select individual memberships, with the original class sizes as costs.
  4. Find essential decisions from unique vertex or edge coverage. If those decisions cover the graph, positive costs prove that they form the unique optimum. Use it without building a BIP or calling HiGHS, then continue to step 6.
  5. Otherwise, compile the BIP once. Fix the essential decisions to 1 and let HiGHS choose the remaining decisions. Resolve equal optima with the canonical binary selection order.
  6. Expand the selected columns to the original vertices. Name the letters and rebuild the pairwise relationship matrix. Return the result only if every original relationship is preserved.

The solver time limit covers the essential scan, certificate checks, and all canonical solves. Graph preparation and BIP compilation are outside that budget. Fallback retains the time left after the scan and proof attempt.

Exact assignment minimization can become expensive for dense or highly structured graphs. All three packages offer a time limit and a cap on the number of maximal cliques (10,000 by default); the call stops with a clear error beyond the cap.

docs/algorithm.md defines the input rules, errors, canonical order, solver settings, and APIs. The shared suite in conformance/ has 2,894 reduction cases: 1,422 for CLD-sigma, 1,442 for CLD-C, and 30 weighted reduction cases. It also has 65 error cases and 18 label cases. Expected displays come from exact search in a standard-library Python script. R, Python, and JavaScript run all cases in CI. Python and JavaScript repeat with HiGHS presolve off.

Repository layout

Path Contents
DESCRIPTION, R/, man/, tests/, data/ R package cldreducer
python/ Python package cld-reducer (import name cld_reducer)
js/ npm package cld-reducer
dashboard/ Browser page for the JavaScript package (private, not in any archive)
docs/algorithm.md Specification
conformance/ Fixtures, the generator, and the R runner

Citation

If you use this software, please cite the foundational paper that introduced the assignment-minimum clique covering problem:

Ennis, J. M., Fayle, C. M., & Ennis, D. M. (2012). Assignment-Minimum Clique Coverings. ACM Journal of Experimental Algorithmics, 17, Article 1.5. https://doi.org/10.1145/2133803.2275596

You may additionally reference the talks that present this implementation:

Ennis, J. M. (2019). Computational Advances in the Production of Compact Letter Displays. Conference on Statistical Practice (CSP 2019), American Statistical Association, New Orleans, LA, February 14-16.

Ennis, J., Graham, C., Castro, L., Lampert, R., Jordan, R., & Rios de Souza, V. (2026). Too many letters? Cutting through the sensory clutter with letter reduction algorithms. Sensometrics 2026, Valencia, Spain.

The wheat example is from Piepho, H.-P. (2004). An algorithm for a letter-based representation of all-pairwise comparisons. Journal of Computational and Graphical Statistics, 13(2), 456-466. https://doi.org/10.1198/1061860043515

See CITATION.cff for machine-readable citation metadata.

License

MIT. See LICENSE.md.

Choice of objective

Both methods merge vertices with identical closed neighborhoods before solving. The sigma model weights each merged vertex by its original group count. The result restores the original groups, labels, means, and assignment counts. This is the vertex reduction from Lemma 2.5 of the 2012 paper.

CLD-sigma (assignment_minimum, alias assignment-minimum) minimizes letter-to-group assignments. The default CLD-C (letter_minimum, alias letter-minimum) minimizes distinct letters by selecting full maximal cliques. It does not minimize assignments as a second objective. Equal optima use the lexicographically greatest binary selection vector in canonical clique order. Public method metadata uses the underscore spelling.

The R package also has two staged methods. letter_then_assignment finds the fewest letters and then the fewest assignments among displays with that many letters (CLD-C, then CLD-sigma). assignment_then_letter finds the fewest assignments and then the fewest letters among those displays (CLD-sigma, then CLD-C). Each keeps its first minimum exactly; calling one method after the other would not. Python and JavaScript do not have them yet. See section 15 of docs/algorithm.md.

For CLD-sigma, objective equals the assignments after reduction. For CLD-C it equals the number of letters after reduction. Assignment counts and reduction percentage remain assignment measures in both methods. For the simple five-group example, sigma uses 8 assignments and 3 letters; C uses 9 assignments and 3 letters.

result <- reduce_letters(simple_abc_pairs, simple_abc_means, method = "letter_minimum")
result$stats$objective            # 3 letters
result$stats$assignments_after    # 9 assignments

Version 0.3.0 is the merged candidate for these changes. Version 0.3.1 adds the browser page. Version 0.3.2 adds a second stage, simulated examples, and the letters before reduction to that page. Neither changes a package algorithm or the public API. Version 0.3.4 adds the staged methods to the R package; it is not published. The current development version is 0.3.5. Version 0.3.3 is published on npm and PyPI. The page is at https://cld.aigora.ai. The frozen 0.2.0 CRAN submission remains unchanged. See development status for implementation, review, version, and timing records.