Exploration of hormonal receptor genes in Wang et al. developing human neocortex dataset - Metacell calculation by SeaCells (only second trimester)¶

Reference paper

Upstream Steps

  • Assemble adata
  • QC filter on cells; Expression filter on genes
  • Filter by only second trimester cells
  • Normalization and log10 transformation by Scanpy functions
  • Feature selection (HVG) by Scanpy functions
  • Dimensionality reduction
  • Cluster identification
  • Batch correction by harmony

This Notebook

Metacell calculation by SEACells (following this tutorial)


1. Environment¶

1.1 Modules¶

In [1]:
import os
import sys

import numpy as np
import pandas as pd
import scanpy as sc

import pickle

#Plotting
import matplotlib.pyplot as plt
import matplotlib
import seaborn as sns

#utils
#import ipynbname
from datetime import datetime

# SeaCell
import SEACells

#import custom functions
sys.path.append('../')
import functions as fn
In [2]:
# Some plotting aesthetics
%matplotlib inline

sns.set_style('ticks')
matplotlib.rcParams['figure.figsize'] = [3.5, 3.5]
matplotlib.rcParams['figure.dpi'] = 100
In [3]:
print("Scanpy version: ", sc.__version__)
print("Pandas version: ", pd.__version__)
print("SEACell version: ", SEACells.__version__)
Scanpy version:  1.10.2
Pandas version:  2.2.2
SEACell version:  0.3.3

1.2 Settings¶

In [4]:
sc.settings.verbosity = 3
sc.settings.set_figure_params(dpi=80)

1.3 Files and parameters¶

In [5]:
input_file = '../../../../DataDir/ExternalData/SingleCellData/Wang_IITrimester_adata.h5ad'
output_file = '../../../../DataDir/ExternalData/SingleCellData/Wang_IITrimester_adataMetacells.h5ad'

1.4 Start computations¶

In [6]:
print(datetime.now())
2026-03-11 12:33:22.575161

2. Data Load¶

2.1 Read adata file¶

  • SEACells uses as input filtered unnormalized counts from scRNASeq data, that are subjected to normalization and log-transformation before metacell assignment
  • Here we start from data that has already undergone the steps of QC, filtering, normalization, log transformation
  • The sum of expression levels from cells to metacells will occur at the count level
In [7]:
adata = sc.read(input_file)
In [8]:
adata
Out[8]:
AnnData object with n_obs × n_vars = 64500 × 20266
    obs: 'Auth_Sample_ID', 'Auth_Estimated_postconceptional_age_in_days', 'Auth_Group', 'Auth_Region', 'Auth_nCount_RNA', 'Auth_nFeature_RNA', 'Auth_ATAC_fragments_in_peaks', 'Auth_Scrublet_doublet_score', 'Auth_S.Score', 'Auth_G2M.Score', 'Auth_Class', 'Auth_Subclass', 'Auth_Type_updated', 'Auth_Cluster', 'Auth_cell_type_ontology_term_id', 'Auth_development_stage_ontology_term_id', 'Auth_donor_id', 'Auth_cell_type', 'Auth_sex', 'Auth_tissue', 'Auth_self_reported_ethnicity', 'Auth_development_stage', 'dataset_id', 'sample_id', 'age', 'stage', 'cell_label', 'brain_region', 'n_genes_by_counts', 'log1p_n_genes_by_counts', 'total_counts', 'log1p_total_counts', 'total_counts_mito', 'log1p_total_counts_mito', 'pct_counts_mito', 'total_counts_ribo', 'log1p_total_counts_ribo', 'pct_counts_ribo', 'log1p_gene_UMI_ratio', 'n_genes', 'n_counts', 'Leiden_02', 'Leiden_04', 'Leiden_06', 'Leiden_Sel'
    var: 'gene_name', 'feature_is_filtered', 'feature_name', 'feature_length', 'feature_type', 'EnsembleCode', 'mito', 'ribo', 'n_cells_by_counts', 'mean_counts', 'log1p_mean_counts', 'pct_dropout_by_counts', 'total_counts', 'log1p_total_counts', 'n_cells', 'highly_variable', 'means', 'dispersions', 'dispersions_norm', 'highly_variable_nbatches', 'highly_variable_intersection'
    uns: 'Auth_Group_colors', 'Leiden_02', 'Leiden_02_colors', 'Leiden_04', 'Leiden_04_colors', 'Leiden_06', 'Leiden_06_colors', 'Leiden_Sel_colors', 'age_colors', 'brain_region_colors', 'cell_label_colors', 'diffmap_evals', 'draw_graph', 'harmony', 'hvg', 'log1p', 'pca', 'sample_id_colors', 'umap'
    obsm: 'X_draw_graph_fa_harmony', 'X_pca', 'X_pca_harmony', 'X_umap_harmony', 'X_umap_nocorr'
    varm: 'PCs'
    layers: 'counts', 'lognorm'
    obsp: 'harmony_connectivities', 'harmony_distances', 'pca_connectivities', 'pca_distances'
