Skip to contents

1. Overview

PERCEPTIONx predicts how individual patients respond to cancer drugs. Models are trained on DepMap cell-line screens and applied to a patient’s single-cell expression profile, giving clone-level viability scores and patient-level response stratification.

The core workflow is short — five function calls:

  1. Load the DepMap reference data
  2. Load pre-trained models (no training needed)
  3. Prepare the data with prepare_data() (clustering + rank normalization)
  4. Predict at clone and patient level
  5. Plot the results

Everything else — training new models, evaluating them, validating on DepMap cell lines, and the full plotting suite — is optional and covered in section 3 (Advanced Topics). If you only want prediction scores, section 2 is all you need.


2. Minimal Workflow


2.1 Load the DepMap reference data

load_depmap() downloads and caches the DepMap bulk expression, single-cell expression, drug response (AUC), and cell line annotations. The first run downloads about 500 MB; afterwards the cache is reused.

load_depmap(read = TRUE, mirror = TRUE)

The mirror = TRUE argument uses GitHub mirrors for faster downloads in regions with limited connectivity. You can also add custom mirrors:

add_mirrors("https://my-mirror.example.com/PERCEPTIONx")

2.2 Load pre-trained models

PERCEPTIONx ships 44 pre-trained models for FDA-approved drugs, so you can predict without training:

# Load a single model
models <- load_model("abemaciclib", read = TRUE)

# Or several at once
models <- load_model(c("abemaciclib", "erlotinib"), read = TRUE)

If you want to train models for other drugs or settings instead, see section 3.1.


2.3 Prepare the clone-level expression data

PERCEPTIONx models are trained on rank-normalized expression, and clone-level prediction needs clones defined first. prepare_data() does both: it clusters the cells with Seurat (UMAP or t-SNE), merges each clone’s expression, and rank-normalizes it — one call:

prepared <- prepare_data(my_expression_matrix, method = "umap")

The returned list carries everything downstream needs: clone_expression_rnorm (the rank-normalized clone-level matrix) and clone_counts (clone abundances per patient). Rank normalization happens inside prepare_data() — it calls rank_normalization_mat() on the merged clone expression — so you do not run it yourself on this path. See section 3.5 for the details and the skip_clustering option.


2.4 Predict drug sensitivity

Prediction runs in two stages: clone-level scoring, then patient-level aggregation.

2.4.1 Clone-level prediction

predict_drugs() scores each clone against every loaded model, using the clone-level expression from prepare_data(). Lower values mean lower viability, i.e. higher drug sensitivity.

clone_pred <- predict_drugs(models, prepared$clone_expression_rnorm)

The result is a matrix with clones as rows and drugs as columns.

2.4.2 Patient-level aggregation

predict_patients() collapses clone scores to patients using the clone proportions carried in the prepare_data() output:

patient_pred <- predict_patients(clone_pred, prepared)

The default mode = "weighted_max" weights each clone by its proportion and takes the most resistant clone as the patient’s score. Other aggregation modes are documented in section 3.2.3.


2.5 Visualize

Each plotting function takes the corresponding prediction output and returns a ggplot. This example overlays predicted viability on a t-SNE embedding:

plot_tsne_response(
  tsne_data = tsne_data,
  color_var = "viability_scaled",
  title     = "Drug Response"
)

The full set of plot functions — clone distribution, lollipop, ROC, boxplot, UMAP overlays, and a composite panel — is listed in section 3.4.


3. Advanced Topics

Everything in this section is optional. Read the parts you need; the minimal workflow in section 2 does not depend on any of it.


3.1 Train your own models

Training is only needed when you want drugs or settings not covered by the 44 pre-trained models. train_models() presets every argument except the drug list, so the whole call is one line:

models <- train_models(drug_list = "erlotinib")

That is all it takes — cancer type (PanCan), model type (glmnet), cross-validation, and feature ranking are handled with sensible defaults. The knobs you might actually touch:

Argument What it does Default
GOI Restrict features to a curated gene set all shared genes
cancer_type / exclude_cancer Cancer types to include / leave out (self-validation) "PanCan"
model_type "glmnet" (elastic net) or "rf" (random forest) "glmnet"
ncores Parallel cores 4
output_dir Where fitted models are saved "./models"

One thing worth knowing: output_dir defaults to "./models", meaning models are written to disk. Set output_dir = NULL to keep them in memory only.

The returned models object plugs straight into predict_drugs() (section 2.4), and can be saved or re-loaded later:

saveRDS(models, "my_models.RDS")
models <- readRDS("my_models.RDS")

3.2 Evaluate model performance

Two helpers answer how well the trained models stratify responders from non-responders:

perf <- compare_performance(models, threshold = 0.3, verbose = TRUE)
plot_model_performance(models)

sig_models <- get_significant_models(
  models,
  min_correlation = 0.3,
  max_pvalue = 0.05
)

3.2.1 Aggregation modes

predict_patients() offers the five clone-to-patient strategies tested in the paper (Fig. 2c):

Mode Description
weighted_max Most resistant clone, weighted by its abundance (default; the paper’s best strategy, AUC = 0.83)
weighted_average All clones weighted by their proportions
average Unweighted mean across clones
min Most sensitive clone (lowest viability)
max Most resistant clone, unweighted

3.2.2 Pseudo-bulk expression

For bulk-level analyses, aggregate single-cell expression per patient:

pseudo_bulk <- each_patient_pseudo_bulk(
  sc_expression = sc_expression_rnorm,
  patient_clone_map = clone_mapping
)

