In R I would like to make some graphs in which I use multicoloured lines as in the examples below. Perhaps I could do this using different lines placed next to each other, but the problem is that it is hard then to use the correct line width so that they are placed exactly next to each other without any white space in between (since the lwd argument of lines in the R graphics package is not in absolute coordinates). Is there perhaps any other way to specify that I would like to draw a single line with two or three (or more) different colours? (ideally corners and line cappings should look OK)
cheers,
Tom
PS the application I am working on is to be able to draw phylogenies with polymorphic states as in the image below
From what I gather from the help of par, the lwd parameter differs from device to device. For x11 it states that "Line widths as controlled by par(lwd =) are in multiples of 1/96inch". Based on the defined lwd, I needed to convert this width to the x and y units of the graph in order to correctly offset the following lines.
So now I have your lines able to turn a corner - some adjustments to the lines are still needed in order to get them to all end at the same length (e.g. subtract the offset from the last value in the series).
Example:
x <- c(1:10, rep(1, 10))
y <- c(rep(1, 10), 1:10)
lwd <- 20
x11() #lwd is multiples of 1/96 inches (from help info)
plot(y ~ x, t="l", lend=2, ljoin=2, lwd=lwd, col=3, xlim=c(0,11), ylim=c(0,11))
x.units.per.inch <- (par("usr")[2] - par("usr")[1]) / par("pin")[1]
y.units.per.inch <- (par("usr")[4] - par("usr")[3]) / par("pin")[2]
x.offset <- x.units.per.inch * 1/96 * lwd
y.offset <- y.units.per.inch * 1/96 * lwd
lines(x + x.offset, y + y.offset, lend=2, ljoin=2, lwd=lwd, col=2)
lines(x - x.offset, y - y.offset, lend=2, ljoin=2, lwd=lwd, col=4)
Related
Occasionally hist(..., nclass=nclass.scott) produces a histogram where the maximum bar extends over the top of the y axis. You may try this example a few times:
x <- sample(1000000, 500, replace=TRUE)
h <- hist(x,nclass=nclass.scott)
text(x=h$mids, y=h$counts, labels=h$counts, pos=3, col="red")
Example:
Occasionally the red number over the highest bar cannot be presented as it seems to be clipped by the plot region. I could add ylim=..., but it's quite tricky to get the maximum height of the bar.
Even when knowing the maximum height, ylim=(0, max) has the problem that max may be ignored: For example, when maximum is 527, then the upper displayed y-axis label is 500, even if ylim=(0, 527) is specified. When using 600 instead, it works, but then the y-axis is a bit too long...
If that is not a bug of R (3.3.3), what is an elegant (minimalistic) solution?
I think you need to set par(xpd= T) in your graph to avoid the trimming.
?par
xpd
A logical value or NA. If FALSE, all plotting is clipped to the
plot region, if TRUE, all plotting is clipped to the figure region,
and if NA, all plotting is clipped to the device region. See also
clip.
You can do it better by collaborating with usr option and xpd.Upon observation the bars seems going out of chart but it is not the bars that are going outside the chart but the axis being restricted to the labels. Hence to fix the labels we can choose to use usr. In case someone wants to play with the margin, one can also use mar.
library(RColorBrewer)
par(mfrow=c(1,1),xpd=T,yaxs="i")
x <- sample(1000000, 500, replace=TRUE)
h <- hist(x,nclass=nclass.scott,axes=FALSE,col=brewer.pal(10,"Set3"))
# usr <- par("usr")
at <- c(0, 10,30, par("usr")[4])
axis(2,at=at,labels = round(at))
text(x=h$mids, y=h$counts, labels=h$counts, pos=3, col="red")
usr
A vector of the form c(x1, x2, y1, y2) giving the extremes of the
user coordinates of the plotting region. When a logarithmic scale is
in use (i.e., par("xlog") is true, see below), then the x-limits will
be 10 ^ par("usr")[1:2]. Similarly for the y-axis.
You may want to run it several times, I have run it for many times, the bar won't seems to go outside the chart now.
Output:
What you describe is not a bug. You are using functionality to draw a histogram and then you want to add text to it. The function has not been designed for that, hence you need to reserve some additional white space for the text.
I suggest you run the function once, to get the "base values" of the graph. Then run the function again with adjusted scale (extra space for the text). In order to achieve this, you could use the following code
set.seed(9876) ### for reproducibility
x <- sample(1000000, 500, replace = TRUE)
h <- hist(x, nclass = nclass.scott, plot = FALSE)
### use the info from the previous call to adjust the y-scale with a constant
hist(x, nclass = nclass.scott, ylim = c(0, max(h$counts) + 10))
text(x = h$mids, y = h$counts, labels = h$counts, pos = 3, col = "red")
### ... or add a proportion (a little bit more robust)
hist(x, nclass = nclass.scott, ylim = c(0, max(h$counts) * 1.075))
text(x = h$mids, y = h$counts, labels = h$counts, pos = 3, col = "red")
Please let me know whether this is what you want.
I am trying to do the following:
plot a time series in R using a polygonal line
plot one or more horizontal lines superimposed
find the intersections of said line with the orizontal ones
I got this far:
set.seed(34398)
c1 <- as.ts(rbeta(25, 33, 12))
p <- plot(c1, type = 'l')
# set thresholds
thresholds <- c(0.7, 0.77)
I can find no way to access the segment line object plotted by R. I really really really would like to do this with base graphics, while realizing that probably there's a ggplot2 concoction out there that would work. Any idea?
abline(h=thresholds, lwd=1, lty=3, col="dark grey")
I will just do one threshold. You can loop through the list to get all of them.
First find the points, x, so that the curve crosses the threshold between x and x+1
shift = (c1 - 0.7)
Lower = which(shift[-1]*shift[-length(shift)] < 0)
Find the actual points of crossing, by finding the roots of Series - 0.7 and plot
shiftedF = approxfun(1:length(c1), c1-0.7)
Intersections = sapply(Lower, function(x) { uniroot(shiftedF, x:(x+1))$root })
points(Intersections, rep(0.7, length(Intersections)), pch=16, col="red")
I would like to place asterisks in my grouped barplot (R base) to indicate where the paired comparisons differ significantly. I know how to place these stars using the points command. However, from the posts that I read sofar it seems that one needs to find the right coordinates manually (e.g., group I: x=0.635, y=26, see the code below). This would take quite some time if one needs to find that out for all significant pairs.
So my question is: Is there an easier way to find the coordinates that correspond with the mid and just next to paired bars? I would prefer to do this in base plotting system at the moment but ggplot answers are also welcome. Thank you very much in advance!
Data example
set.seed(123)
dat<-matrix(runif(32, min = 0.5, max = 1), nrow=2, ncol=16)
colnames(dat)<-c(LETTERS[1:16])
par(mar=c(2,4,2,2))
mp<-barplot(dat, col=c("blue","red"), beside=TRUE, horiz=TRUE, xpd=FALSE, axes=FALSE, axisnames=TRUE, cex.names=0.8, las=2, xlim=c(0.5,1.0), main="Data Example")
axis(1, at=seq(0.5,1.0, by=0.1))
axis(2, at=mp, labels=FALSE, tick=FALSE)
points(x=0.635, y=26, pch="*", cex=2) #sign position at I
Let's say you have a vector telling you which pairs are significant. For example:
sign <- rep(TRUE, 16) ; sign[c(5, 7, 13:14)] <- FALSE
you already know the y coordinates of the letters:
colMeans(mp)
so you can define the y coordinates of the asterisks:
ord_sign <- colMeans(mp)[sign]
For the x coordinates, you can place them for example 0.01 point to the right from the max value:
abs_sign <- apply(dat, 2, max)[sign] + 0.01
Then you can draw all your asterisks at once:
points(x=abs_sign, y=ord_sign, pch="*", cex=2)
I am having bother changing the font style and size on my two x-axes, both of which I have moved to the top of the graph.
Here is my code so far:
x1<-Temperature
x2<-Salinity
y<-Depth
par(mar=c(4, 4, 8, 4))
plot(x2,y, type="l",col="darkgrey",ylim=rev(range(0,300)),las=2,xlim=(range(32.5,34.5)),xaxt='n',xlab='',font.axis=2,lwd=3,ylab="Depth [m]",font=2,font.lab=2,cex.lab=1.3,cex.axis=1.2)
axis(side=3, line=4)
par(new=TRUE)
plot(x1,y, type="l",col="black",ylim=rev(range(0,300)),las=2,xaxt='n',xlab='',lwd=3,ylab='Depth [m]',font=2,font.lab=2,cex.lab=1.3,cex.axis=1.2)
axis(side=3, line=0)
par(new=TRUE)
This successfully changes my y-axis but leaves my x-axes unchanged.
Help!
Reading through the help for axis, ?axis, looking at the documentation for the ... parameters:
...: other graphical parameters may also be passed as arguments to
this function, particularly, ‘cex.axis’, ‘col.axis’ and
‘font.axis’ for axis annotation, ‘mgp’ and ‘xaxp’ or ‘yaxp’
for positioning, ‘tck’ or ‘tcl’ for tick mark length and
direction, ‘las’ for vertical/horizontal label orientation,
or ‘fg’ instead of ‘col’, and ‘xpd’ for clipping. See ‘par’
on these.
So just pass in your cex.axis etc parameters into the axis call as you did for plot. Here's a reproducible example (Note how I've made up the data, and even though the data is not realistic, at least it makes the example reproducible and still solves your problem):
x1 <- runif(10)
x2 <- runif(10) * 2 + 32.5
y <- runif(10) * 300
par(mar=c(4, 4, 8, 4))
plot(x2,y, type="l",col="darkgrey",ylim=rev(range(0,300)),las=2,xlim=(range(32.5,34.5)),xaxt='n',xlab='',font.axis=2,lwd=3,ylab="Depth [m]",font=2,font.lab=2,cex.lab=1.3,cex.axis=1.2)
# added in various font/axis labels as in above
axis(side=3, line=4,font.axis=2,font.lab=2,cex.lab=1.3,cex.axis=1.2)
par(new=TRUE)
plot(x1,y, type="l",col="black",ylim=rev(range(0,300)),las=2,xaxt='n',xlab='',lwd=3,ylab='Depth [m]',font=2,font.lab=2,cex.lab=1.3,cex.axis=1.2)
axis(side=3, line=0,font.axis=2,font.lab=2,cex.lab=1.3,cex.axis=1.2)
(Your subsequent calls to axis where replacing the axis from the plot call, so instead of using the axis parameters from plot it uses the axis parameters from axis).
I am having problems getting segments of small lengths to appear in my plot.
Assuming the following sample data:
x=c(11,22,33,44,55)
y=c(15,23,33,45,57)
z=strptime(20120101:20120105,'%Y%m%d')
If I were to create segments out of this data my segment for the third record does not show up if I want square or butt line ends. It does show up if I allow my line ends to be round lend=0.
plot(z,x,type='n')
segments(as.numeric(z),x,as.numeric(z),y,lwd=5,lend=2)
If I try this:
segments(as.numeric(z),x,as.numeric(z),y,lwd=5,lend=0)
It shows a circle at 33. Is there a way to get at the very least a flat line that will appear at 33 (hopefully in base)?
I would have used my actual data which is also doing this when the range is small for instance 33.0005 to 33.0010, but that data is huge and I was hoping solving for when they are identical would also solve for small ranges.
ETA: If lwd=15 the circle looks even more ridiculous.
Maybe segments are not the right way to approach this?
This is for a candlestick chart, so these numbers would represent open and close. I also have high and low numbers which extend beyond this range and are drawn using lwd=1 under these segments.
As #Joran points out, this may well be the "correct" behaviour.
But a kludgy workaround is to simply add an arbitrary small number to the values. This value should be small enough to not "distort" the data, but large enough to show up in your plot, given your plot device resolution.
delta <- pmax(0.2, y - x)
plot(z,x,type='n')
segments(as.numeric(z),x ,y1 = y + delta, lwd=10, lend=1)
PS. I advise against this. You have been warned.
Base graphics does supply rect. And in fact, it does what you want. Using your definitions above.
xdiff <- max(as.numeric(z)) - min(as.numeric(z))
segwidth <- xdiff/50
plot(z,x,type='n')
rect(z-segwidth/2, x, z+segwidth/2, y, col="black")
Given the edits to your question, I suspect the way to go about this is to plot points to indicate your open and close, and a segment to indicate the range.
In this way, if your open and close points are identical (or close), you get a symbol at the correct point.
x <- strptime(20120101:20120105,'%Y%m%d')
y1 <- c(11,22,33,44,55)
y2 <- c(15,23,33,45,57)
r <- range(c(y1, y2))
plot(c(x, x), c(y1, y2), type="n", xlab="Date", ylab="y")
points(x, y1, pch=18)
points(x, y2, pch=18)
segments(as.numeric(x), y0=y1, y1=y2)
There's something a little odd about "square" lineend
library(grid)
epsilon <- 1e-4
grid.newpage()
grid.points(x=c(0.5-epsilon,0.5+epsilon), y=c(0.5,0.5), pch="+", gp=gpar(cex=2), def="npc")
grid.segments(0.5-epsilon, 0.5, 0.5+epsilon, 0.5, gp=gpar(lineend="square",lwd=50, alpha=0.2))
grid.segments(0.5-epsilon, 0.5, 0.5+epsilon, 0.5, gp=gpar(lineend="round",lwd=50, alpha=0.2))
grid.segments(0.5-epsilon, 0.5, 0.5+epsilon, 0.5, gp=gpar(lineend="butt",lwd=50, alpha=0.2))
the behavior has a jump at epsilon = 0,
for epsilon=1e-4 vs
for epsilon=0
As a workaround, I would draw rectangles instead of lines; they always have at least one linewidth.
grid.newpage()
grid.rect(x=0.5, y=0.5, width=0.01, height=0, gp=gpar(fill="black", col="red", lwd=10, linejoin="mitre"))