Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e65e284
UsingClaude added Compare_Cell_Pop from NIDAPjson. passed AI tests
phoman14 Feb 17, 2026
ec5d485
fixing ModScoreHelpers_function. Error with Roxygen Format
phoman14 Feb 17, 2026
c3c6152
Update Vignette
phoman14 Feb 17, 2026
d5579ff
Update Vignette for MS app
phoman14 Feb 17, 2026
1d015eb
Start CompareCelPop Vignette
phoman14 Mar 4, 2026
1fe0144
Add code for compareCellPopulations Vignette
phoman14 Mar 5, 2026
0c49ece
add Vis_CCPbar.png
phoman14 Mar 5, 2026
1524b4f
Render Visualizations.Rmd with pkgdown
phoman14 Mar 17, 2026
ea28d75
resize CCPbar figure for vignette
phoman14 Mar 17, 2026
7083428
Add CCPbox to Vignette
phoman14 Mar 17, 2026
b9773d1
Add description for Compare Cell populations
phoman14 Mar 17, 2026
1d82e29
Merge branch 'NewFun_CompareCelPop' of https://github.com/NIDAP-Commu…
phoman14 Mar 17, 2026
2783ae0
edit CCP function description in vignette
phoman14 Mar 17, 2026
b14c72f
modify ViolinPlot function name and add standard output
phoman14 Mar 17, 2026
782eb25
change violin plot name for tests violinPlot_mod -> violinPlot
phoman14 Mar 17, 2026
34bd002
Build Vignettes with updates
phoman14 Mar 17, 2026
d0118bf
Modify _pkgdown.yml and /Vignevignettes/README.Rmd -> vignettes/Intro…
phoman14 Mar 17, 2026
0e58060
Reverting Intro.Rmd to README.Rmd
phoman14 Mar 17, 2026
de058fe
tried to troubleshoot running vignetts with devtools::check(). did no…
phoman14 Mar 17, 2026
51e02ed
rmeove Intro.Rmd after changed to README.Rmd
phoman14 Mar 17, 2026
0cee2ac
add htmlttols for Vignettes
phoman14 Mar 17, 2026
e739ff8
LOCAL: tried to troubleshoot running vignetts with devtools::check().…
phoman14 Mar 17, 2026
0147b28
Merge branch 'NewFun_CompareCelPop' of https://github.com/NIDAP-Commu…
phoman14 Mar 17, 2026
523b295
Modifications to pass unit tests
phoman14 Mar 18, 2026
5e86dd3
update vignette to use README.Rmd not Intro.Rmd
phoman14 Mar 18, 2026
e2a513e
Potential fix for pull request finding
phoman14 Mar 18, 2026
cbebbdc
added Vignette docs folder
phoman14 Mar 18, 2026
a928f09
Merge branch 'NewFun_CompareCelPop' of https://github.com/NIDAP-Commu…
phoman14 Mar 18, 2026
c6a8037
remvoed metadata.table from Compare_cell_Populations.R because no log…
phoman14 Mar 18, 2026
e0be21c
Initial plan
Copilot Mar 18, 2026
946b476
Add else-stop to getParamCCP for unknown data values
Copilot Mar 18, 2026
426988b
Initial plan
Copilot Mar 18, 2026
170e50c
Add else branch to getParamCCP() with stop() for unknown datasets
Copilot Mar 18, 2026
6b85231
adding an explicit else that stop()s with a clear message listing the…
phoman14 Mar 18, 2026
5329bf9
remove sptatstat.corre from DESCRIPTION
phoman14 Mar 18, 2026
6bdcbc7
Merge branch 'NewFun_CompareCelPop' into copilot/sub-pr-80
phoman14 Mar 18, 2026
e13dc63
Merge pull request #81 from NIDAP-Community/copilot/sub-pr-80
phoman14 Mar 18, 2026
0f1d9df
Merge branch 'NewFun_CompareCelPop' into copilot/sub-pr-80-again
phoman14 Mar 18, 2026
5f1bc23
Merge pull request #82 from NIDAP-Community/copilot/sub-pr-80-again
phoman14 Mar 18, 2026
4eee8fc
removed htmltools from DESCRIPTION
phoman14 Mar 18, 2026
62ea9b8
Deleted docs/reference/violinPlot_mod.html
phoman14 Mar 18, 2026
ad367d7
Potential fix for pull request finding
phoman14 Mar 18, 2026
4452608
Potential fix for pull request finding
phoman14 Mar 18, 2026
c152b3b
Initial plan
Copilot Mar 18, 2026
8c18f46
Fix trailing comma in knitr chunk header in SCWorkflow-Visualizations…
Copilot Mar 18, 2026
620f2de
Merge pull request #83 from NIDAP-Community/copilot/sub-pr-80-another…
phoman14 Mar 18, 2026
c2f945d
Potential fix for pull request finding
phoman14 Mar 18, 2026
a709130
Potential fix for pull request finding
phoman14 Mar 18, 2026
8d4a31b
Run Tests after copiolot modifications
phoman14 Mar 18, 2026
81353a3
Potential fix for pull request finding
phoman14 Mar 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@
^Dockerfile$
^Dockerfiles/.*$
^Conda_Recipe/.*$
^doc$
^Meta$
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ tests/testthat/output/

