Unknown cell type identification using scAdam

Unknown cell type identification using scAdam#

scAdam is specifically developed for annotating cell types, especially focusing on rare cell types that may be underrepresented in the dataset.

Advantages

  1. scAdam not only detects all cell types in any test dataset but also generates reproducible results, which is an important aspect for reliable biological interpretation.

  2. It enables multitasking by allowing the researchers to extract individual cell types for targeted investigations.

  3. Unknown cell type identification makes it possible to identify new cell types that are absent from the data on which the scAdam model was trained. In addition, this enables cross-tissue cell type annotation, as demonstrated in this tutorial.

Integration with Other Tools: scAdam is part of a bigger toolkit that includes other tools, such as scEve for surface protein prediction and scNoah for benchmarking, making it a comprehensive solution for single-cell analysis.

[1]:
# Python packages
import warnings
warnings.simplefilter('ignore')

import scanpy as sc
import scparadise
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

sc.set_figure_params(dpi = 80)
[2]:
# Download example dataset from Figshare:
# https://figshare.com/ndownloader/files/62994628
# Use wget or simply paste the link into your browser to download.
[3]:
# Load visceral adipose tissue dataset
adata = sc.read_h5ad('VAT_sample.h5ad')
adata
[3]:
AnnData object with n_obs × n_vars = 16520 × 28137
    obs: 'sample', 'ground_truth'
    obsm: 'X_harmony', 'X_pca', 'X_umap'
[4]:
# Saving count data
adata.layers['counts'] = adata.X.copy()
# Normalizing to median total counts
sc.pp.normalize_total(adata)
# Logarithmize the data
sc.pp.log1p(adata)
[6]:
# Available models for cell type annotation
df = scparadise.scadam.available_models(query='Subcutaneous adipose tissue')
df
[6]:
Model name Tissue/Organ Species Description Suspension Accuracy Balanced Accuracy Number of Levels
0 Human_Subcutaneous_AT adipose human Subcutaneous adipose tissue atlas cells 0.973 0.954 3
[7]:
# Download model for cell type prediction
scparadise.scadam.download_model('Human_Subcutaneous_AT', save_path='')
[8]:
# Predict cell types using scAdam model with unknown cell type identification
adata = scparadise.scadam.predict(
    adata,
    path_model = 'Human_Subcutaneous_AT_scAdam',
    detect_unknown = True # Turn on unknown cell type identification
)
Cell type annotation using the 2nd version of the scAdam model
scAdam model with unknown detector loaded from Human_Subcutaneous_AT_scAdam
Gene alignment:
  Model features: 1198
  Matched features: 1197 (99.9%)
Predicting: 100%|██████████████████████████████| 65/65 [00:00<00:00, 94.63it/s]
Gradient threshold calculated using Otsu method: 1.6007633209228516
Entropy threshold calculated using Otsu method: 2.084214687347412
Distance threshold calculated using Otsu method: 9.879642486572266
Unknown cell type detection using 3 methods. Threshold: 2 votes.
Detected 3483 unknown cells (21.1%)
Added cell type column: pred_celltype_l1
Added probabilities column: pred_celltype_l1_probability
Added cell type column: pred_celltype_l2
Added probabilities column: pred_celltype_l2_probability
Added cell type column: pred_celltype_l3
Added probabilities column: pred_celltype_l3_probability
[9]:
# Sort order cells
adata = adata[adata.obs.sort_values("pred_unknown").index]

For all scores, higher values indicate that the model is less confident in the predicted cell type.

[10]:
# Visualise prediction results with unknown cell type scores (gradient, entropy, distance)
sc.pl.umap(
    adata,
    color = [
        'pred_celltype_l1',
        'pred_celltype_l2',
        'pred_unknown',
        'gradient_score', # unknown cell type score
        'entropy_score', # unknown cell type score
        'distance_score' # unknown cell type score
    ],
    frameon = False,
    legend_fontsize = 8,
    ncols = 3,
    cmap = 'bwr',
    vmin = 'p01',
    vmax = 'p99'
)
../../../_images/tutorials_notebooks_scAdam_scAdam_unknown_11_0.png

Analysis of the scores indicates the presence of two additional cell types in visceral adipose tissue that are absent from subcutaneous adipose tissue.

