scrna6/6

Concatenate datasets to a single array store

In the previous notebooks, we’ve seen how to incrementally create a collection of scRNA-seq datasets and train models on it.

Sometimes we want to concatenate all datasets into one big array to speed up ad-hoc queries for slices for arbitrary metadata (see this blog post). This is what CELLxGENE does to create Census: a number of .h5ad files are concatenated to give rise to a single tiledbsoma array store (CELLxGENE: scRNA-seq).

Note

This notebook shows how lamindb can be used with tiledbsoma append mode, also expained in the tiledbsoma documentation.

import lamindb as ln
import pandas as pd
import scanpy as sc
import tiledbsoma.io
from functools import reduce
→ connected lamindb: testuser1/test-scrna
ln.context.uid = "oJN8WmVrxI8m0000"
ln.context.track()
Hide code cell output
→ notebook imports: lamindb==0.76.3 pandas==2.2.2 scanpy==1.10.2 tiledbsoma==1.13.1
→ created Transform('oJN8WmVrxI8m0000') & created Run('2024-09-02 13:27:44.592263+00:00')

Query the collection of h5ad files that we’d like to convert into a single array.

collection = ln.Collection.get(
    name="My versioned scRNA-seq collection", version="2"
)
collection.describe()
Hide code cell output
Collection(uid='I60Bnk1OD1kNTZl10001', version='2', is_latest=True, name='My versioned scRNA-seq collection', hash='dBJLoG6NFZ8WwlWqnfyFdQ', visibility=1, updated_at='2024-09-02 13:27:09 UTC')
  Provenance
    .created_by = 'testuser1'
    .transform = 'Standardize and append a batch of data'
    .run = '2024-09-02 13:26:48 UTC'
    .input_of_runs = ["'2024-09-02 13:27:20 UTC'", "'2024-09-02 13:27:37 UTC'"]
  Feature sets
    'var' = 'MIR1302-2HG', 'FAM138A', 'OR4F5', 'None', 'OR4F29', 'OR4F16', 'LINC01409', 'FAM87B', 'LINC01128', 'LINC00115', 'FAM41C'
    'obs' = 'donor', 'tissue', 'cell_type', 'assay'

Prepare the AnnData objects

We need to prepare theAnnData objects in the collection to be concatenated into one tiledbsoma.Experiment. They need to have the same .var and .obs columns, .uns and .obsp should be removed.

adatas = [artifact.load() for artifact in collection.ordered_artifacts]

Compute the intersetion of all columns. All AnnData objects should have the same columns in their .obs, .var, .raw.var to be ingested into one tiledbsoma.Experiment.

obs_columns = reduce(pd.Index.intersection, [adata.obs.columns for adata in adatas])
var_columns = reduce(pd.Index.intersection, [adata.var.columns for adata in adatas])
var_raw_columns = reduce(pd.Index.intersection, [adata.raw.var.columns for adata in adatas])

Prepare the AnnData objects for concatenation. Prepare id fields, sanitize index names, intersect columns, drop slots. Here we have to drop .obsp, .uns and also columns from the dataframes that are not in the intersections obtained above, otherwise the ingestion will fail. We will need to provide obs and var names in ln.integrations.save_tiledbsoma_experiment, so we create these fileds (obs_id, var_id) from the dataframe indices.

for i, adata in enumerate(adatas):
    del adata.obsp
    del adata.uns
    
    adata.obs = adata.obs.filter(obs_columns)
    adata.obs["obs_id"] = adata.obs.index
    adata.obs["dataset"] = i
    adata.obs.index.name = None
    
    adata.var = adata.var.filter(var_columns)
    adata.var["var_id"] = adata.var.index
    adata.var.index.name = None
    
    drop_raw_var_columns = adata.raw.var.columns.difference(var_raw_columns)
    adata.raw.var.drop(columns=drop_raw_var_columns, inplace=True)
    adata.raw.var["var_id"] = adata.raw.var.index
    adata.raw.var.index.name = None

Create the array store

Ingest the AnnData objects. This saves the AnnData objects in one array store, creates Artifact and saves it. This function also writes current run.uid to tiledbsoma.Experiment obs, under lamin_run_uid.

