Skip to contents

This function performs KNN prediction to annotate cell types based on reference scRNA-seq or bulk RNA-seq data.

Usage

RunKNNPredict(
  srt_query,
  srt_ref = NULL,
  bulk_ref = NULL,
  query_group = NULL,
  ref_group = NULL,
  query_assay = NULL,
  ref_assay = NULL,
  query_reduction = NULL,
  ref_reduction = NULL,
  query_dims = 1:30,
  ref_dims = 1:30,
  query_collapsing = !is.null(query_group),
  ref_collapsing = TRUE,
  return_full_distance_matrix = FALSE,
  features = NULL,
  features_type = c("HVF", "DE"),
  feature_source = "both",
  nfeatures = 2000,
  DEtest_param = list(max.cells.per.ident = 200, test.use = "wilcox"),
  DE_threshold = "p_val_adj < 0.05",
  nn_method = NULL,
  distance_metric = "cosine",
  k = 30,
  filter_lowfreq = 0,
  prefix = "KNNPredict"
)

Arguments

srt_query

An object of class Seurat to be annotated with cell types.

srt_ref

An object of class Seurat storing the reference cells.

bulk_ref

A cell atlas matrix, where cell types are represented by columns and genes are represented by rows, for example, scop::ref_scHCL. Either `srt_ref` or `bulk_ref` must be provided.

query_group

A character vector specifying the column name in the `srt_query` metadata that represents the cell grouping.

ref_group

A character vector specifying the column name in the `srt_ref` metadata that represents the cell grouping.

query_assay

A character vector specifying the assay to be used for the query data. Defaults to the default assay of the `srt_query` object.

ref_assay

A character vector specifying the assay to be used for the reference data. Defaults to the default assay of the `srt_ref` object.

query_reduction

A character vector specifying the dimensionality reduction method used for the query data. If NULL, the function will use the default reduction method specified in the `srt_query` object.

ref_reduction

A character vector specifying the dimensionality reduction method used for the reference data. If NULL, the function will use the default reduction method specified in the `srt_ref` object.

query_dims

A numeric vector specifying the dimensions to be used for the query data. Defaults to the first 30 dimensions.

ref_dims

A numeric vector specifying the dimensions to be used for the reference data. Defaults to the first 30 dimensions.

query_collapsing

A boolean value indicating whether the query data should be collapsed to group-level average expression values. If TRUE, the function will calculate the average expression values for each group in the query data and the annotation will be performed separately for each group. Otherwise it will use the raw expression values for each cell.

ref_collapsing

A boolean value indicating whether the reference data should be collapsed to group-level average expression values. If TRUE, the function will calculate the average expression values for each group in the reference data and the annotation will be performed separately for each group. Otherwise it will use the raw expression values for each cell.

return_full_distance_matrix

A boolean value indicating whether the full distance matrix should be returned. If TRUE, the function will return the distance matrix used for the KNN prediction, otherwise it will only return the annotated cell types.

features

A character vector specifying the features (genes) to be used for the KNN prediction. If NULL, all the features in the query and reference data will be used.

features_type

A character vector specifying the type of features to be used for the KNN prediction. Must be one of "HVF" (highly variable features) or "DE" (differentially expressed features). Defaults to "HVF".

feature_source

A character vector specifying the source of the features to be used for the KNN prediction. Must be one of "both", "query", or "ref". Defaults to "both".

nfeatures

An integer specifying the maximum number of features to be used for the KNN prediction. Defaults to 2000.

DEtest_param

A list of parameters to be passed to the differential expression test function if `features_type` is set to "DE". Defaults to `list(max.cells.per.ident = 200, test.use = "wilcox")`.

DE_threshold

Threshold used to filter the DE features. Default is "p_val < 0.05". If using "roc" test, DE_threshold should be needs to be reassigned. e.g. "power > 0.5".

nn_method

A character vector specifying the method to be used for finding nearest neighbors. Must be one of "raw", "rann", or "annoy". Defaults to "raw".

distance_metric

