Related
I need help understanding why the bars print incorrectly when using barp() when I manually set the ylim argument.
Essentially, I have a vector of values (for the height argument) that contains only positive numeric values, but I want the barp() y-axis set to a y-axis limit of, say, [-100,500]. When I set a y-limit that that goes from negative to positive, barp() creates large bars that start from the very bottom, even though they should only start from the zero line.
I tried replicating the exact bars using the original barplot() function and I get exactly what I wanted. However, I prefer using the plotrix's barp() function because 1) it produces the zero line x axis (which I do not know how to do on barplot()) and 2) barp() allows me to plot bars and lines on the same graph while barplot() does not align added lines with the bars.
Below, I wrote out some code that produces the issue. The Figure 2 plot is the problematic one I want to call attention to. I want to create an image just like Figure 4, but using barp(). If that is not possible, I would settle for using barplot() if it were possible to create the x-axis line (where the zero line would be).
I tried reading the documentation but I did not catch anything that would explain this issue. Could someone assist me in identifying why one barplot function works but the other does not?
Thanks!
library(plotrix)
test.vector.mixed = as.numeric(c(-200,-20,50,170,200,250,200,220,205,230,350))
test.vector.positives = as.numeric(c(200,20,50,170,200,250,200,220,205,230,350))
months.index.abb = month.abb[c(8:12,1:6)]
# this displays correctly
barp(
test.vector.mixed,
names.arg = months.index.abb,
col = ifelse(test.vector.mixed < 0, "red", "#108a4c"),
main = "Figure 1: barp() test with Negatives",
ylim = c(-500,400)
)
# this does not display correctly
barp(
test.vector.positives,
names.arg = months.index.abb,
col = ifelse(test.vector.positives < 0, "red", "#108a4c"),
main = "Figure 2: barp() test with Positives",
ylim = c(-500,400)
)
# this displays correctly
barplot(
test.vector.mixed,
names.arg = months.index.abb,
col = ifelse(test.vector.mixed < 0, "red", "#108a4c"),
main = "Figure 3: barplot() test with Negatives",
ylim = c(-500,400)
)
# this is what I want barp() to do
barplot(
test.vector.positives,
names.arg = months.index.abb,
col = ifelse(test.vector.positives < 0, "red", "#108a4c"),
main = "Figure 4: barplot() test with positives",
ylim = c(-500,400)
)
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.
When I manually add the following labels with (axis(1, at=1:27, labels=labs[0:27])):
> labs[0:27]
[1] "0\n9.3%" "1\n7.6%" "2\n5.6%" "3\n5.1%" "4\n5.7%" "5\n6.5%" "6\n7.3%" "7\n7.6%" "8\n7.5%" "9\n7%" "10\n6.2%" "11\n5.2%"
[13] "12\n4.2%" ........
I get the following:
How do I force all labels to be drawn so 1,3,5,6, and 11 are not skipped? (also, for extra credit, how do I shift the whole thing down a few pixels?)
If you want to force all labels to display, even when they are very close or overlapping, you can "trick" R into displaying them by adding odd and even axis labels with separate calls to the axis function, as follows:
labs <-c("0\n9.3%","1\n7.6%","2\n5.6%","3\n5.1%","4\n5.7%","5\n6.5%","6\n7.3%",
"7\n7.6%","8\n7.5%","9\n7%", "10\n6.2%","11\n5.2%","12\n4.2%",13:27)
n=length(labs)
plot(1:28, xaxt = "n")
axis(side=1, at=seq(1,n,2), labels=labs[seq(1,n,2)], cex.axis=0.6)
axis(side=1, at=seq(2,n,2), labels=labs[seq(2,n,2)], cex.axis=0.6)
You can play with cex.axis to get the text size that you want. Note, also, that you may have to adjust the number of values in at= and/or labels= so that they are equal.
I agree with #PLapointe and #joran that it's generally better not to tamper with R's default behavior regarding overlap. However, I've had a few cases where axis labels looked fine even when they weren't quite a full "m-width" apart, and I hit on the trick of alternating odd and even labels as a way to get the behavior I wanted.
?axis tells you that:
The code tries hard not to draw overlapping tick labels, and so will omit labels where they would abut or overlap previously drawn labels. This can result in, for example, every other tick being labelled. (The ticks are drawn left to right or bottom to top, and space at least the size of an ‘m’ is left between labels.)
Play with cex.axis so that labels are small enough to fit without overlapping
labs <-c("0\n9.3%","1\n7.6%","2\n5.6%","3\n5.1%","4\n5.7%","5\n6.5%","6\n7.3%",
"7\n7.6%","8\n7.5%","9\n7%", "10\n6.2%","11\n5.2%","12\n4.2%",12:27)
plot(1:27,xaxt = "n")
axis(side=1, at=1:27, labels=labs[0:27],cex.axis=0.35)
If you widen you graph (manually by dragging or programmatically), you can increase the size of your labels.
Although there are some good answers here, the OP didn't want to resize the labels or change anything about the plot besides fitting all of the axis labels. It's annoying, since often there appears to be plenty of room to fit all of the axis labels.
Here's another solution. Draw the plot without the axis, then add ticks with empty labels. Store the positions of the ticks in an object, so then you can go through each one and place it in the correct position on the axis.
plot(1:10, 1:10, yaxt = "n")
axis_ticks = axis(2, axTicks(2), labels = rep("", length(axTicks(2))))
for(i in axis_ticks) axis(2, i)
#PLapointe just posted what I was going to say, but omitted the bonus answer.
Set padj = 0.5 in axis to move the labels down slightly.
Perhaps draw and label one tick at a time, by calling axis repeatedly using mapply...
For example, consider the following data:
x = runif(100)*20
y = 10^(runif(100)*3)
The formula for y might look a bit odd; it gives random numbers distributed across three orders of magnitude such that the data will be evenly distributed on a plot where the y axis is on a log scale. This will help demonstrate the utility of axTicks() by calculating nice tick locations for us on a logged axis.
By default:
plot(x, y, log = "y")
returns:
Notice that 100 and 1000 labels are missing.
We can instead use:
plot(x, y, log = "y", yaxt = "n")
mapply(axis, side = 2, at = axTicks(2), labels = axTicks(2))
which calls axis() once for each tick location returned by axTicks(), thus plotting one tick at a time. The result:
What I like about this solution is that is uses only one line of code for drawing the axis, it prints exactly the default axis R would have made, except all ticks are labeled, and the labels don't go anywhere when the plot is resized:
I can't say the axis is useful in the resized example, but it makes the point about axis labels being permanent!
For the first (default) plot, note that R will recalculate tick locations when resizing.
For the second (always labeled) plot, the number and location of tick marks are not recalculated when the image is resized. The axis ticks calculated by axTicks depend upon the size of the display window when the plot is first drawn.
If you want want to force specific tick locations, try something like:
plot(x, y, log = "y", yaxt = "n")
mapply(axis, side = 2, at = c(1,10,100, 1000), labels = c("one", "ten", "hundred", "thousand"))
which yields:
axis() includes a gap.axis parameter that controls when labels are omitted. Setting this to a very negative number will force all labels to display, even if they overlap.
The padj parameter of axis() controls the y offset whilst plotting an individual axis.
par(mgp = c(3, 2, 0) will adjust the position of all axis labels for the duration of a plotting session: the second value (here 2, default 1) controls the position of the labels.
# Set axis text position, including for Y axis
par(mgp = c(3, 2, 0))
# Plot
plot(1:12, 1:12, log = 'x', ann = FALSE, axes = FALSE)
# Some numbers not plotted:
axis(1, 1:12)
# All numbers plotted, with manual offset
axis(1, 1:12, gap.axis = -100, padj = 0.5)
I had a similar problem where I wanted to stagger the labels and get them to print without losing some. I created two sets of ticks showing second set below the other to make it look as if it staggers.
xaxis_stagger = function(positions,labels) {
odd=labels[seq(1,length(labels),2)]
odd_pos=positions[seq(1,length(positions),2)]
even=labels[seq(2,length(labels),2)]
even_pos=positions[seq(2,length(positions),2)]
axis(side=1,at=odd_pos,labels=odd)
axis(side=1,at=even_pos,labels=even,padj=1.5)
}
So you give the positions where you want the ticks to be and the labels for those ticks and this would then re-organise it into two sets of axis and plot them on the original plot. Original plot would be done with xaxt="n".
How can I go about removing the box around an xyplot, while keeping the axis scale tick marks? In the spirit of Edward Tufte's minimalist data graphic aesthetic, these axis lines are "non-data ink," and can (should?) be "erased."
library(lattice)
my.df <- data.frame(x=-10:10)
my.df$y <- my.df$x^2
xyplot(y~x,data=my.df)
It seems that the trellis display parameters (e.g. axis.line$col) control both the axis lines and axis ticks together:
xyplot(y~x,data=my.df,
par.settings=list(axis.line=list(col="transparent")))
...which is not the desired result, so it doesn't look like there's a simple way to turn off the lines while leaving the box.
The best I've been able to come up with is a brute-force hack, where I build the tick marks by hand using panel.segments:
at.x=pretty(c(-10,10))
at.y=pretty(c(0,100))
xyplot(y~x,data=my.df,
par.settings=list(axis.line=list(col="transparent")),
scales=list(x=list(at=at.x,labels=at.x),
y=list(at=at.y,labels=at.y)),
panel=function(...){
panel.xyplot(...)
panel.segments(x0=at.x,x1=at.x,y0=-4,y1=-2)
panel.segments(x0=-11.5,x1=-11,y0=at.y,y1=at.y)
}
)
This is close to the desired result, but there's quite a bit of fiddling required to get the tick marks to be a reasonable length and offset a "nice" distance from the data points. These values won't translate from one graphic to the next. Plus, note that the axis labels are now padded too far from the tick marks. I'm sure there's a way to reduce that padding, but that would only make the code even uglier and less portable.
So how can one go about suppressing just the lines that make up the "box" around the plot area, while leaving the tick marks and axis labels intact? Bonus points if this approach could also be used to suppress some, but not all of the lines (e.g. leave the left and lower lines, but suppress the top and right lines).
This is still a bit hacky, but at least you don't have to do any figuring by hand. It uses a combination of par.settings and a custom axis function that takes an argument line.col and temporarily changes the axis line color by a call to trellis.par.set:
EDIT (removed unnecessary changing of trellis settings)
xyplot(y~x,data=my.df, par.settings = list(axis.line = list(col = "transparent")),
# Pass custom axis function to argument 'axis'
axis = function(side, line.col = "black", ...) {
# Only draw axes on the left and bottom
if(side %in% c("left","bottom")) {
# Call default axis drawing function
axis.default(side = side, line.col = "black", ...)
}
}
)
At the moment, I chalk up why line.col = "black" is required in the arguments of the custom axis function to magic. My guess is that it has to do with argument matching with the ellipses (...). Perhaps I'll be wiser tomorrow and find the true reason.
This results in:
The easiest thing to do is to use the custom axis function (axis). Just set lwd (line width) to zero and tick marks (lwd.ticks) to something else. It worked like a charm!
plot(NA,NA,type="n",xaxt="n", lwd=linewidth, xlim=c(1,24), xlab="", ylab="",ylim=c(-300,500))
axis(side = 4, tck = .05, **lwd=0, lwd.ticks=1**, line = 0, labels = NA, col= cols_border[1], col.axis = cols_black)
axis(side = 4, lwd = 0, line = -4.5, las = 1, cex.axis=axis_fontsize, col= cols_border[1], col.axis = cols_black)
mtext("Light deviations (lum/sec)",side=4, padj=-2.5, cex=title_fontsize, col="black")
I have a histogram from a list d of values that I make by simply typing
hist(d)
And this is what I get:
How can I make it such that the x-axis extends all the way left to the origin of this plot (the bottom left corner)? Why does it cut off at -0.4?
Macro's answer is by far the simplest route. However, if you really are unhappy with with the default behavior of hist (really, it's the default behavior of axis I suppose) you can always suppress the axes and draw them yourself:
set.seed(123)
d <- rnorm(1000)
hist(d,axes = FALSE)
axis(1,at = seq(-3,3,1),labels = TRUE,pos = 0)
axis(2,pos = -3)
As for the "why?", the defaults for drawing axes have to be set at something, and so there's a lot of code under there that tries pretty hard to ensure that the axis and tick labels are "pretty" according to the sensibilities of, well, whoever wrote it. In general, I think it does a good job, but of course not everyone agrees.
you can tweak the range of x using the xlim tag. For example, try
hist(d,xlim=c(-10,10))
Two suggestions:
#See if this is sufficient:
hist(...)
box()
#If not, try custom axes:
hist(..., xlim = c(-.5, .5), axes = F)
box()
axis(1, seq(-.5, .5, length = 6))
axis(2, seq(0, 30, by = 5))