I have a custom stat_... function that I use to make "spaghetti" plots (overlays of many replicates of a timeseries) which also adds a central line. I am typically using this to plot stochastic simulation results alongside observed data. However, I am having trouble getting the automatic specification of alpha to work the way I want - lots of detail below, but in summary I'm looking for solution(s) that enable the user to specific relative alpha across the spaghetti levels + others via the scale_alpha_... and then have the alpha for the replicate / sample lines damped automatically proportion to the number of sample lines (subject to override by the user if desired).
Details of work so far:
Rough basic concept (the data manipulation using data.table ultimately just updates the group to include sample realization and compute a central group result + stamp data with spaghetti & sampleN columns - included for completeness, but can be ignored):
require(data.table); require(ggplot2)
StatSpag <- ggproto(
"StatSpag", Stat, required_aes = c("x", "y", "sample"),
compute_panel = function(
self, data, scales
) {
# for minimal example, assuming group in 1:m, sample in 1:n
central <- as.data.table(data)[,.(
y = median(y), sample = 0, spaghetti = "central", sampleN = 1
), by=setdiff(names(data),c("sample","y"))][order(group, x)]
maxg <- max(data$group)
stride <- max(data$sample)+1
samples <- as.data.table(data)[,
c("group", "spaghetti", "sampleN") := .(
maxg + (group-1)*stride + sample,
"sample",
stride - 1
)
][order(group, x)]
return(rbind(samples, central))
}
)
stat_spag <- function(
mapping = NULL, data = NULL, geom = "line",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE,
...
) {
ggplot2::layer(
stat = StatSpag, data = data, mapping = mapping, geom = geom,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(
na.rm = na.rm,
...
)
)
}
df <- expand.grid(scenario = LETTERS[1:3], sample = 1:100, simt = 0:50)
df <- within(df, {
simf = 0.5*simt*runif(length(simt), 0.9, 1.1) +
10*as.integer(factor(scenario))
})
ggplot(df) + aes(
simt, log10(simf), color = scenario
) + stat_spag(
aes(sample = sample, alpha = after_stat(spaghetti))
) + scale_alpha_manual(values = c(central = 1, sample = 0.1))
Which results in approximately:
However, I'd like to have StatSpag set the default alpha and do so cleverly. Particularly: relative to the sampleN value (generally more samples -> lighter lines) as well as the spaghetti indicator column AND tolerant of adding more geoms that might introduce additional alpha levels. E.g.:
ggplot(df) + aes(
simt, log10(simf), color = scenario
) + stat_spag(
aes(sample = sample)
) + geom_point(
aes(alpha = "observation"), data = empirical.data
) + scale_alpha_manual(values = c(observation = 1, central = 0.7, sample = 0.7))
...should produce solid points for the observed values, slightly faded lines for central time series, and more faded lines (proportion to e.g. 1/sampleN) for each sample time series.
I've tried achieving that effect with the following in StatSpag definition:
default_aes = aes(alpha = stage(
after_stat = spaghetti, after_scale = alpha/sampleN
))
which, in my imagination, would 1) initially code alpha to the spaghetti indicator, 2) user defines the baseline value for the spaghetti levels (+any others) [or they go to the default levels for a discrete alpha scale], 3) the alpha values are rescaled lighter (for the spaghetti layer where sampleN > 1). It does not actually work that way.
So just after_stat(spaghetti) for alpha + hardcoding the values works (but doesn't get at automatically handling alpha by sampleN, and doesn't deal with the case when there are different sample counts by group). What works reasonable for automation, but doesn't specify the relative scale in the right place:
default_aes = aes(
alpha = after_stat(
c(central = ..., sample = ...)[spaghetti]/sampleN
)
)
...and then also manually specifying the alpha for the geom_point. However, that moves specifying the alpha level values away from scale_alpha_..., which is the natural place set it, especially if you're defining that scale once and reusing it over several plots.
Reiterating question: is there something I'm missing about the new stage(...) aesthetic specification steps? E.g. what I want is possible, I'm just doing it wrong vs its never going to work that way and wanting it to do so is crazy. Alternatively, is there another approach that hits my desired interface i.e. relative alpha levels set in scale_alpha_... and then algorithmically rescaled (ideally in a user-overrideable way, but fine with enforcing 1/sampleN)?
