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.

Mapping cells and transferring labels

Mapping is the fixed-reference alternative to merging datasets and rebuilding a joint graph. This allows you to keep one reference atlas unchanged, place new query cells onto it, and transfer labels from reference neighbours. The prepared mapping reference is also the reusable atlas: later queries use the same feature panel, projection model, reference coordinates, and neighbour index.

It does three things in order:

  1. Align the query features to the reference feature panel.

  2. Project each query cell into the reference PCA space and find nearest reference neighbours.

  3. Use those neighbours to transfer labels and score how much of the query landed on each reference cell.

It does not merge count matrices, retrain the reference graph, or move reference cells. When sources must be analysed together in one store, start with Integrating datasets by merging and Correcting batch effects with Harmony instead.

In this tutorial, we will be mapping interferon-stimulated PBMCs onto a control PBMC reference from the same Kang study. The shared author labels let us evaluate the result.

Mapping currently supports RNA queries. Keep the reference and query in separate stores; the query store must be writable so CyteArc can save the mapping and transferred labels.

Open the reference and query

DataStore has 8486 (8487) cells with 1 assays: RNA Cell metadata: 'I', 'ids', 'names', 'RNA_UMAP1', 'RNA_UMAP2', 'RNA_clusters', 'RNA_leiden_cluster', 'RNA_nCounts', 'RNA_nFeatures', 'RNA_percentMito', 'RNA_percentRibo', 'cluster_labels' RNA assay has 35635 features and following metadata: 'I', 'ids', 'names', 'dropOuts', 'nCells'

Started: 2026-10-11T18:54:50.661170Z | Wall time: 11.206 s

DataStore has 10111 (10111) cells with 1 assays: RNA Cell metadata: 'I', 'ids', 'names', 'RNA_UMAP1', 'RNA_UMAP2', 'RNA_clusters', 'RNA_leiden_cluster', 'RNA_nCounts', 'RNA_nFeatures', 'RNA_percentMito', 'RNA_percentRibo', 'cluster_labels' RNA assay has 35635 features and following metadata: 'I', 'ids', 'names', 'dropOuts', 'nCells'

Started: 2026-10-11T18:55:01.870606Z | Wall time: 4.275 s

First, look at the author labels in both datasets.

Control PBMC reference UMAP coloured and labelled by the published cell types, including B cells, T cells, NK cells, monocytes, and dendritic cells.
Interferon-stimulated PBMC query UMAP coloured and labelled by the published cell types. Its independent layout places shared cell types in different positions from the control reference.

Started: 2026-10-11T18:55:06.148759Z | Wall time: 2.381 s

These UMAP layouts were fitted independently, so their coordinates are not comparable. Mapping keeps the control layout fixed and reports where query weight lands on reference cells. The shared label vocabulary is what we use for evaluation.

Prepare a labelled, reusable reference

The control store already contains an analysis named docs_default. Use its neighbours to prepare a reference that later queries can share.

ArtifactRef(assay='RNA', kind='mapping_reference', artifact_id='d9883f6de888...')

Started: 2026-10-11T18:55:08.532788Z | Wall time: 0.991 s

The completed MappingReference is immutable. Its feature_selection field pins the exact reference feature artifact rather than a metadata key. Its feature order, scaling, PCA loadings, neighbour index, and selected cells stay fixed in the reference datastore. reference_layout is the immutable UMAP from the same run and is used only to show where query weight landed. The mapping reference does not contain that layout or the reference labels. Label transfer freezes the labels it uses when it runs (“Transfer labels and inspect evidence” section).

Map the query

mapping.run runs on the writable query datastore. It aligns query features to the reference panel, applies the reference normalization and scaling, projects into the reference PCA space, and stores the nearest neighbours. Query cells are never inserted into the reference index.

We map the cells of the query’s own pipeline run, so that the results can be drawn on that run’s UMAP later. The default mapping keeps the three nearest reference neighbours for each query cell.

ArtifactRef(assay='RNA', kind='projection', artifact_id='ddbb349a032f...')

Started: 2026-10-11T18:55:09.527459Z | Wall time: 2.835 s

Reload the saved mapping to inspect its diagnostics:

{'featureCoverage': 1.0, 'queryBatchCount': 1, 'algorithmVariant': 'scaled_pca', 'uninformativeCellCount': 0, 'queryScaledDispersion': 1.329895474385472}

Started: 2026-10-11T18:55:12.366542Z | Wall time: 0.135 s

By default, an absent query feature is filled with the reference mean, which becomes zero after reference scaling. Check featureCoverage in the diagnostics before interpreting transferred labels.