The dataset already contains ground-truth cell-type annotations that were generated independently of scAdam predictions using marker genes. To compare the ground-truth annotations with the predicted annotations, we will recolor the cells so that the same cell types use the same colors in both visualizations.

[11]:
# Define colors
color_map = {
    "ADSC": "#1f77b4",
    "T": "#ff7f0e",
    "NK": "#279e68",
    "ILC": "#d62728",
    "Mural": "#aa40fc",
    "Mono-Mf-Neu": "#8c564b",
    "Mesothelial": "#b5bd61",
    "Preadipocyte": "#17becf",
    "B": "#aec7e8",
    "Endothelial": "#ffbb78",
    "Mast": "#98df8a",
    "prolif T/NK": "#ff9896",
    "Unknown": "#c5b0d5"
}
default_color = "#bdbdbd"

# Recolor cells
celltype_col = 'ground_truth'
cats = list(adata.obs[celltype_col].cat.categories)
adata.uns[f"{celltype_col}_colors"] = [color_map.get(c, default_color) for c in cats]
celltype_col = 'pred_celltype_l2'
cats = list(adata.obs[celltype_col].cat.categories)
adata.uns[f"{celltype_col}_colors"] = [color_map.get(c, default_color) for c in cats]
[12]:
# Visualize ground_truth vs pred_celltype_l2
sc.pl.umap(
    adata,
    color = [
        'ground_truth',
        'pred_celltype_l2',
        'pred_unknown',
        'gradient_score',
        'entropy_score',
        'distance_score'
    ],
    frameon = False,
    legend_fontsize = 8,
    ncols = 3,
    cmap = 'bwr',
    vmin = 'p01',
    vmax = 'p99'
)
../../../_images/tutorials_notebooks_scAdam_scAdam_unknown_15_0.png

We recommend validating scAdam cell-type predictions using specific marker genes for the predicted cell types. Marker-gene expression is commonly used to assess whether automated annotations are biologically consistent.

[13]:
marker_genes = {
    "ADSC": ['CD55', 'PI16'],
    "Preadipocyte": ['CXCL14', 'IGF1'],
    "B": ['MS4A1', 'BANK1'],
    "DC": ['FCER1A'],
    "Mono-Mf-Neu": ['C1QA', 'FOLR2'],
    "Endothelial": ['PECAM1', 'KDR', 'VWF'],
    "Mural": ['ACTA2', 'RGS5'],
    "ILC": ['IL2RA', 'KIT'],
    "Mast": ['TPSAB1', 'CPA3'],
    "Mesothelial": ['MSLN', 'UPK3B'],
    "NK": ['NCAM1', 'KLRF1', 'NKG7'],
    "T": ['CD3E', 'CD3G', 'CD3D'],
}
[14]:
# Make Axes
# Number of needed rows and columns (based on the row with the most columns)
nrow = len(marker_genes)
ncol = max([len(vs) for vs in marker_genes.values()])
fig, axs = plt.subplots(nrow, ncol, figsize=(4 * ncol, 4 * nrow))
# Plot expression for every marker on the corresponding Axes object
for row_idx, (cell_type, markers) in enumerate(marker_genes.items()):
    col_idx = 0
    for marker in markers:
        ax = axs[row_idx, col_idx]
        sc.pl.umap(
            adata,
            color=marker,
            ax=ax,
            show=False,
            cmap = 'bwr',
            frameon=False
        )
        # Add cell type as row label - here we simply add it as ylabel of
        # the first Axes object in the row
        if col_idx == 0:
            # We disabled axis drawing in UMAP to have plots without background and border
            # so we need to re-enable axis to plot the ylabel
            ax.axis("on")
            ax.tick_params(
                top="off",
                bottom="off",
                left="off",
                right="off",
                labelleft="on",
                labelbottom="off",
            )
            ax.set_ylabel(cell_type + "\n", rotation=90, fontsize=14)
            ax.set(frame_on=False)
        col_idx += 1
    # Remove unused column Axes in the current row
    while col_idx < ncol:
        axs[row_idx, col_idx].remove()
        col_idx += 1
