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.

Correcting batch effects with Harmony

Single-cell experiments are often processed on different days, with different reagent lots or sequencing runs. These handling differences can leave technical patterns in the data, making otherwise similar cells separate by batch. Batch correction aims to reduce that technical structure so we can examine biological differences more clearly.

Harmony works on the PCA coordinates. It iteratively adjusts those coordinates to encourage mixing of comparable cells across batches, without rewriting the underlying counts. The corrected coordinates then feed the neighbour graph and its downstream analyses. Better mixing is useful only if meaningful biological differences survive.

This tutorial compares one uncorrected RNA analysis with the same analysis after Harmony correction. It uses the Binvignat rheumatoid arthritis PBMC dataset from the CELLxGENE collection associated with the study in JCI Insight. The tutorial constructs equal cell counts for each sequencing-batch and disease combination, so technical mixing can be assessed without making disease identical to batch. This sampling is not a donor-balanced biological design.

We will first choose comparable cells, then run the same pipeline twice, adding harmony_batch_columns=["batch"] to the second run.

The code downloads the versioned H5AD file to local disk, converts it to a local Zarr store, and runs every analysis locally. Passing the download URL to urlretrieve transfers the file only. It does not make CyteArc compute against a remote dataset; Explore a remote dataset covers that separate workflow.

Prepare the local source store

Download the H5AD file only when it is absent. Inspecting it before conversion makes the matrix choice and dimensions explicit. This tutorial requires the integer-like count matrix at raw/X with 108,717 cells and 21,648 features.

Started: 2026-10-11T18:54:40.709326Z | Wall time: 0.534 s

Choose local paths for the reusable download and converted counts.

Started: 2026-10-11T18:54:41.245368Z | Wall time: 1.658 s

Check that the file contains the expected counts before converting it:

{'count matrix': 'raw/X', 'cells': 108717, 'genes': 21648, 'integer counts': True}

Started: 2026-10-11T18:54:42.906878Z | Wall time: 0.652 s

Convert once and keep the source store for later sessions. This dataset needs a 6 GB import budget for the default count layout. The temporary directory keeps an interrupted conversion separate from the finished store.

Started: 2026-10-11T18:54:43.560955Z | Wall time: 28.186 s

Initialize the source with min_features_per_cell=0, which drops only cells without detected features (none here), so the imported cell axis stays intact while the tutorial defines its own exact selection.

{'cells': 108717, 'genes': 21648}

Started: 2026-10-11T18:55:11.749912Z | Wall time: 1.982 s

Mount the source into a temporary writable analysis store. Count matrices remain in the local source store, while the selection and pipeline artifacts are written below the temporary directory. Keep analysis_directory bound for as long as the mounted datastore is in use.

{'same cell count': True, 'same gene count': True}

Started: 2026-10-11T18:55:13.733914Z | Wall time: 1.544 s

Create a balanced 9,000-cell analysis

We will compare corrections on a smaller subset of 9,000 cells. The source contains three batches and two disease groups. Selecting 1,500 cells from every batch and disease combination gives both disease groups equal representation within each batch. One seeded generator makes the resulting docs_ra_batch_demo selection reproducible from the same source file.

conditionnormalrheumatoid arthritis
batch
11901121639
2179898043
32308018955

Started: 2026-10-11T18:55:15.279782Z | Wall time: 0.083 s

Draw the same number of cells from each batch and disease group.

conditionnormalrheumatoid arthritis
batch
115001500
215001500
315001500

Started: 2026-10-11T18:55:15.365415Z | Wall time: 0.038 s

The constructed subset has equal cell counts for every batch and disease combination, but donor representation is not balanced, and equal condition counts do not guarantee comparable cell-type composition within each batch. Disease is a biological condition rather than a technical covariate, so only batch is supplied to Harmony below. If each condition occurred in a different batch, these data alone could not tell a disease effect from a batch effect; correction would not resolve that study-design problem.