3.3 Validate predictions on DepMap cell lines

This is a verification exercise, not part of the prediction path: it predicts viability for held-out DepMap cell lines and checks the scores against known drug response. It is the pattern used to produce the validation figures in the paper.

cellLines_test <- get_cellLine_list(
  infunc_cancerType = "PanCan",
  infunc_drugName   = "erlotinib",
  exclude_cancer    = "PanCan",
  infunc_response   = get_response_matrix("erlotinib")
)[[2]]

test_cells <- DepMap$metadata_CPM_scRNA$NAME[
  DepMap$metadata_CPM_scRNA$DepMap_ID %in% cellLines_test]
expr_test <- DepMap$CPM_scRNA_CCLE_rnorm[, test_cells, drop = FALSE]

cell_pred <- predict_drugs(models, expr_test)

For your own patient data, skip this block entirely and go straight to predict_drugs() as in section 2.4.


3.4 The plotting suite

Every plot function accepts tooltip = TRUE (default): when the ggiraph package is installed, points and bars get hover tooltips (clone id, viability score, proportion, FPR/TPR). Set tooltip = FALSE for a plain static ggplot with the identical layout. Tooltip text is auto-built from the plot data; override it with the tooltip_col argument when a custom text column already exists.

# t-SNE / UMAP with drug response overlay
plot_tsne_response(tsne_data, color_var = "viability_scaled", title = "Drug Response")

# Biomarker vs. viability side-by-side
plot_tsne_biomarker_viability(
  tsne_data, biomarker_var = "biomarker_scaled", viability_var = "viability_scaled",
  biomarker_label = "Biomarker Expression", viability_label = "Drug Viability"
)

# Clone abundance stacked bar chart
plot_clone_distribution(clone_distribution = clone_dist_df, response_var = "response")

# Clone-level viability lollipop
plot_clone_viability(
  clone_viability = clone_viability_df,
  viability_var = "comb_viability",
  weights_var = "weights"          # optional: point size by clone proportion
)

# Responder vs. non-responder boxplot
plot_response_boxplot(
  exp_vs_pred = exp_vs_pred,
  response_var = "response",
  predicted_var = "predicted_viability"
)

# ROC curve with AUC
plot_roc_curve(response = response, predictor = predictor, smooth_curve = TRUE)

# Seurat clustering and UMAP
result <- plot_seurat_clustering(expr_matrix)
print(result$umap_plot)

# Composite patient response panel
plot_patient_response_panel(
  clone_distribution = clone_dist_df,
  clone_viability = clone_viability_df,
  exp_vs_pred = exp_vs_pred,
  viability_col = "comb_viability"
)

Interactive example:

plot_clone_viability(
  clone_viability = clone_viability_df,
  viability_var = "comb_viability",
  weights_var = "weights",
  tooltip = TRUE
)

3.5 prepare_data() and Seurat clustering

The prepare_data() function turns a raw expression matrix into the clone-level inputs used by prediction and visualization: it clusters cells with Seurat (UMAP or t-SNE), assigns clones, merges each clone’s expression, and rank-normalizes it (internally calling rank_normalization_mat()), returning a list with clone_expression_rnorm and clone_counts.

prepared <- prepare_data(expr_matrix, method = "umap", resolution = 0.5)
clone_pred <- predict_drugs(models, prepared$clone_expression_rnorm)
patient_pred <- predict_patients(clone_pred, prepared)

Useful options: resolution (Seurat clustering resolution), genes_to_use (restrict the gene space before clustering), and skip_clustering = TRUE — if your data is already clone-level (one column per clone), prepare_data() skips clustering and only builds the normalized clone matrix and counts.

Clone identity is carried in the row names as Patient@@Clone; parse_clone_keys() splits them, and clone_mean_expression() / build_clone_counts() work on the cloned data for downstream analyses.


3.6 The Shiny web application

PERCEPTION-shiny wraps the whole pipeline in a point-and-click dashboard. Heavy computation (training, Seurat clustering, prediction, plotting) runs in background worker processes, so the interface stays responsive even while a large job runs. See the Shiny App Guide for a full walkthrough.

library(PERCEPTIONx)
run_perception_app()          # starts the app in your browser

or directly from the source tree:

shiny::runApp(system.file("shiny", "app", package = "PERCEPTIONx"))

The app has five tabs — Data, Train, Predict, Visualize, and Help — and ships with a Load Demo button that generates a small synthetic dataset (49 genes x 400 cells x 20 patients) to smoke-test the whole flow. Figures in the app are interactive SVG (ggiraph-based), with static downloads available as 600 dpi PNG or vector PDF/SVG.


3.7 End-to-end pipeline script

A ready-to-run script that executes the full workflow (train -> predict -> plot) and saves all output figures lives in the source repository under tools/ (not shipped with the installed package). After cloning the repo:

source("tools/test_pipeline.R")

4. Citation

If you use this app or package, please cite both the package and the original methodology paper:

  • Jia Ding. PERCEPTIONx: Personalized Drug Response Prediction from Single-Cell Transcriptomics. R package version 0.1.0. https://github.com/WangLabCSU/PERCEPTIONx
  • Sinha, S., Vegesna, R., Mukherjee, S. et al. PERCEPTION predicts patient response and resistance to treatment using single-cell transcriptomics of their tumors. Nature Cancer 5, 938–952 (2024). DOI: 10.1038/s43018-024-00756-7

Repository: github.com/WangLabCSU/PERCEPTIONx

Feedback: