Adding points after the fact with ggplot2; user defined function - r

I believe the answer to this is that I cannot, but rather than give in utterly to depraved desperation, I will turn to this lovely community.
How can I add points (or any additional layer) to a ggplot after already plotting it? Generally I would save the plot to a variable and then just tack on + geom_point(...), but I am trying to include this in a function I am writing. I would like the function to make a new plot if plot=T, and add points to the existing plot if plot=F. I can do this with the basic plotting package:
fun <- function(df,plot=TRUE,...) {
...
if (!plot) { points(dYdX~Time.Dec,data=df2,col=col) }
else { plot(dYdX~Time.Dec,data=df2,...) }}
I would like to run this function numerous times with different dataframes, resulting in a plot with multiple series plotted.
For example,
fun(df.a,plot=T)
fun(df.b,plot=F)
fun(df.c,plot=F)
fun(df.d,plot=F)
The problem is that because functions in R don't have side-effects, I cannot access the plot made in the first command. I cannot save the plot to -> p, and then recall p in the later functions. At least, I don't think I can.

have a ggplot plot object be returned from your function that you can feed to your next function call like this:
ggfun = function(df, oldplot, plot=T){
...
if(plot){
outplot = ggplot(df, ...) + geom_point(df, ...)
}else{
outplot = oldplot + geom_point(data=df, ...)
}
print(outplot)
return(outplot)
}
remember to assign the plot object returned to a variable:
cur.plot = ggfun(...)

Related

How to process all text elements of ggplot through a function before output?

I'm creating a set of functions for enciphering data plots as a hook for data literacy lessons.
For example, this function performs an alphabetic shift of +/- X for any string input
enciphR<-function(x,alg,key=F){
x.vec<-tolower(unlist(strsplit(x,fixed=T,split="")))#all lower case as vector
#define new alphabet
alphabet<-1:26+alg
alphabet.shifted.idx<-sapply(alphabet,function(x) {if(x>26){x-26}else{ if(x<1){x+26}else{x}}})
alphabet.shifted<-letters[alphabet.shifted.idx]
keyMat=cbind(IN=letters,OUT=alphabet.shifted)
#encipher
x1.1<-as.vector(sapply(x.vec,function(s) {
if(!s%in%letters){s}else{#If nonletter, leave it alone, else...
keyMat[match(s,keyMat[,"IN"]),"OUT"]
}},USE.NAMES = F))
x2<-paste0(x1.1,collapse="")
if(key){
out<-list(IN=x,OUT=x2,KEY=keyMat)}
else{
out<-x2
}
return(out)
}
enciphR(letters,+1) yields "bcdefghijklmnopqrstuvwxyza"
I want to feed a ggplot object into another function ggEnciphR(), which uses the enciphR() helper to encode all text elements of that object, according to the supplied cipher algorithm.
So, start with a figure:
data("AirPassengers")
require(ggplot2);require(reshape2)
df <- data.frame(Passengers=as.numeric(AirPassengers), year = as.factor(trunc(time(AirPassengers))), mnth = month.abb[cycle(AirPassengers)])
(gg<-qplot(mnth,Passengers,aes(col=year),data=df)+geom_line(aes(group=year,col=year)))
now I define the ggCiphR function:
ggCiphR<-function(ggGraph,alg){
g<-ggGraph
pass2enciphR=function(x){enciphR(x,alg=alg)}
#process all labels
g$labels<-lapply(g$labels,pass2enciphR)
#process custom legend if present (sometimes works, depending on ggplot object)
try(if(length(g$scales$scales)>0){
for(i in 1:length(g$scales$scales)){
g$scales$scales[[i]]$name=enciphR(g$scales$scales[[i]],alg=alg)
}
})
g
}
ggCiphR(gg,+1)
Yields:
This is getting there. Axis labels and legend title have been enciphered, but not tick values. Also, if my factor was Airline, instead of Year, I'd like that to be sent through enciphR, as well.
I've searched high and wide and cannot for the life of me figure out how to modify x- and y-axis values and factor levels from the gg object. I don't want to have to recast the dataframe and regenerate the ggplot... ideally I can do this programmatically using the gg object. And also, it would (ideally) be flexible enough to work with different classes of data in X & Y. Thoughts?