A character vector specifying the distance metric to be used for calculating similarity between cells. Must be one of "cosine", "euclidean", "manhattan", or "hamming". Defaults to "cosine".

k

A number of nearest neighbors to be considered for the KNN prediction. Defaults to 30.

filter_lowfreq

An integer specifying the threshold for filtering low-frequency cell types from the predicted results. Cell types with a frequency lower than `filter_lowfreq` will be labelled as "unreliable". Defaults to 0, which means no filtering will be performed.

prefix

A character vector specifying the prefix to be added to the resulting annotations. Defaults to "KNNPredict".

Examples

# Annotate cells using bulk RNA-seq data
data(pancreas_sub)
data(ref_scMCA)
pancreas_sub <- standard_scop(pancreas_sub)
#>  [2025-09-20 13:43:57] Start standard scop workflow...
#>  [2025-09-20 13:43:58] Checking a list of <Seurat> object...
#> ! [2025-09-20 13:43:58] Data 1/1 of the `srt_list` is "unknown"
#>  [2025-09-20 13:43:58] Perform `NormalizeData()` with `normalization.method = 'LogNormalize'` on the data 1/1 of the `srt_list`...
#>  [2025-09-20 13:44:00] Perform `Seurat::FindVariableFeatures()` on the data 1/1 of the `srt_list`...
#>  [2025-09-20 13:44:00] Use the separate HVF from srt_list
#>  [2025-09-20 13:44:00] Number of available HVF: 2000
#>  [2025-09-20 13:44:00] Finished check
#>  [2025-09-20 13:44:01] Perform `Seurat::ScaleData()`
#> Warning: Different features in new layer data than already exists for scale.data
#>  [2025-09-20 13:44:01] Perform pca linear dimension reduction
#> StandardPC_ 1 
#> Positive:  Aplp1, Cpe, Gnas, Fam183b, Map1b, Hmgn3, Pcsk1n, Chga, Tuba1a, Bex2 
#> 	   Syt13, Isl1, 1700086L19Rik, Pax6, Chgb, Scgn, Rbp4, Scg3, Gch1, Camk2n1 
#> 	   Cryba2, Pcsk2, Pyy, Tspan7, Mafb, Hist3h2ba, Dbpht2, Abcc8, Rap1b, Slc38a5 
#> Negative:  Spp1, Anxa2, Sparc, Dbi, 1700011H14Rik, Wfdc2, Gsta3, Adamts1, Clu, Mgst1 
#> 	   Bicc1, Ldha, Vim, Cldn3, Cyr61, Rps2, Mt1, Ptn, Phgdh, Nudt19 
#> 	   Smtnl2, Smco4, Habp2, Mt2, Col18a1, Rpl12, Galk1, Cldn10, Acot1, Ccnd1 
#> StandardPC_ 2 
#> Positive:  Rbp4, Tagln2, Tuba1b, Fkbp2, Pyy, Pcsk2, Iapp, Tmem27, Meis2, Tubb4b 
#> 	   Pcsk1n, Dbpht2, Rap1b, Dynll1, Tubb2a, Sdf2l1, Scgn, 1700086L19Rik, Scg2, Abcc8 
#> 	   Atp1b1, Hspa5, Fam183b, Papss2, Slc38a5, Scg3, Mageh1, Tspan7, Ppp1r1a, Ociad2 
#> Negative:  Neurog3, Btbd17, Gadd45a, Ppp1r14a, Neurod2, Sox4, Smarcd2, Mdk, Pax4, Btg2 
#> 	   Sult2b1, Hes6, Grasp, Igfbpl1, Gpx2, Cbfa2t3, Foxa3, Shf, Mfng, Tmsb4x 
#> 	   Amotl2, Gdpd1, Cdc14b, Epb42, Rcor2, Cotl1, Upk3bl, Rbfox3, Cldn6, Cer1 
#> StandardPC_ 3 
#> Positive:  Nusap1, Top2a, Birc5, Aurkb, Cdca8, Pbk, Mki67, Tpx2, Plk1, Ccnb1 
#> 	   2810417H13Rik, Incenp, Cenpf, Ccna2, Prc1, Racgap1, Cdk1, Aurka, Cdca3, Hmmr 
#> 	   Spc24, Kif23, Sgol1, Cenpe, Cdc20, Hist1h1b, Cdca2, Mxd3, Kif22, Ska1 
#> Negative:  Anxa5, Pdzk1ip1, Acot1, Tpm1, Anxa2, Dcdc2a, Capg, Sparc, Ttr, Pamr1 
#> 	   Clu, Cxcl12, Ndrg2, Hnf1aos1, Gas6, Gsta3, Krt18, Ces1d, Atp1b1, Muc1 
#> 	   Hhex, Acadm, Spp1, Enpp2, Bcl2l14, Sat1, Smtnl2, 1700011H14Rik, Tgm2, Fam159a 
#> StandardPC_ 4 
#> Positive:  Glud1, Tm4sf4, Akr1c19, Cldn4, Runx1t1, Fev, Pou3f4, Gm43861, Pgrmc1, Arx 
#> 	   Cd200, Lrpprc, Hmgn3, Ppp1r14c, Pam, Etv1, Tsc22d1, Slc25a5, Akap17b, Pgf 
#> 	   Fam43a, Emb, Jun, Krt8, Dnajc12, Mid1ip1, Ids, Rgs17, Uchl1, Alcam 
#> Negative:  Ins2, Ins1, Ppp1r1a, Nnat, Calr, Sytl4, Sdf2l1, Iapp, Pdia6, Mapt 
#> 	   G6pc2, C2cd4b, Npy, Gng12, P2ry1, Ero1lb, Adra2a, Papss2, Arhgap36, Fam151a 
#> 	   Dlk1, Creld2, Gip, Tmem215, Gm27033, Cntfr, Prss53, C2cd4a, Lyve1, Ociad2 
#> StandardPC_ 5 
#> Positive:  Pdx1, Nkx6-1, Npepl1, Cldn4, Cryba2, Fev, Jun, Chgb, Gng12, Adra2a 
#> 	   Mnx1, Sytl4, Pdk3, Gm27033, Nnat, Chga, Ins2, 1110012L19Rik, Enho, Krt7 
#> 	   Mlxipl, Tmsb10, Flrt1, Pax4, Tubb3, Prrg2, Gars, Frzb, BC023829, Gm2694 
#> Negative:  Irx2, Irx1, Gcg, Ctxn2, Tmem27, Ctsz, Tmsb15l, Nap1l5, Pou6f2, Gria2 
#> 	   Ghrl, Peg10, Smarca1, Arx, Lrpap1, Rgs4, Ttr, Gast, Tmsb15b2, Serpina1b 
#> 	   Slc16a10, Wnk3, Ly6e, Auts2, Sct, Arg1, Dusp10, Sphkap, Dock11, Edn3 
#>  [2025-09-20 13:44:02] Perform `Seurat::FindClusters()` with louvain and `cluster_resolution` = 0.6
#>  [2025-09-20 13:44:02] Reorder clusters...
#> ! [2025-09-20 13:44:02] Using `Seurat::AggregateExpression()` to calculate pseudo-bulk data for <Assay5>
#>  [2025-09-20 13:44:02] Perform umap nonlinear dimension reduction
#>  [2025-09-20 13:44:02] Non-linear dimensionality reduction (umap) using (Standardpca) dims (1-50) as input
#>  [2025-09-20 13:44:02] UMAP will return its model
#>  [2025-09-20 13:44:06] Non-linear dimensionality reduction (umap) using (Standardpca) dims (1-50) as input
#>  [2025-09-20 13:44:06] UMAP will return its model
#>  [2025-09-20 13:44:11] Run scop standard workflow done