Cells with no counts in the measured reference features are flagged as uninformative. They cannot provide evidence for a transferred label.

Where did the query land?

A mapping score tells you which reference cells received neighbour weight from the query. This can be plotted on the reference UMAP. Since one panel for the whole query becomes hard to read due to the weight being spread across many cells, we can split by a few known query populations to see whether each population lands on the matching reference region. The author labels of the mapped cells are read in the order of the run’s cells.

cells
query population
other3462
CD 14 Mono2532
CD4 naive T2390
CD4 Memory T1297
NK430

Started: 2026-10-11T18:55:12.504928Z | Wall time: 0.154 s

Show where each query population contributes weight on the reference map:

Five reference UMAP panels show mapping weights from other cells, CD4 memory T cells, CD4 naive T cells, CD14 monocytes, and NK cells in the query. Point size and colour encode received weight.

Started: 2026-10-11T18:55:12.662572Z | Wall time: 1.097 s

Each panel here shows grey points which received no weight from that query group. Coloured points are the reference cells that are neighbours to the cells in the query group; point size scales with score so sparse hits stay visible. A useful mapping of the query group will light up the matching reference population. Alternatively, concentration in an unrelated pocket suggests a domain shift or a feature-alignment problem.

Transfer labels and inspect evidence

Each reference neighbour votes for its annotation with a weight derived from its distance to the query cell. Label transfer sums these weights for each label. By default, a unique winning label needs at least half of the total neighbour weight. Neighbours without labels contribute to that total but cannot support a label. Otherwise the cell abstains and gets no label. A high vote fraction only means the neighbours agreed. It is not a calibrated probability that the label is biologically correct.

mapping.transfer_labels saves the result as an immutable label_transfer artifact in the query datastore and returns its reference. It first freezes the reference labels it reads into the query datastore, so later edits to the reference annotations cannot change this result.

label labelled 9475 abstained 636 Name: query cells, dtype: int64

Started: 2026-10-11T18:55:13.763690Z | Wall time: 0.605 s

An abstained cell has a missing label, and the abstentionReason column of transfer.evidence says why:

  • below_threshold: the winning vote fraction is below threshold_fraction

  • tied_vote: two labels received the same neighbour weight

  • uninformative_cell: the cell has no counts in any reference feature that the query measured

  • no_labeled_neighbors: no neighbour has a usable reference label

  • beyond_max_distance: the nearest reference neighbour is farther than max_distance, when one is set

Uninformative cells also add nothing to mapping scores.

abstentionReason below_threshold 636 Name: count, dtype: int64

Started: 2026-10-11T18:55:14.372104Z | Wall time: 0.009 s

Plot the transferred labels on the query UMAP. A label artifact colours an embedding directly, so nothing is written into the query cell metadata. Abstained cells are drawn as missing, which shows the geography of abstention.

Published and transferred labels on the stimulated-query UMAP. Many cells published as CD14 monocytes receive the CD16 monocyte label after transfer.

Started: 2026-10-11T18:55:14.383761Z | Wall time: 1.135 s

mapping_evidence plots the evidence saved with the transfer:

  • voteFraction: how much neighbour weight supports the winning label

  • topTwoMargin: how far the winner sits above the runner-up

  • referenceDistancePercentile: how unusual the query cell is relative to reference neighbour distances

voteFraction is compared with threshold_fraction when labels are assigned. The margin and distance percentile are diagnostics; they do not add abstention rules. Drawing this plot does not change the saved labels.

To also abstain by nearest-neighbour distance, pass max_distance to mapping.transfer_labels. That saves a second transfer and leaves this one unchanged.

Box plots by published query cell type show winning vote fraction, margin between the top two labels, and reference-distance percentile. High vote support can coexist with unusual reference distances.

Started: 2026-10-11T18:55:15.521597Z | Wall time: 0.752 s

Because this query dataset also carries original author labels, we can compare these known labels with the transferred labels.

Row-normalized confusion matrix compares published query labels with transferred labels and abstentions. Large off-diagonal fractions appear for CD14 monocytes and dendritic cells transferred as CD16 monocytes.

Started: 2026-10-11T18:55:16.276612Z | Wall time: 0.871 s

Cells where the known and predicted labels match show recall within each known query label. Blocks between different labels show systematic swaps. The Abstained column holds the cells that did not receive a transferred label. Pay particular attention to the monocyte rows. Stimulation can change expression enough that a query population maps to another reference label. Inspect such swaps before accepting the labels.

