Generic plot with legend - r

What is the best way to implement a generic plot method, given that i have 2 series + legends?
The problem is that i want to provide some nice defaults for the colors and legends, but the user should be free to change it:
obj = list(y1 = runif(100, 0, 10), y2 = runif(100, 20, 30))
class(obj) = 'foo'
plot.foo = function(myobj, col1 = 'red', col2 = 'blue', type = 'l', ...)
{
ylim = c(min(obj$y1, obj$y2), max(obj$y1, obj$y2))
plot(myobj$y1, type = type, col = col1, panel.first = grid(col = '#A9A9A9'), ylim = ylim, ...)
lines(myobj$y2, col = col2, type = type, ...)
}
plot(obj)
This looks good, but if i call
plot(obj, col = 'black')
It raises an error:
Error in plot.foo(obj, col = "black") :
argument 2 matches multiple formal arguments
Is there a way i can handle the 2 series + legends without breaking the plot protocol?
(another problem is to synch the legend lwd and pch parameters)
And will CRAN reject my package if i get ride of the ... arg?
Thanks!

You could have a vector expected called cols with no default. You can then add:
if(missing(cols)){
col1= "red"
col2 = "black"
} else {
col1=cols[1]
col2=cols[2]
}
There are surely better ways to do it but just thought I would throw this up.

Related

How to correctly evaluate variable within R tilde expression

I have a function (col_grob) that calls another function (pal_bar) with a tilde-notation expression as follows:
## plots a colour bar with specified colour intervals
pal_bar <- function(cols) {
cols <- colorRampPalette(cols)(200)
par(mar = c(0, 0, 0, 0))
plot(1:200, rep(1, 200), col = cols, pch = 15, cex = 1.1, bty = 'n', xaxt = 'n', xlab = '', yaxt = 'n', ylab = '', main="")
}
## calls pal_bar function to plot the bar as a grob, tilde expression
col_grob <- function(pal) {
g <- ggplotify::as.grob(~pal_bar(pal))
grid::grid.draw(g)
}
I am returned the error "object 'pal' not found" when I run:
col_grob(pal = c("red", "blue"))
I came across resources and similar questions but I am not able to solve the issue with my lack of understanding of the evaluation rules. I tried ~pal_bar(I(pal)), bquote() function, and possibly structure(list(), *) but do not have sufficient knowledge of each to format the syntax correctly.
How would I get col_grob(pal = c("red", "blue")) to plot the desired colour bar for me?
A possible solution:
col_grob <- function(pal) {
txt <- substitute(pal_bar(pal))
g <- ggplotify::as.grob(as.expression(txt))
grid::grid.draw(g)
}
col_grob(pal = c("red", "blue"))

R trying to do a for loop, error

I am trying to do a for loop like this:
for (n in 1:200)
{
pre[n] <- aggregate(S[n]~Secs[n], data = dataframe, FUN = sum)
freqsdf[n] <- data.frame(table(SecsOnly2$Secs[n]))
AVAL[n] <- pre[n]$S[n]/freqsdf[n]$Freq
AVAL[n] <- data.frame(AVAL[n])
hist(dataframe$Secs[n], xlab = "", ylab = "", ylim = c(0, 16000), axes = FALSE, col = "grey")
axis(4, ylim = c(0, 16000), col = "black", col.axis = "black", las = 2, cex.axis = .5)
par(new = TRUE)
plot(pre[n]$Secs[n], AVAL[n], col = "red" , type = "l")
abline(h = 0.25) }
But I'm getting this error:
Error in eval(expr, envir, enclos) : object 'S' not found
My dataset that has the "S" variable has a bunch of variables including "S1" through "S200." I want R to go through all this code for all the "S" variables, the "Secs" variables, etc... This code worked fine for just S1, when I wrote it just for S1, Secs1, etc...(not in a loop). But I want R to go through the same code for all my columns. I'm not sure why "S" is not being found. I thought by going from n = 1 to n = 200, R automatically looks for "S1", "Secs1," etc... the first time the loop runs, and then "S2", "Secs2," etc... the second time it runs, and so on.
In this line (and several other places):
pre[n]$S[n]/freqsdf[n]$Freq
You are trying to paste together S or Secs and the [n] you are using to subset the S01 columns, but R is interpreting it as you trying to take the nth item from S which does not exist. It's hard to fix without seeing your data, but you could try replacing every place you try and do this trick with something like:
pre[n][[paste("S", n, sep="")]]

Modifying width of outline in a pie chart in R--what is the equivalent of lwd parameter for pie()?