Write function to plot data, requires passing data.frame column names

I would like to write a function to create plots (in order to create multiple plots without listing the design settings every time). The pirateplot function that I use requires columnnames and a dataframe as input, which causes problems.
My not-working code is:
pirateplot_default <- function(DV,IV,Dataset) {
plot <- pirateplot(formula = DV ~ IV,
data = Dataset,
xlab = "Solution")
return(plot)
}
I have tried "as.name" (saw that here) but it did not work.
using data[DV] is no option because the pirateplot function requires a different notation
I know that there are similar questions here,here,here, and this probably qualifies as duplicate for more skilled programmers, but I did not manage to apply the solutions at other questions to my problem, so hoping for help.
Here is an example
pirateplot_default <- function(DV,IV,Dataset) {
tmp=as.formula(paste0(DV,"~",paste0(IV,collapse="+")))
plot <- pirateplot(formula = tmp,
data = Dataset,
xlab = "Solution")
return(plot)
}
pirateplot_default("mpg",c("disp","cyl","hp"),mtcars)

How can you use ggplot to superimpose many plots of related functions in an automatic way?

I have a family of functions that are all the same except for one adjustable parameter, and I want to plot all these functions on one set of axes all superimposed on one another. For instance, this could be sin(n*x), with various values of n, say 1:30, and I don't want to have to type out each command individually -- I figure there should be some way to do it programatically.
library(ggplot2)
define trig functions as a function of frequency: sin(x), sin(2x), sin(3x) etc.
trigf <- function(i)(function(x)(sin(i*x)))
Superimpose two function plots -- this works manually of course
ggplot(data.frame(x=c(0,pi)), aes(x)) + stat_function(fun=trigf(1)) + stat_function(fun=trigf(2))
now try to generalize -- my idea was to make a list of the stat_functions using lapply
plotTrigf <- lapply(1:5, function(i)(stat_function(fun=function(x)(sin(i*x))) ))
try using the elements of the list manually but it doesn't really work -- only the i=5 plot is shown and I'm not sure why when that's not what I referenced
ggplot(data.frame(x=c(0,pi)), aes(x)) +plotTrigf[[1]] + plotTrigf[[2]]
I Thought this Reduce might handle the 'generalized sum' to add to a ggplot() but it doesn't work -- it complains of a non-numeric argument to binary operator
Reduce("+", plotTrigf)
So I'm kind of stuck both in executing this strategy, or perhaps there's some other way to do this.
Are you using version R <3.2? The problem is that you actually need to evaluate your i parameter in your lapply call. Right now it's being left as a promise and not getting evaulated till you try to plot and at that point i has the last value it had in the lapply loop which is 5. Use:
plotTrigf <- lapply(1:5, function(i) {force(i);stat_function(fun=function(x)(sin(i*x))) })
You can't just add stat_function calls together, even without Reduce() you get the error
stat_function(fun=sin) + stat_function(fun=cos)
# Error in stat_function(fun = sin) + stat_function(fun = cos) :
# non-numeric argument to binary operator
You need to add them to a ggplot object. You can do this with Reduce() if you just specify the init= parameter
Reduce("+", plotTrigf, ggplot(data.frame(x=c(0,pi)), aes(x)))
And actually the special + operator for ggplot objects allows you to add a list of objects so you don't even need the Reduce at all (see code for ggplot2:::add_ggplot)
ggplot(data.frame(x=c(0,pi)), aes(x)) + plotTrigf
The final result is
You need to use force in order to make sure the parameter is being evaluated at the right time. It's a very useful technique and a common source of confusion in loops, you should read about it in Hadley's book http://adv-r.had.co.nz/Functions.html
To solve your question: you just need to add force(i) when defining all the plots, inside the lapply function, before making the call to stat_function. Then you can use Reduce or any other method to combine them. Here's a way to combine the plots using lapply (note that I'm using the <<- operator which is discouraged)
p <- ggplot(data.frame(x=c(0,pi)), aes(x))
lapply(plotTrigf, function(x) {
p <<- p + x
return()
})

