R: How do I use coord_cartesian on facet_grid with free-ranging axis
Old post but i was looking for the same thing and couldn't really find anything - [perhaps this is a way Set limits on y axis for two dependent variables using facet_grid()
The solution is not very elegant/efficient but i think it works - basically create two plots with different coord_cartesian calls and swap over the grobs.
# library(ggplot2)
# library(gtable)
# Plot
mt <- ggplot(mtcars, aes(mpg, wt, colour = factor(cyl))) + geom_point()
# --------------------------------------------------------------------------------
p1 <- mt + facet_grid(vs ~ am, scales = "free") + coord_cartesian(ylim = c(1,6))
g1 <- ggplotGrob(p1)
p2 <- mt + facet_grid(vs ~ am, scales = "free") + coord_cartesian(ylim = c(3,5))
g2 <- ggplotGrob(p2)
# ----------------------------------------------------------
# Replace the upper panels and upper axis of p1 with that of p2
# Tweak panels of second plot - the upper panels
g1[["grobs"]][[6]] <- g2[["grobs"]][[6]]
g1[["grobs"]][[8]] <- g2[["grobs"]][[8]]
#Tweak axis
g1[["grobs"]][[4]] <- g2[["grobs"]][[4]]
grid.newpage()
grid.draw(g1)
ggplot2::coord_cartesian on facets
I modified the function train_cartesian
to match the output format of view_scales_from_scale
(defined here), which seems to work:
train_cartesian <- function(scale, limits, name, given_range = NULL) {
if (is.null(given_range)) {
expansion <- ggplot2:::default_expansion(scale, expand = self$expand)
range <- ggplot2:::expand_limits_scale(scale, expansion,
coord_limits = self$limits[[name]])
} else {
range <- given_range
}
out <- list(
ggplot2:::view_scale_primary(scale, limits, range),
sec = ggplot2:::view_scale_secondary(scale, limits, range),
arrange = scale$axis_order(),
range = range
)
names(out) <- c(name, paste0(name, ".", names(out)[-1]))
out
}
p <- test_data %>%
ggplot(aes(x=Nsubjects, y = Odds, color=EffectSize)) +
facet_wrap(DataType ~ ExpType, labeller = label_both, scales="free") +
geom_line(size=2) +
geom_ribbon(aes(ymax=Upper, ymin=Lower, fill=EffectSize, color=NULL), alpha=0.2)
p +
coord_panel_ranges(panel_ranges = list(
list(x=c(8,64), y=c(1,4)), # Panel 1
list(x=c(8,64), y=c(1,6)), # Panel 2
list(NULL), # Panel 3, an empty list falls back on the default values
list(x=c(8,64), y=c(1,7)) # Panel 4
))
Original answer
I've cheated my way out of a similar problem before.
# alternate version of plot with data truncated to desired range for each facet
p.alt <- p %+% {test_data %>%
mutate(facet = as.integer(interaction(DataType, ExpType, lex.order = TRUE))) %>%
left_join(data.frame(facet = 1:4,
ymin = c(1, 1, -Inf, 1), # change values here to enforce
ymax = c(4, 6, Inf, 7)), # different axis limits
by = "facet") %>%
mutate_at(vars(Odds, Upper, Lower), list(~ ifelse(. < ymin, ymin, .))) %>%
mutate_at(vars(Odds, Upper, Lower), list(~ ifelse(. > ymax, ymax, .))) }
# copy alternate version's panel parameters to original plot & plot the result
p1 <- ggplot_build(p)
p1.alt <- ggplot_build(p.alt)
p1$layout$panel_params <- p1.alt$layout$panel_params
p2 <- ggplot_gtable(p1)
grid::grid.draw(p2)
Setting individual axis limits with facet_wrap and scales = free in ggplot2
Here's some code with a dummy geom_blank
layer,
range_act <- range(range(results$act), range(results$pred))
d <- reshape2::melt(results, id.vars = "pred")
dummy <- data.frame(pred = range_act, value = range_act,
variable = "act", stringsAsFactors=FALSE)
ggplot(d, aes(x = pred, y = value)) +
facet_wrap(~variable, scales = "free") +
geom_point(size = 2.5) +
geom_blank(data=dummy) +
theme_bw()
Adjusting y axis limits in ggplot2 with facet and free scales
First, reproducibility with random data needs a seed. I started using set.seed(42)
, but that generated negative values which caused completely unrelated warnings. Being a little lazy, I changed the seed to set.seed(2021)
, finding all positives.
For #1, we can add limits=
, where the help for ?scale_y_continuous
says that
limits: One of:
• 'NULL' to use the default scale range
• A numeric vector of length two providing limits of the
scale. Use 'NA' to refer to the existing minimum or
maximum
• A function that accepts the existing (automatic) limits
and returns new limits Note that setting limits on
positional scales will *remove* data outside of the
limits. If the purpose is to zoom, use the limit argument
in the coordinate system (see 'coord_cartesian()').
so we'll use c(0, NA)
.
For Q2, we'll add expand=
, documented in the same place.
data %>%
gather(Gene, Levels, -Patient, -Treatment) %>%
mutate(Treatment = factor(Treatment, levels = c("Pre", "Post"))) %>%
mutate(Patient = as.factor(Patient)) %>%
ggplot(aes(x = Treatment, y = Levels, color = Patient, group = Patient)) +
geom_point() +
geom_line() +
facet_wrap(. ~ Gene, scales = "free") +
theme_bw() +
theme(panel.grid = element_blank()) +
scale_y_continuous(limits = c(0, NA), expand = expansion(mult = c(0, 0.1)))
Use free_y scale on first axis and fixed on second + facet_grid + ggplot2
Secondary axes are implemented in ggplot2 as a decoration that is a transformation of the primary axis, so I don't know an elegant way to do this, since it would require the secondary axis formula to be aware of different scaling factors for each facet.
Here's a hacky approach where I scale each secondary series to its respective primary series, and then add some manual annotations for the secondary series. Another way might be to make the plots separately for each facet like here and use patchwork
to combine them.
Given some fake data where the facets have different ranges for the primary series but the same range for the secondary series:
library(tidyverse)
fake <- tibble(facet = rep(1:3, each = 10),
x = rep(1:10, times = 3),
y_prim = (1+sin(x))*facet/2,
y_sec = (1 + sin(x*3))/2)
ggplot(fake, aes(x, y_prim)) +
geom_line() +
geom_line(aes(y= y_sec), color = "green") +
facet_wrap(~facet, ncol = 1)
...we could scale each secondary series to its primary series, and add custom annotations for that secondary series:
fake2 <- fake %>%
group_by(facet) %>%
mutate(y_sec_scaled = y_sec/max(y_sec) * (max(y_prim))) %>%
ungroup()
fake2_labels <- fake %>%
group_by(facet) %>%
summarize(max_prim = max(y_prim), baseline = 0, x_val = 10.5)
ggplot(fake2, aes(x, y_prim)) +
geom_line() +
geom_line(aes(y= y_sec_scaled), color = "green") +
facet_wrap(~facet, ncol = 1, scales = "free_y") +
geom_text(data = fake2_labels, aes(x = x_val, y = max_prim, label = "100%"),
hjust = 0, color = "green") +
geom_text(data = fake2_labels, aes(x = x_val, y = baseline, label = "0%"),
hjust = 0, color = "green") +
coord_cartesian(xlim = c(0, 10), clip = "off") +
theme(plot.margin = unit(c(1,3,1,1), "lines"))
ggplot2 change axis limits for each individual facet panel
preliminaries
Define original plot and desired parameters for the y-axes of each facet:
library(ggplot2)
g0 <- ggplot(mpg, aes(displ, cty)) +
geom_point() +
facet_grid(rows = vars(drv), scales = "free")
facet_bounds <- read.table(header=TRUE,
text=
"drv ymin ymax breaks
4 5 25 5
f 0 40 10
r 10 20 2",
stringsAsFactors=FALSE)
version 1: put in fake data points
This doesn't respect the breaks
specification, but it gets the bounds right:
Define a new data frame that includes the min/max values for each drv
:
ff <- with(facet_bounds,
data.frame(cty=c(ymin,ymax),
drv=c(drv,drv)))
Add these to the plots (they won't be plotted since x
is NA
, but they're still used in defining the scales)
g0 + geom_point(data=ff,x=NA)
This is similar to what expand_limits()
does, except that that function applies "for all panels or all plots".
version 2: detect which panel you're in
This is ugly and depends on each group having a unique range.
library(dplyr)
## compute limits for each group
lims <- (mpg
%>% group_by(drv)
%>% summarise(ymin=min(cty),ymax=max(cty))
)
Breaks function: figures out which group corresponds to the set of limits it's been given ...
bfun <- function(limits) {
grp <- which(lims$ymin==limits[1] & lims$ymax==limits[2])
bb <- facet_bounds[grp,]
pp <- pretty(c(bb$ymin,bb$ymax),n=bb$breaks)
return(pp)
}
g0 + scale_y_continuous(breaks=bfun, expand=expand_scale(0,0))
The other ugliness here is that we have to set expand_scale(0,0)
to make the limits exactly equal to the group limits, which might not be the way you want the plot ...
It would be nice if the breaks()
function could somehow also be passed some information about which panel is currently being computed ...
How can I get the real scale from a facet_grid plot in R?
Yes, to get the scales directly, use layer_scales(p)
, which gives you the range of the axes rather than just the range of the data, which is what you get from layer_data(p)
p + coord_cartesian(clip = "off",
ylim = layer_scales(p)$y$range$range,
xlim = layer_scales(p)$x$range$range)
Or, to combine this question with your last, where you add the text labels outside of the plotting panels, your result might be something like:
p + coord_cartesian(clip = "off",
ylim = layer_scales(p)$y$range$range,
xlim = layer_scales(p)$x$range$range) +
geom_text(data = data.frame(value = c(0, 6), id = c("df2", "df2"),
Sex = c('Female', 'Male')),
aes(y = -0.15, label = c('Female', 'Male')))
Related Topics
How to Cross-Paste All Combinations of Two Vectors (Each-To-Each)
Importing a Big Xlsx File into R
Grouping 2 Levels of a Factor in R
No Visible Global Function Definition for 'Median'
Edit Datatable in Shiny with Dropdown Selection for Factor Variables
Change Path.Expand Location (Win 7)
Reshape Multiple Categorical Variables to Binary Response Variables
Merging a Large List of Xts Objects
Create Sequential Counter That Restarts on a Condition Within Panel Data Groups
How to Add a Table to a Ggplot