There always seems to be some small but noticeable offset between where tick marks end and where the panel starts.

This is more evident when axis limits are specified so that the axis ends on a tick (see above). e.g., On the y-axis, the highest tick is too high and the lowest tick is too low.
Code to reproduce:
library(ggplot2)
p <-
ggplot(mtcars,aes(mpg, wt)) +
geom_point() +
scale_y_continuous(limits=c(1,5), expand=c(0,0))
p <- p + theme_few()
p <- p + theme(axis.ticks=element_line(size=0.5),axis.ticks.length = unit(0.5, "cm")) # enlarged for clarity, although this happens regardless
How can I fix this?
Edit:
In using the accepted answer on my own data, I noticed this also changes how points at the axis extrema are clipped. Is there any way to correctly align ticks and the panel, but not change this behavior? I don't want to remove the points, just still hide the portions outside the panel.

it's not the position that's off, but the panel border that is being clipped,
library(ggplot2)
p <- ggplot(mtcars,aes(mpg, wt)) + geom_point() + scale_y_continuous(limits=c(1,5),expand=c(0,0))
p <- p + theme_bw()
p <- p + theme(axis.ticks=element_line(size=3),
panel.border = element_rect(size = 3),
axis.ticks.length = unit(0.5, "cm"))
g <- ggplotGrob(p)
g$layout$clip <- "off"
library(gridExtra)
grid.arrange(p, g, ncol=2)

Edit: The above observation wasn't meant to be a solution; as noted in the comments turning off the clipping for the whole panel is problematic for the points that leak out of the panel area. A possible workaround is to add a new grob to the gtable, e.g. by extracting it from panel and adding it on top without clipping.
library(ggplot2)
p <- ggplot(mtcars,aes(mpg, wt)) + geom_point(size=20) + scale_y_continuous(limits=c(1,5),expand=c(0,0))
p <- p + theme_bw()
p <- p + theme(axis.ticks=element_line(size=2),
panel.border = element_rect(size = 2),
axis.ticks.length = unit(0.5, "cm"))
g <- ggplotGrob(p)
panel_id <- g$layout$name == "panel"
border <- tail(g$grobs[panel_id][[1]][["children"]], 1)
g <- gtable::gtable_add_grob(g, border,
t = min(g$layout$t[panel_id]),
l = min(g$layout$l[panel_id]),
name = "border",
clip = "off")
library(gridExtra)
grid.arrange(p, g, ncol=2)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With