--- title: "Example Workflow for Single-Cell Annotation with easybio" author: "[cw](https://cying.org)" date: "`{r} Sys.Date()`" output: html vignette: > %\VignetteEngine{litedown::vignette} %\VignetteIndexEntry{Example Workflow for Single-Cell Annotation with easybio} %\VignetteEncoding{UTF-8} --- ## Introduction This vignette demonstrates the powerful and intuitive workflow for single-cell RNA-seq annotation provided by the `easybio` package. The process is designed to combine the speed of automated database matching with the reliability of interactive verification and manual curation. The core workflow follows three logical steps: 1. **Automated Annotation**: Use `matchCellMarker2()` to quickly get a list of potential cell types for each cluster based on its marker genes. 2. **Verification & Exploration**: Interactively investigate the automated results using `check_marker()` and `plotSeuratDot()` to build confidence in the annotations. This step helps answer two critical questions: * "**Why** was this annotation made?" (Which of my genes matched the database?) * "Is this annotation **correct**?" (Are the canonical markers for this cell type expressed in my cluster?) 3. **Final Curation**: Based on the evidence gathered, use `finsert()` to assign the final, high-confidence cell type labels. You can also view the R script for this workflow by running: `fs::file_show(system.file(package = 'easybio', 'example-single-cell.R'))` ## Setup First, let's load the necessary libraries and the example marker data included with `easybio`. This data is derived from the 10x Genomics 3k PBMC dataset. ```{r} litedown::reactor(warning = FALSE) # vignette setting library(easybio) library(Seurat) library(data.table) # The pbmc.markers dataset is included in easybio head(pbmc.markers) ``` ## Step 1: Automated Annotation with `matchCellMarker2` We begin by feeding the cluster markers (from `Seurat::FindAllMarkers`) into `matchCellMarker2()`. This function compares our markers against the CellMarker2.0 database and returns a ranked list of potential cell types for each cluster. ```{r} marker_matched <- matchCellMarker2(marker = pbmc.markers, n = 50, spc = "Human") # Let's look at the top 2 potential cell types for each cluster marker_matched[, head(.SD, 2), by = cluster] ``` The output table gives us `uniqueN` (the number of unique matching markers) and `N` (the total number of matches), which helps rank the potential annotations. We can create a quick preliminary annotation by taking the top hit for each cluster. ```{r} cl2cell_auto <- marker_matched[, head(.SD, 1), by = .(cluster)] cl2cell_auto <- setNames(cl2cell_auto[["cell_name"]], cl2cell_auto[["cluster"]]) print("Initial automated annotation:") cl2cell_auto ``` We can also get a global view of all possible annotations using `plotPossibleCell`. ```{r} #| fig.width=10 plotPossibleCell(marker_matched[, head(.SD), by = .(cluster)], min.uniqueN = 2) ``` ## Step 2: Verification and Exploration This is the most critical step. Instead of blindly trusting the automated result, we use `easybio`'s tools to verify it. ### Answering "Why was this annotation made?" To see the evidence behind an annotation, we use `check_marker()` with `cis = TRUE`. This shows us which of **our own marker genes** from our data matched the database for a given annotation. ```{r} # Let's investigate clusters 1, 5, and 7 local_evidence <- check_marker(marker_matched, cl = c(1, 5, 7), topcellN = 2, cis = TRUE) print(local_evidence) ``` ### Answering "Is this annotation correct?" To validate an annotation, we use `check_marker()` with `cis = FALSE` (the default). This fetches the **canonical markers** for the suggested cell type from the database. We can then check if these well-known markers are expressed in our cluster. ```{r} canonical_markers <- check_marker(marker_matched, cl = c(1, 5, 7), topcellN = 2, cis = FALSE) print(canonical_markers) ``` ### Visual Confirmation with `plotSeuratDot` The best way to check marker expression is visually. `plotSeuratDot` is designed to work seamlessly with `check_marker`. The entire pipeline from annotation to visualization can be done in a single, elegant pipe: ```{r, fig.width=9, fig.height=5} # For this example to be runnable, we need a Seurat object. # We'll create a minimal one. In your real workflow, you would use your own srt object. marker_genes <- unique(pbmc.markers$gene) counts <- matrix( abs(rnorm(length(marker_genes) * 50, mean = 1, sd = 2)), nrow = length(marker_genes), ncol = 50 ) rownames(counts) <- marker_genes colnames(counts) <- paste0("cell_", 1:50) srt <- CreateSeuratObject(counts = counts) # Assign clusters that match the pbmc.markers data srt$seurat_clusters <- sample(0:8, 50, replace = TRUE) Idents(srt) <- "seurat_clusters" # Now, let's plot the evidence for clusters 1, 5, and 7 matchCellMarker2(marker = pbmc.markers, n = 50, spc = "Human") |> check_marker(cl = c(1, 5, 7), topcellN = 2, cis = TRUE) |> plotSeuratDot(srt = srt) ``` This dot plot clearly shows the expression of the genes that led to the annotations for clusters 1, 5, and 7, allowing us to confidently assess the results. ## Step 3: Final Manual Curation After reviewing the evidence from the dot plots, we can make our final, informed decision. The `finsert` function provides a convenient way to create the final annotation vector. ```{r} # Based on our exploration, we finalize the annotations cl2cell_final <- finsert( list( c(3) ~ "B cell", c(8) ~ "Megakaryocyte", c(7) ~ "DC", c(1, 5) ~ "Monocyte", c(0, 2, 4) ~ "Naive CD8+ T cell", c(6) ~ "Natural killer cell" ), len = 9 # Ensure vector length covers all clusters (0-8) ) print("Final curated annotation:") cl2cell_final ``` This `cl2cell_final` vector can now be added to your Seurat object's metadata for downstream analysis and plotting. ## Using a Custom Marker Database For specialized analyses, such as focusing on a specific tissue, working with a non-model organism, or using a proprietary list of markers, you can provide your own custom reference to `matchCellMarker2`. The reference must be a `data.frame` (or `data.table`) with at least two columns: `cell_name` and `marker`. The easiest way to create this is from a named list. **Step 1: Create a named list of your custom markers.** ```{r} custom_ref_list <- list( "T-cell" = c("CD3D", "CD3E", "CD3G"), "B-cell" = c("CD79A", "MS4A1"), "Myeloid" = c("LYZ", "CST3", "AIF1") ) print(custom_ref_list) ``` **Step 2: Convert the list to the required data.frame format.** `easybio` provides the `list2dt` helper function for this. ```{r} custom_ref_df <- list2dt(custom_ref_list, col_names = c("cell_name", "marker")) head(custom_ref_df) ``` **Step 3: Run `matchCellMarker2` with the `ref` parameter.** When `ref` is provided, the function ignores the `spc`, `tissueClass`, and `tissueType` parameters for matching. ```{r} marker_custom <- matchCellMarker2( marker = pbmc.markers, n = 50, ref = custom_ref_df ) # Note that the cell_name column now contains our custom cell types marker_custom[, head(.SD, 2), by = cluster] ``` ## Additional Utilities `easybio` also provides functions for direct queries. ### `get_marker()` Directly retrieve markers for any cell type of interest. ```{r} get_marker(spc = "Human", cell = c("Monocyte", "Neutrophil"), number = 5, min.count = 1) ``` ### `plotMarkerDistribution()` Check the distribution of a specific marker across all cell types and tissues in the database. ```{r, fig.width=7.5, fig.height=7} plotMarkerDistribution(mkr = "CD68") ``` ```{js, echo=FALSE} document.querySelectorAll('p img').forEach(img => { // 检查是否是空白透明图片(可以根据 src 精确匹配) if ( img.src === 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAkwAAAJMCAMAAAA/ugnxAAAAA1BMVEX///+nxBvIAAAACXBIWXMAAAzrAAAM6wHl1kTSAAABZklEQVR4nO3BMQEAAADCoPVPbQo/oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4GtJJwABiuuvjQAAAABJRU5ErkJggg==' ) { const parentP = img.closest('p'); if (parentP) { parentP.remove(); } } }); ```