Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Similarity metrics

The score of a pair compares the two Cell Ontology terms that the labels matched, not the labels themselves. Two labels that matched the same term score 1 under every metric. For different terms, metric decides how they are compared: by the embeddings of their descriptions, by their places in the ontology’s hierarchy, or by both.

df = await cyto.compare(
    author_labels=author_labels,
    algorithms=algorithms,
    run_id="lung_v1_path",
    metric="path",
)

In the hosted API, set the request fields metric and metricParams.

Metrics

metricUsesScore
cosine_kernel (default)EmbeddingsA Gaussian of the cosine similarity of the two terms’ description embeddings, which is 1 at a cosine of 1
cosine_directEmbeddingsThe cosine similarity of the two terms’ description embeddings
pathHierarchy1 over the mean depth of the two terms below their deepest common ancestor. Sibling terms score 1, and a term and its child score less
set:jaccardHierarchyThe Jaccard index of the two sets of ancestors, each including its term
set:cosineHierarchyThe shared ancestors over the square root of the product of the two set sizes, with the terms included
set:weighted_jaccardHierarchyThe Jaccard index of the ancestor sets, with each ancestor weighted by its depth
weighted:num_ancestorsHierarchyThe Jaccard index of the ancestors, without the terms, with each ancestor weighted by 1 over its own number of ancestors plus 1
weighted:specificityHierarchyThe Jaccard index of the ancestors, with each ancestor weighted by its depth relative to the term’s depth
weighted:embedding_cosineBothThe Jaccard index of the ancestors, with each ancestor weighted by the cosine similarity of its embedding with the term’s
simpleNeitherThe character similarity of the two CL IDs, such as CL:0000084 and CL:0000236. It says nothing about the cell types and is only a fallback

The embedding metrics compare the terms’ descriptions, as written by the LLM of the model pair. The hierarchy metrics use cl.owl and depend only on the ontology. Depth is the longest chain of is_a parents from a term up to cell (CL:0000000). The cosine_kernel and cosine_direct scores depend on the model pair, so compare them only between runs with the same models.

Reference scores

These scores of some term pairs come from the precomputed embeddings of the default model pair, Kimi K2.6 with Qwen3-Embedding-8B, and from cl.owl:

Termscosine_kernelcosine_directweighted:embedding_cosine
CD4-positive, alpha-beta T cell vs CD8-positive, alpha-beta T cell0.770.820.97
T cell vs regulatory T cell0.640.760.66
monocyte vs macrophage0.600.750.77
macrophage vs alveolar macrophage0.460.690.69
T cell vs B cell0.360.640.80
T cell vs neuron0.150.510.12
hepatocyte vs T cell0.130.490.14
Termspathset:jaccardset:cosineset:weighted_jaccardweighted:num_ancestorsweighted:specificity
CD4-positive, alpha-beta T cell vs CD8-positive, alpha-beta T cell1.000.870.930.711.001.00
T cell vs regulatory T cell0.500.830.910.610.950.47
monocyte vs macrophage1.000.740.850.620.850.81
macrophage vs alveolar macrophage0.500.840.920.640.970.50
T cell vs B cell0.670.750.860.480.970.65
T cell vs neuron0.330.130.240.030.290.04
hepatocyte vs T cell0.330.140.260.040.310.04

The metrics answer different questions:

Tune cosine_kernel

cosine_kernel maps the cosine similarity c of the two terms’ embeddings to a score with a Gaussian:

score = amplitude * exp(-((c - center) ** 2) / (2 * width**2))

center is 1.0 by default, width 0.25 and amplitude 1.0. Pass them in metric_params; other metrics ignore it:

df = await cyto.compare(
    author_labels=author_labels,
    algorithms=algorithms,
    run_id="lung_v1_wide",
    metric="cosine_kernel",
    metric_params={"width": 0.5},
)

The width sets how fast the score falls as the terms’ cosine similarity drops:

Cosine similaritywidth=0.1width=0.25 (default)width=0.5
0.950.8820.9800.995
0.900.6070.9230.980
0.800.1350.7260.923
0.700.0110.4870.835
0.600.0000.2780.726
0.500.0000.1350.607

A narrow width rewards only near-identical terms; a wide one gives partial credit to more distant terms. Keep center and amplitude at 1: identical terms always score 1, whatever the amplitude.

The match threshold

min_match_similarity is the lowest cosine similarity between the embedding of a label’s description and the embedding of its closest term at which the label counts as matched. It is 0.1 by default, which is low enough for nearly every label to match some term. Raise it to leave weak matches unmatched. A pair of single-part labels with an unmatched label scores 0, and its similarity_method is partial_match or no_matches. In a compound pair, an unmatched part scores 0 against every part of the other label.

df = await cyto.compare(
    author_labels=author_labels,
    algorithms=algorithms,
    run_id="lung_v1_strict",
    min_match_similarity=0.5,
)

To choose a threshold, look at the author_embedding_similarity and algorithm_embedding_similarity columns of a run, which hold the similarity of each label with its closest term, whether or not it was matched.

Unknown metrics and missing terms

A metric name that CyteOnto does not know falls back to simple, and the error is logged for each pair. The hierarchy metrics also fall back to simple for a term that is not in cl.owl. If the scores look wrong, check the log for these messages.