In [9]:
adata.obsm
Out[9]:
AxisArrays with keys: X_draw_graph_fa_harmony, X_pca, X_pca_harmony, X_umap_harmony, X_umap_nocorr
In [10]:
adata.X
Out[10]:
<Compressed Sparse Row sparse matrix of dtype 'float32'
	with 150008510 stored elements and shape (64500, 20266)>
In [11]:
print(adata.X[40:45, 40:45])
<Compressed Sparse Row sparse matrix of dtype 'float32'
	with 0 stored elements and shape (5, 5)>
In [12]:
adata.layers['counts']
Out[12]:
<Compressed Sparse Row sparse matrix of dtype 'uint16'
	with 150008510 stored elements and shape (64500, 20266)>
In [13]:
print(adata.layers['counts'][40:45, 40:45])
<Compressed Sparse Row sparse matrix of dtype 'uint16'
	with 0 stored elements and shape (5, 5)>
In [14]:
sc.pl.scatter(adata, basis='umap_nocorr', color=['sample_id', 'brain_region', 'cell_label'], frameon=False)
In [15]:
sc.pl.scatter(adata, basis='pca_harmony', color=['sample_id', 'brain_region', 'cell_label'], frameon=False)
In [16]:
sc.pl.scatter(adata, basis='umap_harmony', color=['sample_id', 'brain_region', 'cell_label'], frameon=False)
In [17]:
adata.obs.head(3)
Out[17]:
Auth_Sample_ID Auth_Estimated_postconceptional_age_in_days Auth_Group Auth_Region Auth_nCount_RNA Auth_nFeature_RNA Auth_ATAC_fragments_in_peaks Auth_Scrublet_doublet_score Auth_S.Score Auth_G2M.Score ... total_counts_ribo log1p_total_counts_ribo pct_counts_ribo log1p_gene_UMI_ratio n_genes n_counts Leiden_02 Leiden_04 Leiden_06 Leiden_Sel
ARKFrozen-18-PFC_AAACAGCCAAGGTGCA-1 ARKFrozen-18-PFC 98 Second_trimester PFC 5071 2436 6329 0.078697 0.028415 -0.040407 ... 239 5.480639 4.716795 0.392021 2432 5067 0 0 0 0
ARKFrozen-18-PFC_AAACAGCCACTAAATC-1 ARKFrozen-18-PFC 98 Second_trimester PFC 5564 2508 6727 0.118406 -0.085718 -0.007773 ... 322 5.777652 5.789284 0.371948 2506 5562 1 2 2 2
ARKFrozen-18-PFC_AAACAGCCAGGTTCAC-1 ARKFrozen-18-PFC 98 Second_trimester PFC 2191 1468 3477 0.039033 0.040728 0.065663 ... 121 4.804021 5.527638 0.512651 1466 2189 6 8 5 8

3 rows × 45 columns

2.2 Pre-processing¶

Normalization, log-transformation, HVG and dimensionality reduction have been already performed.

3. Run SEACells¶

We follow the workflow described in the SEACells tutorial

3.1 Define parameters¶