Because known labels are available, mapping_calibration shows how label accuracy trades off against retained coverage as the vote threshold rises. It applies each threshold to the candidate labels saved with the transfer, so nothing is recomputed. Coverage is the share of cells with a known label that a threshold keeps. Cells that abstained without evidence stay in that share and are never kept, so the curve counts the same cells as the confusion matrix. The red marker is the transfer’s own threshold_fraction. Higher thresholds keep fewer cells. Use this curve to check whether the retained labels are also more accurate in this dataset.

Curve of agreement with known labels versus retained query coverage across vote thresholds. Agreement remains near 55 percent; the red point marks the saved threshold of 0.5.

Started: 2026-10-11T18:55:17.151543Z | Wall time: 0.293 s

Figure 1:Across the plotted vote thresholds, agreement with the published query labels stays near 55% while retained coverage changes. Raising the threshold alone does not resolve the label disagreements in this example.

Choose stricter settings when needed

The first pass used the defaults. If too many uncertain labels remain, raise threshold_fraction in mapping.transfer_labels. For example, threshold_fraction=0.6 requires 60% of the neighbour weight to support the winning label. It saves a separate transfer, so you can compare it with the first one. Use the confusion matrix and calibration curve to judge the tradeoff between coverage and agreement with known labels.

You can also change save_k in mapping.run to consider more reference neighbours. This creates a new mapping and can change which populations receive weight. More neighbours are not automatically better, especially near boundaries between cell types.

For missing features, missing_feature_policy="error" requires complete overlap; "zero" fills an absent feature with a normalized zero. The default, "reference_mean", is the path used above.

mapping.diagnostics["queryScaledDispersion"] is calculated from comparing query spread with the reference after scaling. Only informative query cells and the reference features the query measured enter it, so the missing-feature fill does not change it. Values near 1 mean a similar average scaled distance from the reference centre; they do not establish matching cell types. Values much below 1 mean the query is compressed toward the centre of the reference cloud and neighbour labels become less trustworthy. RNA normalization renormalizes counts over the selected features by default (renormalize_subset=True). With that setting and featureCoverage below 1, query cells are renormalized over fewer features than the reference used, so the value is not comparable with 1.

A query cell is uninformative when its raw counts are zero in every reference feature that the query measured. Such a cell carries no query evidence. It is flagged in mapping.uninformative and counted by mapping.diagnostics["uninformativeCellCount"].

This example uses a plain PCA reference. Use a Harmony-backed Symphony reference only when both reference and query have defensible technical-batch metadata. Stimulation and disease are biological conditions, so they should not be substituted for technical batches.

Reuse the reference and saved labels

A reusable atlas is the fixed MappingReference prepared in this tutorial, together with its reference datastore and separately retained layout artifact.

In a later session, retain the mapping-reference, projection, and label-transfer artifact refs, reopen both stores, and reload the exact results. A saved transfer loads from the query datastore alone:

(10111, 'none', 'cluster_labels', 0.5)

Started: 2026-10-11T18:55:17.448250Z | Wall time: 0.534 s

For repeated use, reopen the prepared reference datastore read-only and run each mapping in a separate writable query datastore. If query counts come from a read-only source or the same physical store used to prepare the reference, use mount_datastore to create that separate query store.

Reference labels are frozen when labels are transferred. mapping.transfer_labels copies the labels it reads into the query datastore as a reference_labels artifact, which records their source column or artifact and a fingerprint of their values. Transferring again with unchanged labels and settings reuses the saved transfer. After the reference annotations change, the same call saves a new transfer next to the old one, and the old transfer keeps its labels. Reference labels can also be a cell-label artifact of the reference datastore, such as a clustering or a smart_label artifact returned by ds.clusters.relabel_by_overlap. Retain the layout artifact separately when mapping scores must be displayed on the original reference UMAP.

The lineage of a transfer follows its inputs into the reference datastore: from the query labels through the threshold, the frozen reference labels, and the projection, to the reference model and the cells it was built from.

Loading...

Started: 2026-10-11T18:55:17.987307Z | Wall time: 0.816 s

Validate a reused atlas with feature coverage, mapping evidence, abstention, and score concentration. When independent query labels exist, also inspect confusion and threshold calibration. A visually plausible embedding alone does not validate transferred labels.

For troubleshooting, common failures include mapping before the reference exists, ignoring feature mismatch, treating vote support as a probability, using a biological condition as a correction batch, and transferring labels without an abstention path.

See Mapping API reference for method contracts and Plotting API reference for the diagnostic plotting signatures used above.