Cannot get lines of small length to show up in plot - r

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"))

Related

How can I plot a smooth line over plot points, like a contour/skyline of the plot?

What I'm looking for is best explained by a picture: A line that "contours" the maxima of my points (like giving the "skyline" of the plot). I have a plot of scattered points with dense, (mostly) unique x coordinates (not equally distributed in either axis). I want a red line surfacing this plot:
What I've tried/thought of so far is, that a simple "draw as line" approach fails due to the dense nature of the data with unique x values and a lot of local maxima and minima (basically at every point). The same fact makes a mere "get maximum"-approach impossible.
Therefore I'm asking: Is there some kind of smoothing option for a plot? Or any existing "skyline" operator for a plot?
I am specifically NOT looking for a "contour plot" or a "skyline plot" (as in Bayesian skylineplot) - the terms would actually describe what I want, but unfortunately are already used for other things.
Here is a minimal version of what I'm working with so far, a negative example of lines not giving the desired results. I uploaded sample data here.
load("xy_lidarProfiles.RData")
plot(x, y,
xlab="x", ylab="y", # axis
pch = 20, # point marker style (1 - 20)
asp = 1 # aspect of x and y ratio
)
lines(x, y, type="l", col = "red") # makes a mess
You will get close to your desired result if you order() by x values. What you want then is a running maximum, which TTR::runMax() provides.
plot(x[order(x)], y[order(x)], pch=20)
lines(x[order(x)], TTR::runMax(y[order(x)], n=10), col="red", lwd=2)
You may adjust the window with the n= parameter.

filled.contour() in R: nonlinear key range

I am using filled.contour() to plot data stored in a matrix. The data is generated by a (highly) non-linear function, hence its distribution is not uniform at all and the range is very large.
Consequently, I have to use the option "levels" to fine tune the plot. However, filled.contour() does not use these custom levels to make an appropriate color key for the heat map, which I find quite surprising.
Here is a simple example of what I mean:
x = c(20:200/100)
y = c(20:200/100)
z = as.matrix(exp(x^2)) %*% exp(y^2)
filled.contour(x=x,y=y,z=z,color.palette=colorRampPalette(c('green','yellow','red')),levels=c(1:60/3,30,50,150,250,1000,3000))
As you can see, the color key produced with the code above is pretty much useless. I would like to use some sort of projection (perhaps sin(x) or tanh(x)?), so that the upper range is not over-represented in the key (in a linear way).
At this point, I would like to:
1) know if there is something very simple/obvious I am missing, e.g.: an option to make this "key range adapting" automagically;
2) seek suggestions/help on how to do it myself, should the answer to 1) be negative.
Thanks a lot!
PS: I apologize for my English, which is far from perfect. Please let me know if you need me to clarify anything.
I feel your frustration. I never found a way to do this with filled contour, so have usually reverted to using image and then adding my own scale as a separate plot. I wrote the function image.scale to help out with this (link). Below is an example of how you can supply a log-transform to your scale in order to stretch out the small values - then label the scale with the non-log-transformed values as labels:
Example:
source("image.scale.R") # http://menugget.blogspot.de/2011/08/adding-scale-to-image-plot.html
x = c(20:200/100)
y = c(20:200/100)
z = as.matrix(exp(x^2)) %*% exp(y^2)
pal <- colorRampPalette(c('green','yellow','red'))
breaks <- c(1:60/3,30,50,150,250,1000,3000)
ncolors <- length(breaks)-1
labs <- c(0.5, 1, 3,30,50,150,250,1000,3000)
#x11(width=6, height=6)
layout(matrix(1:2, nrow=1, ncol=2), widths=c(5,1), heights=c(6))
layout.show(2)
par(mar=c(5,5,1,1))
image(x=x,y=y,z=log(z), col=pal(ncolors), breaks=log(breaks))
box()
par(mar=c(5,0,1,4))
image.scale(log(z), col=pal(ncolors), breaks=log(breaks), horiz=FALSE, xlab="", ylab="", xaxt="n", yaxt="n")
axis(4, at=log(labs), labels=labs)
box()
Result:

Multicoloured lines on phylogeny plot in R

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)

Convert lwd unit to user coordinates (R base graphic)?