Parameters to be defined:

  • Number of metacells (graining level): choosing one metacell for every 75 single-cells.
  • X_pca_harmony as the key in .obsm for computing in metacells. PCs already corrected by harmony.
  • Number of eigenvalues for metacell initialization

We then employ the SEACells.core.SEACells function to initialize the model.

In [18]:
## Core parameters 
n_SEACells = round(adata.n_obs/75)
print(n_SEACells)

build_kernel_on = 'X_pca_harmony' # key in ad.obsm to use for computing metacells
                          # This would be replaced by 'X_svd' for ATAC data

## Additional parameters
n_waypoint_eigs = 10 # Number of eigenvalues to consider when initializing metacells
860
In [19]:
#help(SEACells.core.SEACells)

model = SEACells.core.SEACells(adata, 
                  build_kernel_on=build_kernel_on, 
                  n_SEACells=n_SEACells, 
                  n_waypoint_eigs=n_waypoint_eigs,
                  convergence_epsilon = 1e-5,
                  use_gpu=True)
Welcome to SEACells GPU!

3.2 Construct kernel matrix¶

construct_kernel_matrix function constructs the kernel matrix from data matrix using PCA/SVD and nearest neighbors. Key parameters are:

  • n_neighbors: (int) number of nearest neighbors to use for graph construction (default 15). Increasing this number results in a more homogeneous distribution of metacell size, but may impact on the preservation of rare cell types. Usually stable performance in the range 5-30.
  • graph_construction: method for graph construction.
In [20]:
model.construct_kernel_matrix(n_neighbors=20)
M = model.kernel_matrix
Computing kNN graph using scanpy NN ...
computing neighbors
    finished: added to `.uns['neighbors']`
    `.obsp['distances']`, distances for each pair of neighbors
    `.obsp['connectivities']`, weighted adjacency matrix (0:00:34)
Computing radius for adaptive bandwidth kernel...
  0%|          | 0/64500 [00:00<?, ?it/s]
Making graph symmetric...
Parameter graph_construction = union being used to build KNN graph...
Computing RBF kernel...
  0%|          | 0/64500 [00:00<?, ?it/s]
Building similarity LIL matrix...
  0%|          | 0/64500 [00:00<?, ?it/s]
Constructing CSR matrix...

3.3 Initialize archetypes¶

  • initialize_archetypes function initializes the B matrix which defines cells as SEACells.
  • The function uses waypoint analysis for initialization into to fully cover the phenotype space, and then greedily selects the remaining cells.
In [21]:
# Initialize archetypes
model.initialize_archetypes()
Building kernel on X_pca_harmony
Computing diffusion components from X_pca_harmony for waypoint initialization ... 
computing neighbors
    finished: added to `.uns['neighbors']`
    `.obsp['distances']`, distances for each pair of neighbors
    `.obsp['connectivities']`, weighted adjacency matrix (0:00:13)
Done.
Sampling waypoints ...
Done.
Selecting 846 cells from waypoint initialization.
Initializing residual matrix using greedy column selection
Initializing f and g...
100%|██████████| 24/24 [00:03<00:00,  6.54it/s]
Selecting 14 cells from greedy initialization.

