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.

Reuse results and compare analysis settings

Changing the number of neighbours from 21 to 31 does not require rerunning normalization or PCA, or rebuilding the neighbour-search index. Query the saved index, build a new graph, and keep the original graph for comparison.

This example changes one setting at a time: first the neighbour count, then the number of principal components. A lineage diagram shows which results the analyses share. The examples require the extra optional dependencies.

Open a prepared analysis

The prepared 100,000-cell Tabula Sapiens subset has a saved run labelled docs_default, with 30 principal components and 21 neighbours. The saved run uses PCA without batch correction. We use its cells, variable genes, and saved results as the starting point. These settings belong to this example; they can differ from the current pipeline defaults.

DataStore has 100000 (100000) cells with 1 assays: RNA Cell metadata: 'I', 'ids', 'names', '10X_run', 'RNA_nCounts', 'RNA_nFeatures', 'RNA_percentMito', 'RNA_percentRibo', '_scvi_batch', '_scvi_labels', 'ambient_removal', 'anatomical_position', 'assay', 'assay_ontology_term_id', 'broad_cell_class', 'cdna_plate', 'cdna_well', 'cell_type', 'cell_type_ontology_term_id', 'compartment', 'development_stage', 'development_stage_ontology_term_id', 'disease', 'disease_ontology_term_id', 'donor_assay', 'donor_id', 'donor_method', 'donor_tissue', 'donor_tissue_assay', 'ethnicity_original', 'free_annotation', 'is_primary_data', 'library_plate', 'manually_annotated', 'method', 'n_genes_by_counts', 'notes', 'observation_joinid', 'pct_counts_ercc', 'pct_counts_mt', 'published_2022', 'replicate', 'sample_id', 'sample_number', 'scvi_leiden_donorassay_full', 'self_reported_ethnicity', 'self_reported_ethnicity_ontology_term_id', 'sex', 'sex_ontology_term_id', 'suspension_type', 'tissue', 'tissue_in_publication', 'tissue_ontology_term_id', 'tissue_type', 'total_counts', 'total_counts_ercc', 'total_counts_mt' RNA assay has 60606 features and following metadata: 'I', 'ids', 'names', '_index', 'dropOuts', 'ercc', 'feature_biotype', 'feature_length', 'feature_reference', 'feature_type', 'genome', 'mean', 'mean_counts', 'mt', 'nCells', 'n_cells_by_counts', 'pct_dropout_by_counts', 'std', 'total_counts'

Started: 2026-10-11T18:54:54.462572Z | Wall time: 37.168 s

Change the neighbour count from 21 to 31

Increasing k requests more neighbours from the same search index. CyteArc writes a new neighbour list and connectivity graph, keeping the normalization, PCA, and index unchanged.

{'normalization reused': True, 'PCA reused': True, 'ANN index reused': True, 'neighbors changed': True, 'graph changed': True}

Started: 2026-10-11T18:55:31.634789Z | Wall time: 30.334 s

See Build a neighborhood graph stage by stage for how changing k affects an analysis. Here, notice that only neighbours and the graph needed new results. Normalization, PCA, and the search index did not need to run again.

The index check reads the new neighbour result’s recorded input. Matching it to ann confirms that the query used the existing index.

Compare 30 and 40 principal components

A new PCA dimensionality creates a separate analysis branch. The search index, neighbours, and connectivity depend on the reduction, so the new branch needs its own result at each of those steps. The earlier 30-component analysis remains available alongside the new 40-component one.

{'PCA changed': True, 'ANN index changed': True, 'neighbors changed': True, 'graph changed': True}

Started: 2026-10-11T18:56:01.972655Z | Wall time: 14.837 s

The comparisons above refer to these original 30-component results. Creating the new branch has not replaced them:

{'baseline PCA': ArtifactRef(assay='RNA', kind='reduction', artifact_id='532aa2d022fe...'), 'baseline neighbors': ArtifactRef(assay='RNA', kind='neighbors', artifact_id='91937d072cd9...'), 'baseline graph': ArtifactRef(assay='RNA', kind='connectivity_map', artifact_id='f2ac2849e34c...')}

Started: 2026-10-11T18:56:16.813081Z | Wall time: 0.006 s

Compare analysis branches