If you know tiledbsoma API, then note, that ln.integrations.save_tiledbsoma_experiment includes both tiledbsoma.io.register_anndatas and tiledbsoma.io.from_anndata.

soma_artifact = ln.integrations.save_tiledbsoma_experiment(
    adatas,
    description="tiledbsoma experiment",
    measurement_name="RNA",
    obs_id_name="obs_id",
    var_id_name="var_id",
    append_obsm_varm=True
)
Hide code cell output
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/abc.py:119: FutureWarning: SparseDataset is deprecated and will be removed in late 2024. It has been replaced by the public classes CSRDataset and CSCDataset.

For instance checks, use `isinstance(X, (anndata.experimental.CSRDataset, anndata.experimental.CSCDataset))` instead.

For creation, use `anndata.experimental.sparse_dataset(X)` instead.

  return _abc_instancecheck(cls, instance)
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/abc.py:119: FutureWarning: SparseDataset is deprecated and will be removed in late 2024. It has been replaced by the public classes CSRDataset and CSCDataset.

For instance checks, use `isinstance(X, (anndata.experimental.CSRDataset, anndata.experimental.CSCDataset))` instead.

For creation, use `anndata.experimental.sparse_dataset(X)` instead.

  return _abc_instancecheck(cls, instance)

Query the array store

Open and query the experiment. We can use the registered Artifact. Here we query obs from the array store.

with soma_artifact.open() as soma_store:
    obs = soma_store["obs"]
    var = soma_store["ms"]["RNA"]["var"]
    
    obs_columns_store = obs.schema.names
    var_columns_store = var.schema.names
    
    obs_store_df = obs.read().concat().to_pandas()
    
    print(obs_store_df)
Hide code cell output
      soma_joinid                                          cell_type  \
0               0                                     dendritic cell   
1               1                              B cell, CD19-positive   
2               2                                     dendritic cell   
3               3                              B cell, CD19-positive   
4               4  effector memory CD4-positive, alpha-beta T cel...   
...           ...                                                ...   
1713         1713  naive thymus-derived CD4-positive, alpha-beta ...   
1714         1714  naive thymus-derived CD4-positive, alpha-beta ...   
1715         1715  naive thymus-derived CD4-positive, alpha-beta ...   
1716         1716             CD8-positive, alpha-beta memory T cell   
1717         1717  naive thymus-derived CD4-positive, alpha-beta ...   

                             obs_id  dataset         lamin_run_uid  
0                  GCAGGGCTGGATTC-1        0  7h2zZbmfFQyzdWU4xa3e  
1                  CTTTAGTGGTTACG-6        0  7h2zZbmfFQyzdWU4xa3e  
2                  TGACTGGAACCATG-7        0  7h2zZbmfFQyzdWU4xa3e  
3                  TCAATCACCCTTCG-8        0  7h2zZbmfFQyzdWU4xa3e  
4                  CGTTATACAGTACC-8        0  7h2zZbmfFQyzdWU4xa3e  
...                             ...      ...                   ...  
1713  Pan_T7991594_CTCACACTCCAGGGCT        1  7h2zZbmfFQyzdWU4xa3e  
1714  Pan_T7980358_CGAGCACAGAAGATTC        1  7h2zZbmfFQyzdWU4xa3e  
1715    CZINY-0064_AGACCATCACGCTGCA        1  7h2zZbmfFQyzdWU4xa3e  
1716    CZINY-0050_TCGATTTAGATGTTGA        1  7h2zZbmfFQyzdWU4xa3e  
1717    CZINY-0064_AGTGTTGTCCGAGCTG        1  7h2zZbmfFQyzdWU4xa3e  

[1718 rows x 5 columns]

Append AnnData to the array store

Prepare a new AnnData object to be appended to the store.

adata = ln.core.datasets.anndata_with_obs()
adata.obs_names_make_unique()
adata.var_names_make_unique()

adata.obs["obs_id"] = adata.obs.index
adata.var["var_id"] = adata.var.index

adata.obs["dataset"] = obs_store_df["dataset"].max()