In [22]:
adata
Out[22]:
AnnData object with n_obs × n_vars = 64500 × 20266
    obs: 'Auth_Sample_ID', 'Auth_Estimated_postconceptional_age_in_days', 'Auth_Group', 'Auth_Region', 'Auth_nCount_RNA', 'Auth_nFeature_RNA', 'Auth_ATAC_fragments_in_peaks', 'Auth_Scrublet_doublet_score', 'Auth_S.Score', 'Auth_G2M.Score', 'Auth_Class', 'Auth_Subclass', 'Auth_Type_updated', 'Auth_Cluster', 'Auth_cell_type_ontology_term_id', 'Auth_development_stage_ontology_term_id', 'Auth_donor_id', 'Auth_cell_type', 'Auth_sex', 'Auth_tissue', 'Auth_self_reported_ethnicity', 'Auth_development_stage', 'dataset_id', 'sample_id', 'age', 'stage', 'cell_label', 'brain_region', 'n_genes_by_counts', 'log1p_n_genes_by_counts', 'total_counts', 'log1p_total_counts', 'total_counts_mito', 'log1p_total_counts_mito', 'pct_counts_mito', 'total_counts_ribo', 'log1p_total_counts_ribo', 'pct_counts_ribo', 'log1p_gene_UMI_ratio', 'n_genes', 'n_counts', 'Leiden_02', 'Leiden_04', 'Leiden_06', 'Leiden_Sel'
    var: 'gene_name', 'feature_is_filtered', 'feature_name', 'feature_length', 'feature_type', 'EnsembleCode', 'mito', 'ribo', 'n_cells_by_counts', 'mean_counts', 'log1p_mean_counts', 'pct_dropout_by_counts', 'total_counts', 'log1p_total_counts', 'n_cells', 'highly_variable', 'means', 'dispersions', 'dispersions_norm', 'highly_variable_nbatches', 'highly_variable_intersection'
    uns: 'Auth_Group_colors', 'Leiden_02', 'Leiden_02_colors', 'Leiden_04', 'Leiden_04_colors', 'Leiden_06', 'Leiden_06_colors', 'Leiden_Sel_colors', 'age_colors', 'brain_region_colors', 'cell_label_colors', 'diffmap_evals', 'draw_graph', 'harmony', 'hvg', 'log1p', 'pca', 'sample_id_colors', 'umap', 'neighbors'
    obsm: 'X_draw_graph_fa_harmony', 'X_pca', 'X_pca_harmony', 'X_umap_harmony', 'X_umap_nocorr'
    varm: 'PCs'
    layers: 'counts', 'lognorm'
    obsp: 'harmony_connectivities', 'harmony_distances', 'pca_connectivities', 'pca_distances', 'distances', 'connectivities'
In [23]:
adata.obsm['X_umap'] = adata.obsm['X_umap_harmony'].copy()
In [24]:
# Plot the initilization to ensure they are spread across phenotypic space
SEACells.plot.plot_initialization(adata, model)

3.4 Fit Model¶

We apply the model.fit function.

In [25]:
model.fit(min_iter=10, max_iter=50)
model
Randomly initialized A matrix.
Setting convergence threshold at 0.00533
Starting iteration 1.
Completed iteration 1.
Starting iteration 10.
Completed iteration 10.
Starting iteration 20.
Completed iteration 20.
Converged after 25 iterations.
Out[25]:
<SEACells.gpu.SEACellsGPU at 0x155432286260>

4. Access SEACells results¶

4.1 Check model convergence¶

In [26]:
# Plot model convergence
model.plot_convergence()

4.2 Inspect SEACells soft assignment¶

  • SEACells analysis returns soft assignments of cells to SEACells (full assignment matrix can be accessed at model.A_)
  • The majority of single-cells are assigned to no more than 4 archetypes with non-trivial weight
  • The algorithm returns the top 5 metacell assignments as well as the corresponding assignment weights in the function model.get_soft_assignments()
In [27]:
#model.A_

plt.figure(figsize=(4,3))
sns.distplot((model.A_.T > 0.1).sum(axis=1), kde=False)
plt.title(f'Non-trivial (> 0.1) assignments per cell')
plt.xlabel('# Non-trivial SEACell Assignments')
plt.ylabel('# Cells')
plt.show()

plt.figure(figsize=(4,3))
b = np.partition(model.A_.T, -5)    
sns.heatmap(np.sort(b[:,-5:])[:, ::-1], cmap='viridis', vmin=0)
plt.title('Strength of top 5 strongest assignments')
plt.xlabel('$n^{th}$ strongest assignment')
plt.show()
/tmp/ipykernel_137042/2880491417.py:4: UserWarning: 

`distplot` is a deprecated function and will be removed in seaborn v0.14.0.

Please adapt your code to use either `displot` (a figure-level function with
similar flexibility) or `histplot` (an axes-level function for histograms).

