Remove axis text from one facet - r

I'm trying to display three time series using facet_grid() and in order to save space, I'm reducing panel spacing between them. The problem is that their vertical axis overlap so I want to move it to the right only on the plot in the middle.
Since this seem impossible in ggplot2, what I'm trying to do is to render every axis and then remove it editing the gtable but so far I was not successful.
This is a minimal example:
library(ggplot2)
set.seed(123)
df <- data.frame(expand.grid(x = 1:150, type = letters[1:3]))
df$y <- df$x*0.016 + rnorm(150, sd = .5)
ggplot(df, aes(x, y)) + geom_line() +
facet_grid(type~.) +
theme(panel.spacing.y = unit(-3, "lines"), strip.text = element_blank()) +
scale_y_continuous(sec.axis = dup_axis(name = ""), name = "y")
Which produces this:
And I want to delete each axis text to get to this:
Thanks!

The solution was to assign a nullGrob() to the relevant elements of the gTable.
gt <- ggplotGrob(g)
t <- gt[["grobs"]][[8]][["children"]][[2]]
# Found those grobs by looking around the table.
gt[["grobs"]][[8]][["children"]][[2]] <- nullGrob()
gt[["grobs"]][[10]][["children"]][[2]] <- nullGrob()
gt[["grobs"]][[12]][["children"]][[2]] <- nullGrob()
grid.newpage()
grid.draw(gt)

Related

Adjust spacing between subplots of panel figure (ggplot) in case of different axis.title/text properties

I would like to adjust the spacing between plots that are aligned in a panel using the cowplot package when some plots contain axis titles/labels, and others don't.
Example
Let's create three plots:
library(tidyverse)
library(cowplot)
set.seed(123)
df <- data.frame(x = rnorm(n = 100),
y = rnorm(n = 100))
plot <- ggplot(data = df, aes(x, y)) + geom_point()
plot_grid(plot, plot, plot, nrow = 1, align = "vh")
These plots are aligned perfectly! But often, I have a scenario in which I would like to create a 'cleaner' panel figure. One way to do this is to remove the titles/text of the y-axis of the second and third plots.
Like this:
plot2 <- plot + theme(axis.title.y = element_blank(),
axis.text.y = element_blank())
plot_grid(plot, plot2, plot2, nrow = 1, align = "vh")
Again, perfectly aligned, but the spacing between the first and the second plot (and the second and third plot) is quite large. I would like to reduce the spacing to create a more compact plot, while the axis remain exactly the same size.
Expected output
Is this possible with cowplot? Or is there another way to do this?
Referencing this post on github, plot_grid() doesn't add any space by default and uses the margins of your plot. To remove the space outside your plot area, you can use them(plot.margin=...) to remove.
With that being said... that's not what's going on here! Printing either plot or plot2 will yield a plot with no margins. It appears the issue is with the use of the align= argument in plot_grid(). I'm not sure why, but setting it to anything other than the default values (align="none") results in the extra whitespace around the plots. Very strange... needless to say, removing that argument fixes your problem:
Original code using align="vh"
plot_grid(plot, plot2, plot2, nrow = 1, align="vh")
Using align="none"
plot_grid(plot, plot2, plot2, nrow = 1, align="none")
Any further space would be added according to your graphics device, since the actual plot you get depends on the size and resolution of that device.
Here is a solution using the patchwork package
library(tidyverse)
set.seed(123)
df <- data.frame(x = rnorm(n = 100),
y = rnorm(n = 100))
plot1 <- ggplot(data = df, aes(x, y)) + geom_point()
plot2 <- plot1 + theme(axis.title.y = element_blank(),
axis.text.y = element_blank())
# install.packages("patchwork", dependencies = TRUE)
library(patchwork)
plot1 + plot2 + plot2 +
plot_layout(ncol = 3)
Created on 2020-07-24 by the reprex package (v0.3.0)

ggplot - Align ticks with axis