# Set the number of threads for RcppParallel
# details see: ?RcppParallel::setThreadOptions
# if (requireNamespace("RcppParallel", quietly = TRUE)) {
#   RcppParallel::setThreadOptions()
# }
pancreas_sub <- RunKNNPredict(
  srt_query = pancreas_sub,
  bulk_ref = ref_scMCA
)
#>  [2025-09-20 13:44:11] Use 549 features to calculate distance.
#>  [2025-09-20 13:44:11] Detected query data type: "log_normalized_counts"
#>  [2025-09-20 13:44:11] Detected reference data type: "log_normalized_counts"
#>  [2025-09-20 13:44:11] Calculate similarity...
#>  [2025-09-20 13:44:11] Use raw method to find neighbors
#>  [2025-09-20 13:44:11] Predict cell type...
CellDimPlot(
  pancreas_sub,
  group.by = "KNNPredict_classification",
  label = TRUE
)
#> Warning: No shared levels found between `names(values)` of the manual scale and the
#> data's fill values.


# Removal of low credible cell types from the predicted results
pancreas_sub <- RunKNNPredict(
  srt_query = pancreas_sub,
  bulk_ref = ref_scMCA,
  filter_lowfreq = 30
)
#>  [2025-09-20 13:44:12] Use 549 features to calculate distance.
#>  [2025-09-20 13:44:12] Detected query data type: "log_normalized_counts"
#>  [2025-09-20 13:44:12] Detected reference data type: "log_normalized_counts"
#>  [2025-09-20 13:44:12] Calculate similarity...
#>  [2025-09-20 13:44:12] Use raw method to find neighbors
#>  [2025-09-20 13:44:12] Predict cell type...
CellDimPlot(
  pancreas_sub,
  group.by = "KNNPredict_classification",
  label = TRUE
)
#> Warning: No shared levels found between `names(values)` of the manual scale and the
#> data's fill values.


