Blank Plot Output when using "geom_xspline" in ggalt package - r

When trying to use geom_xspline from ggalt in conjunction with ggarrange from ggpubr, the output is blank and no other plot can be made before clearing with dev.off().
In my use-case I wanted the geom_xspline to replace some exisitng geom_line in my ggplot object. Is anyone aware of issues using geoms added from other R packages?
Here is some code to compare, nothing of interest really, just to give a reproducible example:
Initial Working Code w/o geom_xspline
library(ggplot2)
library(ggpubr)
myplot = ggplot(data = mtcars, aes(x = wt, y = mpg)) +
geom_line()
ggarrange(myplot, myplot) # Works and outputs fine
Code that fails with ggalt package
library(ggalt)
library(ggplot2)
library(ggpubr)
myplot = ggplot(data = mtcars, aes(x = wt, y = mpg)) +
geom_xspline()
ggarrange(myplot, myplot) # Output becomes blank and freezes the plot panel
Alternative Method
Instead of using ggarrange I tried the function grid_arrange_shared_legend from this link, which uses grid and gridExtra. However, I am still curious as to why ggarrange does not work.
Here is my session info:
R version 3.5.1 (2018-07-02)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)
Matrix products: default
locale:
[1] LC_COLLATE=English_United States.1252 LC_CTYPE=English_United States.1252 LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C LC_TIME=English_United States.1252
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] ggpubr_0.1.8 magrittr_1.5 ggplot2_3.0.0
loaded via a namespace (and not attached):
[1] Rcpp_0.12.18 pillar_1.3.0 compiler_3.5.1 RColorBrewer_1.1-2 plyr_1.8.4 bindr_0.1.1
[7] tools_3.5.1 extrafont_0.17 tibble_1.4.2 gtable_0.2.0 pkgconfig_2.0.1 rlang_0.2.1
[13] rstudioapi_0.7 yaml_2.2.0 bindrcpp_0.2.2 Rttf2pt1_1.3.7 withr_2.1.2 dplyr_0.7.6
[19] maps_3.3.0 grid_3.5.1 ggalt_0.4.0 tidyselect_0.2.4 cowplot_0.9.3 glue_1.3.0
[25] R6_2.2.2 purrr_0.2.5 extrafontdb_1.0 scales_1.0.0 MASS_7.3-50 assertthat_0.2.0
[31] proj4_1.0-8 colorspace_1.3-2 labeling_0.3 KernSmooth_2.23-15 ash_1.0-15 lazyeval_0.2.1
[37] munsell_0.5.0 crayon_1.3.4
Quick addition, if I convert the object to a ggplotGrob(), it will work with ggarrange, but it will fail when I attempt to use common.legend = T.

Well I am not sure why ggpubr::ggarrange causes failure of Plots pannel when used with ggalt::geom_xspline but I can tell you that plots are still getting created but just now shown on the plot pannel.
So it seems that using those together causes failure in the graphing device and it is only happening for ggalt::geom_xspline and not all the geoms in ggalt. That is a bug so you are on the right track posting to GitHub.
You can check that by running the code below:
library(ggalt)
library(ggplot2)
library(ggpubr)
myplot = ggplot(data = mtcars, aes(x = wt, y = mpg)) +
geom_xspline()
myplot
g <- ggarrange(myplot, myplot) # Output becomes blank and freezes the plot panel
g
jpeg('rplot.jpg')
g
dev.off()
#> pdf
#> 3
Created on 2019-05-30 by the reprex package (v0.3.0)
And this is the saved plot:

