I want to plot the results from a six factor personality test as a circumplex.
The test in question is the Allgemeiner Interessen-Struktur-Test (AIST-R; Bergmann & Eder, 2005) [General Interest Structure Test], which measures vocational choice based on the theory of J. L. Holland (Holland codes, RIASEC). You can use the answers below to plot the "Felddarstellung" [field representation] recommended in the manual in stead of the interest profile to better visualize the vector of differentiation.
The resulting graphic should look similar to this:
The test results are given as angles and lengths.
How can I draw an axis or geometric vector in R from a starting point with a specific length, without defining the end coordinates (as required by arrows)?
How can I add tickmarks to such a vector?
How can I define the points of a polygon (here in grey) in a similar manner, i.e. by providing an angle and a distance from the origin, instead of coordinates)?
I can of course calculate the endpoints, but I would like to avoid this. Also, I wouldn't know how to add tick marks to an arrow.
My attempts that did not work:
par(pin = c(4, 4))
plot(0, 0, type = "n", xlim = c(-60, 60), ylim = c(-60, 60))
symbols(c(0, 0, 0), c(0, 0, 0), circles = c(60, 1.5, 1.5), inches = FALSE, add = TRUE, fg = c("black", "black", "white"), bg = c("transparent", "#000000", "transparent"))
arrows(0, 0, length = c(60, 60, 60, 60, 60, 60), angle = c(0, 60, 120, 180, 240, 300))
The following uses base functions and a couple of functions that we define ourselves.
While you requested a method that doesn't require calculating coordinates of segments' end points, I think this is impossible. However, we can define a simple helper function that uses some basic trigonometry to calculate the coordinates given the angle (clockwise from the positive y-axis) and the segment length. We do this below, as well as defining a function that plots a rotated axis.
get.coords <- function(a, d, x0, y0) {
a <- ifelse(a <= 90, 90 - a, 450 - a)
data.frame(x = x0 + d * cos(a / 180 * pi),
y = y0+ d * sin(a / 180 * pi))
}
rotatedAxis <- function(x0, y0, a, d, symmetrical=FALSE, tickdist, ticklen, ...) {
if(isTRUE(symmetrical)) {
axends <- get.coords(c(a, a + 180), d, x0, y0)
tick.d <- c(seq(0, d, tickdist), seq(-tickdist, -d, -tickdist))
} else {
axends <- rbind(get.coords(a, d, x0, y0), c(x0, y0))
tick.d <- seq(0, d, tickdist)
}
invisible(lapply(apply(get.coords(a, d=tick.d, x0, y0), 1, function(x) {
get.coords(a + 90, c(-ticklen, ticklen), x[1], x[2])
}), function(x) lines(x$x, x$y, ...)))
lines(axends$x, axends$y, ...)
}
get.coords takes arguments a (a vector of angles), d (a vector of segment lengths), and x0 and y0, the coordinates of the known point. Vectors a and d are recycled as necessary. The function returns a data.frame with elements x and y giving the coordinates corresponding to each angle/length pair.
rotatedAxis plots an axis between x0, y0 and the point d units away along the line at angle a. If symmetrical is TRUE, the axis extends d units in opposite directions. Tick marks, of height ticklen are plotted tickdist units apart.
Plotting of the circle uses get.coords to calculate coordinates along the circumference, and plots the line connecting these with polygon (inspired by #timriffe).
Below we use these functions to replicate the plot provided by the OP.
# Set up plotting device
plot.new()
plot.window(xlim=c(-70, 70), ylim=c(-70, 70), asp=1)
# Plot circle with radius = 60 units and centre at the origin.
polygon(get.coords(seq(0, 360, length.out=1000), 60, 0, 0), lwd=2)
# Plot a polygon with vertices along six axes, at distances of 17, 34, 44, 40,
# 35, and 10 units from the centre.
poly.pts <- get.coords(seq(0, 300, 60), c(17, 34, 44, 40, 35, 10), 0, 0)
polygon(poly.pts$x, poly.pts$y, col='gray', lwd=2)
# Plot the rotated axes
rotatedAxis(0, 0, a=60, d=60, symmetrical=TRUE, tickdist=10, ticklen=1)
rotatedAxis(0, 0, a=120, d=60, symmetrical=TRUE, tickdist=10, ticklen=1)
rotatedAxis(0, 0, a=180, d=60, symmetrical=TRUE, tickdist=10, ticklen=1)
# Add text labels to circumference
text.coords <- get.coords(seq(0, 300, 60), 65, 0, 0)
text(text.coords$x, text.coords$y, c('I', 'A', 'S', 'E', 'C', 'R'))
# Plot a second point and connect to centre by a line
point2 <- get.coords(145, 50, 0, 0)
points(point2, pch=20, cex=2)
segments(0, 0, point2$x, point2$y, lwd=3)
# Plot central point
points(0, 0, pch=21, bg=1, col=0, lwd=2, cex=2)
(Edit: I heavily edited this post - without changing it's general message drastically - in order to make it easier to read and more generally applicable. Additions/changes include that I now define a function to plot rotated axes, plot the circle by calculating coordinates of vertices along the circumference and plotting with polygon, as inspired by #timriffe.)
A solution based on the comment by Thomas and the answer by jbaums.
I used jbaums' method to draw the axes, because I did not want the unbroken circular grid provided by plotrix.
I did not use jbaums' method to draw the circle, because that has a wavy/bumpy line.
I call par(new = TRUE) twice, because the scale in jbaums answer is a tenth of the true scale and I couldn't figure out how to adjust that.
I manually placed the lables, which I'm not happy with.
There's also a lot of superfluous code in there, but I left it in case someone wants to use it to work on their own version.
Here's the code:
# test results
R <- 95
I <- 93
A <- 121
S <- 111
E <- 114
C <- 80
dimensions <- c("R", "I", "A", "S", "E", "C")
values <- c(R, I, A, S, E, C)
RIASEC <- data.frame(
"standard.values" = values,
"RIASEC" = dimensions
)
person.typ <- paste(
head(
RIASEC[
with(
RIASEC,
order(-standard.values)
),
]$RIASEC,
3
),
collapse = ""
)
# length of vector
vi1 <- 0
vi2 <- I
va1 <- 0.8660254 * A
va2 <- 0.5 * A
vs1 <- 0.8660254 * S
vs2 <- -0.5 * S
ve1 <- 0
ve2 <- -E
vc1 <- -0.8660254 * C
vc2 <- -0.5 * C
vr1 <- -0.8660254 * R
vr2 <- 0.5 * R
vek1 <- va1 + vi1 + vr1 + vc1 + ve1 + vs1 # x-axix
vek2 <- vr2 + vi2 + va2 + vs2 + ve2 + vc2 # y-axis
vektor <- sqrt(vek1^2 + vek2^2) # vector length
# angle of vector
if (vek1 == 0) {tg <- 0} else {tg <- vek2 / vek1}
wink <- atan(tg) * 180 / pi
if (vek1 > 0) {
winkel <- 90 - wink
} else if (vek1 == 0) {
if (vek2 >= 0) {winkel <- 360}
else if (vek2 < 0) {winkel <- 180}
} else if (vek1 < 0) {
if (vek2 <= 0) {winkel <- 270 - wink}
else if (vek2 >= 0) {winkel <- 270 - wink}
}
library(plotrix)
axis.angle <- c(0, 60, 120, 180, 240, 300)
axis.rad <- axis.angle * pi / 180
value.length <- values - 70
dev.new(width = 5, height = 5)
radial.plot(value.length, axis.rad, labels = dimensions, start = pi-pi/6, clockwise=TRUE,
rp.type="p", poly.col = "grey", show.grid = TRUE, grid.col = "transparent", radial.lim = c(0,60))
radial.plot.labels(value.length + c(4, 2, -2, 1, 1, 4), axis.rad, radial.lim = c(0,60), start = pi-pi/6, clockwise = TRUE, labels = values, pos = c(1,2,3,1,2,1))
get.coords <- function(a, d, x0=0, y0=0) {
a <- ifelse(a <= 90, 90 - a, 450 - a)
data.frame(x = x0 + d * cos(a / 180 * pi), y = y0+ d * sin(a / 180 * pi) )
}
par(new = TRUE)
plot(NA, xlim = c(-6, 6), ylim=c(-6, 6), type='n', xlab='', ylab='', asp = 1,
axes=FALSE, new = FALSE, bg = "transparent")
circumf.pts <- get.coords(seq(60, 360, 60), 6)
segments(circumf.pts$x[1:3], circumf.pts$y[1:3],
circumf.pts$x[4:6], circumf.pts$y[4:6])
ticks.locs <- lapply(seq(60, 360, 60), get.coords, d=1:6)
ticks <- c(apply(do.call(rbind, ticks.locs[c(1, 4)]), 1, function(x)
get.coords(150, c(-0.1, 0.1), x[1], x[2])),
apply(do.call(rbind, ticks.locs[c(2, 5)]), 1, function(x)
get.coords(30, c(-0.1, 0.1), x[1], x[2])),
apply(do.call(rbind, ticks.locs[c(3, 6)]), 1, function(x)
get.coords(90, c(-0.1, 0.1), x[1], x[2])))
lapply(ticks, function(x) segments(x$x[1], x$y[1], x$x[2], x$y[2]))
par(new = TRUE)
plot(NA, xlim = c(-60, 60), ylim=c(-60, 60), type='n', xlab='', ylab='', asp = 1,
axes=FALSE, new = FALSE, bg = "transparent")
segments(0, 0, vek1, vek2, lwd=3)
points(vek1, vek2, pch=20, cex=2)
symbols(c(0, 0, 0), c(0, 0, 0), circles = c(60, 2, 1.3), inches = FALSE, add = TRUE, fg = c("black", "white", "black"), bg = c("transparent", "white", "black"))
And here's the graphic:
Related
I have a problem where I have data with (x,y) coordinates that I want to plot in the x-y plane. Furthermore, I have some box constraints such that -7 < x < 7 and -5 < y < 5 need to be drawn and checked. All points that fall outside of this box constraint I would like to color red. To do this I have the following code in R:
library(rgl)
x <- 7
y <- 5
data.x <- rnorm(10,0,5)
data.y <- rnorm(10,0,5)
plot(data.x, data.y, xlim = c(min(-x,data.x),max(x,data.x)),
ylim = c(min(-y,data.y),max(y,data.y)), pch = 19)
rect(-x, -y, x, y, col = "lightgrey")
idx <- abs(data.x) > x | abs(data.y) > y
points(data.x[idx], data.y[idx], col = "red", pch = 19)
points(data.x[!idx], data.y[!idx], col = "deepskyblue", pch = 19)
Now, where I am stuck, is on how to plot this type of data picture when I have a third group of data and a third constraint. I.e.,
### How to generalize when I have a third axis and constraint, i.e., a 3D cube
z <- 4
data.z <- rnorm(10, 0, 5)
So essentially I want to plot a box constraint as a cube in the x-y-z plane, and to color the points that fall outside the box constraint red again.
Also, I should say I understand there are functions for plottig 3d scatter plots in R, however, what I am struggling with is how to draw the 3D cube that defines the constraints.
The difficulty with a 3D plot such as this is being able to interpret the "depth" of the points in the image. An animated 3D image might be helpful here:
library(plot3D)
x <- 7
y <- 5
z <- 6
set.seed(123)
data.x <- rnorm(10, 0, 5)
data.y <- rnorm(10, 0, 5)
data.z <- rnorm(10, 0, 5)
in_out <- abs(data.x) > x | abs(data.y) > y | abs(data.z) > z
for(i in seq(0, 358, 2)) {
png(paste0("box", sprintf("%03d", i), ".png"))
box3D(-x, -y, -z, x, y, z, col = NA, border = "gray50", theta = i, phi = 15,
xlim = c(-10, 10), ylim = c(-10, 10), zlim = c(-10, 10),
axes = TRUE, ticktype = "detailed")
points3D(data.x, data.y, data.z, colvar = in_out, pch = 16, cex = 3,
add = TRUE, colkey = FALSE, col = c("lightblue", "red"))
dev.off()
}
library(magick)
list.files(pattern = 'box\\d+\\.png', full.names = TRUE) %>%
image_read() %>%
image_join() %>%
image_animate(fps=50) %>%
image_write("box.gif")
box.gif
I have data collected on seabird disturbance from ships. I was on board ships with range finder binoculars and an angle board. For each bird I surveyed I have a starting distance and bearing relative to the ships course. I also have the distance and bearing at which the bird reacted (or didn't in some cases).
I would like to make a two panel plot showing on one the starting distance and bearing positions and on the other the terminating distance and bearings. Ideally the second plot will be color coded (or pch coded) to show the different reaction type.
My data is in this format
date_id dist bear act
550 40711_027 200 30 f
551 40711_028 500 45 n
552 40711_028 450 60 n
553 40711_028 400 75 n
554 40711_028 371 80 f
555 40711_029 200 5 f
556 40711_030 200 10 d
557 40711_031 400 30 n
558 40711_031 350 30 d
Here is the data in a format you can play around with
id <- c(1,2,2,2,2,3,4,5,5)
dist <- c(200,500,450,400,371,200,200,400,350)
bear <- c(30,45,60,75,80,5,10,30,30)
act <- c("f","n","n","n","f","f","d","n","d")
dat <- data.frame(id, dist, bear, act)
As you can see there are some id's that repeat and some that have only one row. I would like to plot the first dist and bear on one plot and the last dist and bear (per id) on another plot. These may be the same for birds with only one observation. It would be nice to color code the points in the second plot based on the 'act' column. Also there is no left or right designation for bearing so I am okay with all the points being on one side of the middle line or the other but if you know how it would be cool to randomly place them left or right of the center line. Ideally the plots will look something like this.
UPDATE: Following suggestions from #jbaums using his code from another question found here.
get.coords <- function(a, d, x0, y0) {
a <- ifelse(a <= 90, 90 - a, 450 - a)
data.frame(x = x0 + d * cos(a / 180 * pi),
y = y0+ d * sin(a / 180 * pi))
}
rotatedAxis <- function(x0, y0, a, d, symmetrical=FALSE, tickdist, ticklen, ...) {
if(isTRUE(symmetrical)) {
axends <- get.coords(c(a, a + 180), d, x0, y0)
tick.d <- c(seq(0, d, tickdist), seq(-tickdist, -d, -tickdist))
} else {
axends <- rbind(get.coords(a, d, x0, y0), c(x0, y0))
tick.d <- seq(0, d, tickdist)
}
invisible(lapply(apply(get.coords(a, d=tick.d, x0, y0), 1, function(x) {
get.coords(a + 90, c(-ticklen, ticklen), x[1], x[2])
}), function(x) lines(x$x, x$y, ...)))
lines(axends$x, axends$y, ...)
}
plot.new()
plot.window(xlim=c(-1000,1000),ylim=c(-1000, 1000), asp=1)
polygon(get.coords(seq(0,180, length.out=1000),1000,0,0),lwd=2)
polygon(get.coords(seq(0,180, length.out=750),750,0,0),lwd=2)
polygon(get.coords(seq(0,180, length.out=500),500,0,0),lwd=2)
polygon(get.coords(seq(0,180, length.out=250),250,0,0),lwd=2)
rotatedAxis(0, 0, a=90, d=1000, tickdist=100, ticklen=1)
rotatedAxis(0, 0, a=45, d=1000, tickdist=100, ticklen=1)
rotatedAxis(0, 0, a=135, d=1000, tickdist=100, ticklen=1)
obs <- with(dat, get.coords(bear, dist, 0, 0))
points(obs)
This gives me this plotted figure which is getting closer to my goal! Thanks #jbaums.
My issue is that I cannot figure out how to just plot the 90 wedge from 0 to 90 (as this is where my data was collected in.
I also still need some guidance on only selecting the first (and later the last) observation when more than one observations have been collected.
If you want to recreate your example plot more closely, try the following, which uses your dat, and the get.coords function originally posted here:
# Define function to calculate coordinates given distance and bearing
get.coords <- function(a, d, x0, y0) {
a <- ifelse(a <= 90, 90 - a, 450 - a)
data.frame(x = x0 + d * cos(a / 180 * pi),
y = y0+ d * sin(a / 180 * pi))
}
# Set up plotting device
plot.new()
par(mar=c(2, 0, 0, 0), oma=rep(0, 4))
plot.window(xlim=c(-1100, 1100), ylim=c(-100, 1100), asp=1)
# Semicircles with radii = 100 through 1000
sapply(seq(100, 1000, 100), function(x) {
lines(get.coords(seq(270, 450, length.out=1000), x, 0, 0))
})
# Horizontal line
segments(-1000, 0, 1000, 0)
# 45-degree lines
apply(get.coords(c(360-45, 45), 1000, 0, 0), 1,
function(x) lines(rbind(x, c(0, 0)), lwd=2))
# Plot white curves over black curves and add text
sapply(seq(100, 1000, 100), function(x) {
txt <- paste0(x, 'm')
w <- strwidth(txt, cex=0.9)/2
a <- atan(w/x)/pi*180
lines(get.coords(seq(-a, a, length=100), x, 0, 0),
lwd=2.5, col='white')
text(0, x, txt, cex=0.8)
})
# Add points
points(with(dat, get.coords(-bear, dist, 0, 0)), pch=20)
# Add triangle
polygon(c(0, -30, 30), c(-5, -55, -55), col='black')
Note that I've passed the angles of your points to get.coords as -bear, since your example figure suggests you are calculating bearings counter-clockwise from the positive y-axis. The get.coords function expects angles to be calculated clockwise from the positive x-axis, and negative angles (as will arise with -bear) will be interpreted as 360 minus the angles.
Not sure if I understand all your requirements but below is my solution for the "starting points" plot:
#install.packages("plotrix")
library("plotrix")
id <- c(1,2,2,2,2,3,4,5,5)
dist <- c(200,500,450,400,371,200,200,400,350)
bear <- c(30,45,60,75,80,5,10,30,30)
act <- c("f","n","n","n","f","f","d","n","d")
dat <- data.frame(id, dist, bear, act)
##Define a function that converts degrees to radians
#NOTE: Authored by Fabio Marroni
#URL: http://fabiomarroni.wordpress.com/2010/12/23/r-function-to-convert-degrees-to-radians/
degrees.to.radians<-function(degrees=45,minutes=30)
{
if(!is.numeric(minutes)) stop("Please enter a numeric value for minutes!\n")
if(!is.numeric(degrees)) stop("Please enter a numeric value for degrees!\n")
decimal<-minutes/60
c.num<-degrees+decimal
radians<-c.num*pi/180
return(radians)
}
#Plot the canvas
plot(0, 0, type = "n", xaxt = "n", yaxt = "n", asp=1,
xlim = c(0, max(dat$dist)), ylim = c(0, max(dist)),
bty="n", xlab = "", ylab = "",
main = "Whatever observations (starting points only)")
#Plot x/y axes
segments(0, 0, max(dat$dist), 0)
segments(0, 0, 0, max(dat$dist))
#Plot axes labels
axis(1, at = seq(0, max(dat$dist), 100), labels = seq(0, max(dat$dist), 100))
#Plot the equal-distance arcs
dist = 100
while(dist < max(dat$dist)){
draw.arc(0, 0, radius = dist, deg1 = 0, deg2 = 90, n = 100, col = "blue")
dist <- dist + 100
}
#Plot the 1st point (cause it's always an starting point)
x <- dat[1, ]$dist*sin(degrees.to.radians(dat[1, ]$bear))
y <- dat[1, ]$dist*cos(degrees.to.radians(dat[1, ]$bear))
points(x, y, pch = 21)
for(i in 2:nrow(dat)){
#Only plot starting points
if(dat[i, ]$id != dat[i-1, ]$id){
#Determin the x and y for each point
x <- dat[i, ]$dist*sin(degrees.to.radians(dat[i, ]$bear))
y <- dat[i, ]$dist*cos(degrees.to.radians(dat[i, ]$bear))
#Adding starting points
points(x, y, pch = 21)
}
}
If this is what you want, you can adapt it for the "ending points" plot. And you can add a col parameter to the point() function and use "act" to color code the points.
I want to plot a confusion matrix, but, I don't want to just use a heatmap, because I think they give poor numerical resolution. Instead, I want to also plot the frequency in the middle of the square. For instance, I like the output of this:
library(mlearning);
data("Glass", package = "mlbench")
Glass$Type <- as.factor(paste("Glass", Glass$Type))
summary(glassLvq <- mlLvq(Type ~ ., data = Glass));
(glassConf <- confusion(predict(glassLvq, Glass, type = "class"), Glass$Type))
plot(glassConf) # Image by default
However, 1.) I don't understand that the "01, 02, etc" means along each axis. How can we get rid of that?
2.) I would like 'Predicted' to be as the label of the 'y' dimension, and 'Actual' to be as the label for the 'x' dimension
3.) I would like to replace absolute counts by frequency / probability.
Alternatively, is there another package that will do this?
In essence, I want this in R:
http://www.mathworks.com/help/releases/R2013b/nnet/gs/gettingstarted_nprtool_07.gif
OR:
http://c431376.r76.cf2.rackcdn.com/8805/fnhum-05-00189-HTML/image_m/fnhum-05-00189-g009.jpg
The mlearning package seems quite inflexible with plotting confusion matrices.
Starting with your glassConf object, you probably want to do something like this:
prior(glassConf) <- 100
# The above rescales the confusion matrix such that columns sum to 100.
opar <- par(mar=c(5.1, 6.1, 2, 2))
x <- x.orig <- unclass(glassConf)
x <- log(x + 0.5) * 2.33
x[x < 0] <- NA
x[x > 10] <- 10
diag(x) <- -diag(x)
image(1:ncol(x), 1:ncol(x),
-(x[, nrow(x):1]), xlab='Actual', ylab='',
col=colorRampPalette(c(hsv(h = 0, s = 0.9, v = 0.9, alpha = 1),
hsv(h = 0, s = 0, v = 0.9, alpha = 1),
hsv(h = 2/6, s = 0.9, v = 0.9, alpha = 1)))(41),
xaxt='n', yaxt='n', zlim=c(-10, 10))
axis(1, at=1:ncol(x), labels=colnames(x), cex.axis=0.8)
axis(2, at=ncol(x):1, labels=colnames(x), las=1, cex.axis=0.8)
title(ylab='Predicted', line=4.5)
abline(h = 0:ncol(x) + 0.5, col = 'gray')
abline(v = 0:ncol(x) + 0.5, col = 'gray')
text(1:6, rep(6:1, each=6),
labels = sub('^0$', '', round(c(x.orig), 0)))
box(lwd=2)
par(opar) # reset par
The above code uses bits and pieces of the confusionImage function called by plot.confusion.
Here is a function for plotting confusion matrices I developed from jbaums excellent answer.
It is similar, but looks a bit nicer (IMO), and does not transpose the confusion matrix you feed it, which might be helpful.
### Function for plotting confusion matrices
confMatPlot = function(confMat, titleMy, shouldPlot = T) {
#' Function for plotting confusion matrice
#'
#' #param confMat: confusion matrix with counts, ie integers.
#' Fractions won't work
#' #param titleMy: String containing plot title
#' #return Nothing: It only plots
## Prepare data
x.orig = confMat; rm(confMat) # Lazy conversion to function internal variable name
n = nrow(x.orig) # conf mat is square by definition, so nrow(x) == ncol(x)
opar <- par(mar = c(5.1, 8, 3, 2))
x <- x.orig
x <- log(x + 0.5) # x<1 -> x<0 , x>=1 -> x>0
x[x < 0] <- NA
diag(x) <- -diag(x) # change sign to give diagonal different color
## Plot confusion matrix
image(1:n, 1:n, # grid of coloured boxes
# matrix giving color values for the boxes
# t() and [,ncol(x):1] since image puts [1,1] in bottom left by default
-t(x)[, n:1],
# ylab added later to avoid overlap with tick labels
xlab = 'Actual', ylab = '',
col = colorRampPalette(c("darkorange3", "white", "steelblue"),
bias = 1.65)(100),
xaxt = 'n', yaxt = 'n'
)
# Plot counts
text(rep(1:n, each = n), rep(n:1, times = n),
labels = sub('^0$', '', round(c(x.orig), 0)))
# Axis ticks but no lables
axis(1, at = 1:n, labels = rep("", n), cex.axis = 0.8)
axis(2, at = n:1, labels = rep("", n), cex.axis = 0.8)
# Tilted axis lables
text(cex = 0.8, x = (1:n), y = -0.1, colnames(x), xpd = T, srt = 30, adj = 1)
text(cex = 0.8, y = (n:1), x = +0.1, colnames(x), xpd = T, srt = 30, adj = 1)
title(main = titleMy)
title(ylab = 'Predicted', line = 6)
# Grid and box
abline(h = 0:n + 0.5, col = 'gray')
abline(v = 0:n + 0.5, col = 'gray')
box(lwd = 1, col = 'gray')
par(opar)
}
Example of output:
I have 3D matrix of floating point numbers and I would like to produce a smoothed 3D surface of this matrix using R. Any suggestions are welcome. Thanks
Now I am using scatterplot3d ... But this function did not produce a smoothed surface
x<-read.table("/Users/me/Desktop/data.txt")
scatterplot3d(x$V1, x$V2, x$V3, highlight.3d = TRUE, angle = 30, col.axis = "blue", col.grid = "lightblue", cex.axis = 1.3, cex.lab = 1.1, pch = 20)
I think that mba.surf from the MBA package would be a good choice for the smoothing, and as larrydag above suggests, persp would be good to image it. The code below is from the help page for the mba.surf function (swap LIDAR for your 3 column dataframe):
data(LIDAR)
mba.int <- mba.surf(LIDAR, 300, 300, extend=TRUE)$xyz.est
# Two ways of imaging....
image(mba.int, xaxs="r", yaxs="r")
persp(mba.int, theta = 135, phi = 30, col = "green3", scale = FALSE,
ltheta = -120, shade = 0.75, expand = 10, border = NA, box = FALSE)
If you are able to create a 2D matrix (x,y) with the value being the z-axis value you could use the following
persp
Here is an example from R Graph Gallery. persp example
require(misc3d)
a <- 2/5
wsqr <- 1 - a^2
w <- sqrt(wsqr)
denom <- function(a,w,u,v) a*((w*cosh(a*u))^2 + (a*sin(w*v))^2)
fx <- function(u,v) -u + (2*wsqr*cosh(a*u)*sinh(a*u)/denom(a,w,u,v))
fy <- function(u,v) 2*w*cosh(a*u)*(-(w*cos(v)*cos(w*v)) - (sin(v)*sin(w*v)))/denom(a,w,u,v)
fz = function(u,v) 2*w*cosh(a*u)*(-(w*sin(v)*cos(w*v)) + (cos(v)*sin(w*v)))/denom(a,w,u,v)
parametric3d(fx = fx, fy = fy, fz = fz,
umin = -17,
umax = 17,
vmin = -77,
vmax = 77,
n = 100,
color = c("grey17","grey21","red4","darkred","red4","grey21","grey17"),
engine = "rgl")
I am trying to create the plot like following (many times I end up drawing a plot like this by hand, but this time I want to plot it myself).
Here is my data and my trial:
myd <- data.frame (period = c("Triassic", "Jurasic",
"Cretaceous", "Cenzoic"), myears = c(245, 208, 145, 65),
label = c(226, 176,105, 32 ))
myd2 <- data.frame (event = c("Diansaurs_strt", "Birds",
"Diansaurs_ext", "Human"), myears = c(235, 200, 60, 0.5))
myd2$x <- -0.25
with (myd2, plot(x,myears,ylim=c(0,250),
xlim = c(0, 10), axes=F,xlab="",ylab="",type="p",pch=17))
with (myd2,text(x,myears,event,pos=4,xpd=T))
axis(side=2,at = myd$label, labels = myd$period)
I have issues particularly matching of axis with plot and orientation of text and points. Any other idea or improvement help appreciated.
For constructing novel plots "from the ground up", and for maximal control over individual graphical elements, the grid graphical system is hard to beat:
library(grid)
## Set up plotting area with reasonable x-y limits
## and a "native" scale related to the scale of the data.
x <- -1:1
y <- extendrange(c(myd$myears, myd2$myears))
dvp <- dataViewport(x, y, name = "figure")
grid.newpage()
pushViewport(dvp)
## Plot the central timeline
grid.lines(unit(0, "native"), unit(c(0,245), "native"),
gp = gpar(col="dodgerblue"))
## Annotate LHS
grid.segments(x0=0.5, x1=0.47,
y0=unit(c(0, myd$myears), "native"),
y1=unit(c(0, myd$myears), "native"),
gp=gpar(col="dodgerblue"))
grid.text(label=c(0, myd$myears), x=0.44, y=unit(c(0, myd$myears), "native"))
grid.text(label=myd$period, x=0.3, y=unit(myd$label, "native"),
just=0, gp=gpar(col="dodgerblue", fontface="italic"))
## Annotate RHS
## Create a function that plots a pointer to the specified coordinate
pointer <- function(x, y, width=1) {
grid.polygon(x = x + unit(width*(c(0, .1, .1)), "npc"),
y = y + unit(width*(c(0, .03, -.03)), "npc"),
gp = gpar(fill="dodgerblue", col="blue", lwd=2))
}
## Call it once for each milestone
for(y in myd2$myears) {
pointer(unit(.5, "npc"), y=unit(y, "native"), width=0.3)
}
## Or, if you just want blue line segments instead of those gaudy pointers:
## grid.segments(x0=0.5, x1=0.53,
## y0=unit(c(myd2$myears), "native"),
## y1=unit(c(myd2$myears), "native"), gp=gpar(col="dodgerblue"))
grid.text(label=myd2$event, x=0.55, y=unit(myd2$myears, "native"),
just=0)
You can try something like this to get you started:
myd <- data.frame(period = c("", "Triassic", "Jurasic",
"Cretaceous", "Cenzoic", ""),
myears = c(260, 245, 208, 145, 65, -5),
label = c(260, 226, 176,105, 32, -5))
myd2 <- data.frame(event = c("Dinosaurs_strt", "Birds",
"Dinosaurs_ext", "Human"),
myears = c(235, 200, 60, 0.5))
myd2$x <- 1
with(myd2, plot(x, myears, ylim = c(-5, 250), xlim = c(0, 10),
axes = FALSE, xlab = "", ylab = "", type = "n"))
with(myd2, text(x, myears, event, pos = 4, xpd = TRUE))
axis(side = 2, at = myd$label, labels = myd$period, las = 2)
X0 <- rep(myd2$x, 4)
Y0 <- myd2$myears
X1 <- rep(-.25, 4)
Y1 <- Y0
arrows(X0, Y0, X1, Y1)
I've added an extra empty element at the start and end of your data in "myd" to help with the axis. Then, instead of using pch, I've used arrows to match the right hand labels with the axis.
Some tweaking could probably make it look a lot nicer.
Here are some enhancements ( I suggest to add 0 for now just to make scale well):
myd <- data.frame (period = c("Triassic", "Jurasic",
"Cretaceous", "Cenzoic", "now"), myears = c(245, 208, 145, 65, 0),
label = c(226, 176,105, 32, NA ))
myd2 <- data.frame (event = c("Diansaurs_strt", "Birds", "Diansaurs_ext", "Human"),
myears = c(235, 200, 60, 0.5))
myd2$x <- -0.25
with (myd2, plot(x,myears,ylim=c(0,250), xlim = c(0, 10),
axes=F,xlab="",ylab="",type="p",pch=17, col = "green"))
with (myd2, plot(x,myears,ylim=c(0,250),
xlim = c(0, 10), axes=F,xlab="",ylab="",type="p",pch="-", col = "green"))
with (myd2,text(x,myears,event,pos=4,xpd=T), col = "green")
axis(side=2,at = myd$label, labels = myd$period, tick = FALSE,
las = 2, col = "green", )
axis(side=2,at = myd$myears, labels = myd$myears, las = 2, col = "green")
There are few issues remaining you might want to change oriantation of the arrow (I belief that you can someway find <- symbol, but I do not know how to).
For drawing the triangles look at the my.symbols and ms.polygon functions in the TeachingDemos package.
In your right graph above the Dinosaurs are moved up, if you want this in general (moving labels that would otherwise be too close or overlap) then look at the spread.labs function in the TeachingDemos package.
Some other possible functions that could help with the plot are text, mtext, grconvertX, grconvertY, segments, and axis.