# Annotate clusters using bulk RNA-seq data
pancreas_sub <- RunKNNPredict(
  srt_query = pancreas_sub,
  query_group = "SubCellType",
  bulk_ref = ref_scMCA
)
#>  [2025-09-20 13:44:13] Use 549 features to calculate distance.
#>  [2025-09-20 13:44:13] Detected query data type: "log_normalized_counts"
#>  [2025-09-20 13:44:13] Detected reference data type: "log_normalized_counts"
#>  [2025-09-20 13:44:13] Calculate similarity...
#>  [2025-09-20 13:44:13] Use raw method to find neighbors
#>  [2025-09-20 13:44:13] Predict cell type...
CellDimPlot(
  pancreas_sub,
  group.by = "KNNPredict_classification",
  label = TRUE
)
#> Warning: No shared levels found between `names(values)` of the manual scale and the
#> data's fill values.


# Annotate using single cell RNA-seq data
data(panc8_sub)
# Simply convert genes from human to mouse and preprocess the data
genenames <- make.unique(
  thisutils::capitalize(
    rownames(panc8_sub),
    force_tolower = TRUE
  )
)
names(genenames) <- rownames(panc8_sub)
panc8_sub <- RenameFeatures(
  panc8_sub,
  newnames = genenames
)
#>  [2025-09-20 13:44:13] Rename features for the assay: RNA
panc8_sub <- CheckDataMerge(
  panc8_sub,
  batch = "tech"
)[["srt_merge"]]
#>  [2025-09-20 13:44:14] Spliting `srt_merge` into `srt_list` by column "tech"...
#>  [2025-09-20 13:44:14] Checking a list of <Seurat> object...
#> ! [2025-09-20 13:44:14] Data 1/5 of the `srt_list` is "unknown"
#>  [2025-09-20 13:44:14] Perform `NormalizeData()` with `normalization.method = 'LogNormalize'` on the data 1/5 of the `srt_list`...
#>  [2025-09-20 13:44:16] Perform `Seurat::FindVariableFeatures()` on the data 1/5 of the `srt_list`...
#> ! [2025-09-20 13:44:16] Data 2/5 of the `srt_list` is "unknown"
#>  [2025-09-20 13:44:16] Perform `NormalizeData()` with `normalization.method = 'LogNormalize'` on the data 2/5 of the `srt_list`...
#>  [2025-09-20 13:44:18] Perform `Seurat::FindVariableFeatures()` on the data 2/5 of the `srt_list`...
#> ! [2025-09-20 13:44:18] Data 3/5 of the `srt_list` is "unknown"
#>  [2025-09-20 13:44:18] Perform `NormalizeData()` with `normalization.method = 'LogNormalize'` on the data 3/5 of the `srt_list`...
#>  [2025-09-20 13:44:20] Perform `Seurat::FindVariableFeatures()` on the data 3/5 of the `srt_list`...
#> ! [2025-09-20 13:44:20] Data 4/5 of the `srt_list` is "unknown"
#>  [2025-09-20 13:44:20] Perform `NormalizeData()` with `normalization.method = 'LogNormalize'` on the data 4/5 of the `srt_list`...
#>  [2025-09-20 13:44:22] Perform `Seurat::FindVariableFeatures()` on the data 4/5 of the `srt_list`...
#> ! [2025-09-20 13:44:22] Data 5/5 of the `srt_list` is "unknown"
#>  [2025-09-20 13:44:22] Perform `NormalizeData()` with `normalization.method = 'LogNormalize'` on the data 5/5 of the `srt_list`...
#>  [2025-09-20 13:44:24] Perform `Seurat::FindVariableFeatures()` on the data 5/5 of the `srt_list`...
#>  [2025-09-20 13:44:24] Use the separate HVF from srt_list
#>  [2025-09-20 13:44:25] Number of available HVF: 2000
#>  [2025-09-20 13:44:25] Finished check
panc8_sub <- SeuratObject::JoinLayers(panc8_sub)
pancreas_sub <- RunKNNPredict(
  srt_query = pancreas_sub,
  srt_ref = panc8_sub,
  ref_group = "celltype"
)
#>  [2025-09-20 13:44:30] Use the HVF to calculate distance metric
#>  [2025-09-20 13:44:30] Use 632 features to calculate distance.
#>  [2025-09-20 13:44:30] Detected query data type: "log_normalized_counts"
#>  [2025-09-20 13:44:30] Detected reference data type: "log_normalized_counts"
#>  [2025-09-20 13:44:30] Calculate similarity...
#>  [2025-09-20 13:44:30] Use raw method to find neighbors
#>  [2025-09-20 13:44:30] Predict cell type...
CellDimPlot(
  pancreas_sub,
  group.by = "KNNPredict_classification",
  label = TRUE
)
#> Warning: No shared levels found between `names(values)` of the manual scale and the
#> data's fill values.