The xspline function, upon whichgeom_xspline is based, typically automatically plots using graphics. This led the ggalt package authors to find a few work-arounds to ensure it would play nicely with ggplot. My rough solutions both involve creating or adjusting a geom or stat from ggplot without using xspline. This makes it easier to use without a lot of pre-processing the data prior to ingesting with ggplot.
(1) New stat using splines
Using spline for interpolation of points instead of xspline.
# Create a new stat (adjusted from ggalt GitHub page)
stat_spline <- function(mapping = NULL, data = NULL, geom = "line",
position = "identity", na.rm = TRUE, show.legend = NA, inherit.aes = TRUE,
n=200, method = "fmm", ...) { # Just picking a rough default for n
layer(
stat = StatSpline,
data = data,
mapping = mapping,
geom = geom,
position = position,
show.legend = show.legend,
inherit.aes = inherit.aes,
params = list(n=n,
method=method,
na.rm = na.rm,
...
)
)
}
StatSpline <- ggproto("StatSpline", Stat,
required_aes = c("x", "y"),
compute_group = function(self, data, scales, params,
n=200, method = "fmm") {
tmp <- spline(data$x, data$y, n = n, method = method, ties = mean)
data.frame(x=tmp$x, y=tmp$y)
}
)
# Plot with ggarrange
myplot = ggplot(data = mtcars, aes(x = wt, y = mpg)) +
stat_spline(mapping = aes(x = wt, y = mpg)) +
geom_point()
ggpubr::ggarrange(myplot, myplot)
This method isn't ideal if you want splines similar to Catmull-Rom instead of Cubic; you can see some large bends between control points.
(2) New geom using xsplineGrob
This is a slightly adjusted version of geom_xspline2 from ggalt
# Create new geom based upon code from ggalt GitHub page
GeomXSpline3 <- ggproto("GeomXSpline3", Geom,
required_aes = c("x", "y"),
default_aes = aes(colour = "black", shape=-1, open=T),
draw_key = draw_key_point,
draw_panel = function(data, panel_params, coord) {
coords <- coord$transform(data, panel_params)
grid::xsplineGrob(
coords$x, coords$y,
shape = coords$shape,
open = coords$open[1],
gp = grid::gpar(col = coords$colour)
)
}
)
geom_xspline3 <- function(mapping = NULL, data = NULL, stat = "identity",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE, ...) {
layer(
geom = GeomXSpline3, mapping = mapping, data = data, stat = stat,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...)
)
}
# Plot with ggarrange
myplot = ggplot(data = mtcars, aes(x = wt, y = mpg)) +
geom_xspline3(shape = -.25) + geom_point()
ggpubr::ggarrange(myplot, myplot)
There were a couple issues with ensuring the shape parameter still accepted inputs between -1 and 1, however, this seems to be working okay now with ggarrange.
I used the following resources while writing this solution:
A blog post from an author from ggalt
The GitHub page for geom_xspline and geom_xspline2
ggplot vignette on extending ggplot

Related

ggplot loses scale_color_manual when saving to png with ggsave

I am having a strange issue where saving a ggplot figure that I make does not maintain the colors I set using scale_color_manual. I have made a reproducible example (with some editing) using the mtcars dataset.
plot1 <- ggplot(data = mtcars %>% rownames_to_column("type") %>%
dplyr::filter(between(cyl, 6, 8)) %>%
dplyr::filter(between(gear, 4, 5))
) +
aes(y = wt, x = type) +
geom_boxplot(outlier.size = 0) +
geom_jitter(aes(color = factor(cyl), shape = factor(gear)), size = 10, position=position_jitter(width=.25, height=0)) +
#geom_smooth(method = lm, se = TRUE) +
scale_shape_manual(values=c("👧","👦"), name = "Gear", labels = c("4", "5")) + # I need 9 values (I for each ID)
scale_color_manual(values=c('red4', 'springgreen4'), name = "cyl", labels = c("4 cylinder", "5 cylinder")) +
# # geom_jitter(size=8, aes(shape=Sex, color=Sex), position = position_dodge(.4)) +
theme(legend.position = "top",
plot.title = element_text(hjust = 0.5) # Center the text title)
)
ggsave("images/review/mean_AllAgents_test.png",plot1, width=11, height=6.5, dpi=400)
The figure in the RStudio "Plots" pane has cyl colored in red and green shown below
Whereas the file saved using ggsave does not show these colors.
I have tried using the fix from this SO post. I also have tried using cowplot::save_plot. The colors do remain if I manually Export the figure from the "Plots" pane.
Does anyone know why this is occurring?
R version 4.0.4 (2021-02-15)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19043)
Matrix products: default
locale:
[1] LC_COLLATE=English_Canada.1252 LC_CTYPE=English_Canada.1252 LC_MONETARY=English_Canada.1252 LC_NUMERIC=C
[5] LC_TIME=English_Canada.1252
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] apastats_0.3 ggstatsplot_0.8.0 rstatix_0.7.0 hrbrthemes_0.8.0 gtsummary_1.4.2.9011 car_3.0-11
[7] carData_3.0-4 forcats_0.5.1 stringr_1.4.0 dplyr_1.0.6 purrr_0.3.4 readr_2.0.0
[13] tidyr_1.1.3 tibble_3.1.2 tidyverse_1.3.1 Rmisc_1.5 plyr_1.8.6 lattice_0.20-41
[19] ggplot2_3.3.5 rio_0.5.27 pacman_0.5.1
EDIT
I was asked to provide additional detail in my Preferences

Is there a way to subset data in ggrepel with data inherited from the pipe? [duplicate]