I am using the ndodge function explained by #jan-glx here;
https://stackoverflow.com/a/60650595/13399047
However I could not figure out how to align the axis ticks aligned as for example;
I should probably use theme(axis.ticks.length=) but I am not sure how to do it in an even/odd way.
Please help!
As far as I am aware there is no build in way to do this in ggplot, though that might change when they rewrite the guide system.
It is neither pretty nor easy, but here is an example how you could do it by messing around in the gtable / grid.
library(ggplot2)
library(grid)
data(diamonds)
diamonds$cut <- paste("Super Dee-Duper",as.character(diamonds$cut))
g <- ggplot(diamonds, aes(cut, carat)) +
geom_boxplot() +
scale_x_discrete(guide = guide_axis(n.dodge = 2))
# Convert to gtable
gt <- ggplotGrob(g)
# Grab bottom axis
is_axis <- grep("axis-b", gt$layout$name)
axisgrob <- gt$grobs[is_axis][[1]]
axis <- axisgrob$children$axis
# Grab tickmarks
is_ticks <- which(vapply(axis$grobs, inherits, logical(1), "polyline"))
ticks <- axis$grobs[[is_ticks]]
# Modify tickmarks
labelheight <- axis$heights[[2]] # First row of labels
modify <- which(seq_along(ticks$y) %% 4 == 0) - 1 # Change every the 3rd item in every quadruplet
ticks$y[modify] <- ticks$y[modify] - labelheight
# Insert ticks back into axis back into table
axis$grobs[[is_ticks]] <- ticks
axisgrob$children$axis <- axis
gt$grobs[[is_axis]] <- axisgrob
# Plot
grid.newpage()
grid.draw(gt)
Created on 2020-05-18 by the reprex package (v0.3.0)
Here is a solution using just ggplot2 stuff and not modifying any grobs. It requires ggplot2 3.0.0 and is based off https://stackoverflow.com/a/51312611/6615512
library(ggplot2)
data(diamonds)
diamonds$cut <- paste("Super Dee-Duper",as.character(diamonds$cut))
tick_min_pos_odd = -0.6
tick_min_pos_even = -0.4
custom_ticks = data.frame(cut = sort(unique(diamonds$cut)))
n_discrete_x_values = nrow(custom_ticks)
# Alternate tick lengths
custom_ticks$tick_min_pos = ifelse(1:n_discrete_x_values %% 2 == 0, tick_min_pos_odd, tick_min_pos_even)
ggplot(diamonds, aes(cut, carat)) +
geom_boxplot() +
scale_x_discrete(guide = guide_axis(n.dodge = 2)) +
geom_linerange(data = custom_ticks, # The custom tickmarks
aes(x=cut, ymax=-0.25, ymin=tick_min_pos),
size=0.5, color='black',
inherit.aes = F) +
coord_cartesian(clip='off', ylim=c(0,NA)) + # Clip off makes it so the geoms can be drawn outside the plot
# ylim sets the y-axis from 0 to the max.
theme(plot.margin = margin(0,0,20,0), # Add some whitespace to the bottom of the plot
axis.title.x = element_text(vjust=-1.5), # nudge the x-axis title and text down a tad
axis.text.x = element_text(vjust=-1.5))

Moving table created by annotation_custom with geom_bar plot

I tried searching for answers but couldn't find anything.
I have have a plot and want to add a table within the plot itself. I can do it but the table ends up being right in the middle.
It is possible to relocate a table created by annotation_custom if the x-axis is discrete? If so, how?
Thank you!
For example, I want to relocate this table.
library(ggplot2)
library(gridExtra)
my.summary <- summary(chickwts$weight)
my.table <- data.frame(ids = names(my.summary), nums = as.numeric(my.summary))
ggplot(chickwts, aes(feed, weight)) +
geom_bar(stat = "identity") +
annotation_custom(tableGrob(my.table))
The custom annotation in ggplot2 can be rearragned inside the plotting area. This at least moves them out of the center. Maybe this solution is already sufficient for you. I'll try and tweak this. It should be possible to put this outside the plotting area as well.
library(ggplot2)
library(gridExtra)
my.summary <- summary(chickwts$weight)
my.table <- data.frame(ids = names(my.summary), nums = as.numeric(my.summary))
ggplot(chickwts, aes(feed, weight)) +
geom_bar(stat = "identity") +
annotation_custom(tableGrob(my.table), xmin=5,xmax=6,ymin=300,ymax=1300)
EDIT:
To place the table outside the plot, regardless of what the plot consists of, the grid package could be used:
library(ggplot2)
library(gridExtra)
library(grid)
# data
my.summary <- summary(chickwts$weight)
my.table <- data.frame(ids = names(my.summary), nums = as.numeric(my.summary))
# plot items
my.tGrob <- tableGrob(my.table)
plt <- ggplot(chickwts, aes(feed, weight)) +
geom_bar(stat = "identity")
# layout
vp.layout <- grid.layout(nrow=1, ncol=2, heights=unit(1, "null"),
widths=unit(c(1,9), c("null","line")) )
# start drawing
grid.newpage()
pushViewport(viewport(layout=vp.layout, name="layout"))
# plot
pushViewport(viewport(layout.pos.row=1, layout.pos.col=1, name="plot"))
print(plt, newpage=FALSE)
upViewport()
# table
pushViewport(viewport(layout.pos.row=1, layout.pos.col=2, name="table"))
grid.draw(my.tGrob)
upViewport()
#dev.off()