obs_columns_same = [obs_col for obs_col in adata.obs.columns if obs_col in obs_columns_store]
adata.obs = adata.obs[obs_columns_same]

var_columns_same = [var_col for var_col in adata.var.columns if var_col in var_columns_store]
adata.var = adata.var[var_columns_same]
adata.write_h5ad("adata_to_append.h5ad")

Append the AnnData object from disk. This also creates a new version of soma_artifact.

soma_artifact = ln.integrations.save_tiledbsoma_experiment(
    ["adata_to_append.h5ad"],
    revises=soma_artifact,
    measurement_name="RNA",
    obs_id_name="obs_id",
    var_id_name="var_id"
)
Hide code cell output
! artifact version None will _update_ the state of folder /home/runner/work/lamin-usecases/lamin-usecases/docs/test-scrna/.lamindb/nCqIkb08iwOsKEnN.tiledbsoma - to _retain_ the old state by duplicating the entire folder, do _not_ pass `revises`
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/abc.py:119: FutureWarning: SparseDataset is deprecated and will be removed in late 2024. It has been replaced by the public classes CSRDataset and CSCDataset.

For instance checks, use `isinstance(X, (anndata.experimental.CSRDataset, anndata.experimental.CSCDataset))` instead.

For creation, use `anndata.experimental.sparse_dataset(X)` instead.

  return _abc_instancecheck(cls, instance)

Update the array store

Read X from the store.

with soma_artifact.open() as soma_store: # mode="r" by default
    ms_rna = soma_store["ms"]["RNA"]
    n_obs = len(soma_store["obs"])
    n_var = len(ms_rna["var"])
    X = ms_rna["X"]["data"].read().coos((n_obs, n_var)).concat().to_scipy()

Calculate PCA from the queried X.

pca_array = sc.pp.pca(X, n_comps=2)
soma_artifact
Artifact(uid='nCqIkb08iwOsKEnN0001', is_latest=True, description='tiledbsoma experiment', key='.lamindb/nCqIkb08iwOsKEnN.tiledbsoma', suffix='.tiledbsoma', size=15068509, hash='1zWHgdughEQI1t8Ig1AJQQ', n_objects=173, _hash_type='md5-d', visibility=1, _key_is_virtual=False, created_by_id=1, storage_id=1, transform_id=6, run_id=6, updated_at='2024-09-02 13:27:52 UTC')

Open the array store in write mode and add PCA. When the store is updated, the corresponding artifact also gets updated with a new version.

with soma_artifact.open(mode="w") as soma_store:
    tiledbsoma.io.add_matrix_to_collection(
        exp=soma_store,
        measurement_name="RNA",
        collection_name="obsm",
        matrix_name="pca",
        matrix_data=pca_array
    )
Hide code cell output
! The hash of the tiledbsoma store has changed, creating a new version of the artifact.
! artifact version None will _update_ the state of folder /home/runner/work/lamin-usecases/lamin-usecases/docs/test-scrna/.lamindb/nCqIkb08iwOsKEnN.tiledbsoma - to _retain_ the old state by duplicating the entire folder, do _not_ pass `revises`
/opt/hostedtoolcache/Python/3.10.14/x64/lib/python3.10/abc.py:119: FutureWarning: SparseDataset is deprecated and will be removed in late 2024. It has been replaced by the public classes CSRDataset and CSCDataset.

For instance checks, use `isinstance(X, (anndata.experimental.CSRDataset, anndata.experimental.CSCDataset))` instead.

For creation, use `anndata.experimental.sparse_dataset(X)` instead.

  return _abc_instancecheck(cls, instance)

Note that the artifact has been changed.

soma_artifact
Artifact(uid='nCqIkb08iwOsKEnN0002', is_latest=True, description='tiledbsoma experiment', key='.lamindb/nCqIkb08iwOsKEnN.tiledbsoma', suffix='.tiledbsoma', size=15089313, hash='skVIFgqaFfBVQtspHRl0TQ', n_objects=182, _hash_type='md5-d', visibility=1, _key_is_virtual=False, created_by_id=1, storage_id=1, transform_id=6, run_id=6, updated_at='2024-09-02 13:27:53 UTC')