I am trying to subset a layer of a plot where I am passing the data to ggplot through a pipe.
Here is an example:
library(dplyr)
library(ggplot2)
library(scales)
set.seed(12345)
df_example = data_frame(Month = rep(seq.Date(as.Date("2015-01-01"),
as.Date("2015-12-31"), by = "month"), 2),
Value = sample(seq.int(30, 150), size = 24, replace = TRUE),
Indicator = as.factor(rep(c(1, 2), each = 12)))
df_example %>%
group_by(Month) %>%
mutate(`Relative Value` = Value/sum(Value)) %>%
ungroup() %>%
ggplot(aes(x = Month, y = Value, fill = Indicator, group = Indicator)) +
geom_bar(position = "fill", stat = "identity") +
theme_bw()+
scale_y_continuous(labels = percent_format()) +
geom_line(aes(x = Month, y = `Relative Value`))
This gives:
I would like only one of those lines to appear, which I would be able to do if something like this worked in the geom_line layer:
geom_line(subset = .(Indicator == 1), aes(x = Month, y = `Relative Value`))
Edit:
Session info:
R version 3.2.1 (2015-06-18) Platform: x86_64-w64-mingw32/x64 (64-bit) Running under: Windows Server 2012 x64
(build 9200)
locale: 2 LC_COLLATE=English_United States.1252
LC_CTYPE=English_United States.1252 [3] LC_MONETARY=English_United
States.1252 LC_NUMERIC=C [5]
LC_TIME=English_United States.1252
attached base packages: 2 stats graphics grDevices utils
datasets methods base
other attached packages: 2 scales_0.3.0 lubridate_1.3.3
ggplot2_1.0.1 lazyeval_0.1.10 dplyr_0.4.3 RSQLite_1.0.0
readr_0.2.2 [8] RJDBC_0.2-5 DBI_0.3.1 rJava_0.9-7
loaded via a namespace (and not attached): 2 Rcpp_0.12.2
knitr_1.11 magrittr_1.5 MASS_7.3-40 munsell_0.4.2
lattice_0.20-31 [7] colorspace_1.2-6 R6_2.1.1 stringr_1.0.0
plyr_1.8.3 tools_3.2.1 parallel_3.2.1 [13] grid_3.2.1
gtable_0.1.2 htmltools_0.2.6 yaml_2.1.13 assertthat_0.1
digest_0.6.8 [19] reshape2_1.4.1 memoise_0.2.1
rmarkdown_0.8.1 labeling_0.3 stringi_1.0-1 zoo_1.7-12
[25] proto_0.3-10
tl;dr: Pass the data to that layer as a function that subsets the plot's data according to your criteria.
According to ggplots documentation on layers, you have 3 options when passing the data to a new layer:
If NULL, the default, the data is inherited from the plot data as specified in the call to ggplot().
A data.frame, or other object, will override the plot data. All objects will be fortified to produce a data frame. See fortify() for
which variables will be created.
A function will be called with a single argument, the plot data. The return value must be a data.frame, and will be used as the
layer data.
The first two options are the most usual ones, but the 3rd is perfect for our needs when the data has been modified through pyps.
In your example, adding data = function(x) subset(x,Indicator == 1) to the geom_line does the trick:
library(dplyr)
library(ggplot2)
library(scales)
set.seed(12345)
df_example = data_frame(Month = rep(seq.Date(as.Date("2015-01-01"),
as.Date("2015-12-31"), by = "month"), 2),
Value = sample(seq.int(30, 150), size = 24, replace = TRUE),
Indicator = as.factor(rep(c(1, 2), each = 12)))
df_example %>%
group_by(Month) %>%
mutate(`Relative Value` = Value/sum(Value)) %>%
ungroup() %>%
ggplot(aes(x = Month, y = Value, fill = Indicator, group = Indicator)) +
geom_bar(position = "fill", stat = "identity") +
theme_bw()+
scale_y_continuous(labels = percent_format()) +
geom_line(data = function(x) subset(x,Indicator == 1), aes(x = Month, y = `Relative Value`))
This is the resulting plot
library(dplyr)
library(ggplot2)
library(scales)
set.seed(12345)
df_example = data_frame(Month = rep(seq.Date(as.Date("2015-01-01"),
as.Date("2015-12-31"), by = "month"), 2),
Value = sample(seq.int(30, 150), size = 24, replace = TRUE),
Indicator = as.factor(rep(c(1, 2), each = 12)))
df_example %>%
group_by(Month) %>%
mutate(`Relative Value` = Value/sum(Value)) %>%
ungroup() %>%
ggplot(aes(x = Month, y = Value, fill = Indicator, group = Indicator)) +
geom_bar(position = "fill", stat = "identity") +
theme_bw()+
scale_y_continuous(labels = percent_format()) +
geom_line(aes(x = Month, y = `Relative Value`,linetype=Indicator)) +
scale_linetype_manual(values=c("1"="solid","2"="blank"))
yields:
You might benefit from stat_subset(), a stat I made for my personal use that is available in metR: https://eliocamp.github.io/metR/articles/Visualization-tools.html#stat_subset
It has an aesthetic called subset that takes a logical expression and subsets the data accordingly.
library(dplyr)
library(ggplot2)
library(scales)
set.seed(12345)
df_example = data_frame(Month = rep(seq.Date(as.Date("2015-01-01"),
as.Date("2015-12-31"), by = "month"), 2),
Value = sample(seq.int(30, 150), size = 24, replace = TRUE),
Indicator = as.factor(rep(c(1, 2), each = 12)))
df_example %>%
group_by(Month) %>%
mutate(`Relative Value` = Value/sum(Value)) %>%
ungroup() %>%
ggplot(aes(x = Month, y = Value, fill = Indicator, group = Indicator)) +
geom_bar(position = "fill", stat = "identity") +
theme_bw()+
scale_y_continuous(labels = percent_format()) +
metR::stat_subset(aes(x = Month, y = `Relative Value`, subset = Indicator == 1),
geom = "line")