Keep all plot components same size in ggplot2 between two plots

I would like two separate plots. I am using them in different frames of a beamer presentation and I will add one line to the other (eventually, not in example below). Thus I do not want the presentation to "skip" ("jump" ?) from one slide to the next slide. I would like it to look like the line is being added naturally. The below code I believe shows the problem. It is subtle, but not how the plot area of the second plot is slightly larger than of the first plot. This happens because of the y axis label.
library(ggplot2)
dfr1 <- data.frame(
time = 1:10,
value = runif(10)
)
dfr2 <- data.frame(
time = 1:10,
value = runif(10, 1000, 1001)
)
p1 <- ggplot(dfr1, aes(time, value)) + geom_line() + scale_y_continuous(breaks = NULL) + scale_x_continuous(breaks = NULL) + ylab(expression(hat(z)==hat(gamma)[1]*time+hat(gamma)[4]*time^2))
print(p1)
dev.new()
p2 <- ggplot(dfr2, aes(time, value)) + geom_line() + scale_y_continuous(breaks = NULL) + scale_x_continuous(breaks = NULL) + ylab(".")
print(p2)
I would prefer to not have a hackish solution such as setting the size of the axis label manually or adding spaces on the x-axis (see one reference below), because I will use this technique in several settings and the labels can change at any time (I like reproducibility so want a flexible solution).
I'm searched a lot and have found the following:
Specifying ggplot2 panel width
How can I make consistent-width plots in ggplot (with legends)?
https://groups.google.com/forum/#!topic/ggplot2/2MNoYtX8EEY
How can I add variable size y-axis labels in R with ggplot2 without changing the plot width?
They do not work for me, mainly because I need separate plots, so it is not a matter of aligning them virtically on one combined plot as in some of the above solutions.
haven't tried, but this might work,
gl <- lapply(list(p1,p2), ggplotGrob)
library(grid)
widths <- do.call(unit.pmax, lapply(gl, "[[", "widths"))
heights <- do.call(unit.pmax, lapply(gl, "[[", "heights"))
lg <- lapply(gl, function(g) {g$widths <- widths; g$heights <- heights; g})
grid.newpage()
grid.draw(lg[[1]])
grid.newpage()
grid.draw(lg[[2]])
How about using this for p2:
p2 <- ggplot(dfr2, aes(time, value)) + geom_line() +
scale_y_continuous(breaks = NULL) +
scale_x_continuous(breaks = NULL) +
ylab(expression(hat(z)==hat(gamma)[1]*time+hat(gamma)[4]*time^2)) +
theme(axis.title.y=element_text(color=NA))
This has the same label as p1, but the color is NA so it doesn't display. You could also use color="white".

ggplot: reduce the space reserved for y-axis text

I have the following dual plot (from another SO question):
Here's the code that generates the plot:
library(ggplot2)
library(gtable)
df <- data.frame(x=c(5,2,7,3),
y=c("asdasxfqwe","a","b","c"),
facet=c(1,1,2,2))
# First plot (a bit of extra space between facets)
p <- ggplot(df, aes(x, y)) + facet_grid(~facet) +
geom_point() +
theme(panel.margin = unit(4, "lines"),
axis.text.y = element_text( hjust=0.5))
# get y-axis labels
g <- ggplotGrob(p)
axis <- gtable_filter(g, "axis-l")[["grobs"]][[1]][["children"]][["axis"]][,1]
# remove axis
g[["grobs"]][[4]][["children"]][["axis"]] <- NULL
# build plot & add axis to LHS of left facet
panels <- subset(g$layout, name == "panel")
g <- gtable_add_grob(g, grobs=axis, t = unique(panels$t),
l=tail(panels$l, -1)-1)
grid.newpage()
grid.draw(g)
As I understand, the empty space on the left is where the y-axis text used to be before it was moved using the gtable code. How to get rid of this empty space?
Upgraded comment:
Since you're editing the gtable, you can set the relevant width to something smaller,
g[["widths"]][3] <- list(unit(1, "line"))

Resources