FeatureDimPlot(
  pancreas_sub,
  features = "KNNPredict_simil"
)


pancreas_sub <- RunKNNPredict(
  srt_query = pancreas_sub,
  srt_ref = panc8_sub,
  ref_group = "celltype",
  ref_collapsing = FALSE
)
#>  [2025-09-20 13:44:30] Use the HVF to calculate distance metric
#>  [2025-09-20 13:44:30] Use 632 features to calculate distance.
#>  [2025-09-20 13:44:31] Detected query data type: "log_normalized_counts"
#>  [2025-09-20 13:44:31] Detected reference data type: "log_normalized_counts"
#>  [2025-09-20 13:44:31] Calculate similarity...
#>  [2025-09-20 13:44:31] Use raw method to find neighbors
#>  [2025-09-20 13:44:33] Predict cell type...
CellDimPlot(
  pancreas_sub,
  group.by = "KNNPredict_classification",
  label = TRUE
)
#> Warning: No shared levels found between `names(values)` of the manual scale and the
#> data's fill values.

FeatureDimPlot(
  pancreas_sub,
  features = "KNNPredict_prob"
)


pancreas_sub <- RunKNNPredict(
  srt_query = pancreas_sub,
  srt_ref = panc8_sub,
  query_group = "SubCellType",
  ref_group = "celltype"
)
#>  [2025-09-20 13:44:33] Use the HVF to calculate distance metric
#>  [2025-09-20 13:44:33] Use 632 features to calculate distance.
#>  [2025-09-20 13:44:33] Detected query data type: "log_normalized_counts"
#>  [2025-09-20 13:44:33] Detected reference data type: "log_normalized_counts"
#>  [2025-09-20 13:44:33] Calculate similarity...
#>  [2025-09-20 13:44:33] Use raw method to find neighbors
#>  [2025-09-20 13:44:33] Predict cell type...
CellDimPlot(
  pancreas_sub,
  group.by = "KNNPredict_classification",
  label = TRUE
)
#> Warning: No shared levels found between `names(values)` of the manual scale and the
#> data's fill values.