For a guide to updating your code to use the new functions, please see
https://gist.github.com/mwaskom/de44147ed2974457ad6372750bbe5751

  sns.distplot((model.A_.T > 0.1).sum(axis=1), kde=False)
In [28]:
labels,weights = model.get_soft_assignments()
#labels.head()

4.3 Inspect and retrieve SEACells hard assignment¶

We will use hard assignment of each cell to a single metacell to define our metacells for downstream steps. Hard assingment can be retreived in two ways:

  • in the modified anndata object in .obs['SEACell']
  • from the model using .get_hard_assignments()
In [29]:
adata.obs[['SEACell']].head()

# Alternatively: 
# model.get_hard_assignments().head()
Out[29]:
SEACell
index
ARKFrozen-18-PFC_AAACAGCCAAGGTGCA-1 SEACell-710
ARKFrozen-18-PFC_AAACAGCCACTAAATC-1 SEACell-206
ARKFrozen-18-PFC_AAACAGCCAGGTTCAC-1 SEACell-763
ARKFrozen-18-PFC_AAACAGCCATCCATCT-1 SEACell-237
ARKFrozen-18-PFC_AAACAGCCATGAATAG-1 SEACell-392

5. Summarizing data by hard assignment¶

  • The original dataset can be summarized by SEACell by aggregating cells within each SEACell, summing over all raw data for all cells belonging to a SEACell.
  • The output of this function is an anndata object of shape n_metacells x original_data_dimension.
  • Data is unnormalized and raw aggregated counts are stored in X.
  • Attributes associated with variables (.var) are copied over, but relevant per SEACell attributes must be manually copied, since certain attributes may need to be summed, or averaged etc, depending on the attribute.
