Learning Single-Cell with Theis Lab: Not All Features Are Created Equal

· 8 min read · Tutorials

Learning Single-Cell with Theis Lab: Not All Features Are Created Equal

Single-Cell Best Practices

Feature Selection

Chapter Overview — SECTION PREVIEW

1. Why feature selection is necessary and what it aims to achieve

2. Limitations of traditional methods and the new deviance-based approach

3. Hands-on: Feature selection with the scry package

This section corresponds to Chapter 8: Feature Selection in the original book

In single-cell RNA-seq data analysis, we often face a common challenge: sequencing may detect up to 30,000 genes, but many of these have zero or near-zero expression and contribute little to downstream analyses. Today, we'll learn how to use Feature Selection to identify the genes that are truly biologically meaningful, improving the efficiency of our analysis.

01

Why Do We Need Feature Selection?

After data normalization, our data has been corrected for technical noise while preserving biological variation — but a problem remains: scRNA-seq typically detects up to 30,000 genes, many of which are either not expressed or expressed at very low levels, contributing little to our understanding of cell states and types.

It's like searching for key individuals in a crowd of thousands — we need to sift through those 30,000 genes and find the "star genes" that truly reflect the biological characteristics of each cell. That's exactly what feature selection is for.

feature_selection.jpeg (3292×1040)

The Essence of Feature Selection

The core goal of feature selection is to identify a subset of genes that satisfies three criteria:

  • • High information content: Captures the major differences between cells
  • • High variability: Shows clear variation across cell types or states
  • • Low noise: Not driven by random technical fluctuations

Typically, single-cell experiments focus on a specific tissue, so only a small fraction of genes exhibit biologically meaningful expression variation. Most genes are either not expressed in any cell (tissue-specific genes outside the tissue of interest) or are expressed at constant levels (housekeeping genes).

Limitations of Traditional Methods

Traditional approaches to feature selection follow two main strategies:

    1. Highly Variable Genes (HVGs)
  • • Compute the coefficient of variation for each gene
  • • Select the top 500–2,000 most variable genes
  • (This is the approach used by the popular Seurat package's FindVariableFeatures function)
    1. Highly Expressed Genes
  • • Rank genes by mean expression level
  • • Select the most highly expressed genes

However, these methods have a critical flaw: remember the log1p transformation we discussed in the previous chapter? Since you can't take the logarithm of zero, a pseudocount (typically 1) is added. This seemingly harmless operation can seriously distort downstream variance calculations.

A quick note: The choice of pseudocount is arbitrary. Using 0.1, 1, or 10 yields completely different variance estimates, especially for lowly expressed genes. Studies have shown that small pseudocount values artificially inflate the variance of zero-count genes.

02

Deviance-Based Feature Selection

The Principle Behind the Deviance Approach

Germain et al. proposed an elegant solution: using deviance for feature selection. The advantages of this approach are (terminology alert ⚠️):

    1. Works directly on raw counts: No normalization needed, avoiding the pseudocount problem entirely
    1. Has a clear statistical interpretation: Deviance measures how well the data fits a null model
    1. Computationally simple: Can be calculated using a closed-form solution

What Is Deviance?

In simple terms, deviance quantifies whether a gene has a constant expression pattern across all cells. If a gene is expressed at roughly the same level in every cell (consistent with the null model), its deviance is low. Conversely, if a gene shows large expression differences across cells (deviating from the null model), its deviance is high.

High deviance = High information content = A gene worth paying attention to

03

Hands-On: Feature Selection with scry

First, import the required packages:

import logging
import anndata2ri
import matplotlib.pyplot as plt
import numpy as np
import rpy2.rinterface_lib.callbacks as rcb
import rpy2.robjects as ro
import scanpy as sc
import seaborn as sns

# Set up the environment
sc.settings.verbosity = 0
sc.settings.set_figure_params(
    dpi=80,
    facecolor="white",
    frameon=False,
)

# Configure the R interface
rcb.logger.setLevel(logging.ERROR)
ro.pandas2ri.activate()
anndata2ri.activate()

%load_ext rpy2.ipython

This tutorial is designed to run in a Jupyter notebook. If you prefer to run it in R (e.g., R Markdown), refer to Learning Single-Cell with Theis Lab: Format Conversion — Understanding the Underlying Logic (Part 2) for implementing the Python portions.

Load the R package:

%%R
library(scry)

Load the normalized data (i.e., the data saved in the previous chapter, Learning Single-Cell with Theis Lab: The Math Behind Normalization. If you want to jump right in, the data download link is available at the end of this article):

# Load data saved from the previous chapter
adata = sc.read(
    filename="s4d8_normalization.h5ad",
    backup_url="https://figshare.com/ndownloader/files/40015741",
)

# Pass the data to the R environment
ro.globalenv["adata"] = adata

Note: The deviance method operates on raw counts (stored in adata.X) and does not require any normalization.

Compute deviance and select features:

%%R
# Compute binomial deviance using scry
sce = devianceFeatureSelection(adata, assay="X")

Export the deviance values back to Python:

# Extract deviance values
binomial_deviance = ro.r("rowData(sce)$binomial_deviance").T

Select the top 4,000 genes by deviance:

# Sort by deviance and select the top 4000
idx = binomial_deviance.argsort()[-4000:]
mask = np.zeros(adata.var_names.shape, dtype=bool)
mask[idx] = True

# Save results to the adata object
adata.var["highly_deviant"] = mask
adata.var["binomial_deviance"] = binomial_deviance

To visualize the effect of feature selection, let's create a scatter plot of mean expression vs. dispersion for each gene:

# Compute per-gene statistics using Scanpy
sc.pp.highly_variable_genes(adata, layer="scran_normalization")

# Create the scatter plot
ax = sns.scatterplot(
    data=adata.var, x="means", y="dispersions", hue="highly_deviant", s=5
)
ax.set_xlim(None, 1.5)
ax.set_ylim(None, 3)
plt.show()

Highly deviant genesHighly deviant genes

From the plot, we can observe that genes with high mean expression tend to be selected as highly deviant genes. In real-world data, highly expressed genes are often the most biologically meaningful ones.

Finally, save the results:

adata.write("s4d8_feature_selection.h5ad")

06

Afterword

When using the deviance method, selecting 2,000–5,000 highly deviant genes is generally a good starting point. Too few genes may cause you to lose important biological signals, while too many will introduce excessive noise. Of course, the optimal number may vary depending on the tissue and experimental conditions. If you plan to perform fine-grained subpopulation analysis downstream, consider increasing the number of selected genes. You can also revisit and adjust this parameter later when evaluating clustering quality.

What do you think — does the deviance-based approach outperform traditional methods?


Originally posted on WeChat Official Account: bioinfo_studyroom

Single Cell Tutorial