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)
Related
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')
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 want to create labels in an rgl plot that have subscripts and superscripts using text3d.
open3d(windowRect=c(500,500,1000,1000))
text3d(0, 0, 0, expression(CO[2]))
produces an image that looks like this:
And,
open3d(windowRect=c(500,500,1000,1000))
text3d(0, 0, 0, bquote("CO"[2]))
Produces
Any way to get subscripts / superscripts in rgl?
Not really. Base graphics has a whole "plotmath" infrastructure to parse those expressions and turn them into plot commands. rgl doesn't make use of that at all.
I don't think the plotmath code is available outside base graphics, so the only possibilities are kind of ugly:
Display 2D graphics as a bitmap in a 3D scene (see ?show2d or ?sprites3d).
Write a base graphics driver (or piggyback on an existing one) to grab what comes out of plotmath, and redo it in rgl. This would be useful for other things, but is hard.
Edited to add:
Here's a second attempt at doing it with sprites. It can still be tweaked to be better:
sprites get resized in the scene, whereas text normally doesn't. (Maybe that's a feature, not a bug.) You'll likely need to play with the cex setting to get what you want.
there's no support for putting the text in the margin, as you'd want for a label. Take a look at the mtext3d function to do that.
it now supports multiple elements in text.
it now has an adj parameter, that should behave like text3d
it still hasn't had much testing.
Anyway, it's a start. If you think of improvements, please post them.
plotmath3d <- function(x, y = NULL, z = NULL,
text,
cex = par("cex"), adj = par("adj"),
startsize = 480,
...) {
xyz <- xyz.coords(x, y, z)
n <- length(xyz$x)
if (is.vector(text))
text <- rep(text, length.out = n)
cex <- rep(cex, length.out = n)
adj <- c(adj, 0.5, 0.5)[1:2]
save <- par3d(skipRedraw = TRUE)
on.exit(par3d(save))
for (i in seq_len(n)) {
# The first device is to measure it.
f <- tempfile(fileext = ".png")
png(f, bg = "transparent", width = startsize, height = startsize)
par(mar = c(0, 0, 0, 0), xaxs = "i", xaxt = "n",
yaxs = "i", yaxt = "n",
usr = c(0, 1, 0, 1))
plot.new()
if (is.vector(text))
thistext <- text[i]
else
thistext <- text
w <- strwidth(thistext, cex = 5, ...)*(2*abs(adj[1] - 0.5) + 1)
h <- strheight(thistext, cex = 5, ...)*(2*abs(adj[2] - 0.5) + 1)
dev.off()
# Now make a smaller bitmap and draw it
expand <- 1.5
size <- round(expand*startsize*max(w, h))
png(f, bg = "transparent", width = size, height = size)
par(mar = c(0, 0, 0, 0), xaxs = "i", xaxt = "n",
yaxs = "i", yaxt = "n",
usr = c(0, 1, 0, 1))
plot.new()
text(0.5, 0.5, thistext, adj = adj, cex = 5, ...)
dev.off()
with(xyz, sprites3d(x[i], y[i], z[i], texture = f, textype = "rgba",
col = "white", lit = FALSE, radius = cex[i]*size/100))
}
}
I'm new to R and working on some code that outputs a scatter plot matrix. The data frame is in the following format:
A B C D
2 3 0 5
8 9 5 4
0 0 5 3
7 0 0 0
My data sets can run into the 100-1000s of rows and 10-100s of columns, with a wide scale of values (hence log transforming my data).
This bit of code gives me some partial success in enhancing the basic plot (see embedded image):
panel.cor <- function(x, y, digits = 2, prefix = "", cex.cor, ...)
{
usr <- par("usr"); on.exit(par(usr))
par(usr = c(0, 1, 0, 1), xlog = FALSE, ylog = FALSE)
r <- abs(cor(x, y))
txt <- format(c(r, 0.123456789), digits = digits)[1]
txt <- paste(prefix, txt)
if(missing(cex.cor)) cex.cor <- 0.8/strwidth(txt)
text(0.5, 0.5, txt, cex = cex.cor * r)
}
# Add regression line to plots.
my_line <- function(x,y,...){
points(x,y,...)
LR <- lm(log(x) ~ log(y), data = SP)
abline(LR, col = "red", untf = TRUE)
}
# Plot scatter plot matrices.
pairs(mydataframe, pch = 20, main = "test",
cex = 0.125, cex.labels = 1,
xlim = c(100, 1e9),
ylim = c(100, 1e9),
upper.panel = panel.cor,
lower.panel = my_line,
log = "xy")'
example
Problem 1 - instead of getting R^2 values in the upper panel, I get NAs instead. How can I correct this?
Problem 2 - I'd like to remove the function for adjusting text size of R^2 value in proportion to correlation. I know it's in panel.cor but not sure which part will need removal or adjustment.
Many thanks in advance
EDIT: 08/06/2016
I have found a work around which also simplifies the code:
panel.cor <- function(x, y, digits = 2, cex.cor, ...)
{
usr <- par("usr"); on.exit(par(usr))
par(usr = c(0, 1, 0, 1))
# correlation coefficient
r <- cor(x, y)
txt <- format(c(r, 0.123456789), digits = digits)[1]
txt <- paste("r= ", txt, sep = "")
text(0.5, 0.6, txt)
}
# add regression line to plots.
my_line <- function(x,y,...)
{
points(x,y,...)
LR <- lm(x ~ y, data = SP)
abline(LR, col = "red", untf = TRUE)
}
# Plot scatterplot matrices.
pairs(SP, pch = 20, main = "test",
cex = 0.125, cex.labels = 1,
upper.panel = panel.cor,
lower.panel = my_line)
example 2
The issue appears to be missing values i.e. 0's. I change these to NA's initially so I can use a log scale. This in combination with log transformation leads to missing R^2 values in the upper panel.
Ideally I'd like to have a log scale. Is there a way i can do this without introducing the aformentioned issue?
Clarification - I'd like a log (xy) scale in the scatter plots (lower panel) and for x-axis in the histograms (diagonal panel). I've been playing about with it today but can't quite get it as i want. Perhaps i'm asking too much from pairs. Any help would be appreciated.
Edit: 10/06/2016
Success!....well approximately 99% happy.
I have made changes - added histograms to diagonal panel and p-value to upper panel (the base code in "pairs()" for adding the histogram needed adjustment due to the log scale used on the x-axis). Please feel free to correct my descriptions if they're not accurate or correct:
library(lattice)
DF <- read.csv("File location", header = TRUE)
DF.1 <- DF+1 # Added small epsilon to data frame otherwise plot errors arise due to missing values.
# Function to calculate R^2 & p-value for upper panels in pairs() - scatterplot matrices.
panel.cor <- function(x, y, digits = 3, cex.cor, ...)
{
usr <- par("usr"); on.exit(par(usr))
par(usr = c(0, 1, 0, 1), xlog = FALSE, ylog = FALSE) # xlog/ylog: ensures that R^2 and p-values display in upper panel.
# Calculate correlation coefficient and add to diagonal plot.
r <- cor(x, y)
txt <- format(c(r, 0.123456789), digits = digits)[1]
txt <- paste("r= ", txt, sep = "")
text(0.5, 0.7, txt, cex = 1.25) # First 2 arguments determine postion of R^2-value in upper panel cells.
# Calculate P-value and add to diagonal plot.
p <- cor.test(x, y)$p.value
txt2 <- format(c(p, 0.123456789), digits = digits)[1]
txt2 <- paste("p= ", txt2, sep = "")
if(p<0.01) txt2 <- paste("p= ", "<0.01", sep = "")
text(0.5, 0.3, txt2, cex = 1.25) # First 2 arguments determine postion of p-value in upper panel cells.
}
# Function to calculate frequency distribution and plot histogram in diagonal plot.
panel.hist <- function(x, ...)
{
usr <- par("usr"); on.exit(par(usr))
par(usr = c(0.5, 1.5, 0, 1.75), xlog = TRUE, ylog = FALSE) # xlog argument allows log x-axis when called in pairs.
h <- hist(log(x), plot = FALSE, breaks = 20)
breaks <- h$breaks; nB <- length(breaks)
y <- h$counts; y <- y/max(y)
rect(breaks[-nB], 0, breaks[-1], y, col = "cyan")
}
# add regression line to plots.
my_line <- function(x,y, ...)
{
points(x,y,...)
LR <- lm(log(x) ~ log(y), data = DF.1)
abline(LR, col = "red", untf = TRUE)
}
# Plot scatterplot matrices.
pairs(DF.1, pch = 20, main = "Chart Title",
cex = 0.75, cex.labels = 1.5, label.pos = 0.0001,
upper.panel = panel.cor,
lower.panel = my_line,
diag.panel = panel.hist,
log = ("xy"),
xlim = c(5, 1e9),
ylim = c(5, 1e9))
The fly in the ointment:
1 - the text labels in the diagonal panel only partially appear. I used a decreasing value for the "label.pos" argument in "pairs()" which moved the label down until they appeared. However, they won't move anymore no matter how much I decrease that value. I've tried to coerce the position from the histogram function, but that doesn't work. I hope someone can see what I'm missing. Thanks in advance...I've not had any responses yet:(
PS: I tried to link 3rd image with my successful plot but I was foiled by my lack of reputation...groan.
EDIT: 13/06/2016
Solved! I feel a bit foolish. The fix for the positioning of the main title in the diagonal panel was super simple and I spent a long time trying much more complex ways to do this. The "label.pos" argument in pairs should be negative! I used a small value of -0.0675 which placed it near the top of the cell containing the histogram.
I hope someone else finds this useful. I'll mark as solved but I'd appreciate any comments regarding my code commenting or if someone sees a way of making the code more efficient. Thanks Alex
Sometimes i feel totally dense. Answer my own question...who would have thought...slaps head. Please see edits in my post for the fixes I found.
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.