I am not sure exactly how to override aesthetic properties of a custom plot made with ggplot. The only way I could think of right now was using the functionality of the grid package, though is really hackish. Maybe there is a easier way, like using guides or so from ggplot2, though I could't manage to make it work?
Below is an example where I just want to adjust the line width in the graph. Of course, I would like that to trickle down in the legend as well. So, below are my steps with grid, but any simpler solution is greatly appreciated (ideally something that doesn't need grid but just ggplot2, if possible).
library(iNEXT)
library(ggplot2)
library(grid)
# Some custom plot from the iNEXT package
data(spider)
out <- iNEXT(spider, q=0, datatype="abundance")
custom_plot <- ggiNEXT(out)
custom_plot
# Get the grobs
g <- grid.force(ggplotGrob(custom_plot))
# Check the list of names of grobs:
# grid.ls(g)
# View(g$grobs)
# Get an idea about the grob paths
gpaths <- paste(gsub(pattern = "layout::",
replacement = "",
x = grid.ls(g, print = FALSE)$gPath),
grid.ls(g, print = FALSE)$name,
sep = "::")
gpaths[grepl("polyline", gpaths)]
#> [1] "panel.7-5-7-5::grill.gTree.114::panel.grid.minor.y..polyline.107"
#> [2] "panel.7-5-7-5::grill.gTree.114::panel.grid.minor.x..polyline.109"
#> [3] "panel.7-5-7-5::grill.gTree.114::panel.grid.major.y..polyline.111"
#> [4] "panel.7-5-7-5::grill.gTree.114::panel.grid.major.x..polyline.113"
#> [5] "panel.7-5-7-5::GRID.polyline.91"
#> [6] "panel.7-5-7-5::geom_ribbon.gTree.101::geom_ribbon.gTree.95::GRID.polyline.93"
#> [7] "panel.7-5-7-5::geom_ribbon.gTree.101::geom_ribbon.gTree.99::GRID.polyline.97"
# Edit the width of the lines
g <- editGrob(grob = g,
gPath = gpaths[grepl("panel.7-5-7-5::GRID.polyline", gpaths)],
gp = gpar(lwd = c(1,1,1,1)))
plot(g)
Created on 2020-07-22 by the reprex package (v0.3.0)
The answer you are looking for is under "Draw R/E curves by yourself" at
https://cran.r-project.org/web/packages/iNEXT/vignettes/Introduction.html.
Fortunately, the authors of the package have provided the function fortify() along with some code to copy verbatim, to achieve what you desire.
You should copy the following from that section and change the lwd (line width) parameter in the geom_line() function call to your liking.
df <- fortify(out, type=1) # Note the type parameter!
df.point <- df[which(df$method=="observed"),]
df.line <- df[which(df$method!="observed"),]
df.line$method <- factor(df.line$method,
c("interpolated", "extrapolated"),
c("interpolation", "extrapolation"))
ggplot(df, aes(x=x, y=y, colour=site)) +
geom_point(aes(shape=site), size=5, data=df.point) +
geom_line(aes(linetype=method), lwd=1.5, data=df.line) +
geom_ribbon(aes(ymin=y.lwr, ymax=y.upr,
fill=site, colour=NULL), alpha=0.2) +
labs(x="Number of individuals", y="Species diversity") +
theme(legend.position = "bottom",
legend.title=element_blank(),
text=element_text(size=18),
legend.box = "vertical")
I think you're making life overly complicated. Does this approach gives you what you need?
Generate a plot
plot <- mtcars %>% ggplot() + geom_line(aes(x=mpg, y=cyl, colour=as.factor(gear)))
plot
Modify the plot
plot + aes(size=5) + guides(size=FALSE)
The guides call suppresses the legend for size. Obviously, you can delete it if you do want the legend to appear.
Update
Responding to OP's question in the comments. I agree: my suggestion does not modify the ggiNEXT plot as I predicted.
I've done some digging. The diversity curves in the plot are produced by the following statement in the ggiNEXT.iNEXT function
g <- g + geom_line(aes_string(linetype = "lty"), lwd = 1.5) + ...
I find this strange. As far as I know, lwd is not an aesthetic in ggplot2. (And "lty" is not a valid value for the linetype aesthetic. However, lty and lwd are the base R equivalents of ggplot2's linetype and size respectively.)
In case lwd was an undocumented feature, I tried
custom_plot + aes(lwd=3)
But this had no effect.
I then copied the body of the ggiNEXT.iNEXT function into my own function and changed the call to geom_line to read
g <- g + geom_line(aes_string(linetype = "lty"), size = 1.5)
Calling my new function produced a plot identical (to my eye at least) to that produced by the original ggiNEXT.iNEXT call. Then
custom_plot <- myPlot(out)
custom_plot
custom_plot + aes(size=3) + guides(size=FALSE)
Produced the predicted changes. So my best suggestion is either (1) to create a local version of ggiNEXT.iNEXT and load it whenever you need to make this modification [Of course, you then need to make sure you update your local copy in line with any changes to the "official" version] or (2) to create the graph from scratch. Looking at the source code for ggiNEXT.iNEXT, it's not that complicated.
This might be worth raising as an issue with the authors of iNEXT.
Related
I am using quantile regression in R with the qgam package and visualising them using the mgcViz package, but I am struggling to understand how to control the appearance of the plots. The package effectively turns gams (in my case mqgams) into ggplots.
Simple reprex:
egfit <- mqgam(data = iris,
Sepal.Length ~ s(Petal.Length),
qu = c(0.25,0.5,0.75))
plot.mgamViz(getViz(egfit))
I am able to control things that can be added, for example the axis labels and theme of the plot, but I'm struggling to effect things that would normally be addressed in the aes() or geom_x() functions.
How would I control the thickness of the line? If this were a normal geom_smooth() or geom_line() I'd simply put size = 1 inside of the geoms, but I cannot see how I'd do so here.
How can I control the linetype of these lines? The "id" is continuous and one cannot supply a linetype to a continuous scale. If this were a nomral plot I would convert "id" to a character, but I can't see a way of doing so with the plot.mgamViz function.
How can I supply a new colour scale? It seems as though if I provide it with a new colour scale it invents new ID values to put on the legend that don't correlate to the actual "id" values, e.g.
plot.mgamViz(getViz(egfit)) + scale_colour_viridis_c()
I fully expect this to be relatively simple and I'm missing something obvious, and imagine the answer to all three of these subquestions are very similar to one another. Thanks in advance.
You need to extract your ggplot element using this:
p1 <- plot.mgamViz(getViz(egfit))
p <- p1$plots [[1]]$ggObj
Then, id should be as.factor:
p$data$id <- as.factor(p$data$id)
Now you can play with ggplot elements as you prefer:
library(mgcViz)
egfit <- mqgam(data = iris,
Sepal.Length ~ s(Petal.Length),
qu = c(0.25,0.5,0.75))
p1 <- plot.mgamViz(getViz(egfit))
# Taking gg infos and convert id to factor
p <- p1$plots [[1]]$ggObj
p$data$id <- as.factor(p$data$id)
# Changing ggplot attributes
p <- p +
geom_line(linetype = 3, size = 1)+
scale_color_brewer(palette = "Set1")+
labs(x="Petal Length", y="s(Petal Length)", color = "My ID labels:")+
theme_classic(14)+
theme(legend.position = "bottom")
p
Here the generated plot:
Hope it is useful!
First, I am aware of this answer : Mapping different states in R using facet wrap
But I work with object of library sf.
It seems that facet_wrap(scales = "free") is not available for objects plotted with geom_sf in ggplot2. I get this message:
Erreur : Free scales are only supported with coord_cartesian() and
coord_flip()
Is there any option I have missed ?
Anyone has solve the problem without being forced to use cowplot (or any other gridarrange)?
Indeed, here is an example. I would like to show the different French regions separately in facets but with their own x/y limits.
The result without scales = "free"
Scales are calculated with the extent of the entire map.
FRA <- raster::getData(name = "GADM", country = "FRA", level = 1)
FRA_sf <- st_as_sf(FRA)
g <- ggplot(FRA_sf) +
geom_sf() +
facet_wrap(~NAME_1)
The result using cowplot
I need to use a list of ggplots and can then combine them.
This is the targeted output. It is cleaner. But I also want a clean way to add a legend. (I know may have a common legend like in this other SO question :
facet wrap distorts state maps in R)
g <- purrr::map(FRA_sf$NAME_1,
function(x) {
ggplot() +
geom_sf(data = filter(FRA_sf, NAME_1 == x)) +
guides(fill = FALSE) +
ggtitle(x)
})
g2 <- cowplot::plot_grid(plotlist = g)
I know you are looking for a solution using ggplot2, but I found the tmap package could be a choice depends on your need. The syntax of tmap is similar to ggplot2, it can also take sf object. Take your FRA_sf as an example, we can do something like this.
library(tmap)
tm_shape(FRA_sf) +
tm_borders() +
tm_facets(by = "NAME_1")
Or we can use geom_spatial from the ggspatial package, but geom_spatial only takes Spatial* object.
library(ggplot2)
library(ggspatial)
ggplot() +
geom_spatial(FRA) + # FRA is a SpatialPolygonsDataFrame object
facet_wrap(~NAME_1, scales = "free")
Is it possible to desaturate a ggplot easily?
In principle, there could be two possible strategies.
First, apply some function to a ggplot object (or, possibly, Grob object) to desaturate all colors. Second, some trick to print ggplot desaturated while rendering a .rmd file. Both strategies would be ok for me, but first one is, of course, more promissing.
Creating ggplot in greys from the beginning is not a good option as the idea is to have the same plot as if it was printed in shades of grey.
There were some similar questions and remarkably good answers on how to perform desaturation in R. Here is a convenient way to desaturate color palette. And here is the way of desaturating a raster image. What I'm looking for, is a simple way of desaturating the whole ggplot.
Just came across this question. The experimental package colorblindr (written by Claire McWhite and myself) contains a function that can do this in a generic way. I'm using the example figure from #hrbrmstr:
library(ggplot2)
library(viridis)
gg <- ggplot(mtcars) +
geom_point(aes(x=mpg, y=wt, fill=factor(cyl), size=factor(carb)),
color="black", shape=21) +
scale_fill_viridis(discrete = TRUE) +
scale_size_manual(values = c(3, 6, 9, 12, 15, 18)) +
facet_wrap(~am)
gg
Now let's desaturate this plot, using the edit_colors() function from colorblindr:
library(colorblindr) # devtools::install_github("clauswilke/colorblindr")
library(colorspace) # install.packages("colorspace", repos = "http://R-Forge.R-project.org") --- colorblindr requires the development version
# need also install cowplot; current version on CRAN is fine.
gg_des <- edit_colors(gg, desaturate)
cowplot::ggdraw(gg_des)
The function edit_colors() takes a ggplot2 object or grob and applies a color transformation function (here desaturate) to all colors in the grob.
We can provide additional arguments to the transformation function, e.g. to do partial desaturation:
gg_des <- edit_colors(gg, desaturate, amount = 0.7)
cowplot::ggdraw(gg_des)
We can also do other transformations, e.g. color-blind simulations:
gg_des <- edit_colors(gg, deutan)
cowplot::ggdraw(gg_des)
Finally, we can manipulate line colors and fill colors separately. E.g., we could make all filled areas blue. (Not sure this is useful, but whatever.)
gg_des <- edit_colors(gg, fillfun = function(x) "lightblue")
cowplot::ggdraw(gg_des)
As per my comment above, this might be the quickest/dirtiest way to achieve the desaturation for a ggplot2 object:
library(ggplot2)
set.seed(1)
p <- qplot(rnorm(50), rnorm(50), col="Class")
print(p)
pdf(file="p.pdf", colormodel="grey")
print(p)
dev.off()
I tried this with the new viridis color palette since it desaturates well (i.e. it should be noticeable between the colored & non-colored plots):
library(ggplot2)
library(grid)
library(colorspace)
library(viridis) # devtools::install_github("sjmgarnier/viridis") for scale_fill_viridis
gg <- ggplot(mtcars) +
geom_point(aes(x=mpg, y=wt, fill=factor(cyl), size=factor(carb)),
color="black", shape=21) +
scale_fill_viridis(discrete = TRUE) +
scale_size_manual(values = c(3, 6, 9, 12, 15, 18)) +
facet_wrap(~am)
gb <- ggplot_build(gg)
gb$data[[1]]$colour <- desaturate(gb$data[[1]]$colour)
gb$data[[1]]$fill <- desaturate(gb$data[[1]]$fill)
gt <- ggplot_gtable(gb)
grid.newpage()
grid.draw(gt)
You end up having to manipulate on the grob level.
Here's the plot pre-desaturate:
and here's the plot post-desature:
I'm trying to figure out why the legend got skipped and this may miss other highly customized ggplot aesthetics & components, so even while it's not a complete answer, perhaps it might be useful (and perhaps someone else can tack on to it or expand on it in another answer). It should just be a matter of replacing the right bits in either the gb object or gt object.
UPDATE I managed to find the right grob element for the legend:
gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[4]]$gp$fill <-
desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[4]]$gp$fill)
gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[6]]$gp$fill <-
desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[6]]$gp$fill)
gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[8]]$gp$fill <-
desaturate(gt$grobs[[12]][[1]][["99_9c27fc5147adbe9a3bdf887b25d29587"]]$grobs[[8]]$gp$fill)
grid.newpage()
grid.draw(gt)
The machinations to find the other gp elements that need desaturation aren't too bad either.
Hi I've got the following code
d1=data.frame(a=c(4,5,6,7),b=as.Date(c('2005-12-31','2006-12-31','2007-12-31','2008-12-31'),"%Y-%m-%d"))
a = ggplot(d1,aes(x=b,y=a)) + geom_line()
a + annotate('text',x=as.Date('2006-12-31','%Y-%m-%d'),y=5.5,label='blah')
But annotating the graph is really clunky. I'd like to be able to specify the x axis using percentage of axis (for example) or inches or something else. Is this possible and how would I go about it?
Thanks
Your only option, I think, is to post-process the graph using grid. You'll need to expose the viewports and navigate to the plot panel, and there you have access to all grid units. Following Paul Murrell's example:
library(ggplot2)
library(grid)
qplot(1:10, rnorm(10))
# grid.force() # doesn't seem necessary?
# grid.ls()
downViewport("panel.3-4-3-4")
grid.text(label = "Some text", x = unit(0,"inch"),hjust=0)
grid.text(label = "Some text", x = unit(0.5,"npc"),hjust=0.5)
upViewport(0)
The package 'scales' includs a ton of formatter options: e.g. to format the y-axis in your example to percent use "scale_y_continuous(labels = percent)"
require(ggplot2)
require(scales)
d1=data.frame(a=c(4,5,6,7),b=as.Date(c('2005-12-31','2006-12-31','2007-12-31','2008-12- 31'),"%Y-%m-%d"))
a = ggplot(d1,aes(x=b,y=a)) + geom_line() + scale_y_continuous(labels = percent)
a + annotate('text',x=as.Date('2006-12-31','%Y-%m-%d'),y=5.5,label='blah')
Have a look at the ggplot docs as well.
I am trying to produce something similar to densityplot() from the lattice package, using ggplot2 after using multiple imputation with the mice package. Here is a reproducible example:
require(mice)
dt <- nhanes
impute <- mice(dt, seed = 23109)
x11()
densityplot(impute)
Which produces:
I would like to have some more control over the output (and I am also using this as a learning exercise for ggplot). So, for the bmi variable, I tried this:
bar <- NULL
for (i in 1:impute$m) {
foo <- complete(impute,i)
foo$imp <- rep(i,nrow(foo))
foo$col <- rep("#000000",nrow(foo))
bar <- rbind(bar,foo)
}
imp <-rep(0,nrow(impute$data))
col <- rep("#D55E00", nrow(impute$data))
bar <- rbind(bar,cbind(impute$data,imp,col))
bar$imp <- as.factor(bar$imp)
x11()
ggplot(bar, aes(x=bmi, group=imp, colour=col)) + geom_density()
+ scale_fill_manual(labels=c("Observed", "Imputed"))
which produces this:
So there are several problems with it:
The colours are wrong. It seems my attempt to control the colours is completely wrong/ignored
There are unwanted horizontal and vertical lines
I would like the legend to show Imputed and Observed but my code gives the error invalid argument to unary operator
Moreover, it seems like quite a lot of work to do what is accomplished in one line with densityplot(impute) - so I wondered if I might be going about this in the wrong way entirely ?
Edit: I should add the fourth problem, as noted by #ROLO:
.4. The range of the plots seems to be incorrect.
The reason it is more complicated using ggplot2 is that you are using densityplot from the mice package (mice::densityplot.mids to be precise - check out its code), not from lattice itself. This function has all the functionality for plotting mids result classes from mice built in. If you would try the same using lattice::densityplot, you would find it to be at least as much work as using ggplot2.
But without further ado, here is how to do it with ggplot2:
require(reshape2)
# Obtain the imputed data, together with the original data
imp <- complete(impute,"long", include=TRUE)
# Melt into long format
imp <- melt(imp, c(".imp",".id","age"))
# Add a variable for the plot legend
imp$Imputed<-ifelse(imp$".imp"==0,"Observed","Imputed")
# Plot. Be sure to use stat_density instead of geom_density in order
# to prevent what you call "unwanted horizontal and vertical lines"
ggplot(imp, aes(x=value, group=.imp, colour=Imputed)) +
stat_density(geom = "path",position = "identity") +
facet_wrap(~variable, ncol=2, scales="free")
But as you can see the ranges of these plots are smaller than those from densityplot. This behaviour should be controlled by parameter trim of stat_density, but this seems not to work. After fixing the code of stat_density I got the following plot:
Still not exactly the same as the densityplot original, but much closer.
Edit: for a true fix we'll need to wait for the next major version of ggplot2, see github.
You can ask Hadley to add a fortify method for this mids class. E.g.
fortify.mids <- function(x){
imps <- do.call(rbind, lapply(seq_len(x$m), function(i){
data.frame(complete(x, i), Imputation = i, Imputed = "Imputed")
}))
orig <- cbind(x$data, Imputation = NA, Imputed = "Observed")
rbind(imps, orig)
}
ggplot 'fortifies' non-data.frame objects prior to plotting
ggplot(fortify.mids(impute), aes(x = bmi, colour = Imputed,
group = Imputation)) +
geom_density() +
scale_colour_manual(values = c(Imputed = "#000000", Observed = "#D55E00"))
note that each ends with a '+'. Otherwise the command is expected to be complete. This is why the legend did not change. And the line starting with a '+' resulted in the error.
You can melt the result of fortify.mids to plot all variables in one graph
library(reshape)
Molten <- melt(fortify.mids(impute), id.vars = c("Imputation", "Imputed"))
ggplot(Molten, aes(x = value, colour = Imputed, group = Imputation)) +
geom_density() +
scale_colour_manual(values = c(Imputed = "#000000", Observed = "#D55E00")) +
facet_wrap(~variable, scales = "free")