inst/doc
#inst/extdata/*
docs
*.Rds
*.rds
/doc/
/Meta/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# CHANGELOG

## v1.0.3 (in development)

### Feature

* feat: Add compareCellPopulations() function for comparing cell population distributions across experimental groups
- Visualizes cell population frequencies or absolute counts across multiple groups
- Generates alluvial flow bar plots and faceted box plots
- Supports custom group ordering and color palettes
- Added ggalluvial dependency for flow visualizations
- Generated from JSON template using json2r.prompt.md instructions

## v1.0.2 (2024-02-01)

Expand Down
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Imports:
gargle (>= 1.2.0),
ggplot2 (>= 3.3.6),
ggpubr (>= 0.4.0),
ggalluvial,
globals (>= 0.16.1),
harmony (>= 0.1.1),
hdf5r (>= 1.3.5),
Expand Down Expand Up @@ -83,8 +84,8 @@ Imports:
dendextend,
dendsort,
pheatmap,
scales,
celldex,
scales,
gdata,
ggrepel,
tidyr,
Expand Down
22 changes: 21 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
export(aggregateCounts)
export(annotateCellTypes)
export(appendMetadataToSeuratObject)
export(build_modscore_plots)
export(colorByGene)
export(colorByMarkerTable)
export(combineNormalize)
export(compareCellPopulations)
export(compute_modscore_data)
export(degGeneExpressionMarkers)
export(dotPlotMet)
export(dualLabeling)
export(filterQC)
export(filterSeuratObjectByMetadata)
export(heatmapSC)
export(launch_module_score_app)
export(modScore)
export(nameClusters)
export(object)
Expand All @@ -21,7 +25,7 @@ export(processRawData)
export(reclusterFilteredSeuratObject)
export(reclusterSeuratObject)
export(tSNE3D)
export(violinPlot_mod)
export(violinPlot)
import(MAST)
import(RColorBrewer)
import(Seurat)
Expand All @@ -40,12 +44,14 @@ import(gridExtra)
import(harmony)
import(httr)
import(jsonlite)
import(magrittr)
import(parallel)
import(plotly)
import(quantmod)
import(reshape2)
import(rlang)
import(scales)
import(tibble)
import(tidyverse)
import(tools)
import(utils)
Expand Down Expand Up @@ -76,6 +82,7 @@ importFrom(dplyr,arrange)
importFrom(dplyr,case_when)
importFrom(dplyr,desc)
importFrom(dplyr,filter)
importFrom(dplyr,group_by)
importFrom(dplyr,if_else)
importFrom(dplyr,mutate)
importFrom(dplyr,mutate_if)
Expand All @@ -86,14 +93,26 @@ importFrom(dplyr,row_number)
importFrom(dplyr,select)
importFrom(dplyr,summarise)
importFrom(ggExtra,ggMarginal)
importFrom(ggalluvial,geom_flow)
importFrom(ggplot2,aes)
importFrom(ggplot2,coord_fixed)
importFrom(ggplot2,element_blank)
importFrom(ggplot2,element_text)
importFrom(ggplot2,geom_hline)
importFrom(ggplot2,geom_line)
importFrom(ggplot2,geom_point)
importFrom(ggplot2,geom_segment)
importFrom(ggplot2,geom_violin)
importFrom(ggplot2,geom_vline)
importFrom(ggplot2,ggplot)
importFrom(ggplot2,ggtitle)
importFrom(ggplot2,guide_legend)
importFrom(ggplot2,guides)
importFrom(ggplot2,scale_color_gradientn)
importFrom(ggplot2,scale_color_identity)
importFrom(ggplot2,scale_x_continuous)
importFrom(ggplot2,scale_y_continuous)
importFrom(ggplot2,scale_y_log10)
importFrom(ggplot2,scale_y_reverse)
importFrom(ggplot2,theme)
importFrom(ggplot2,theme_bw)
Expand Down Expand Up @@ -128,6 +147,7 @@ importFrom(stats,kmeans)
importFrom(stats,mad)
importFrom(stats,median)
importFrom(stats,quantile)
importFrom(stats,setNames)
importFrom(stringr,str_replace_all)
importFrom(stringr,str_sort)
importFrom(stringr,str_split_fixed)
Expand Down
263 changes: 263 additions & 0 deletions R/Compare_Cell_Populations.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
#' @title Compare Cell Populations
#' @description Compare cell population distributions across different groups
#' using bar plots and box plots. Creates visualizations showing cell type
#' frequencies or counts across user-defined groupings.
#'
#' @details This function generates comparative visualizations of cell
#' populations from a Seurat object. It can display data as either frequency
#' percentages or absolute counts, and creates both stacked bar plots
#' (with alluvial flow connections) and grouped box plots for comparison
#' across samples and conditions.
#'
#' @param object A Seurat object containing the single-cell data
#' @param annotation.column Character string specifying the metadata column
#' containing cell type annotations to summarize in the bar plot
#' @param group.column Character string specifying the metadata column
#' defining groups to compare (e.g., treatment conditions)
#' @param sample.column Character string specifying the metadata column
#' containing sample identifiers. Default is "orig.ident"
#' @param counts.type Character string specifying plot data type:
#' "Frequency" (percentages) or "Counts" (absolute numbers). Default is "Frequency"
#' @param group.order Character vector specifying the order of groups in plots.
#' If NULL, uses natural order from data. Default is NULL
#' @param seurat.object.filename Character string for the Seurat object
#' filename. Default is "seurat_object.rds"
#' @param wrap.ncols Integer specifying number of columns for facet wrapping
#' in box plots. Default is 5
#'
#' @import Seurat
#' @import ggplot2
#' @import ggpubr
#' @import RColorBrewer
#' @import tibble
#' @import reshape2
#' @import data.table
#' @import dplyr
#' @import magrittr
#' @import cowplot
#' @import gridExtra
#' @import grid
#' @import scales
#'
#' @importFrom ggalluvial geom_flow
#' @importFrom stats setNames
#' @importFrom grDevices colorRampPalette
#'
#' @export
#'
#' @return A list containing:
#' \itemize{
#' \item \code{Plots} - A list with two ggplot objects:
#' \itemize{
#' \item \code{Barplot} - Stacked bar plot with alluvial flows
#' \item \code{Boxplot} - Faceted box plots by cell type (only if counts.type="Frequency")
#' }
#' \item \code{Table} - A data.frame with cell counts and percentages
#' }
#'
#' @examples
#' \dontrun{
#' # Compare cell populations by treatment group
#' results <- compareCellPopulations(
#' object = seurat_obj,
#' annotation.column = "cell_type",
#' group.column = "treatment",
#' sample.column = "sample_id",
#' counts.type = "Frequency"
#' )
#'
#' # Display plots
#' plot(results$Plots$Barplot)
#' plot(results$Plots$Boxplot)
#'
#' # View summary table
#' head(results$Table)
#' }