In [30]:
# By default, ad.raw is used for summarization. Other layers present in the anndata can be specified using the parameter summarize_layer parameter
SEA_adata = SEACells.core.summarize_by_SEACell(adata, SEACells_label='SEACell', summarize_layer='counts', celltype_label='cell_label')
SEA_adata
100%|██████████| 860/860 [00:14<00:00, 60.06it/s]
/env/lib/python3.10/site-packages/anndata/_core/anndata.py:430: FutureWarning: The dtype argument is deprecated and will be removed in late 2024.
  warnings.warn(
Out[30]:
AnnData object with n_obs × n_vars = 860 × 20266
    obs: 'cell_label', 'cell_label_purity'
    layers: 'raw'
In [31]:
SEA_adata.obs.head()
Out[31]:
cell_label cell_label_purity
SEACell-710 ExN 1.000000
SEACell-206 ExN 1.000000
SEACell-763 RadialGlia 1.000000
SEACell-237 InN 0.972973
SEACell-392 ExN 1.000000
In [32]:
print(SEA_adata.X[40:43, 40:43])
<Compressed Sparse Row sparse matrix of dtype 'float64'
	with 5 stored elements and shape (3, 3)>
  Coords	Values
  (0, 1)	1.0
  (1, 1)	1.0
  (1, 2)	4.0
  (2, 1)	3.0
  (2, 2)	4.0

6. Summarize metadata (for hard assignment)¶

  • Metadata that is available at the cell level can be summarized as an annotation at the metacell level
  • Countinous variables are averaged across the cells assigned to each metacell
  • Categorical variables are determined by the most prevalent category amongst the cells assigned to the metacell
In [33]:
adata.obs['brain_region']
Out[33]:
index
ARKFrozen-18-PFC_AAACAGCCAAGGTGCA-1    PreFrontalCortex
ARKFrozen-18-PFC_AAACAGCCACTAAATC-1    PreFrontalCortex
ARKFrozen-18-PFC_AAACAGCCAGGTTCAC-1    PreFrontalCortex
ARKFrozen-18-PFC_AAACAGCCATCCATCT-1    PreFrontalCortex
ARKFrozen-18-PFC_AAACAGCCATGAATAG-1    PreFrontalCortex
                                             ...       
ARKFrozen-8-V1_TTTGTGGCACATTAAC-1          VisualCortex
ARKFrozen-8-V1_TTTGTGGCACTCAACA-1          VisualCortex
ARKFrozen-8-V1_TTTGTGGCAGAAACGT-1          VisualCortex
ARKFrozen-8-V1_TTTGTTGGTCCTCCAA-1          VisualCortex
ARKFrozen-8-V1_TTTGTTGGTTTATGGG-1          VisualCortex
Name: brain_region, Length: 64500, dtype: category
Categories (3, object): ['Neocortex', 'PreFrontalCortex', 'VisualCortex']
In [34]:
top_sampleid = adata.obs['sample_id'].groupby(adata.obs['SEACell']).value_counts().groupby(level=0, group_keys=False).head(1) 
SEA_adata.obs['agg_sample_id'] = top_sampleid[SEA_adata.obs_names].index.get_level_values(1)

top_brain_region = adata.obs['brain_region'].groupby(adata.obs['SEACell']).value_counts().groupby(level=0, group_keys=False).head(1)
SEA_adata.obs['Aggregated_brain_region'] = top_brain_region[SEA_adata.obs_names].index.get_level_values(1)

SEA_adata.obs["Auth_Group"] = 'Second_trimester'

SEA_adata.obs.head()
Out[34]:
cell_label cell_label_purity agg_sample_id Aggregated_brain_region Auth_Group
SEACell-710 ExN 1.000000 ARKFrozen-43-PFC PreFrontalCortex Second_trimester
SEACell-206 ExN 1.000000 ARKFrozen-43-PFC PreFrontalCortex Second_trimester
SEACell-763 RadialGlia 1.000000 ARKFrozen-45-CTX Neocortex Second_trimester
SEACell-237 InN 0.972973 ARKFrozen-41-PFC-2 PreFrontalCortex Second_trimester
SEACell-392 ExN 1.000000 ARKFrozen-18-PFC PreFrontalCortex Second_trimester

7. Visualize results¶

  • plot_2D(): visualize metacell assignments on UMAP or any 2-dimensional embedding froma adata.obsm. Plots can also be coloured by metacell assignment. The visualization allows to assess the representativeness of the calculated metacells: ability to represent the global structure of the original dataset. Better representation corresponds to a more uniform coverage of the dataset.

  • plot_SEACell_sizes(): distribution of number of cells assigned to each metacell. Given to the non-uniform distribution of cell type density, metacells are expected to vary in size: larger metacells are retrieved from denser regions, and smaller metacells from sparser ones. However outlier values may require further inspection.

In [35]:
SEACells.plot.plot_2D(adata, key='X_umap', colour_metacells=False)
/env/lib/python3.10/site-packages/SEACells/plot.py:63: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
  mcs = umap.groupby("SEACell").mean().reset_index()
/env/lib/python3.10/site-packages/seaborn/relational.py:438: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored
  points = ax.scatter(x=x, y=y, **kws)
/env/lib/python3.10/site-packages/seaborn/relational.py:438: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored
  points = ax.scatter(x=x, y=y, **kws)
In [36]:
SEACells.plot.plot_2D(adata, key='X_umap', colour_metacells=True)
/env/lib/python3.10/site-packages/SEACells/plot.py:63: FutureWarning: The default of observed=False is deprecated and will be changed to True in a future version of pandas. Pass observed=False to retain current behavior or observed=True to adopt the future default and silence this warning.
  mcs = umap.groupby("SEACell").mean().reset_index()
/env/lib/python3.10/site-packages/seaborn/relational.py:438: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored
  points = ax.scatter(x=x, y=y, **kws)
/env/lib/python3.10/site-packages/seaborn/relational.py:438: UserWarning: No data for colormapping provided via 'c'. Parameters 'cmap' will be ignored
  points = ax.scatter(x=x, y=y, **kws)
In [37]:
SEACells.plot.plot_SEACell_sizes(adata, bins=10, figsize=(10, 5))
/env/lib/python3.10/site-packages/SEACells/plot.py:130: UserWarning: 

`distplot` is a deprecated function and will be removed in seaborn v0.14.0.

Please adapt your code to use either `displot` (a figure-level function with
similar flexibility) or `histplot` (an axes-level function for histograms).

For a guide to updating your code to use the new functions, please see
https://gist.github.com/mwaskom/de44147ed2974457ad6372750bbe5751

  sns.distplot(label_df.groupby("SEACell").count().iloc[:, 0], bins=bins)
Out[37]:
size
SEACell
SEACell-0 174
SEACell-1 81
SEACell-10 286
SEACell-100 104
SEACell-101 68
... ...
SEACell-95 56
SEACell-96 41
SEACell-97 51
SEACell-98 36
SEACell-99 104

860 rows × 1 columns

8. Quantifying results¶

  • Purity: compute_celltype_purity(ad, col_name) computes the purity of different celltype labels within a SEACell metacell.

  • Compactness: per-SEAcell variance in diffusion components (typically 'X_pca' for RNA). Lower values of compactness suggest more compact/lower variance metacells.

  • Separation: distance between a SEACell and its nth_nbr. If cluster is provided as a string, e.g. 'celltype', nearest neighbors are restricted to have the same celltype value. Higher values of separation suggest better distinction between metacells.

8.1 Purity¶

Cell Population¶

In [38]:
SEACell_purity = SEACells.evaluate.compute_celltype_purity(adata, 'cell_label')

plt.figure(figsize=(6,4))
sns.boxplot(data=SEACell_purity, y='cell_label_purity')
plt.title('cell_label Purity')
sns.despine()
plt.show()
plt.close()

SEACell_purity.head()
Out[38]:
cell_label cell_label_purity
SEACell
SEACell-0 RadialGlia 0.827586
SEACell-1 IPC_ExN 0.703704
SEACell-10 ExN 1.000000
SEACell-100 ExN 1.000000
SEACell-101 InN 1.000000

8.2 Compactness¶

In [39]:
compactness = SEACells.evaluate.compactness(adata, 'X_pca_harmony')

plt.figure(figsize=(6,4))
sns.boxplot(data=compactness, y='compactness')
plt.title('Compactness')
sns.despine()
plt.show()
plt.close()

compactness.head()
computing neighbors
    finished: added to `.uns['neighbors']`
    `.obsp['distances']`, distances for each pair of neighbors
    `.obsp['connectivities']`, weighted adjacency matrix (0:00:12)
Out[39]:
compactness
SEACell
SEACell-0 0.000398
SEACell-1 0.022915
SEACell-10 0.000796
SEACell-100 0.000737
SEACell-101 0.004004

8.3 Separation¶

In [40]:
separation = SEACells.evaluate.separation(adata, 'X_pca_harmony')

plt.figure(figsize=(6,4))
sns.boxplot(data=separation, y='separation')
plt.title('Separation')
sns.despine()
plt.show()
plt.close()

separation.head()
computing neighbors
    finished: added to `.uns['neighbors']`
    `.obsp['distances']`, distances for each pair of neighbors
    `.obsp['connectivities']`, weighted adjacency matrix (0:00:12)
Out[40]:
separation
SEACell
SEACell-0 0.016510
SEACell-1 0.107914
SEACell-10 0.020469
SEACell-100 0.027564
SEACell-101 0.112693

9. Hard assignment: downstream processing¶

  • normalization and log-transformation
  • dimensionality reduction: PCA and UMAP on SEACells
In [41]:
SEA_adata.layers['counts'] = SEA_adata.X.copy()

sc.pp.normalize_total(SEA_adata)
sc.pp.log1p(SEA_adata)
SEA_adata.layers['lognorm'] = SEA_adata.X.copy()

sc.pp.highly_variable_genes(SEA_adata, inplace=True, batch_key="agg_sample_id")
sc.tl.pca(SEA_adata, use_highly_variable=True)
sc.pp.neighbors(SEA_adata, use_rep='X_pca')
sc.tl.umap(SEA_adata)
normalizing counts per cell
    finished (0:00:00)
extracting highly variable genes
    finished (0:00:00)
--> added
    'highly_variable', boolean vector (adata.var)
    'means', float vector (adata.var)
    'dispersions', float vector (adata.var)
    'dispersions_norm', float vector (adata.var)
computing PCA
    with n_comps=50
/env/lib/python3.10/site-packages/scanpy/preprocessing/_pca.py:385: FutureWarning: Argument `use_highly_variable` is deprecated, consider using the mask argument. Use_highly_variable=True can be called through mask_var="highly_variable". Use_highly_variable=False can be called through mask_var=None
  warn(msg, FutureWarning)
    finished (0:00:00)
computing neighbors
    finished: added to `.uns['neighbors']`
    `.obsp['distances']`, distances for each pair of neighbors
    `.obsp['connectivities']`, weighted adjacency matrix (0:00:00)
computing UMAP
    finished: added
    'X_umap', UMAP coordinates (adata.obsm) (0:00:01)
In [42]:
sc.pl.umap(SEA_adata, color=['cell_label'], s=75)
In [43]:
SEA_adata
Out[43]:
AnnData object with n_obs × n_vars = 860 × 20266
    obs: 'cell_label', 'cell_label_purity', 'agg_sample_id', 'Aggregated_brain_region', 'Auth_Group'
    var: 'highly_variable', 'means', 'dispersions', 'dispersions_norm', 'highly_variable_nbatches', 'highly_variable_intersection'
    uns: 'log1p', 'hvg', 'pca', 'neighbors', 'umap', 'cell_label_colors'
    obsm: 'X_pca', 'X_umap'
    varm: 'PCs'
    layers: 'raw', 'counts', 'lognorm'
    obsp: 'distances', 'connectivities'
In [44]:
sc.pl.umap(SEA_adata, color=['agg_sample_id'], s=75)
In [45]:
sc.pl.umap(SEA_adata, color=['Aggregated_brain_region'], s=75)
In [46]:
sc.pl.umap(SEA_adata, color=['cell_label_purity'], s=75)
In [47]:
SEA_adata.obs.columns
Out[47]:
Index(['cell_label', 'cell_label_purity', 'agg_sample_id',
       'Aggregated_brain_region', 'Auth_Group'],
      dtype='object')

10. Saving¶

10.1 Save SEACell Adata¶

In [48]:
SEA_adata
Out[48]:
AnnData object with n_obs × n_vars = 860 × 20266
    obs: 'cell_label', 'cell_label_purity', 'agg_sample_id', 'Aggregated_brain_region', 'Auth_Group'
    var: 'highly_variable', 'means', 'dispersions', 'dispersions_norm', 'highly_variable_nbatches', 'highly_variable_intersection'
    uns: 'log1p', 'hvg', 'pca', 'neighbors', 'umap', 'cell_label_colors', 'agg_sample_id_colors', 'Aggregated_brain_region_colors'
    obsm: 'X_pca', 'X_umap'
    varm: 'PCs'
    layers: 'raw', 'counts', 'lognorm'
    obsp: 'distances', 'connectivities'
In [49]:
SEA_adata.X
Out[49]:
<Compressed Sparse Row sparse matrix of dtype 'float64'
	with 12511062 stored elements and shape (860, 20266)>
In [50]:
SEA_adata.write(output_file)

10.2 Save in other formats¶

In [52]:
%%bash

# save also html and python versions for git
jupyter nbconvert SEACellsWang_SecondTrimester.ipynb --to="python" --output="SEACellsWang_SecondTrimester"
jupyter nbconvert SEACellsWang_SecondTrimester.ipynb --to="html" --output="SEACellsWang_SecondTrimester"
[NbConvertApp] Converting notebook SEACellsWang_SecondTrimester.ipynb to python
[NbConvertApp] Writing 13154 bytes to SEACellsWang_SecondTrimester.py
[NbConvertApp] Converting notebook SEACellsWang_SecondTrimester.ipynb to html
[NbConvertApp] WARNING | Alternative text is missing on 17 image(s).
[NbConvertApp] Writing 7359277 bytes to SEACellsWang_SecondTrimester.html

10.3 Finished computations: timestamp¶

In [53]:
print(datetime.now())
2026-03-11 16:04:08.299139
In [ ]: