I would like to extract some plotting code using a functional sequence as described in http://www.r-bloggers.com/magrittr-1-5/. However, it does not work
require(magrittr); require(ggplot2); require(dplyr)
plot_me <- . %>% (ggplot(aes(Sepal.Width, Sepal.Length)) + geom_point())
iris %>% plot_me
When trying this, R gives the following error
Error: ggplot2 doesn't know how to deal with data of class uneval
Doing the same using simple piping works nicely:
iris %>% ggplot(aes(Sepal.Width, Sepal.Length)) + geom_point()
What's wrong with my functional sequence/code?
I can't really explain why, but the following works.
(It might be because of the use of { instead of ( to control the order of computation inside the pipe).
library(magrittr)
library(ggplot2)
plot_me <- . %>% {ggplot(., aes(Sepal.Width, Sepal.Length)) + geom_point()}
iris %>% plot_me
Related
My question is about using a for loop to repeat data analysis based on a categorial variable.
Using the built in Iris data set how would I run a for loop on the code below so it first produces this chart for just setosa and then versicolor and then virginica without me having to manually change/set the species?
ggplot(iris, aes(Sepal.Length, Sepal.Width)) +
geom_point()
I'm just starting out and have no idea what I'm doing
You need to use print() as described here
library(tidyverse)
data(iris)
species <- iris |> distinct(Species) |> unlist()
for(i in species) {
p <- iris |>
filter(Species == i) |>
ggplot() +
geom_point(aes(x=Sepal.Length, y=Sepal.Width)) +
ggtitle(i)
print(p)
}
You can use a for loop as u/DanY posted; however, it's harder to store and retrieve plots in a universal way with that structure. Running the loop code makes it difficult to retrieve any one particular plot - you would only see the last plot in the output window and have to go "back" to see the others. I would suggest using a list structure instead to allow you to retrieve any one of the individual plots in subsequent functions.
For this, you can use lapply() rather than for(...) { ... }.
Here's an example which uses dplyr and tidyr:
library(ggplot2)
library(dplyr)
library(tidyr)
unique_species <- unique(iris$Species)
myPlots <- lapply(unique_species, function(x) {
ggplot(
data = iris %>% dplyr::filter(Species == x),
mapping = aes(x=Sepal.Length, y=Sepal.Width)
) +
geom_point() +
labs(title=paste("Plot of ", x))
})
You then have the plots stored within myPlots. You can access each plot via myPlots[1], myPlots[2] or myPlots[3]... or you can plot them all together via patchwork or another similar package. Here's one way using cowplot:
cowplot::plot_grid(plotlist = myPlots, nrow=1)
library(tidyverse)
(
e <- ggplot(mpg, aes(cty, hwy)) +
geom_point()
) %>%
print()
Is there any "prettier" way to do this? 'This' meaning print a stored ggplot object. I often have to store plots as an object, yet also want to see them. The () wrap really makes things ugly. Seems contrary to core tidyverse principles. I know I could simply call out e at the end, but I don't like that either. Something like this is so much cooler. Just look at the difference.
library(tidyverse)
f <- mtcars %>%
select(cyl) %>%
as_tibble() %>%
print() # redundant, just proving a point
If it is only a matter of consistency with the use of the pipe, you could try the package ggformula that gives access to the features of ggplot2 without the syntax of ggplot2 :
library(ggformula)
g <- gf_point(cty ~ hwy, data=mpg) %>% print()
You can do it with the package ggfun :
# devtools::install_github("moodymudskipper/ggfun")
library(tidyverse)
library(ggfun)
ggplot(mpg, aes(cty, hwy)) +
geom_point() +
print
When doing data analysis, we often use dplyr to modify the dataframe further in specific geoms. This allows us to change the default dataframe of a ggplot later, and have everything still work.
template <- ggplot(db, aes(x=time, y=value)) +
geom_line(data=function(db){db %>% filter(event=="Bla")}) +
geom_ribbon(aes(ymin=low, ymax=up))
ggsave( template, "global.png" )
for(i in unique(db$simulation))
ggsave( template %+% subset(db, simulation==i), paste0(i, ".png")
Is there a nicer/shorter way to specify the filter command, e.g. using some magical .?
EDIT
To clarify some of the comments: By using geom_line(data = db %>% filter(event=="Bla")), the layer would not be updated when I change the default dataframe later using %+%. I am really aiming to use the data argument of geom_* as a function.
Upon reading the documentation of %>% better, I have found the solution:
Using the dot-place holder as lhs
When the dot is used as lhs, the result will be a functional sequence, i.e. a function which applies the entire chain of right-hand sides in turn to its input. See the examples.
Therefore, the nicest way to formulate the above example, incorporating the suggestions from above as well:
db <- diamonds
template <- ggplot(db, aes(x=carat, y=price, color=cut)) +
geom_point() +
geom_smooth(data=. %>% filter(color=="J")) +
labs(caption="Smooths only for J color")
ggsave( template, "global.png" )
db %>% group_by(cut) %>% do(
ggsave( paste0(.$cut[1], ".png"), plot=template %+% .)
)
I find no solution for these two following issues:
First I try this:
library(tidyverse)
gg <- mtcars %>%
mutate(group=ifelse(gear==3,1,2)) %>%
ggplot(aes(x=carb, y=drat)) + geom_point(shape=group)
Error in layer(data = data, mapping = mapping, stat = stat, geom =
GeomPoint,:object 'group' not found
which is obviously not working. But using something like this .$group is also not successfull. Of note, I have to specifiy the shape outside from aes()
The second problem is this. I'm not able to call a saved ggplot (gg) within a pipe.
gg <- mtcars %>%
mutate(group=ifelse(gear==3,1,2)) %>%
ggplot(aes(x=carb, y=drat)) + geom_point()
mtcars %>%
filter(vs == 0) %>%
gg + geom_point(aes(x=carb, y=drat), size = 4)
Error in gg(.) : could not find function "gg"
Thanks for your help!
Edit
After a long time I found a solution here. One has to set the complete ggplot term in {}.
mtcars %>%
mutate(group=ifelse(gear==3,1,2)) %>% {
ggplot(.,aes(carb,drat)) +
geom_point(shape=.$group)}
If you wrap your shape definition in aes() you can get the desired behavior. To use shape outside of aes() you can pass it a single value (ie shape=1). Also note that group is converted to a discrete var, geom_point throws an error when you pass a continuous var to shape.
library(tidyverse)
gg <- mtcars %>%
mutate(group=ifelse(gear==3,1,2)) %>%
ggplot(aes(x=carb, y=drat)) +
geom_point(aes(shape=as.factor(group)))
gg
Second, the %>% operator, when called as lhs %>% rhs, assumes that the rhs is a function. So as the error shows, you are calling gg as a function. Calling a plot as a function on a dataframe (ie gg(mtcars)) isnt a valid operation.
See #docendo discimus comment on the question for how to use {} to accomplish adding a layer to an existing ggplot object from a magrittr pipeline.
When I integrate tables and figures in a document using knitr, adding the code makes it more reproducible and interesting.
Often a combination of dplyr and ggvis can make a plot that has relatively legible code (using the magrittr pipe operator %>).
mtcars %>%
group_by(cyl, am) %>%
summarise( weight = mean(wt) ) %>%
ggvis(x=~am, y=~weight, fill=~cyl) %>%
layer_bars()
The problem is that the ggvis plot:
does not look quite as as pretty as the ggplot2 plot (I know, factoring of cyl):
However, for ggplot2 we need:
mtcars %>%
group_by(am, cyl) %>%
summarise( weight = mean(wt) ) %>%
ggplot( aes(x=am, y=weight, fill=cyl) ) +
geom_bar(stat='identity')
My problem is that this switches from %>% to + for piping. I know this is a very minor itch, but I would much prefer to use:
mtcars %>%
group_by(am, cyl) %>%
summarise( weight = mean(wt) ) %>%
ggplot( aes(x=am, y=weight, fill=cyl) ) %>%
geom_bar(stat='identity')
Is there a way to modify the behaviour of ggplot2 so that this would work?
ps. I don't like the idea of using magrittr's add() since this again make the code more complicated to read.
Since it would be too long to expand in the comments, and based on your answer I am not sure if you tried the bit of code I provided and it didn't work or you tried previously and didn't manage
geom_barw<-function(DF,x,y,fill,stat){
require(ggplot2)
p<-ggplot(DF,aes_string(x=x,y=y,fill=fill)) + geom_bar(stat=stat)
return(p)
}
library(magrittr)
library(dplyr)
library(ggplot2)
mtcars %>%
group_by(cyl, am) %>%
summarise( weight = mean(wt) ) %>%
geom_barw(x='am', y='weight', fill='cyl', stat='identity')
This works for me with:
dplyr_0.4.2 ggplot2_2.1.0 magrittr_1.5
Of course geom_barw could be modified so you don't need to use the quotes anymore.
EDIT: There should be more elegant and safer way with lazy (see the lazyeval package), but a very quick adaptation would be to use substitute (as pointed by Axeman - however without the deparse part):
geom_barw<-function(DF,x,y,fill,stat){
require(ggplot2)
x<-substitute(x)
y<-substitute(y)
fill<-substitute(fill)
p<- ggplot(DF,aes_string(x=x,y=y,fill=fill))
p<- p + geom_bar(stat=stat)
return(p)
}