Put all three graphs in one read-only report: the two neighbour-count branches and the dims=40 branch. Each shared input appears once, so the diagram shows where a parameter change first requires a new result.

Output
Loading...

Started: 2026-10-11T18:56:16.822409Z | Wall time: 0.604 s

Expand the output to inspect the full report, including its Mermaid source and exact artifact references. This diagram summarizes the branches verified above:

The k branches diverge after the ANN index. The dims=40 branch forks earlier, at PCA, then carries its own ANN, neighbours, and graph. The full report also includes the shared cell and feature selections and feature-scaling result.

Export the same report when it needs to travel with an analysis. to_markdown() is what notebook display uses. Inspect a short preview before writing or sending the complete string elsewhere:

['```mermaid', 'flowchart LR', ' artifact0["RNA / metadata_snapshot | snapshot_run_metadata | 7340c6fca965"]', ' artifact1["datastore / cell_selection | snapshot_pipeline_input_selection | 829e00dc1708"]', ' artifact2["RNA / feature_summary | summarize_rna_features | cab031960e6e"]', ' artifact3["RNA / feature_selection | select_hvgs | 561ec515123d"]', ' artifact4["RNA / normalized | run_normalization | 525af1d08271"]', ' artifact5["RNA / feature_scaling | calculate_feature_scaling | 739b82327259"]', ' artifact6["RNA / reduction | run_pca | 532aa2d022fe"]', ' artifact7["RNA / ann_index | build_ann_index | e7f9b6ebbb42"]', ' artifact8["RNA / neighbors | query_neighbors | 91937d072cd9"]', ' artifact9["RNA / connectivity_map | build_connectivity_map | f2ac2849e34c | outputs: k21 graph"]']

Started: 2026-10-11T18:56:17.429618Z | Wall time: 0.007 s

lineage.to_mermaid() returns only the diagram source when a tooling pipeline needs that form alone.

Find and inspect saved results

The completed run retains every immutable reference needed to keep side comparisons separate.

{'normalization': ArtifactRef(assay='RNA', kind='normalized', artifact_id='525af1d08271...'), 'PCA': ArtifactRef(assay='RNA', kind='reduction', artifact_id='532aa2d022fe...'), 'neighbor index': ArtifactRef(assay='RNA', kind='ann_index', artifact_id='e7f9b6ebbb42...'), 'neighbors': ArtifactRef(assay='RNA', kind='neighbors', artifact_id='91937d072cd9...'), 'graph': ArtifactRef(assay='RNA', kind='connectivity_map', artifact_id='f2ac2849e34c...')}

Started: 2026-10-11T18:56:17.440209Z | Wall time: 0.006 s

For how results record their inputs, see Why CyteArc records provenance. The artifact catalog can also find results by their exact inputs. It returns every match instead of guessing which result you intended. Unpacking into [reopened_graph] checks that the search found exactly one graph; zero or multiple matches raise an error:

{'operation': 'build_connectivity_map', 'parameters': {'local_connectivity': 1.0, 'bandwidth': 1.5}, 'inputs': {'neighbors': {'type': 'artifact', 'scope': 'assay', 'kind': 'neighbors', 'artifact_id': '91937d072cd999181452a281debaa17cb51adcae39ccda32c7defb6d3271523e', 'assay': 'RNA'}}, 'complete': True}

Started: 2026-10-11T18:56:17.448758Z | Wall time: 0.013 s

Recompute a matching result

invalidate_cache=True skips reuse even when the parameters match. Previously completed artifacts remain on disk. The new reference has a different id and path. The operation and parameters stay the same.

CyteArc also stops reusing a result on its own when a release changes what the operation computes. Such a release gives the operation a new revision, and an earlier result of that operation is computed again, with one log line that names the result it replaced and what changed. The earlier result remains on disk, and lineage reports mark it as stale. See Operation revisions.

For normalization, the call below would write a fresh normalized artifact while retaining the exact immutable cell_selection and feature_selection inputs. It is not executed here because a throwaway duplicate adds no evidence to the lineage figure.

# Force normalization to create a new result with the same inputs.
forced = ds.features.normalize(cell_selection, hvg_ref, invalidate_cache=True)

# Check that forcing recomputation produced a distinct result.
forced != normalized