Run matched uncorrected and Harmony pipelines

Both runs use the same cells and the default feature count, PCA dimensions, and neighbour count. The only analysis difference is harmony_batch_columns=["batch"] in the second run. We turn off filtering because we already chose the cells, and skip clustering, cell-cycle scores, doublet detection, and markers because this comparison only needs the graph and UMAP.

Started: 2026-10-11T18:55:15.405817Z | Wall time: 0.004 s

Run the uncorrected analysis with these shared settings.

{'analysis cells': 9000}

Started: 2026-10-11T18:55:15.411751Z | Wall time: 41.769 s

Repeat the analysis with batch correction.

{'analysis cells': 9000}

Started: 2026-10-11T18:55:57.182206Z | Wall time: 14.172 s

Harmony adjusts PCA coordinates before the neighbour graph is built. It does not change the count matrix. The two frozen runs keep the exact selections, metadata, layouts, and graph artifacts used for the comparison.

Compare the layouts

Plot each run by sequencing batch and by the imported broad annotation. Look for better mixing of batches while the broad cell types remain distinct.

Uncorrected PBMC UMAP colored by three sequencing batches. Batch 3 occupies several regions separated from batches 1 and 2.
Uncorrected PBMC UMAP colored by broad cell annotations. Monocytes, B cells, CD4 T cells, CD8 T cells, and NK cells provide context for the batch separation.

Started: 2026-10-11T18:56:11.356345Z | Wall time: 1.551 s

Before correction, batch 3 occupies several regions apart from batches 1 and 2.

The Harmony result:

Harmony-corrected PBMC UMAP colored by batch. The three batches overlap more within the major groups than in the uncorrected layout.
Harmony-corrected PBMC UMAP colored by broad annotations. B cells and monocytes form distinct regions; CD4 T cells, CD8 T cells, and NK cells occupy neighboring regions.

Started: 2026-10-11T18:56:12.908505Z | Wall time: 0.908 s

After correction, the three batches overlap more within the major groups, while B cells and monocytes remain distinct from the T-cell and NK-cell region. Use the metrics below to assess this visual comparison on the graph.

Quantify mixing and structural preservation

iLISI asks how many batches occur in a cell’s neighbourhood and how evenly they are represented. Higher values indicate more local batch mixing. A neighbourhood drawn evenly from all three batches is more diverse than one dominated by a single batch. That does not mean every cell type should mix equally when its abundance differs by batch. cLISI applies the same diversity calculation to the cell-type labels in rough_annot, but here mixing different cell types is not the goal. CyteArc inverts and scales cLISI so higher values indicate better separation of those annotations.

Graph connectivity asks whether cells sharing an annotation remain connected. All three reported scores use the convention that higher is better, but they answer different questions. Use the exact neighbour and connectivity artifacts from each run. The LISI metrics choose their neighbourhood scale from the neighbour count by default.

Started: 2026-10-11T18:56:13.817846Z | Wall time: 0.004 s

Calculate the same diagnostics for both completed runs.

iLISI (batch)cLISI (rough_annot)graph connectivity (rough_annot)
Uncorrected0.0620.9970.949
Harmony0.2050.9960.945

Started: 2026-10-11T18:56:13.823308Z | Wall time: 1.205 s

Read these scores together. Higher iLISI after Harmony, with similar cLISI and graph connectivity, would support improved technical mixing without obvious loss of broad cell-type structure. These metrics are diagnostics, not proof that correction is biologically valid or that every disease-associated signal was preserved. The subset equalizes cell counts, not biological replicates, and disease was not used as a correction covariate. Biological conclusions still require a donor-aware design and targeted downstream checks. In particular, compare markers and disease-associated programs within matched cell types and donors; broad annotation scores can miss changes within a type.

See Graph construction API reference for the PCA and Harmony contracts, and Integration and metrics API reference for the metric definitions.