ggplot2 both axis labels inside plot area

I would like to create a ggplot2 with both the y-axis and x-axis labels on the inside, i.e., facing inwards and placed inside the plot area.
This previous SO answer by Z.Lin solves it for the case of the y-axis, and I've got that working just fine. But extending that approach to both axes has me stumped. grobs is hard, I think.
So I attempted to start small, by adapting Z.Lin's code to work for the x-axis instead of the y-axis, but I have not been able to achieve even that. grobs is really complicated. My attempt (below) runs without errors/warnings until grid.draw(), where it crashes and burns (I think I'm misusing some args somewhere, but I can't identify which and at this point I'm just guessing).
# locate the grob that corresponds to x-axis labels
x.label.grob <- gp$grobs[[which(gp$layout$name == "axis-b")]]$children$axis
# remove x-axis labels from the plot, & shrink the space occupied by them
gp$grobs[[which(gp$layout$name == "axis-b")]] <- zeroGrob()
gp$widths[gp$layout$l[which(gp$layout$name == "axis-b")]] <- unit(0, "cm")
# create new gtable
new.x.label.grob <- gtable::gtable(widths = unit(1, "npc"))
# place axis ticks in the first row
new.x.label.grob <-
gtable::gtable_add_rows(
new.x.label.grob,
heights = x.label.grob[["heights"]][1])
new.x.label.grob <-
gtable::gtable_add_grob(
new.x.label.grob,
x.label.grob[["grobs"]][[1]],
t = 1, l = 1)
# place axis labels in the second row
new.x.label.grob <-
gtable::gtable_add_rows(
new.x.label.grob,
heights = x.label.grob[["heights"]][2])
new.x.label.grob <-
gtable::gtable_add_grob(
new.x.label.grob,
x.label.grob[["grobs"]][[2]],
t = 1, l = 2)
# add third row that takes up all the remaining space
new.x.label.grob <-
gtable::gtable_add_rows(
new.x.label.grob,
heights = unit(1, "null"))
gp <-
gtable::gtable_add_grob(
x = gp,
grobs = new.x.label.grob,
t = gp$layout$t[which(gp$layout$name == "panel")],
l = gp$layout$l[which(gp$layout$name == "panel")])
grid.draw(gp)
# Error in unit(widths, default.units) :
# 'x' and 'units' must have length > 0
I guess my question can be split into three semi-independent parts, where each subsequent question supersedes the earlier ones (so if you can answer a later question, there will be no need to bother with the earlier ones):
can anyone adapt the existing answer to the x-axis?
can anyone provide code in that vein to get both axes inside?
does anyone know of a neater way to achieve both axes inside for ggplot2?
Here's my MWE (mostly replicating Z.Lin's answer, but with new data):
library(dplyr)
library(magrittr)
library(ggplot2)
library(grid)
library(gtable)
library(errors)
df <- structure(list(
temperature = c(200, 300, 400, 500, 600, 700, 800, 900),
diameter =
structure(
c(13.54317, 10.32521, 10.23137, 17.90464, 29.98183, 55.65514, 101.60747, 147.3074),
id = "<environment>",
errors = c(1.24849, 0.46666, 0.36781, 0.48463, 0.94639, 1.61459, 6.98346, 12.18353),
class = "errors")),
row.names = c(NA, -8L),
class = "data.frame")
p <- ggplot() +
geom_smooth(data = df %>% filter(temperature >= 400),
aes(x = temperature, y = diameter),
method = "lm", formula = "y ~ x",
se = FALSE, fullrange = TRUE) +
# experimental errors as red ribbon (instead of errorbars)
geom_ribbon(data = df,
aes(x = temperature,
ymin = errors_min(diameter),
ymax = errors_max(diameter)),
fill = alpha("red", 0.2),
colour = alpha("red", 0.2)) +
geom_point(data = df,
aes(x = temperature, y = diameter),
size = 0.7) +
geom_line(data = df,
aes(x = temperature, y = diameter),
size = 0.15) +
scale_x_continuous(breaks = seq(200, 900, 200)) +
scale_y_log10(breaks = c(10, seq(30, 150, 30)),
labels = c("10", "30", "60", "90", "120", "150=d/nm")) +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.title.y = element_blank(),
axis.text.y = element_text(hjust = 0))
# convert from ggplot to grob object
gp <- ggplotGrob(p)
y.label.grob <- gp$grobs[[which(gp$layout$name == "axis-l")]]$children$axis
gp$grobs[[which(gp$layout$name == "axis-l")]] <- zeroGrob()
gp$widths[gp$layout$l[which(gp$layout$name == "axis-l")]] <- unit(0, "cm")
new.y.label.grob <- gtable::gtable(heights = unit(1, "npc"))
new.y.label.grob <-
gtable::gtable_add_cols(
new.y.label.grob,
widths = y.label.grob[["widths"]][2])
new.y.label.grob <-
gtable::gtable_add_grob(
new.y.label.grob,
y.label.grob[["grobs"]][[2]],
t = 1, l = 1)
new.y.label.grob <-
gtable::gtable_add_cols(
new.y.label.grob,
widths = y.label.grob[["widths"]][1])
new.y.label.grob <-
gtable::gtable_add_grob(
new.y.label.grob,
y.label.grob[["grobs"]][[1]],
t = 1, l = 2)
new.y.label.grob <-
gtable::gtable_add_cols(
new.y.label.grob,
widths = unit(1, "null"))
gp <-
gtable::gtable_add_grob(
x = gp,
grobs = new.y.label.grob,
t = gp$layout$t[which(gp$layout$name == "panel")],
l = gp$layout$l[which(gp$layout$name == "panel")])
grid.draw(gp)
> sessionInfo()
R version 3.6.2 (2019-12-12)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1
locale:
[1] LC_CTYPE=en_GB.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_GB.UTF-8 LC_COLLATE=en_GB.UTF-8
[5] LC_MONETARY=en_GB.UTF-8 LC_MESSAGES=en_GB.UTF-8
[7] LC_PAPER=en_GB.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] grid stats graphics grDevices utils datasets methods
[8] base
other attached packages:
[1] errors_0.3.4 gtable_0.3.0 ggplot2_3.3.2 magrittr_1.5 dplyr_1.0.2
loaded via a namespace (and not attached):
[1] rstudioapi_0.11 splines_3.6.2 tidyselect_1.1.0 munsell_0.5.0
[5] lattice_0.20-41 colorspace_1.4-1 R6_2.5.0 rlang_0.4.8
[9] tools_3.6.2 nlme_3.1-148 mgcv_1.8-31 withr_2.3.0
[13] ellipsis_0.3.1 digest_0.6.27 yaml_2.2.1 tibble_3.0.4
[17] lifecycle_0.2.0 crayon_1.3.4 Matrix_1.2-18 purrr_0.3.4
[21] farver_2.0.3 vctrs_0.3.4 glue_1.4.2 compiler_3.6.2
[25] pillar_1.4.6 generics_0.1.0 scales_1.1.1 pkgconfig_2.0.3
Rather than "freezing" the plot as a grob tree then hacking the grobs, I thought it might be useful to see how we could move the axes inside but keep the object as a ggplot. The way to do this is to write a function that takes your plot, extracts the necessary information, then builds axes and adds them as annotations.
The returned object is a normal ggplot, to which you can add layers, scales and modify themes as normal:
move_axes_inside <- function(p)
{
b <- ggplot_build(p)
x_breaks <- b$layout$panel_scales_x[[1]]$break_info()
y_breaks <- b$layout$panel_scales_y[[1]]$break_info()
x_range <- b$layout$panel_params[[1]]$x.range
y_range <- b$layout$panel_params[[1]]$y.range
y_breaks$major <- diff(y_breaks$range)/diff(y_range) * y_breaks$major +
(y_breaks$range[1] - y_range[1])/diff(y_range)
x_breaks$major <- diff(x_breaks$range)/diff(x_range) * x_breaks$major +
(x_breaks$range[1] - x_range[1])/diff(x_range)
y <- grid::yaxisGrob(at = y_breaks$major, label = y_breaks$labels, main = FALSE)
x <- grid::xaxisGrob(at = x_breaks$major, label = x_breaks$labels, main = FALSE)
p + annotation_custom(y, xmin = x_range[1], xmax = x_range[1]) +
annotation_custom(x, ymin = y_range[1], ymax = y_range[1]) +
theme(axis.text.y = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank())
}
So testing it with your plot we get:
p2 <- move_axes_inside(p)
p2
And we can change theme elements etc:
p2 + theme(panel.grid.major = element_line())
This would need a bit of development and testing to get it working with discrete axes and so on, but it should work for arbitrary continuous axes as-is.
In case anyone else happens to be looking for a way to make a compact plot using ggplot2, for example for placement inside a page margin, I perhaps you'll be helped by the full code for a fairly publication-ready inside-the-margin plot made possible by Allan Cameron's elegant approach in the answer above.
Placing a plot inside a page margin is usually not advisable, and depends on the available margin, the type of document, etc. In any case, it's probably smart to make the plot as clutter-free and stream-lined as possible. That's why, in my case, I was looking for a way to keep as much of the plot inside the panel's footprint, so to speak.
Enough background, here's the code:
library(dplyr)
library(magrittr)
library(ggplot2)
library(grid)
library(gtable)
library(errors)
theme_set(theme_grey())
move_axes_inside <- function(p) {
b <- ggplot_build(p)
x_breaks <- b$layout$panel_scales_x[[1]]$break_info()
y_breaks <- b$layout$panel_scales_y[[1]]$break_info()
x_range <- b$layout$panel_params[[1]]$x.range
y_range <- b$layout$panel_params[[1]]$y.range
y_breaks$major <-
diff(y_breaks$range) / diff(y_range) * y_breaks$major +
(y_breaks$range[1] - y_range[1]) / diff(y_range)
x_breaks$major <-
diff(x_breaks$range) / diff(x_range) * x_breaks$major +
(x_breaks$range[1] - x_range[1]) / diff(x_range)
y <-
grid::yaxisGrob(
at = y_breaks$major,
label = y_breaks$labels,
gp =
gpar(
lwd = 1, # line width of axis and tick marks
fontsize = 8,
cex = 0.8, # multiplier to font size
lineheight = 0.8), # tick mark length
main = FALSE)
x <-
grid::xaxisGrob(
at = x_breaks$major,
label = x_breaks$labels,
gp =
gpar(
lwd = 2, # draw axis with thicker line width
fontsize = 8,
cex = 0.8, # multiplier to font size
lineheight = 0.8), # tick mark length
main = FALSE)
p <-
p +
annotation_custom(
# draw y-axis, shifted slightly inwards (so that axis is inside panel.border)
grob = y,
xmin = x_range[1] + 0.01 * diff(x_range),
xmax = x_range[1] + 0.01 * diff(x_range)) +
annotation_custom(
grob = x,
ymin = y_range[1] + 0.01 * diff(y_range),
ymax = y_range[1] + 0.01 * diff(y_range)) +
theme(
axis.ticks = element_blank(),
axis.title.y = element_blank(),
axis.text.y = element_blank(),
axis.text.x = element_blank())
return(p)
}
p <- ggplot() +
geom_line(
stat = "smooth", method = lm, formula = "y ~ x",
se = FALSE, fullrange = TRUE,
data = df %>% filter(temperature >= 400),
aes(x = temperature, y = diameter),
colour = "blue", size = 2, alpha = 0.35) +
# experimental errors as red ribbon (instead of errorbars)
geom_ribbon(
data = df,
aes(x = temperature,
ymin = errors_min(diameter),
ymax = errors_max(diameter)),
fill = alpha("red", 0.25),
colour = NA) +
# data points excluded in linear fit
geom_point(
data = df %>% filter(temperature < 400),
aes(x = temperature, y = diameter),
# by default, shape=19 (filled circle)
# https://blog.albertkuo.me/post/point-shape-options-in-ggplot/
# I'd like a solid circle, so shape 16 it is
size = 1.2, shape = 16, colour = alpha("red", 0.25)) +
# data points included in linear fit
geom_point(
data = df %>% filter(temperature >= 400),
aes(x = temperature, y = diameter),
size = 1.2, shape = 16, colour = alpha("red", 0.45)) +
# I ended up putting the x-axis unit label on the outside because
# however I tried, it would not fit inside and I was not able to
# rotate the x-axis labels on the inside.
labs(x = "$T_\\mathrm{a}/\\si{\\celsius}$") +
scale_x_continuous(
breaks = seq(200, 900, 100),
# first element can't be empty string - if so then all labels dont print (weird bug?)
labels = c(" ", " ", "400", " ", "600", " ", "800", " ")) +
scale_y_log10(
breaks = c(10, 50, 90, 130),
labels = c("\\num{10}", "\\num{50}", "\\num{90}", "$\\num{130}=d/\\si{\\nm}$")) +
# note that we set some theme settings inside the move_axes_inside() function
theme(
# l = -1 was required to completely fill the space with plot panel
# b = 0 because we are making room for x-axis title on the outside
plot.margin = margin(t = 0, r = 0, b = 0, l = -1, "mm"),
# smaller text size in x-axis title, trying to conform with fontsize inside axis
# vjust moves the title closer to the x-axis line, value optimised optically
axis.title.x = element_text(size = 8 * 0.8, vjust = 2.0),
# grid lines just look busy in such a small plot
panel.grid.major = element_blank(),
panel.grid.minor = element_blank())
move_axes_inside(p)
Here's a screen-shot of the result, in a document compiled with knitr and LaTeX and with the plot inside \marginpar{}:

