SingleR icon indicating copy to clipboard operation
SingleR copied to clipboard

annotation_colors option in plotMarkerHeatmap?

Open jeremymchacon opened this issue 9 months ago • 4 comments

Hello, thanks for a lovely package.

I was wondering if it is possible to provide annotation_colors to plotMarkerHeatmap, similar to how one can provide it to plotScoreHeatmap?

Thanks!

jeremymchacon avatar Mar 18 '25 21:03 jeremymchacon

Should work, yep.

annotation_colors just needs to look something like this. labels needs to be the name of the element in the list, and then label1, label2, etc. should match your actual labels:

annotation_colors = list(
    labels = c(label1 = "red", label2 = "blue")
)

If you give equivalent of plotScoreHeatmap(..., annotation_colors = list(labels = c(label1 = "red", label2 = "blue"))) then annotation_colors gets passed directly through to pheatmap::pheatmap to do its job. (In the docs as "... Additional parameters for heatmap control passed to pheatmap")

dtm2451 avatar Mar 19 '25 14:03 dtm2451

Thanks for your fast response! That's what I thought should work, but it isn't for me. Note that plotScoreHeatmap accepts my annotation_colors without error, but plotMarkerHeatmap errors out (I tried it twice in case the capitalization change mattered).

> plt = SingleR::plotScoreHeatmap(celltypes_mousernaseq, 
+                                 annotation_colors = list(Labels = mouse_colors))
> x = SingleR::plotMarkerHeatmap(celltypes_mousernaseq, test = norm_counts,
+                            label = "Neurons",
+                            annotation_colors = list(labels = mouse_colors))
Error in SingleR::plotMarkerHeatmap(celltypes_mousernaseq, test = norm_counts,  : 
  unused argument (annotation_colors = list(labels = mouse_colors))
> x = SingleR::plotMarkerHeatmap(celltypes_mousernaseq, test = norm_counts,
+                                label = "Neurons",
+                                annotation_colors = list(Labels = mouse_colors))
Error in SingleR::plotMarkerHeatmap(celltypes_mousernaseq, test = norm_counts,  : 
  unused argument (annotation_colors = list(Labels = mouse_colors))

jeremymchacon avatar Mar 19 '25 14:03 jeremymchacon

Oh! You're right. Looking further, the commit where the ... machinery was added happened after the last Bioconductor release, so it's likely that it's only a feature in the devel version at the moment.

There's another Bioconductor release coming next month, but I'll have to leave the decision to @LTLA whether to make an extra push to the current release.

In the meantime, try using this plotMarkerHeatmapAlt version. (Simple enough function... only change I've actually made here was the SingleR::: to access the only internal I've noticed, in the first line. But I'm unfortunately not in a position to test so 🤞):

plotMarkerHeatmapAlt <- function(
    results,
    test,
    label,
    other.labels=NULL,
    assay.type="logcounts",
    display.row.names=NULL,
    use.pruned=FALSE,
    order.by.effect="cohens.d",
    order.by.summary="min.rank",
    top=20,
    num.threads=bpnworkers(BPPARAM),
    BPPARAM = SerialParam(),
    ...) 
{
    test <- SingleR:::.to_clean_matrix(test, assay.type, check.missing=FALSE, num.threads=num.threads)
    config <- configureMarkerHeatmap(
        results, 
        test,
        label=label,
        other.labels=other.labels,
        assay.type=assay.type,
        use.pruned=use.pruned,
        order.by.effect=order.by.effect,
        order.by.summary=order.by.summary,
        num.threads=num.threads
    )

    to.show <- head(config$rows, top)
    predictions <- config$predictions
    test <- test[to.show, config$columns, drop=FALSE]
    if (!is.null(display.row.names)) {
        rownames(test) <- display.row.names[to.show]
    }

    limits <- range(test, na.rm=TRUE)
    colnames(test) <- seq_len(ncol(test))
    pheatmap::pheatmap(
        test,
        breaks=seq(limits[1], limits[2], length.out=26),
        color=viridis::viridis(25),
        annotation_col=data.frame(labels=predictions, row.names=colnames(test)),
        cluster_col=FALSE,
        show_colnames=FALSE,
        ...
    )
}

dtm2451 avatar Mar 19 '25 15:03 dtm2451

Thanks! I'm using version 2.8, so I made similar adjustments to that version, and now it is working! I appreciate your help! (Also, I didn't realize you can access internal functions with :::, so cool!)

Altered version:



plotMarkerHeatmapAlt = function(
    results,
    test,
    label,
    other.labels=NULL,
    assay.type="logcounts",
    display.row.names=NULL,
    use.pruned=FALSE,
    order.by="rank.logFC.cohen",
    top=20,
    BPPARAM = BiocParallel::SerialParam(),
    ...) 
{
  test <- SingleR:::.to_clean_matrix(test, assay.type, check.missing=FALSE)
  all.markers <- metadata(results)$de.genes[[label]]
  
  if (use.pruned) {
    labfield <- "pruned.labels"
  } else {
    labfield <- "labels"
  }
  predictions <- results[[labfield]]
  
  ckeep <- seq_len(ncol(test))
  if (!is.null(other.labels)) {
    ckeep <- predictions %in% other.labels
    predictions <- predictions[ckeep]
    for (n in names(all.markers)) {
      if (!(n %in% other.labels) && n != label) {
        all.markers[[n]] <- NULL
      }
    }
  } else if (anyNA(predictions)) {
    ckeep <- !is.na(predictions)
    predictions <- predictions[ckeep]
  }
  
  rkeep <- rownames(test) %in% unlist(all.markers)
  test <- test[rkeep,ckeep,drop=FALSE]
  
  # Prioritize the markers with interesting variation in the test data for
  # visualization. If we only have one label, we use the most abundant markers.
  if (length(unique(predictions)) > 1L) {
    interesting <- scran::scoreMarkers(test, predictions, BPPARAM=BPPARAM)
    stats <- interesting[[label]]
    o <- order(stats[[order.by]], decreasing=!startsWith(order.by, "rank."))
    to.show <- rownames(test)[o]
  } else {
    abundance <- rowMeans(test)
    to.show <- names(abundance)[order(abundance, decreasing=TRUE)]
  }
  
  to.show <- match(head(to.show, top), rownames(test))
  colnames(test) <- seq_len(ncol(test))
  col.order <- order(predictions)
  test <- test[to.show,col.order,drop=FALSE]
  predictions <- predictions[col.order]
  
  if (!is.null(display.row.names)) {
    rownames(test) <- display.row.names[rkeep][to.show]
  }
  
  limits <- range(test, na.rm=TRUE)
  pheatmap::pheatmap(
    test,
    breaks=seq(limits[1], limits[2], length.out=26),
    color=viridis::viridis(25),
    annotation_col=data.frame(labels=predictions, row.names=colnames(test)),
    cluster_col=FALSE,
    show_colnames=FALSE,
    ...
  )
}

jeremymchacon avatar Mar 19 '25 16:03 jeremymchacon