I need to add some text to several different plots in R. The best location for this text will depend on where each particular plot has no(t many) points, to minimize text/point overlap. Example:
par(mfrow = c(1, 2))
plot(cars)
text(5, 100, "some text here.", adj = 0, cex = 0.7)
plot(iris[ , 3:4])
text(3, 0.5, "some text here.", adj = 0, cex = 0.7)
Is there a way (preferably with base R) to automatically get a good placement for the text in each plot, instead of me having to first look at each plot and then set 5, 100 and 3, 0.5 manually?
I do not know of any built-in function, but it is not hard to write a function to compute the emptiest quadrant. The comment by #jay.sf mentioned "topright" etc. Those keywords work for legend but do not seem to work in text so I will use legend to write the text. First, the function
emptyQuadrant = function(x) {
mid1 = sum(range(x[,1]))/2
mid2 = sum(range(x[,2]))/2
Q1 = sum(x[,1]>=mid1 & x[,2]>=mid2)
Q2 = sum(x[,1]<=mid1 & x[,2]>=mid2)
Q3 = sum(x[,1]<=mid1 & x[,2]<=mid2)
Q4 = sum(x[,1]>=mid1 & x[,2]<=mid2)
BestQ = which.min(c(Q1, Q2, Q3, Q4))
return(c("topright", "topleft",
"bottomleft", "bottomright")[BestQ])
}
Now you can just compute the best location for the legend.
par(mfrow = c(1, 2))
plot(iris[ , 1:2])
legend(emptyQuadrant(iris[,1:2]), "some text here.", cex = 0.7, bty='n')
plot(iris[ , 3:4])
legend(emptyQuadrant(iris[,3:4]), "some text here.", cex = 0.7, bty='n')
Related
I'm sure this is simple, but I'm pretty stuck. Below is my code:
# Create graphs in list
# Create titles for plots
titlenames <- c(harps)
for (i in length(harps)){ for(j in titlenames) {
counts <- table(Y[[i]][[5]], Y[[i]][[3]])
data_percentage <- apply(counts, 2, function(x){x*100/sum(x,na.rm=T)})
# Create pdf of score breakdown
# For Hotel Name Subtitle
hotelname <- hotel_report$`Hotel (Q15 1)`[hotel_report$`Harp Number`==j]
pdf(file = paste0(j, ".pdf"), paper = "USr", width=8, height=7)
par(mar = c(5.1, 7, 4.1, 2.1))
breakdown <- barplot(data_percentage, main = "Breakdown of Property Score Distribution", sub = hotelname,
col = coul, las = 1, cex.names = .6, horiz = TRUE, yaxs="i", xlab = "Percentage",
cex.axis = .8, cex.lab = .8, cex.main = .8, cex.sub = .8)
dev.off()
}}
I would like data_percentage to change based on the iteration of the loop (there are 391 dataframes in list Y) before it graphs, but all my graphs are using the final version of data_percentage from Y[[391]][[5]], Y[[391]][[3]].
Does anyone know how I need to alter my code so the first graph is using data_percentage based on Y[[1]][[5]], Y[[1]][[3]], the second graph Y[[2]][[5]], Y[[2]][[3]], etc?
Thank you!
I'm using lsmip from lsmeans to plot my model,
library(lsmeans)
PhWs1 <- lsmip(GausNugget1, Photoperiod:Ws ~ Month,
ylab = "Observed log(number of leaves)", xlab = "Month",
main = "Interaction between Photoperiod and Water stress over the months (3 photoperiods)",
par.settings = list(fontsize = list(text = 15, points = 10)))
but I was not able to get a suggestion on the internet on how to handle the legend position, size, title, etc.
I used trellis.par.get() to see the parameters but I could not find the one related to my issue. As you can see from the graph, the legend should be "Photoperiod*Ws" but Ws is not visible.
I see two possibly complementing alternatives to approach this issue. The first would be to create a fully customized legend and pass it on to the key argument of xyplot (which lsmip is heavily based on). Here is an example taken from ?lsmip to clarify my point.
## default trellis point theme
trellis_points <- trellis.par.get("superpose.symbol")
## create customized key
key <- list(title = "Some legend title", # legend title
cex.title = 1.2,
x = .7, y = .9, # legend position
points = list(col = trellis_points$col[1:2], # points
pch = trellis_points$pch[1:2],
cex = 1.5),
text = list(c("A", "B"), cex = .9)) # text
## create results and extract lattice plot
d <- lsmip(warp.lm, wool ~ tension, plotit = FALSE,
main = "Some figure title", key = key)
p <- attr(d, "lattice")
p
As you can see, setting up a customized legend let's you modify all the different components of the legend - including labels, text and symbol sizes, legend spacing, etc. Have a deeper look at the key argument described in ?xyplot which describes the various modification options in detail.
Now, if you have a long legend title and you do not want to include the legend inside the plot area, you could also define separate viewports, thus allowing the legend to occupy more space at the right margin. Note the use of update to remove the initially created legend from p and the subsequent assembly of the single figure components using grid functionality.
## remove legend from figure
p <- update(p, legend = NULL)
## assemble figure incl. legend
library(grid)
png("plot.png", width = 14, height = 10, units = "cm", res = 300)
grid.newpage()
## add figure without legend
vp0 <- viewport(x = 0, y = 0, width = .75, height = 1,
just = c("left", "bottom"))
pushViewport(vp0)
print(p, newpage = FALSE)
## add legend
upViewport(0)
vp1 <- viewport(x = .7, y = 0, width = .3, height = 1,
just = c("left", "bottom"))
pushViewport(vp1)
draw.key(key, draw = TRUE)
dev.off()
I am having problems with aligning my subtitle in chart_Series.
At present it is just writing over the top of the x axis.
Also is it possible to switch off the text that is automatically written at the top of a
chart_Series chart so I can replace it with my own
library(quantmod)
getSymbols("SPY", from="2013-01-01", to=Sys.Date())
chart_Series(SPY)
title("S&P Index", sub = "text1\n\text2\ntext3",
cex.main = 2, font.main= 4, col.main= "blue",
cex.sub = 0.75, font.sub = 3, col.sub = "red")
I would be grateful for your help.
The 'quantmod' graphics are object-oriented. Data is stored in an environment (named 'Env') inside another environment (named whatever you name it, 'cspy' in this case). Special charting functions are stored with along with the data in a 'proto'-object. It is a more object-oriented approach than is used in either the S3 or S4 programming paradigms that are much more common in R. The 'proto'-package should be consulted for more details. After nosing around the code in chartSeries and the object it creates, I can get the labeling at the top to go away with this:
cspy <- chart_Series(SPY, name = NULL)
cspy$Env$actions[[4]] <- NULL
cspy
The 'quantmod' code has this:
cs$Env$name <- name
text.exp <- c(expression(text(1 - 1/3, 0.5, name, font = 2,
col = "#444444", offset = 0, cex = 1.1, pos = 4)),
expression(text(NROW(xdata[xsubset]),
0.5, paste(start(xdata[xsubset]), end(xdata[xsubset]),
sep = " / "), col = 1, adj = c(0, 0), pos = 2)))
cs$add(text.exp, env = cs$Env, expr = TRUE)
... but I wasn't able to figure out a name for that leaf so I looked at :
cspy$Env$actions
... and saw that the name and date-range were in the 4th item. so I just deleted it. (To get rid of only the name it is trivial: chart_Series(SPY, name = NULL). (I don't know if the location of that graphical item in the object will be consistent and I do not see a method for access that object-leaf, so this is possibly an unstable hack.)
To make room for the margin text (subtitle):
png("out.png")
myoma <- par("oma")
myoma[1] <- 3
par("oma" =myoma)
cspy
title("S&P Index", cex.main = 2, font.main= 4, col.main= "blue")
mtext(text= "text1\ntext2\ntext3", side=1, cex = 0.75, font = 3, col = "red",line=7)
dev.off()
I am not familiar with chart_Series plot from before. Normally I would have used the plotting parameter mar to increase the margin at the bottom of the plot, to make some more room for the sub-title. However, I didn't manage to increase the margin that way. Instead I had to use oma, to increase the outer margins of the plot. I added the sub-titles using mtext, instead of using the sub argument in title. You set the distance from the plot with line. The default chart_Series title is turned off by setting name = NULL. Please also note the 'Note' in ?chart_Series: "Highly experimental (read: alpha) use with caution.". Anyway,
par(oma = c(5, 0, 0, 0))
chart_Series(SPY, name = NULL)
title("S&P Index", cex.main = 2, font.main = 4, col.main = "blue")
mtext(text = "text1\n\text2\ntext3",
side = 1, line = 9, cex = 0.75, font = 3, col = "red")
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.
Alright I decided to waste the evening making a hangman game in R. Got everything looking pretty good except an unknown number of letters that get plotted as seen here:
a
b
f
d
g
Here's an example/attempt using text and mtext:
FUN <- function(n) {
plot.new()
mtext("wrong", side = 3, cex=1.5, adj = 0, padj = 1, col = "red")
wrong <- letters[1:n]
text(0, .8, paste(wrong, collapse = "\n"), offset=.3, cex=1.5)
}
FUN(5)
FUN(10)
FUN2 <- function(n) {
plot.new()
mtext("wrong", side = 3, cex=1.5, adj = 0, padj = 1, col = "red")
wrong <- letters[1:n]
mtext(paste(wrong, collapse = "\n"), side = 3, cex=1.5,
adj = 0, padj = 2.5)
}
FUN2(5)
FUN2(10)
How can I make it so the a in both FUN(5) and FUN(10) plots in the same location?
First - way to go! R Games! You should totally make a package out of it so I can play ;)
For text you can use the adj argument, if you set it to 1. Then the (x,y) coordinates provided to text refer to the top-left corner of the rectangle that contains the text.
adj: one or two values in [0, 1] which specify the x (and
optionally y) adjustment of the labels. On most devices
values outside that interval will also work.
FUN <- function(n) {
plot.new()
mtext("better?", side = 3, cex=1.5, adj = 0, padj = 1, col = "red")
wrong <- letters[1:n]
text(0, .8, paste(wrong, collapse = "\n"), offset=.3, cex=1.5, adj=c(0,1))
}
Note adj=c(0,1), 0 being x alignment and 1 being y alignment (the documentation doesn't really make this clear but since it's a value in [0,1] I assume it to be an adjustment of position as a fraction of the label length in that dimension).
Similarly for mtext you need to use padj=1, being top alignment according to the documentation (since your text direction is left to right). The adj argument is the left-right alignment.
FUN2 <- function(n) {
plot.new()
mtext("better?", side = 3, cex=1.5, adj = 0, padj = 1, col = "red")
wrong <- letters[1:n]
mtext(paste(wrong, collapse = "\n"), side = 3, cex=1.5,
adj = 0, padj = 1) # adj=1 means text on right side instead of left.
}
(Are you going to do humorous stick man figures?? This sounds so fun :D)