Strange interaction between Alpha and legend

While plotting several ecdf curves that overlapped, I tried adjusting the alpha of the curves to improve visibility. While tinkering with the correct placement of alpha, I found the following.
library(ggplot2)
library(dplyr)
x <- data.frame(Var = rep(1:3, 10000)) %>%
mutate(Val = rnorm(10000)*Var,
Var = factor(Var)) %>%
arrange(Var, Val) %>%
group_by(Var) %>%
mutate(ecdf = ecdf(Val)(Val))
ggplot(x, aes(x=Val)) +
stat_ecdf(aes(color = Var), size = 1.25, alpha = .9)
This gives the lines the correct alpha, but makes the legend useless. (I'm only using alpha=.9 here to demonstrate the point that the legend colors completely disappear). The work around I've found is to add:
ggplot(x, aes(x=Val)) +
stat_ecdf(aes(color = Var), size = 1.35, alpha = .9) +
guides(color = guide_legend(override.aes= list(alpha = 1)))
So while I have a solution for my immediate problem, can someone explain why the first call to ggplot is messed up? Is this a bug? If it makes any difference, I believe this issue also exists when using geom_line (though a slightly different data.frame is needed).
Wierd. Here's my sessionInfo(). I've also checked to see if there are any outdated packages.
sessionInfo()
R version 3.2.1 (2015-06-18)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 7 x64 (build 7601) Service Pack 1
locale:
[1] LC_COLLATE=Japanese_Japan.932 LC_CTYPE=Japanese_Japan.932 LC_MONETARY=Japanese_Japan.932
[4] LC_NUMERIC=C LC_TIME=Japanese_Japan.932
attached base packages:
[1] splines stats graphics grDevices utils datasets methods base
other attached packages:
[1] RColorBrewer_1.1-2 ggplot2_1.0.1 stringr_1.0.0 tidyr_0.2.0 dplyr_0.4.2
[6] data.table_1.9.4
loaded via a namespace (and not attached):
[1] Rcpp_0.11.6 magrittr_1.5 MASS_7.3-40 munsell_0.4.2 colorspace_1.2-6
[6] R6_2.0.1 plyr_1.8.3 tools_3.2.1 parallel_3.2.1 grid_3.2.1
[11] gtable_0.1.2 DBI_0.3.1 lazyeval_0.1.10 assertthat_0.1 digest_0.6.8
[16] reshape2_1.4.1 labeling_0.3 stringi_0.5-4 scales_0.2.5 chron_2.3-47
[21] proto_0.3-10
How are they different? What am I missing?
library(ggplot2)
library(dplyr)
library(gridExtra)
x <- data.frame(Var = rep(1:3, 10000)) %>%
mutate(Val = rnorm(10000)*Var,
Var = factor(Var)) %>%
arrange(Var, Val) %>%
group_by(Var) %>%
mutate(ecdf = ecdf(Val)(Val))
ggplot(x, aes(x=Val)) +
stat_ecdf(aes(color = Var), size = 1.25, alpha = .9) -> gg1
ggplot(x, aes(x=Val)) +
stat_ecdf(aes(color = Var), size = 1.35, alpha = .9) +
guides(color = guide_legend(override.aes= list(alpha = 1))) -> gg2
grid.arrange(gg1, gg2)

R: round() can find object, sprintf() cannot, why?

I have a function that takes a dataframe and plots a number of columns from that data frame using ggplot2. The aes() function in ggplot2 takes a label argument and I want to use sprintf to format that argument - and this is something I have done many times before in other code. When I pass the format string to sprintf (in this case "%1.1f") it says "object not found". If I use the round() function and pass an argument to that function it can find it without problems. Same goes for format(). Apparently only sprintf() is unable to see the object.
At first I thought this was a lazy evaluation issue caused by calling the function rather than using the code inline, but using force() on the format string I pass to sprintf does not resolve the issue. I can work around this, but I would like to know why it happens. Of course, it may be something trivial that I have overlooked.
Q. Why does sprintf() not find the string object?
Code follows (edited and pruned for more minimal example)
require(gdata)
require(ggplot2)
require(scales)
require(gridExtra)
require(lubridate)
require(plyr)
require(reshape)
set.seed(12345)
# Create dummy time series data with year and month
monthsback <- 64
startdate <- as.Date(paste(year(now()),month(now()),"1",sep = "-")) - months(monthsback)
mydf <- data.frame(mydate = seq(as.Date(startdate), by = "month", length.out = monthsback), myvalue5 = runif(monthsback, min = 200, max = 300))
mydf$year <- as.numeric(format(as.Date(mydf$mydate), format="%Y"))
mydf$month <- as.numeric(format(as.Date(mydf$mydate), format="%m"))
getchart_highlight_value <- function(
plotdf,
digits_used = 1
)
{
force(digits_used)
#p <- ggplot(data = plotdf, aes(x = month(mydate, label = TRUE), y = year(mydate), fill = myvalue5, label = round(myvalue5, digits_used))) +
# note that the line below using sprintf() does not work, whereas the line above using round() is fine
p <- ggplot(data = plotdf, aes(x = month(mydate, label = TRUE), y = year(mydate), fill = myvalue5, label = sprintf(paste("%1.",digits_used,"f", sep = ""), myvalue5))) +
scale_x_date(labels = date_format("%Y"), breaks = date_breaks("years")) +
scale_y_reverse(breaks = 2007:2012, labels = 2007:2012, expand = c(0,0)) +
geom_tile() + geom_text(size = 4, colour = "black") +
scale_fill_gradient2(low = "blue", high = "red", limits = c(min(plotdf$myvalue5), max(plotdf$myvalue5)), midpoint = median(plotdf$myvalue5)) +
scale_x_discrete(expand = c(0,0)) +
opts(panel.grid.major = theme_blank()) +
opts(panel.background = theme_rect(fill = "transparent", colour = NA)) +
png(filename = "c:/sprintf_test.png", width = 700, height = 300, units = "px", res = NA)
print(p)
dev.off()
}
getchart_highlight_value (plotdf <- mydf,
digits_used <- 1)
Using the minimal example of Martin (that is a minimal example, see also this question), you can make the code work by specifying the environment ggplot() should use. For that, specify the argument environment in the ggplot() function, eg like this:
require(ggplot2)
getchart_highlight_value <- function(df)
{
fmt <- "%1.1f"
ggplot(df, aes(x, x, label=sprintf(fmt, lbl)),
environment = environment()) +
geom_tile(bg="white") +
geom_text(size = 4, colour = "black")
}
df <- data.frame(x = 1:5, lbl = runif(5))
getchart_highlight_value (df)
The function environment() returns the current (local) environment, which is the environment created by the function getchart_highlight_value(). If you don't specify this, ggplot() will look in the global environment, and there the variable fmt is not defined.
Nothing to do with lazy evaluation, everything to do with selecting the right environment.
The code above produces following plot:
Here's a minimal-er example
require(ggplot2)
getchart_highlight_value <- function(df)
{
fmt <- "%1.1f"
ggplot(df, aes(x, x, label=sprintf(fmt, lbl))) + geom_tile()
}
df <- data.frame(x = 1:5, lbl = runif(5))
getchart_highlight_value (df)
It fails with
> getchart_highlight_value (df)
Error in sprintf(fmt, lbl) : object 'fmt' not found
If I create fmt in the global environment then everything is fine; maybe this explains the 'sometimes it works' / 'it works for me' comments above.
> sessionInfo()
R version 2.15.0 Patched (2012-05-01 r59304)
Platform: x86_64-unknown-linux-gnu (64-bit)
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=C LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] ggplot2_0.9.1
loaded via a namespace (and not attached):
[1] colorspace_1.1-1 dichromat_1.2-4 digest_0.5.2 grid_2.15.0
[5] labeling_0.1 MASS_7.3-18 memoise_0.1 munsell_0.3
[9] plyr_1.7.1 proto_0.3-9.2 RColorBrewer_1.0-5 reshape2_1.2.1
[13] scales_0.2.1 stringr_0.6

Resources