Skip to content

Commit 5cfa785

Browse files
committed
Expand plotValsBySeqContext to allow a broader set of faceting and selection options
1 parent 81400fd commit 5cfa785

4 files changed

Lines changed: 269 additions & 112 deletions

File tree

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ importFrom(dplyr,filter)
160160
importFrom(dplyr,group_by)
161161
importFrom(dplyr,group_modify)
162162
importFrom(dplyr,group_split)
163+
importFrom(dplyr,inner_join)
163164
importFrom(dplyr,left_join)
164165
importFrom(dplyr,mutate)
165166
importFrom(dplyr,rename)

R/plotValsBySeqContext.R

Lines changed: 115 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,28 @@
1111
#' across (samples and) reads before plotting. Must be one of "none" (no
1212
#' aggregation, individual values from the assay are plotted) and "mean"
1313
#' (calculate average value for each position before plotting).
14+
#' @param selectContextsBy Character scalar indicating how to select and order
15+
#' the contexts to show in the plot. Should be one of \code{"sample"} (in
16+
#' which case the selection of sequence contexts will be done independently
17+
#' for each sample, and only the ones selected for a given sample will
18+
#' be displayed in the plot panel for that sample; in this case
19+
#' \code{facetBy} must be \code{"sample"}), \code{"sample_union"} (
20+
#' in which case the selection of sequence contexts will be done for each
21+
#' sample, and the union of all selected contexts will be shown in the
22+
#' plot) or \code{"overall"} (in which case the selection of sequence
23+
#' contexts will be made without considering the sample information). Note
24+
#' that for \code{"sample_union"}, the plots may contain more than
25+
#' \code{topN + bottomN} contexts. For \code{"sample_union"} and
26+
#' \code{"overall"}, the order of the contexts in the plots will be
27+
#' determined by the average value across samples).
1428
#' @param facetBy Character scalar indicating how to facet values in the plot.
1529
#' Must be either \code{NULL} (in which case no facetting is done, and
1630
#' values are aggregated across all samples in \code{se}) or \code{"sample"}
1731
#' (in which case values are aggregated within each sample, and plots are
1832
#' facetted accordingly).
1933
#' @param plotType Character scalar indicating what type of plot to create.
20-
#' Should be one of \code{"violin"}, \code{"bar"} and \code{"errorbar"}.
34+
#' Should be one of \code{"violin"} (note that the violins will be plotted
35+
#' with \code{scale="width"}), \code{"bar"} and \code{"errorbar"}.
2136
#' @param topN,bottomN Numeric scalars determining the number of sequence
2237
#' contexts to include in the plot. The \code{topN} contexts with the
2338
#' highest average values and the \code{bottomN} contexts with the lowest
@@ -52,7 +67,7 @@
5267
#' topN = 3, bottomN = 3, facetBy = "sample")
5368
#'
5469
#' @importFrom dplyr group_by mutate ungroup arrange desc select distinct
55-
#' bind_rows slice_max slice_min filter summarize
70+
#' bind_rows slice_max slice_min filter summarize inner_join
5671
#' @importFrom SummarizedExperiment rowData assay colnames assayNames
5772
#' @importFrom ggplot2 ggplot aes geom_violin theme_bw geom_col geom_linerange
5873
#' geom_errorbar coord_flip
@@ -65,6 +80,7 @@ plotValsBySeqContext <- function(se,
6580
seqContextColumn = "sequenceContext",
6681
assayName = "mod_prob",
6782
aggregation = "mean",
83+
selectContextsBy = "sample_union",
6884
facetBy = NULL,
6985
plotType = "violin",
7086
topN = 10,
@@ -77,83 +93,133 @@ plotValsBySeqContext <- function(se,
7793
validValues = colnames(rowData(se)))
7894
.assertScalar(x = aggregation, type = "character",
7995
validValues = c("none", "mean"))
80-
.assertScalar(x = assayName, type = "character",
81-
validValues = .getReadLevelAssayNames(se))
8296
.assertScalar(x = facetBy, type = "character", validValues = "sample",
8397
allowNULL = TRUE)
98+
if (is.null(facetBy)) {
99+
.assertScalar(x = selectContextsBy, type = "character",
100+
validValues = c("sample_union", "overall"))
101+
} else {
102+
.assertScalar(x = selectContextsBy, type = "character",
103+
validValues = c("sample", "sample_union", "overall"))
104+
}
105+
.assertScalar(x = assayName, type = "character",
106+
validValues = .getReadLevelAssayNames(se))
84107
.assertScalar(x = plotType, type = "character",
85108
validValues = c("violin", "bar", "errorbar"))
86109
.assertScalar(x = topN, type = "numeric", rngIncl = c(0, Inf))
87110
.assertScalar(x = bottomN, type = "numeric", rngIncl = c(0, Inf))
88111
.assertScalar(x = flipCoord, type = "logical")
89112
.assertScalar(x = yAxisLabel, type = "character")
90113

91-
# calculate plot values
92-
if (aggregation == "mean" & is.null(facetBy)) {
93-
a <- as.matrix(assay(se, assayName))
94-
df <- data.frame(sample = "1",
95-
seqContext = rowData(se)[[seqContextColumn]],
96-
vals = rowSums(a, na.rm = TRUE) / rowSums(is_nonna(a))) |>
97-
# remove positions with all NA values (could happen e.g. if the input
98-
# se is obtained by subsetting an se with more samples)
99-
dplyr::filter(!is.na(.data$vals))
100-
} else if (aggregation == "none" & is.null(facetBy)) {
101-
a <- as.matrix(assay(se, assayName))
102-
nna <- nnawhich(a, arr.ind = TRUE)
103-
nnav <- nnavals(a)
104-
df <- data.frame(sample = "1",
105-
seqContext = rowData(se)[[seqContextColumn]][nna[, 1]],
106-
vals = nnav)
107-
} else if (aggregation == "mean" & facetBy == "sample") {
108-
df <- do.call(bind_rows, lapply(colnames(se), function(cn) {
109-
a <- assay(se, assayName)[[cn]]
110-
data.frame(sample = cn,
111-
seqContext = rowData(se)[[seqContextColumn]],
112-
vals = rowSums(a, na.rm = TRUE) / rowSums(is_nonna(a)))
113-
})) |>
114-
dplyr::filter(!is.na(.data$vals))
115-
} else if (aggregation == "none" & facetBy == "sample") {
116-
df <- do.call(bind_rows, lapply(colnames(se), function(cn) {
117-
a <- assay(se, assayName)[[cn]]
114+
# calculate plot/selection values
115+
# ... global ones (required if either facetBy = NULL or selectContextsBy = "overall")
116+
if (is.null(facetBy) || selectContextsBy == "overall") {
117+
if (aggregation == "mean") {
118+
a <- as.matrix(assay(se, assayName))
119+
dfGlobal <- data.frame(sample = "overall",
120+
seqContext = rowData(se)[[seqContextColumn]],
121+
vals = rowSums(a, na.rm = TRUE) / rowSums(is_nonna(a))) |>
122+
# remove positions with all NA values (could happen e.g. if the input
123+
# se is obtained by subsetting an se with more samples)
124+
dplyr::filter(!is.na(.data$vals))
125+
} else if (aggregation == "none") {
126+
a <- as.matrix(assay(se, assayName))
118127
nna <- nnawhich(a, arr.ind = TRUE)
119128
nnav <- nnavals(a)
120-
data.frame(sample = cn,
121-
seqContext = rowData(se)[[seqContextColumn]][nna[, 1]],
122-
vals = nnav)
123-
}))
129+
dfGlobal <- data.frame(sample = "overall",
130+
seqContext = rowData(se)[[seqContextColumn]][nna[, 1]],
131+
vals = nnav)
132+
}
133+
}
134+
135+
# ... sample-wise ones (required if either facetBy = "sample" or
136+
# selectContextsBy = "sample" or "sample_union")
137+
if ((!is.null(facetBy) && facetBy == "sample") ||
138+
selectContextsBy %in% c("sample", "sample_union")) {
139+
if (aggregation == "mean") {
140+
dfSample <- do.call(bind_rows, lapply(colnames(se), function(cn) {
141+
a <- assay(se, assayName)[[cn]]
142+
data.frame(sample = cn,
143+
seqContext = rowData(se)[[seqContextColumn]],
144+
vals = rowSums(a, na.rm = TRUE) / rowSums(is_nonna(a)))
145+
})) |>
146+
dplyr::filter(!is.na(.data$vals))
147+
} else if (aggregation == "none") {
148+
dfSample <- do.call(bind_rows, lapply(colnames(se), function(cn) {
149+
a <- assay(se, assayName)[[cn]]
150+
nna <- nnawhich(a, arr.ind = TRUE)
151+
nnav <- nnavals(a)
152+
data.frame(sample = cn,
153+
seqContext = rowData(se)[[seqContextColumn]][nna[, 1]],
154+
vals = nnav)
155+
}))
156+
}
124157
}
125-
df$flipCoord <- flipCoord
158+
159+
# choose data frames to use for selection/plotting
160+
if (selectContextsBy %in% c("sample", "sample_union")) {
161+
dfSel <- dfSample
162+
} else {
163+
dfSel <- dfGlobal
164+
}
165+
166+
if (is.null(facetBy)) {
167+
dfPlot <- dfGlobal
168+
} else {
169+
dfPlot <- dfSample
170+
}
171+
172+
dfPlot$flipCoord <- flipCoord
126173

127174
# calculate mean/sd for each context and select top/bottom ones to include
128-
dfsum <- df |>
129-
group_by(.data$seqContext, .data$sample, .data$flipCoord) |>
175+
dfSelSum <- dfSel |>
176+
group_by(.data$seqContext, .data$sample) |>
130177
summarize(valsMean = mean(.data$vals),
131-
valsSd = sd(.data$vals),
132178
.groups = "drop")
133-
dfsum <- bind_rows(
134-
dfsum |> group_by(sample) |>
179+
dfSelSum <- bind_rows(
180+
dfSelSum |> group_by(sample) |>
135181
slice_max(.data$valsMean, n = topN, with_ties = FALSE),
136-
dfsum |> group_by(sample) |>
182+
dfSelSum |> group_by(sample) |>
137183
slice_min(.data$valsMean, n = bottomN, with_ties = FALSE)
138184
) |>
139185
ungroup() |>
140186
distinct()
141187

188+
# Subset dfPlot for plotting
189+
# If selectContextsBy = "sample", need to keep the link between sample and
190+
# context. Otherwise, the union of the selected contexts should all be
191+
# selected for all samples in dfPlot
192+
if (selectContextsBy == "sample") {
193+
# here we know that facetBy = "sample"
194+
dfPlot <- dfPlot |>
195+
inner_join(dfSelSum, by = c("sample", "seqContext")) |>
196+
mutate(orderCol = .data$sample)
197+
} else {
198+
contextsToKeep <- unique(dfSelSum$seqContext)
199+
dfPlot <- dfPlot |>
200+
dplyr::filter(seqContext %in% contextsToKeep) |>
201+
mutate(orderCol = "overall")
202+
}
203+
142204
# plot
143205
if (plotType == "violin") {
144-
df <- df |>
145-
dplyr::filter(.data$seqContext %in% dfsum$seqContext) |>
206+
dfPlot <- dfPlot |>
146207
mutate(seqContext = reorder_within(.data$seqContext, by = ifelse(
147208
.data$flipCoord, .data$vals, -.data$vals),
148-
within = sample, fun = mean))
149-
gg <- ggplot(df, aes(x = .data$seqContext, y = .data$vals)) +
209+
within = orderCol, fun = mean))
210+
gg <- ggplot(dfPlot, aes(x = .data$seqContext, y = .data$vals)) +
150211
geom_violin(scale = "width")
151212
} else if (plotType %in% c("bar", "errorbar")) {
152-
dfsum <- dfsum |>
213+
dfPlot <- dfPlot |>
214+
group_by(.data$seqContext, .data$sample, .data$orderCol,
215+
.data$flipCoord) |>
216+
summarize(valsMean = mean(.data$vals),
217+
valsSd = sd(.data$vals),
218+
.groups = "drop") |>
153219
mutate(seqContext = reorder_within(.data$seqContext, by = ifelse(
154220
.data$flipCoord, .data$valsMean, -.data$valsMean),
155-
within = sample, fun = mean))
156-
gg <- ggplot(dfsum,
221+
within = orderCol, fun = mean))
222+
gg <- ggplot(dfPlot,
157223
aes(x = .data$seqContext, y = .data$valsMean)) +
158224
geom_col()
159225
if (plotType == "errorbar") {

man/plotValsBySeqContext.Rd

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)