compareCellPopulations <- function(
object,
annotation.column,
group.column,
sample.column = "orig.ident",
counts.type = "Frequency",
group.order = NULL,
wrap.ncols = 5
) {

## -------------------------------- ##
## Input Validation ##
## -------------------------------- ##

# Validate object
if (!inherits(object, "Seurat")) {
stop("Error: 'object' must be a Seurat object")
}

# Validate counts.type
if (!counts.type %in% c("Frequency", "Counts")) {
stop("Error: 'counts.type' must be either 'Frequency' or 'Counts'")
}

## --------- ##
## Functions ##
## --------- ##

createAnnoTable <- function(SO, AnnoCol, GroupCol) {
## Extract annotation data for each group using a 2D contingency table
cntMat <- table(SO@meta.data[[AnnoCol]], SO@meta.data[[GroupCol]])

# Convert to data frame while preserving row/column names
cntTble <- as.data.frame.matrix(cntMat)
cntTble <- data.frame(
lapply(cntTble, function(x) as.numeric(as.character(x))),
check.names = FALSE,
row.names = rownames(cntTble)
)

freqTble <- apply(cntTble, 2, FUN = function(x) {
return(x / sum(x))
})
freqTble <- (freqTble * 100)

outTbl <- merge(cntTble, as.data.frame(freqTble),
by = 'row.names',
suffixes = c('_CellCounts', '_Percent'))
outTbl <- dplyr::rename(outTbl, 'Clusters' = "Row.names")

return(list(
'CellFreq' = freqTble,
'CellCounts' = cntTble,
'OutTable' = outTbl
))
}

## --------------- ##
## Main Code Block ##
## --------------- ##

# Replace dots with underscores in column names
colnames(object@meta.data) <- gsub("\\.", "_", colnames(object@meta.data))

# Update column names if they were modified
annotation.column <- gsub("\\.", "_", annotation.column)
group.column <- gsub("\\.", "_", group.column)
sample.column <- gsub("\\.", "_", sample.column)


# Validate metadata columns exist
required.cols <- c(annotation.column, group.column, sample.column)
missing.cols <- setdiff(required.cols, colnames(object@meta.data))
if (length(missing.cols) > 0) {
stop("Error: The following columns are missing from metadata: ",
paste(missing.cols, collapse = ", "))
}




# Set up ordering
ordr <- object@meta.data[[annotation.column]] %>%
unique() %>%
sort()

if (is.null(group.order)) {
group.order <- unique(object@meta.data[[group.column]])
}

# Set up colors
numColors <- max(
length(unique(object@meta.data[[annotation.column]])),
20
)
colpaired <- colorRampPalette(brewer.pal(12, "Paired"))
cols <- c(
"#e6194B", "#3cb44b", "#4363d8", "#f58231", "#911eb4", "#42d4f4",
"#f032e6", "#bfef45", "#fabebe", "#469990", "#e6beff", "#9A6324",
"#800000", "#aaffc3", "#808000", "#000075",
colpaired(numColors)
)
names(cols) <- ordr

object@meta.data[[annotation.column]] <- factor(
object@meta.data[[annotation.column]],
levels = ordr
)

# Create tables
ColTables <- createAnnoTable(object, annotation.column, group.column)
BoxTables <- createAnnoTable(object, annotation.column, sample.column)

metaGroups <- object@meta.data[, c(group.column, sample.column)]
rownames(metaGroups) <- NULL
metaGroups <- metaGroups %>% unique()

## Create plots based on counts type
if (counts.type == 'Frequency') {
ptbl <- melt(ColTables$CellFreq)
ptblBox <- melt(as.matrix(BoxTables$CellFreq))
ptblBox <- merge(ptblBox, metaGroups,
by.x = 'Var2', by.y = sample.column, all.x = TRUE)

labelCol <- 'PerValue'
ylab <- 'Frequency of each cell type (100%)'
} else if (counts.type == "Counts") {
ptbl <- melt(as.matrix(ColTables$CellCounts))
ptblBox <- melt(as.matrix(BoxTables$CellCounts))
ptblBox <- merge(ptblBox, metaGroups,
by.x = 'Var2', by.y = sample.column, all.x = TRUE)

labelCol <- 'value'
ylab <- 'Cell Counts'
}

# Format bar plot data
ptbl$Var1 <- factor(ptbl$Var1, levels = ordr)
ptbl$value <- round(ptbl$value, 1)
ptbl$PerValue <- paste0(ptbl$value, '%')
ptbl$PerValue <- gsub('^%$', "_", ptbl$PerValue)
ptbl[ptbl$value < 1, 'PerValue'] <- ""
ptbl$Var2 <- factor(ptbl$Var2, levels = group.order)

# Create bar plot with alluvial flows
p2 <- ptbl %>%
ggplot(aes_string(y = 'value', x = 'Var2', fill = 'Var1', label = labelCol)) +
geom_flow(aes(alluvium = Var1), alpha = .2,
lty = 2, color = "black",
curve_type = "linear",
width = .5) +
geom_col(aes(fill = Var1), width = .5, color = "black") +
geom_text(size = 3, position = position_stack(vjust = 0.5)) +
theme_classic() +
ylab(ylab) +
xlab("") +
scale_x_discrete(guide = guide_axis(angle = 45)) +
scale_fill_manual(annotation.column, values = cols)

# Create box plot
ptblBox$value <- round(ptblBox$value, 1)
ptblBox$PerValue <- paste0(ptblBox$value, '%')
ptblBox$PerValue <- gsub('^%$', "_", ptblBox$PerValue)
ptblBox[ptblBox$value < 1, 'PerValue'] <- ""
ptblBox[[group.column]] <- factor(ptblBox[[group.column]], levels = group.order)

p2_Box <- ptblBox %>%
ggboxplot(y = 'value', x = group.column, add = "jitter", color = "Var1") +
facet_wrap(~Var1, ncol = wrap.ncols, scales = 'fixed') +
ylab(ylab) +
xlab("") +
theme(legend.title = element_blank())

# Return results
result <- list(
'Plots' = list('Barplot' = p2, 'Boxplot' = p2_Box),
'Table' = ColTables$OutTable
)

return(result)
}

# Add global variables to avoid R CMD check NOTEs
utils::globalVariables(c(
"Var1", "Var2", "value", "PerValue", "alluvium",
".", "CellFreq", "CellCounts", "OutTable"
))
Loading
Loading