How can I use the lwd to represent some quantity?
For instance:
plot(NULL, type="n", xlim=c(4,7), ylim=c(1,6), xlab="", ylab="")
points(c(5.25,5.25), c(4,5), type="l", lwd=87)
points(c(5.5,6.5), c(3.5,3.5), type="l", lwd=92)
rect(5,3,5.5,4, col="white")
I want the lines drawn with the points functions exactly as wide/tall as the rectangle. The values 87 and 92 above I found manually. Is there a way to calculate those quantities?
EDIT:
The background for the question is: I want to draw bezier curves, and I want the thickness of the curve represent my data. My first idea was to use lwd for that. Can I do better?
lwd is the wrong tool for what you're trying to do. The actual line width will change relative to user coordinates depending on how your plot window is resized (or the dimensions when you save it). You obviously know about the rect command, why not just use that? You might also look into the shape package.
--Edit--
For more complex shapes, my experience doesn't extend beyond polygon. With that, you could get the coordinates bc for a Bezier curve, and then draw a polygon around x = c(bc$x + dx, rev(bc$x - dx), y = c(bc$y + dy, rev(bc$y - dy), but I'm not sure how well that would look for a complex curve.
As an aside, you can replace points(..., type = "l") with lines(...) if you'd like. (I think it makes my code more readable.)

R barplot axis scaling

I want to plot a barplot of some data with some x-axis labels but so far I just keep running into the same problem, as the axis scaling is completely off limits and therefore my labels are wrongly positioned below the bars.
The most simple example I can think of:
x = c(1:81)
barplot(x)
axis(side=1,at=c(0,20,40,60,80),labels=c(20,40,60,80,100))
As you can see, the x-axis does not stretch along the whole plot but stops somewhere in between. It seems to me as if the problem is quite simple, but I somehow I am not able to fix it and I could not find any solution so far :(
Any help is greatly appreciated.
The problem is that barplot is really designed for plotting categorical, not numeric data, and as such it pretty much does its own thing in terms of setting up the horizontal axis scale. The main way to get around this is to recover the actual x-positions of the bar midpoints by saving the results of barplot to a variable, but as you can see below I haven't come up with an elegant way of doing what you want in base graphics. Maybe someone else can do better.
x = c(1:81)
b <- barplot(x)
## axis(side=1,at=c(0,20,40,60,80),labels=c(20,40,60,80,100))
head(b)
You can see here that the actual midpoint locations are 0.7, 1.9, 3.1, ... -- not 1, 2, 3 ...
This is pretty quick, if you don't want to extend the axis from 0 to 100:
b <- barplot(x)
axis(side=1,at=b[c(20,40,60,80)],labels=seq(20,80,by=20))
This is my best shot at doing it in base graphics:
b <- barplot(x,xlim=c(0,120))
bdiff <- diff(b)[1]
axis(side=1,at=c(b[1]-bdiff,b[c(20,40,60,80)],b[81]+19*bdiff),
labels=seq(0,100,by=20))
You can try this, but the bars aren't as pretty:
plot(x,type="h",lwd=4,col="gray",xlim=c(0,100))
Or in ggplot:
library(ggplot2)
d <- data.frame(x=1:81)
ggplot(d,aes(x=x,y=x))+geom_bar(stat="identity",fill="lightblue",
colour="gray")+xlim(c(0,100))
Most statistical graphics nerds will tell you that graphing quantitative (x,y) data is better done with points or lines rather than bars (non-data-ink, Tufte, blah blah blah :-) )
Not sure exactly what you wnat, but If it is to have the labels running from one end to the other evenly places (but not necessarily accurately), then:
x = c(1:81)
bp <- barplot(x)
axis(side=1,at=bp[1+c(0,20,40,60,80)],labels=c(20,40,60,80,100))
The puzzle for me was why you wanted to label "20" at 0. But this is one way to do it.
I run into the same annoying property of batplots - the x coordinates go wild. I would add one another way to show the problem, and that is adding more lines to the plot.
x = c(1:81)
barplot(x)
axis(side=1,at=c(0,20,40,60,80),labels=c(20,40,60,80,100))
lines(c(81,81), c(0, 100)) # this should cross the last bar, but it does not
The best I came with was to define a new barplot function that will take also the parameter "at" for plotting positions of the bars.
barplot_xscaled <- function(bar_heights, at = NA, width = 0.5, col = 'grey'){
if ( is.na(at) ){
at <- c(1:length(bar_heights))
}
plot(bar_heights, type="n", xlab="", ylab="",
ylim=c(0, max(bar_heights)), xlim=range(at), bty = 'n')
for ( i in 1:length(bar_heights)){
rect(at[i] - width, 0, at[i] + width, bar_heights[i], col = col)
}
}
barplot_xscaled(x)
lines(c(81, 81), c(0, 100))
The lines command crosses the last bar - the x scale works just as naively expected, but you could also now define whatever positions of the bars you would like (you could play more with the function a bit to have the same properties as other R plotting functions).

Resources