# Alignment within the Figure
fig.tight_layout()
../../../_images/tutorials_notebooks_scAdam_scAdam_unknown_18_0.png
[15]:
adata.write_h5ad('adata_predicted_with_unknown.h5ad')
[16]:
import session_info2
session_info2.session_info()
[16]:
PackageVersion
pandas2.3.3
anndata0.12.16
scanpy1.11.5
scparadise1.2.0
numpy2.4.6
matplotlib3.11.0
ComponentInfo
Python3.11.15 (main, Jun 11 2026, 15:20:16) [GCC 14.3.0]
OSLinux-6.8.0-138-generic-x86_64-with-glibc2.39
Updated2026-09-03 21:21
Dependencies
DependencyVersion
joblib1.5.3
setuptools81.0.0
comm0.2.3
cuda-bindings13.3.1
h5py3.16.0
muon0.1.7
zarr3.1.6
matplotlib-inline0.2.2
seaborn0.13.2
aiohttp3.14.1
traitlets5.15.1
slicer0.0.8
urllib32.7.0
patsy1.0.2
ipykernel7.3.0
jupyter_client8.9.1
multidict6.7.1
scikit-learn1.9.0
packaging26.0
stack-data0.6.3
scikit-image0.26.0
python-dateutil2.9.0.post0
frozenlist1.8.0
PyYAML6.0.3
texttable1.7.0
certifi2026.5.20 (2026.05.20)
idna3.18
optuna4.9.0
statsmodels0.14.6
legacy-api-wrap1.5
argparse1.4.0 (1.1)
parso0.8.7
colorlog6.10.1
charset-normalizer3.4.7
pynndescent0.6.0
numba0.65.1
platformdirs4.10.0
prompt_toolkit3.0.52
propcache0.5.2
donfig0.8.1.post1
umap-learn0.5.12
session-info20.4.1
pytorch-tabnet4.1.0
decorator5.3.1
scipy1.17.1
jupyter_core5.9.1
rich15.0.0
opt_einsum3.4.0
pathlib1.0.1
typing3.7.4.3
tqdm4.68.2
ipython9.14.1
yarl1.24.2
asttokens3.0.1
igraph1.0.0
sparse0.18.0
cycler0.12.1
llvmlite0.47.0
natsort8.4.0
Pygments2.20.0
wcwidth0.8.1
attrs26.1.0
narwhals2.22.1
cuda-pathfinder1.5.5
pytz2026.2
aiosignal1.4.0
tornado6.5.7
lazy-loader0.5
numcodecs0.16.5
google-crc32c1.8.0
executing2.2.1
Cython3.3.0
leidenalg0.12.0
mudata0.3.8
pure_eval0.2.3
sklearn-compat0.1.6
kiwisolver1.5.0
shap0.49.1
dataclasses0.6
pycparser3.0 (3.00)
imbalanced-learn0.14.2
threadpoolctl3.6.0
fonttools4.63.0
jedi0.20.0
pillow12.2.0
pyarrow24.0.0
cloudpickle3.1.2
pyzmq27.1.0
xarray2026.4.0
typing_extensions4.15.0
scverse-misc0.0.3
aiohappyeyeballs2.6.2
defusedxml0.7.1
torch2.12.0 (2.12.0+cu130)
fsspec2026.4.0
six1.17.0
requests2.34.2
psutil7.2.2
plottable0.1.5
debugpy1.8.21
pyparsing3.3.2
cffi2.0.0
Copyable Markdown
| Package    | Version |
| ---------- | ------- |
| pandas     | 2.3.3   |
| anndata    | 0.12.16 |
| scanpy     | 1.11.5  |
| scparadise | 1.2.0   |
| numpy      | 2.4.6   |
| matplotlib | 3.11.0  |