Change of colors in compare.matrix command in r

I'm trying to change the colors for the compare.matrix command in r, but the error is always the same:
Error in image.default(x = mids, y = mids, z = mdata, col = c(heat.colors(10)[10:1]), :
formal argument "col" matched by multiple actual arguments
My code is very simple:
compare.matrix(current,ech_b1,nbins=40)
and some of my attempts are:
compare.matrix(current,ech_b1,nbins=40,col=c(grey.colors(5)))
compare.matrix(current,ech_b1,nbins=40,col=c(grey.colors(10)[10:1]))
Assuming you're using compare.matrix() from the SDMTools package, the color arguments appear to be hard-coded into the function, so you'll need to redefine the function in order to make them flexible:
# this shows you the code in the console
SDMTools::compare.matrix
function(x,y,nbins,...){
#---- preceding code snipped ----#
suppressWarnings(image(x=mids, y=mids, z=mdata, col=c(heat.colors(10)[10:1]),...))
#overlay contours
contour(x=mids, y=mids, z=mdata, col="black", lty="solid", add=TRUE,...)
}
So you can make a new one like so, but bummer, there are two functions using the ellipsis that have a col argument predefined. If you'll only be using extra args to image() and not to contour(), this is cheap and easy.
my.compare.matrix <- function(x,y,nbins,...){
#---- preceding code snipped ----#
suppressWarnings(image(x=mids, y=mids, z=mdata,...))
#overlay contours
contour(x=mids, y=mids, z=mdata, col="black", lty="solid", add=TRUE)
}
If, however, you want to use ... for both internal calls, then the only way I know of to avoid confusion about redundant argument names is to do something like:
my.compare.matrix <- function(x,y,nbins,
image.args = list(col=c(heat.colors(10)[10:1])),
contour.args = list(col="black", lty="solid")){
#---- preceding code snipped ----#
contour.args[[x]] <- contour.args[[y]] <- image.args[[x]] <- image.args[[y]] <- mids
contour.args[[z]] <- image.args[[z]] <- mdata
suppressWarnings(do.call(image, image.args))
#overlay contours
do.call(contour, contour.args)
}
Decomposing this change: instead of ... make a named list of arguments, where the previous hard codes are now defaults. You can then change these items by renaming them in the list or adding to the list. This could be more elegant on the user side, but it gets the job done. Both of the above modifications are untested, but should get you there, and this is all prefaced by my above comment. There may be some other problem that cannot be detected by SO Samaritans because you didn't specify the package or the data.

Plotting to a file from a function in R

Background
Hey everyone!
I'm new to using R, and became interested in using it after having a team member give a tutorial of how useful it can be in an academic setting.
I am trying to write a script to automatically read my data from multiple files and then plot the resultant graphs to multiple files, so that they can be easily added to a manuscript (PowerPoint, latex, etc.)
Problem
I have found that the following code will allow me to produce a graph
p = qplot(factor(step), y, data=x, colour=c))
p = p + theme_bw()
# etc...
wrapping this around a png call will allow me to output the plot to a PNG:
png("test.png")
p = qplot(factor(step), y, data=x, colour=c))
p = p + theme_bw()
# etc...
p
dev.off()
I wanted to put the graph creation into a function so that I can create graphs and consequent seperate PNGs. So I put everything into a function:
func <- function()
{
png("test.png")
p = qplot(factor(step), y, data=x, colour=c))
p = p + theme_bw()
# etc...
p
dev.off()
}
If I call func() A PNG is created, but it is empty. Is there any specific reason why I can do this without the function but can't when I'm calling it from a function?
When using ggplot2 or lattice non-interactively (i.e. not from the command-line), you need to explicitly print() plots that you've constructed. So just do print(p) in the final line of your code, and all should be fine.
This is unintuitive enough that it's one of the most frequent of all FAQs.

Resources