FeatureDimPlot(
  pancreas_sub,
  features = "KNNPredict_simil"
)


# Annotate with DE gene instead of HVF
pancreas_sub <- RunKNNPredict(
  srt_query = pancreas_sub,
  srt_ref = panc8_sub,
  ref_group = "celltype",
  features_type = "DE",
  feature_source = "ref"
)
#>  [2025-09-20 13:44:34] immunogenomics/presto installed successfully
#>  [2025-09-20 13:44:35] Data type is log-normalized
#>  [2025-09-20 13:44:35] Start differential expression test
#>  [2025-09-20 13:44:35] Find all markers(wilcox) among 13 groups...
#>  [2025-09-20 13:44:35] Using 1 core
#> ⠙ [2025-09-20 13:44:35] Running [1/13] ETA:  2s
#> ⠹ [2025-09-20 13:44:35] Running [9/13] ETA:  1s
#>  [2025-09-20 13:44:35] Completed 13 tasks in 2.1s
#> 
#>  [2025-09-20 13:44:37] Building results
#> Error in names(output_list) <- x: 'names' attribute [13] must be the same length as the vector [12]

CellDimPlot(
  pancreas_sub,
  group.by = "KNNPredict_classification",
  label = TRUE
)
#> Warning: No shared levels found between `names(values)` of the manual scale and the
#> data's fill values.


FeatureDimPlot(
  pancreas_sub,
  features = "KNNPredict_simil"
)


pancreas_sub <- RunKNNPredict(
  srt_query = pancreas_sub,
  srt_ref = panc8_sub,
  query_group = "SubCellType",
  ref_group = "celltype",
  features_type = "DE",
  feature_source = "both"
)
#>  [2025-09-20 13:44:37] immunogenomics/presto installed successfully
#>  [2025-09-20 13:44:38] Data type is log-normalized
#>  [2025-09-20 13:44:38] Start differential expression test
#>  [2025-09-20 13:44:38] Find all markers(wilcox) among 8 groups...
#>  [2025-09-20 13:44:38] Using 1 core
#>  [2025-09-20 13:44:39] Building results
#>  [2025-09-20 13:44:39] Differential expression test completed
#>  [2025-09-20 13:44:39] Use the DE features from AllMarkers_wilcox to calculate distance metric.
#>  [2025-09-20 13:44:39] DE features number of the query data: 2000
#>  [2025-09-20 13:44:39] immunogenomics/presto installed successfully
#>  [2025-09-20 13:44:40] Data type is log-normalized
#>  [2025-09-20 13:44:40] Start differential expression test
#>  [2025-09-20 13:44:40] Find all markers(wilcox) among 13 groups...
#>  [2025-09-20 13:44:40] Using 1 core
#> ⠙ [2025-09-20 13:44:40] Running [1/13] ETA:  3s
#>  [2025-09-20 13:44:40] Completed 13 tasks in 2.2s
#> 
#>  [2025-09-20 13:44:42] Building results
#> Error in names(output_list) <- x: 'names' attribute [13] must be the same length as the vector [12]

CellDimPlot(
  pancreas_sub,
  group.by = "KNNPredict_classification",
  label = TRUE
)
#> Warning: No shared levels found between `names(values)` of the manual scale and the
#> data's fill values.


FeatureDimPlot(
  pancreas_sub,
  features = "KNNPredict_simil"
)