| Dependency         | Version                |
| ------------------ | ---------------------- |
| joblib             | 1.5.3                  |
| setuptools         | 81.0.0                 |
| comm               | 0.2.3                  |
| cuda-bindings      | 13.3.1                 |
| h5py               | 3.16.0                 |
| muon               | 0.1.7                  |
| zarr               | 3.1.6                  |
| matplotlib-inline  | 0.2.2                  |
| seaborn            | 0.13.2                 |
| aiohttp            | 3.14.1                 |
| traitlets          | 5.15.1                 |
| slicer             | 0.0.8                  |
| urllib3            | 2.7.0                  |
| patsy              | 1.0.2                  |
| ipykernel          | 7.3.0                  |
| jupyter_client     | 8.9.1                  |
| multidict          | 6.7.1                  |
| scikit-learn       | 1.9.0                  |
| packaging          | 26.0                   |
| stack-data         | 0.6.3                  |
| scikit-image       | 0.26.0                 |
| python-dateutil    | 2.9.0.post0            |
| frozenlist         | 1.8.0                  |
| PyYAML             | 6.0.3                  |
| texttable          | 1.7.0                  |
| certifi            | 2026.5.20 (2026.05.20) |
| idna               | 3.18                   |
| optuna             | 4.9.0                  |
| statsmodels        | 0.14.6                 |
| legacy-api-wrap    | 1.5                    |
| argparse           | 1.4.0 (1.1)            |
| parso              | 0.8.7                  |
| colorlog           | 6.10.1                 |
| charset-normalizer | 3.4.7                  |
| pynndescent        | 0.6.0                  |
| numba              | 0.65.1                 |
| platformdirs       | 4.10.0                 |
| prompt_toolkit     | 3.0.52                 |
| propcache          | 0.5.2                  |
| donfig             | 0.8.1.post1            |
| umap-learn         | 0.5.12                 |
| session-info2      | 0.4.1                  |
| pytorch-tabnet     | 4.1.0                  |
| decorator          | 5.3.1                  |
| scipy              | 1.17.1                 |
| jupyter_core       | 5.9.1                  |
| rich               | 15.0.0                 |
| opt_einsum         | 3.4.0                  |
| pathlib            | 1.0.1                  |
| typing             | 3.7.4.3                |
| tqdm               | 4.68.2                 |
| ipython            | 9.14.1                 |
| yarl               | 1.24.2                 |
| asttokens          | 3.0.1                  |
| igraph             | 1.0.0                  |
| sparse             | 0.18.0                 |
| cycler             | 0.12.1                 |
| llvmlite           | 0.47.0                 |
| natsort            | 8.4.0                  |
| Pygments           | 2.20.0                 |
| wcwidth            | 0.8.1                  |
| attrs              | 26.1.0                 |
| narwhals           | 2.22.1                 |
| cuda-pathfinder    | 1.5.5                  |
| pytz               | 2026.2                 |
| aiosignal          | 1.4.0                  |
| tornado            | 6.5.7                  |
| lazy-loader        | 0.5                    |
| numcodecs          | 0.16.5                 |
| google-crc32c      | 1.8.0                  |
| executing          | 2.2.1                  |
| Cython             | 3.3.0                  |
| leidenalg          | 0.12.0                 |
| mudata             | 0.3.8                  |
| pure_eval          | 0.2.3                  |
| sklearn-compat     | 0.1.6                  |
| kiwisolver         | 1.5.0                  |
| shap               | 0.49.1                 |
| dataclasses        | 0.6                    |
| pycparser          | 3.0 (3.00)             |
| imbalanced-learn   | 0.14.2                 |
| threadpoolctl      | 3.6.0                  |
| fonttools          | 4.63.0                 |
| jedi               | 0.20.0                 |
| pillow             | 12.2.0                 |
| pyarrow            | 24.0.0                 |
| cloudpickle        | 3.1.2                  |
| pyzmq              | 27.1.0                 |
| xarray             | 2026.4.0               |
| typing_extensions  | 4.15.0                 |
| scverse-misc       | 0.0.3                  |
| aiohappyeyeballs   | 2.6.2                  |
| defusedxml         | 0.7.1                  |
| torch              | 2.12.0 (2.12.0+cu130)  |
| fsspec             | 2026.4.0               |
| six                | 1.17.0                 |
| requests           | 2.34.2                 |
| psutil             | 7.2.2                  |
| plottable          | 0.1.5                  |
| debugpy            | 1.8.21                 |
| pyparsing          | 3.3.2                  |
| cffi               | 2.0.0                  |

| Component | Info                                               |
| --------- | -------------------------------------------------- |
| Python    | 3.11.15 (main, Jun 11 2026, 15:20:16) [GCC 14.3.0] |
| OS        | Linux-6.8.0-138-generic-x86_64-with-glibc2.39      |
| Updated   | 2026-09-03 21:21                                   |