I'm using base R plotting functions to produce a pie chart and I want to change the line thickness of the outlines of each pie segment. ?pie seems to indicate that I can add optional graphic parameters, but adding lwd= does not appear to work. Anyone have any clues as to how I might be able to do this. I'm not yet proficient in producing pie charts in ggplot, and would like to stick with base R plotting (if possible).
library(RColorBrewer)
x1 <- data.frame(V1 = c(200, 100)) ## generate data
row.names(x1) <- c("A", "B")
x1$pct <- round((x1$V1/sum(x1$V1))*100, 1)
lbls1 <- paste(row.names(x1), "-(",x1$pct, '%)', sep='') ## add some informative stuff
pie(x1$V1, labels=lbls1, col=tail(brewer.pal(3, 'PuBu'), n=2),
main=paste('My 3.1415'), cex=1.1, lwd= 3)
Notice lwd= does not increase line thickness like it would in other base plotting.
Anyone have any clues?
The call to polygon and lines within pie does not pass ... or lwd
...
polygon(c(P$x, 0), c(P$y, 0), density = density[i], angle = angle[i],
border = border[i], col = col[i], lty = lty[i])
P <- t2xy(mean(x[i + 0:1]))
lab <- as.character(labels[i])
if (!is.na(lab) && nzchar(lab)) {
lines(c(1, 1.05) * P$x, c(1, 1.05) * P$y)
....
You can get around this by setting par(lwd = 2) (or whatever) outside and prior to your call to pie
i.e.
# save original settings
opar <- par(no.readonly = TRUE)
par(lwd = 2)
pie(x1$V1, labels=lbls1, col=tail(brewer.pal(3, 'PuBu'), n=2),
main=paste('My 3.1415'), cex=1.1)
par(lwd = 3)
# reset to original
par(opar)
At the moment, the function inside pie that does the actual drawing is polygon and here is how it is called:
polygon(c(P$x, 0), c(P$y, 0), density = density[i], angle = angle[i],
border = border[i], col = col[i], lty = lty[i])
Notice there is no lwd argument and more critically no ... argument to accept arguments that might not have been hard coded.
Create a new pie2 function. First type pie, copy the code and make a few changes:
pie2 <-
function (x, labels = names(x), edges = 200, radius = 0.8, clockwise = FALSE,
init.angle = if (clockwise) 90 else 0, density = NULL, angle = 45,
col = NULL, border = NULL, lty = NULL, main = NULL, lwd=1,...)
{
................
polygon(c(P$x, 0), c(P$y, 0), density = density[i], angle = angle[i],
border = border[i], col = col[i], lty = lty[i], lwd=lwd )
.................
}
pie2(x1$V1, labels=lbls1, col=tail(brewer.pal(3, 'PuBu'), n=2),
main=paste('My 3.1415'), cex=1.1, lwd=5)

R legend issue, symbols of points are masked by lines

Is there a way to draw the lines in such a way that they would start on the side of the points, or allow the symbols to be in foreground?
My solution was to make the symbols bigger and more visible.
Edit 1: it's for plot {graphics} of the R program.
Edit 2: the code per popular request.
legend(2,.4,bty='n', c('sugar','citrus','none'), pch=c('s','c','u'), pt.bg='white',lty= c(1,2,3), lwd=1.5, title="Condition",pt.cex=c(1.5),cex=1.5)
Edit 3: This is solved for plot(type='b') but somehow not for legend.
Thanks for reading!
The only thing I can come up with is to manually finagle the dash lengths until they end up looking the way you want them. For instance, this:
> plot(1,1)
> legend(c("A", "B"), col = 1:2, x = 1, y = .8, lty="99", pch=1:2)
produces the image below.
The lty parameter allows you to specify the lengths of lines and dashes as hex characters. In this case, it's saying to create a line of length 9 then create a space of length 9 then repeat. It looks like 9 is about the best fit to space around a normal pch symbol.
Note that you'd probably need to adjust this depending on the size of the image, symbol, etc. My advice ultimately would be to export the image from R and touch up the image to meet your needs in graphic editing software.
Going with the suggestion by #JeffAllen, here is a way to get what I think you might want. It requires modifying the legend() function to return the position of the points (these are given by x1 and y1 in body(legend)[[46]]).
legend2 <- legend
body(legend2)[[49]] <- quote(
invisible(list(rect = list(w = w, h = h, left = left, top = top),
text = list(x = xt, y = yt), points = list(x = x1, y = y1)))
)
Make a plot:
plot(-100:100, -100:100, type = "b")
While drawing the legend, draw white circles (pch = 21 with pt.bg = 'white') over the lines, and assign the values invisibly returned by legend2() to an object. Note also the changes to pt.lwd and pt.cex.
myLegend <- legend2(1, .8, bty = 'n', c('sugar','citrus','none'), pch = 21,
pt.bg = 'white', pt.lwd = 0, lty = c(1, 2, 3), lwd = 1.5, title = "Condition",
pt.cex = c(1.8), cex = 1.5)
Finally, draw the characters you'd like to use in the legend using points(), supplying the x and y values from the object myLegend.
points(myLegend$points$x, myLegend$points$y, pch = c('s','c','u'), cex = 1.5)
And this should get you something like:
You could also use the filled points offered by R (pch=21:25) and specify the fill color using pc.bg which gets passed to the points call when creating a legend.
plot(1,1)
legend(c("A", "B"), col = 1:2, x = 1, y = .8, lty=1, pt.bg=1:2, pch=21:22)
generates the following:

Changing parameters while using plotCI in R. (Shift points left or right)

I know that you can shift boxplots left or right on a graph by adding "at=1:6-0.2" or "at=1:6+0.2" to the code, but the same is not the case when I am using plotCI. Does anyone know how to perform this simple parameter adjustment? I know it has to be easy but there are very few questions about plotCI on here. It is in the package {gplots}. This is driving me crazy! Thanks for any help.
-Alex
If you want to shift everything (points and error bars) then all you need to do is add a small amount to the x parameter of plotCI:
plotCI(x=myx+0.2,y=...)
But that seems weird, so maybe you meant that you want to plot the points in the correct position, but shift the error bars slightly to the right? That still seems odd to me, but it can be done fairly easily by grabbing the code for plotCI, putting it in a wrapper function and adding a small offset parameter to your wrapper function that is passed to the relevant portion of the plotCI code.
Upon checking, that code for plotCI is a bit long, so I won't reproduce the whole thing here. Type plotCI at the console, and copy and paste the result in a text file, and call the function something new, like plotCI_offset. I believe if you then change the x coordinate parameters of the myarrow function call in the final if/else statement, you'll be golden.
The new function def would look like this:
plotCI_offset <- function (x, y = NULL, uiw, liw = uiw, ui, li, err = "y", ylim = NULL,
xlim = NULL, type = "p", col = par("col"), barcol = col,
pt.bg = par("bg"), sfrac = 0.01, gap = 1, lwd = par("lwd"),
lty = par("lty"), labels = FALSE, add = FALSE, xlab, ylab,
minbar, maxbar,offset=0.2, ...)
And I've quoted the altered bits of the function below:
if (!add) {
if (invalid(labels) || labels == FALSE)
#Add offset here to ensure plot window is right size
plot(x+offset, y, ylim = ylim, xlim = xlim, col = col, xlab = xlab,
ylab = ylab, ...)
else {
plot(x, y, ylim = ylim, xlim = xlim, col = col, type = "n",
xlab = xlab, ylab = ylab, ...)
text(x, y, label = labels, col = col, ...)
}
}
Then just below there alter this code as follows:
if (err == "y") {
if (gap != FALSE)
gap <- strheight("O") * gap
smidge <- par("fin")[1] * sfrac
if (!is.null(li))
#Add offset to CIs
myarrows(x+offset, li, x+offset, pmax(y - gap, li), col = barcol,
lwd = lwd, lty = lty, angle = 90, length = smidge,
code = 1)
if (!is.null(ui))
myarrows(x+offset, ui, x+offset, pmin(y + gap, ui), col = barcol,
lwd = lwd, lty = lty, angle = 90, length = smidge,
code = 1)
}
This only takes care of the case where the error bars are vertical. But the alterations for the horizontal case are similar.
Try this:
require(plotrix)
plotCI(1:3-0.1, m1, ui1, li1, xlab="Itens", ylab="Eta2",axes=FALSE)
axis(side=1,at=1:9,label=c(x1,x2,x3),padj=0,las=1)
axis(side=2)
Now do the same but like this:
plotCI(1:3+.1,m2, ui2, li2,axes=FALSE,col="blue",add=TRUE)
......
I have a hack for that: you do not move your data points, rather, you move the labels on the axes!
e.g. you want to shift your data points to the right by 1 on x-axis, you hide the x-axis and redraw it with new labels:
plotCI(..., axes=F) # just ignore the warning
axis(side=1, at=0:99, labels=1,100)

Resources