I would like to write a plot function for my specific purposes and put the y labels on the left margin. The length of these labels, however, can differ dramatically and depends on the model terms the user comes up with. For this reason, I would like to measure the width of the longest label and set the left margin width accordingly. I found the strwidth function, but I don't understand how to convert its output unit to the unit of the mar argument. An example:
label <- paste(letters, collapse = " ") # create a long label
par(mar = c(5, 17, 4, 2) + 0.1) # 17 is the left margin width
plot(1:2, axes = FALSE, type = "n") # stupid plot example
# if we now draw the axis label, 17 seems to be a good value:
axis(side = 2, at = 1, labels = label, las = 2, tck = 0, lty = 0)
# however, strwidth returns 0.59, which is much less...
lab.width <- strwidth(label) # so how can I convert the units?
You can use mai instead of mar to specify a distance in inches
(instead of "lines").
par(mai = c(1, strwidth(label, units="inches")+.25, .8, .2))
plot(1:2, axes=FALSE)
axis(side = 2, at = 1, labels = label, las = 2, tck = 0, lty = 0)
You can compute the conversion factor between lines and inches
by dividing mar by mai.
inches_to_lines <- ( par("mar") / par("mai") )[1] # 5
lab.width <- strwidth(label, units="inches") * inches_to_lines
par(mar = c(5, 1 + lab.width, 4, 2) + 0.1)
plot(1:2, axes=FALSE)
axis(side = 2, at = 1, labels = label, las = 2, tck = 0, lty = 0)
Related
I was wondering if it is possible to seperate two plots from eachother (both should be on the same plot, using double Y axis). So the double plot should be split into two but without actually plotting them seperate - par(mfrow(1,2)).
I was trying to imitate it with layout plot, or with latticeExtra, ggplot but no success.
I have two different dataset one for the exchange rate one for the logaritmic returns.
par(mar=c(4,4,3,4))
plot(rates$EURHUF~rates$Date, type="l", ylab="Rate", main="EUR/HUF", xlab="Time")
par(new=TRUE)
plot(reteslog$EURHUF~rateslog$Date, type="l", xaxt="n", yaxt="n", ylab="", xlab="", col="red")
axis(side=4)
mtext("Log return", side=4, line=3)
legend("topleft", c("EUR/HUF Rates","EUR/HUF Logreturns"), col=c("black", "red"), lty=c(1,1))
So far I am here, I just don't know how to seperate them or scale them (maybe using margin, or layout?)
Thank you very much guys for helping
I have a solution to this that isn't too outlandish, and is entirely in base, which is nice. For it to work, you just need to be able to force all of your data onto the same scale, which usually isn't a hassle.
The idea is that once your data is on the same scale, you can plot it all normally, and then add in custom axes that show the respective scales of the different data.
set.seed(1986)
d01 <- sample(x = 1:20,
size = 200,
replace = TRUE)
d02 <- sample(x = 31:45,
size = 200,
replace = TRUE)
# pdf(file = "<some/path/to/image.pdf>",
# width = 4L,
# height = 4L) # plot to a pdf
jpeg(file = "<some/path/to/image.jpeg>") # plot to a jpeg
par(mar=c(3.5, 3.5, 2, 3.5)) # parameters to make things prettier
par(mgp=c(2.2, 1, 0)) # parameters to make things prettier
plot(x = 0,
y = 0,
type = "n",
xlim = c(1, 200),
ylim = c(1, 50),
xlab = "Label 01!",
ylab = "Label 02!",
axes = FALSE,
frame.plot = TRUE)
points(d01,
pch = 1,
col = "blue") # data 01
points(d02,
pch = 2,
col = "red") # data 02
mtext("Label 03!",
side = 4,
line = 2) # your extra y axis label
xticks <- seq(from = 0,
to = 200,
by = 50) # tick mark labels
xtickpositions <- seq(from = 0,
to = 200,
by = 50) # tick mark positions on the x axis
axis(side = 1,
at = xtickpositions,
labels = xticks,
col.axis="black",
las = 2,
lwd = 0,
lwd.ticks = 1,
tck = -0.025) # add your tick marks
y01ticks <- seq(from = 0,
to = 1,
by = 0.1) # tick mark labels
y01tickpositions <- seq(from = 0,
to = 50,
by = 5) # tick mark positions on the y01 axis
axis(side = 2,
at = y01tickpositions,
labels = y01ticks,
las = 2,
lwd = 0,
lwd.ticks = 1,
tck = -0.025) # add your tick marks
y02ticks <- seq(from = 0,
to = 50,
by = 5L) # tick mark labels
y02tickpositions <- seq(from = 0,
to = 50,
by = 5) # tick mark positions on the y02 axis
axis(side = 4,
at = y02tickpositions,
labels = y02ticks,
las = 2,
lwd = 0,
lwd.ticks = 1,
tck = -0.025) # add your tick marks
dev.off() # close plotting device
A few notes:
Sizing for this plot was originally set for a pdf, which unfortunately cannot be uploaded here, however that device call is included as commented out code above. You can always play with parameters to find out what works best for you.
It can be advantageous to plot all of your axis labels with mtext().
Including simple example data in your original post is often much more helpful than the exact data you're working with. As of me writing this, I don't really know what your data looks like because I don't have access to those objects.
I've inherited this R code that plots a simple line graph. However, it does it so that the y axis values are plotted downwards below 0 (plots it in the 4th quadrant with 0 at the top and +3600 at the bottom). I want to plot the data right-side up (1st quadrant) so the y axis data goes from 0 up to +3600 at the top like a typical grade-school plot.
I've tried ylim = rev(y) but it returns an error...
I've also tried flipping the seq() command but no luck there.
list.vlevel = numeric(9) # placeholder
plot(
rep(0, length(list.vlevel)),
seq(1, length(list.vlevel)),
type = "n",
xlim = biaslim,
axes = F,
main = paste(list.var.bias[vv], list.score.bias[vv]),
xlab = "",
ylab = ""
)
abline(h = seq(1, length(list.vlevel)),
lty = 3,
col = 8)
axis(2,
labels = list.vlevel,
at = seq(length(list.vlevel), 1, -1),
las = 1)
axis(1)
box()
legend(
x = min(biasarray.var.runhour),
y = length(list.vlevel),
legend = expname,
lty = 3,
lwd = 3,
col = expcol
)
for (exp in seq(length(expname), 1, -1)) {
lines(
biasarray.var.runhour[exp, ],
seq(length(list.vlevel), 1, -1),
col = expcol[exp],
lwd = 3,
lty = 3
)
}
abline(v = 0, lty = 3)
The plot should end up in the first quadrant with yaxis values increasing from 0 upwards to +###.
The axis(2, ...) line draws the y axis. You can see that is the labels follow a descending sequence: seq(length(list.vlevel), 1, -1). seq(1, length(list.vlevel))
Similarly, inside lines(), probably you need to make the same change from seq(length(list.vlevel), 1, -1) to ``seq(1, length(list.vlevel))`
That's as much as we can tell with the info you've provided - can't run any of yoru code without values for all the constants you use, e.g., biasarray.var.runhour, list.var.bias, vv, etc.
I'm wondering how I could add another row of x-axis values right below (along side) the current x-axis values (i.e., 0 to 1) that begin from (ie., 0% to 100%)?
To summarize in 3 steps:
(A): the second row of axis values need NOT have tick marks. (B):, the second row of axis values need to appear exactly below each corresponding value of the first x-axis values. (C):, the second row of axis values must show a "%" sign next to them.
plot(1, ty='l', ann = F, axes = F, xlim = c(0, 1) )
axis(1, at = round(seq(0, 1, len = 9), 2), font = 2, cex.axis = 1.2 )
Look up pos, tck, and tick of axis
par(mar = c(10,3,3,3))
plot(1, 1, type = 'l', ann = F, xlim = c(0, 1), xaxt = "n")
axis(1, at = round(seq(0, 1, len = 9), 2), font = 2, cex.axis = 1.2 ) #First axis
axis(1, at = round(seq(0, 1, len = 9), 2),
labels = paste(100* round(seq(0, 1, len = 9), 2),"%",sep=""),
pos = par("usr")[3] - 1 * 0.1 * (par("usr")[4] - par("usr")[3]),
tick = FALSE, font = 2, cex.axis = 1.2) #2nd axis labels
Background: As you can see in the image below, currently the y-axis extends from -2 to 2 (i.e, the y-axis contains both positive and negative values).
Question:
Keeping everything as shown in the image below, I was wondering is there might be a way to only show the positive part of the y-axis (from 0 on) and somehow HIDE the negative portion of the y-axis? (please see the R code further below)
Here is the R code:
if(!require(library(plotrix))){install.packages('plotrix') }
library(plotrix) ## A package for drawing ellipses ##
plot(1, ty='n', ann = F, axes = F, xlim = c(-4, 6), ylim = c(-2.5, 2) ) ## platform for ellipses
axis(side = 2) ## HERE is my question ## ???
draw.ellipse(x = rep(1, 11), y = rep(-1.2, 11),
a = seq(1, 6, by = .4), b = seq(1/4.5, 6/4.5 , by = .4/4.5 ),
lty = 2, border = 'gray60' ) ## Draw multiple Concentric ellipses ##
AA <- seq(-4, 6, len = 13) ## A range of values on the x-xis just like "xlim" ##
BB <- dcauchy( AA, 1, .95)*5 ## The Height for the AA according to a distribution ##
segments(AA, rep(-1.2, length(AA) ), AA, BB, lty = 3, lwd = 2, col= 'green4' )
curve(dcauchy(x, 1, .95)*5, -4, 6, add = T, col ='magenta', lwd = 3)
See ?axis. You just need to specify the at argument.
axis(side = 2, at = 0:2)
I'd like to place four plots onto a single page. Axis labels should be printed only at the very rim, i.e. x axis labels for the bottom diagrams only, and y axis labels for the left diagrams only. This goes both for the name of the axis as a whole and the individual tick marks. I can generate something along these lines using the following code:
pdf(file = "ExampleOutput.pdf",
width = 6.61,
height = 6.61,
pointsize = 10
)
set.seed(42)
catA <- factor(c("m100", "m500", "m1000", "m2000", "m3000", "m5000"))
catB <- factor(20:28)
samples <- 100
rsample <- function(v) v[ceiling(runif(samples, max=length(v)))]
Tab <- data.frame(catA = rsample(catA),
catB = rsample(catB),
valA = rnorm(samples, 150, 8),
valB = pmin(1,pmax(0,rnorm(samples, 0.5, 0.3))))
par(mfrow = c(2,2))
for (i in 0:3) {
x <- Tab[[1 + i %% 2]]
plot(x, Tab[[3 + i %/% 2]],
xlab = if (i %/% 2 == 1) "Some Categories" else NULL,
ylab = if (i %% 2 == 0) "Some Values" else NULL,
axes = FALSE
)
axis(side = 1,
at=1:nlevels(x),
labels = if (i %/% 2 == 1) levels(x) else FALSE)
axis(side = 2, labels = (i %% 2 == 0))
box(which = "plot", bty = "l")
}
par(mfrow = c(1,1))
dev.off()
I'll welcome suggestions for how to improve my ploting commands, perhaps avoid draing the axes and the L in the lower left corner manually. But that's only a besides.
The result of this sequence looks like this:
The problem here is the huge amount of wasted whitespace. I have the impression that R reserves space for axis and tick labels even if they are not used. As a consequence of this wasted space, for the left bottom diagram, only every second x tick actually gets labeled, which is really bad here.
I'd like to generate a similar plot without that much white space. The actual plots should be the same size, so they line up properly, but the space for the labels should be only at the outside. I imagine a layout like this (mockup created in GIMP):
How can I achieve such a layout?
Here is a slight modification of the general plot you show, assuming that the y and x axis labels pertain to all plots. It uses an outer margin to contain the axis labelling, which we add with title() using argument outer = TRUE. The effect is somewhat like the labelling in ggplot2 or lattice plots.
The key line here is:
op <- par(mfrow = c(2,2),
oma = c(5,4,0,0) + 0.1,
mar = c(0,0,1,1) + 0.1)
which sets plot parameters (the values in place prior to the call are stored in op). We use 5 and 4 lines on sides 1 and 2 for the outer margin, which is the usual number for the mar parameter. Plot region margins (mar) of 1 line each are added to the top and right sides, to give a little room between plots.
The axis labels are added after the for() loop with
title(xlab = "Some Categories",
ylab = "Some Values",
outer = TRUE, line = 3)
The entire script is:
set.seed(42)
catA <- factor(c("m100", "m500", "m1000", "m2000", "m3000", "m5000"))
catB <- factor(20:28)
samples <- 100
rsample <- function(v) v[ceiling(runif(samples, max=length(v)))]
Tab <- data.frame(catA = rsample(catA),
catB = rsample(catB),
valA = rnorm(samples, 150, 8),
valB = pmin(1,pmax(0,rnorm(samples, 0.5, 0.3))))
op <- par(mfrow = c(2,2),
oma = c(5,4,0,0) + 0.1,
mar = c(0,0,1,1) + 0.1)
for (i in 0:3) {
x <- Tab[[1 + i %% 2]]
plot(x, Tab[[3 + i %/% 2]], axes = FALSE)
axis(side = 1,
at=1:nlevels(x),
labels = if (i %/% 2 == 1) levels(x) else FALSE)
axis(side = 2, labels = (i %% 2 == 0))
box(which = "plot", bty = "l")
}
title(xlab = "Some Categories",
ylab = "Some Values",
outer = TRUE, line = 3)
par(op)
which produces
Building heavily on the answer from Gavin Simpson, I now use the following solution:
par(mfrow = c(2, 2), # 2x2 layout
oma = c(2, 2, 0, 0), # two rows of text at the outer left and bottom margin
mar = c(1, 1, 0, 0), # space for one row of text at ticks and to separate plots
mgp = c(2, 1, 0), # axis label at 2 rows distance, tick labels at 1 row
xpd = NA) # allow content to protrude into outer margin (and beyond)
The result looks like this:
As you can see, this is enough to allow printing of all the tick labels as well. If it were not, then according to Gavin's comment, adding cex.axis with a value smaller than 1 to the parameter list should help reduce the font size there.
Just manipulate your parameters, in par. The argument mar controls margin size for individual plot. Change your par to this:
par(mfrow = c(2,2), mar=c(1, 4, 1, 1) + 0.1)#it goes c(bottom, left, top, right)
You need a conditional evaluation that assigns to par('mar') values that are appropriate to the positioning; Here is an example of code (inside your loop) that checks for the "x-layout-position":
pdf(file = "ExampleOutput2.pdf",
width = 6.61,
height = 6.61,
pointsize = 10
)
set.seed(42)
catA <- factor(c("m100", "m500", "m1000", "m2000", "m3000", "m5000"))
catB <- factor(20:28)
samples <- 100
rsample <- function(v) v[ceiling(runif(samples, max=length(v)))]
Tab <- data.frame(catA = rsample(catA),
catB = rsample(catB),
valA = rnorm(samples, 150, 8),
valB = pmin(1,pmax(0,rnorm(samples, 0.5, 0.3))))
par(mfrow = c(2,2), mar= c(3, 4, 1, 1) + 0.1)
for (i in 0:3) {
x <- Tab[[1 + i %% 2]]
plot(x, Tab[[3 + i %/% 2]], mar= if(i %/%2 == 0) {c(4, 4, 1, 1) + 0.1
}else{c(1, 1, 1, 1) + 0.1},
xlab = if (i %/% 2 == 1) "Some Categories" else NULL,
ylab = if (i %% 2 == 0) "Some Values" else NULL,
axes = FALSE
)
axis(side = 1,
at=1:nlevels(x),
labels = if (i %/% 2 == 1) levels(x) else FALSE)
axis(side = 2, labels = (i %% 2 == 0))
box(which = "plot", bty = "l")
}
par(mfrow = c(1,1))
dev.off()
You will need to adjust this to suit you needs, since it only handles two margin conditions andy you really have 4 separate conditions (2 below both needing more bottom-space, with the right one needing less left-space and two above (also with different requirements) . If you shrink the 'mar' value globally it will cut off your x and y labels as can be seen in the loss of the xlab values when you only drop this code into your loop.