I've been working on a rather complicated chart in R. I have a wireframe with a surface and points distributed in X,Y,Z space all over (e.g. under the surface and over it).
The problem is that the points that plot don't "look" like they are underneath the surface.
I am trying to figure out how best to visualize this chart to make the points look under the surface. Some sample code for the wireframe & cloud come from here: R-List Posting
The code in an example:
library(lattice)
surf <-
expand.grid(x = seq(-pi, pi, length = 50),
y = seq(-pi, pi, length = 50))
surf$z <-
with(surf, {
d <- 3 * sqrt(x^2 + y^2)
exp(-0.02 * d^2) * sin(d)
})
g <- surf
pts <- data.frame(x =rbind(2,2,2), y=rbind(-2,-2,-2), z=rbind(.5,0,-.5))
wireframe(z ~ x * y, g, aspect = c(1, .5),
drape=TRUE,
scales = list(arrows = FALSE),
pts = pts,
panel.3d.wireframe =
function(x, y, z,
xlim, ylim, zlim,
xlim.scaled, ylim.scaled, zlim.scaled,
pts,
...) {
panel.3dwire(x = x, y = y, z = z,
xlim = xlim,
ylim = ylim,
zlim = zlim,
xlim.scaled = xlim.scaled,
ylim.scaled = ylim.scaled,
zlim.scaled = zlim.scaled,
...)
xx <-
xlim.scaled[1] + diff(xlim.scaled) *
(pts$x - xlim[1]) / diff(xlim)
yy <-
ylim.scaled[1] + diff(ylim.scaled) *
(pts$y - ylim[1]) / diff(ylim)
zz <-
zlim.scaled[1] + diff(zlim.scaled) *
(pts$z - zlim[1]) / diff(zlim)
panel.3dscatter(x = xx,
y = yy,
z = zz,
xlim = xlim,
ylim = ylim,
zlim = zlim,
xlim.scaled = xlim.scaled,
ylim.scaled = ylim.scaled,
zlim.scaled = zlim.scaled,
...)
})
Looking at my example, the points in pts are in actually in vertical line where X,Y =(2,-2) and the z goes from .5 to -.5.
However, to my eye the third point doesn't look like it is under the surface, to it looks like it is at coordinates(2,-3,0).
Is this just my eye mis-interpreting this ?
Does anyone have a suggestion on how to make my points look more "3D" ? Perhaps muting the color of the point to make it look "under the surface" by using some sort of transparency on the surface ?
I tried making the colors of the points different (red for over the surface, blue for under the surface) but that does not really help the graph much.
This might get you started:
library(emdbook)
sfun <- function(x,y) {
d <- 3 * sqrt(x^2 + y^2)
exp(-0.02 * d^2) * sin(d)
}
cc <- curve3d(sfun(x,y),xlim=c(-pi,pi),ylim=c(-pi,pi),n=c(50,50),
sys3d="rgl")
colvec <- colorRampPalette(c("pink","white","lightblue"))(100)
with(cc,persp3d(x,y,z,col=colvec[cut(z,100)],alpha=0.5))
pts <- data.frame(x=c(2,2,2), y=c(-2,-2,-2), z=c(.5,0,-.5))
with(pts,spheres3d(x,y,z,col="blue",radius=0.1))
rgl.snapshot("rgltmp1.png")
Related
I wrote following R script:
#energy diagram
x <- c(0.1, 0.3, 0.5, 0.7, 0.9 ) #chosen randomly, reaction axis
y <- c(-5.057920, -5.057859, -5.057887,-5.057674, -5.057919 ) #energy of the educt, intermediate, transtition states and product
plot(x,y, type="p",
xlim=c(0,1),
ylim=c(-5.058,-5.0575),
xlab="reaction axis",
ylab=expression(paste(E[el] ," / ",10^6," ",kJ/mol)),
xaxt="n" #hide x-axis
)
#h- and v-lines, so i can draw curves by hand
abline(v=seq(0,1,0.1),h=seq(-5.0600,-5.0500,0.00005),col="black",lty=1,lwd=1)
abline(h=c(-5.057920, -5.057859, -5.057887,-5.057674), col="blue", lty=1,lwd=0.7)
Is it possible to draw a curve through the points that would look like a energy diagram. An example of an energy diagram is here:
A lot could be done to streamline / vectorize this code, but for a smallish diagram this works pretty well:
# get that data
x <- c(0.1, 0.3, 0.5, 0.7, 0.9 ) # reaction axis
y <- c(-5.057920, -5.057859, -5.057887,-5.057674, -5.057919 ) # energies
I'm going to make a little Bezier curve to connect each point to the next---this way we can make sure the smooth line passes through the data, not just close to it. I'll give each point a single 'control point' to define the slope. By using the same y-values for a point and it's control point, the slope at the point will be 0. I'll call the offset between the point and the control point delta. We'll start with one point-pair:
library(Hmisc)
delta = 0.15
bezx = c(0.1, 0.1 + delta, 0.3 - delta, 0.3)
bezy = rep(y[1:2], each = 2)
plot(bezx, bezy, type = 'b', col = "gray80")
lines(bezier(bezx, bezy), lwd = 2, col = "firebrick4")
Here I plotted the points and control points in gray, and the smooth line in red so we can see what's going on.
It looks promising, let's turn it into a function that we can apply to each pair of points:
bezf = function(x1, x2, y1, y2, delta = 0.15) {
bezier(x = c(x1, x1 + delta, x2 - delta, x2), y = c(y1, y1, y2, y2))
}
You can play with the delta parameter, I think 0.1 looks pretty good.
plot(x, y, xlab = "Reaction coordinate", ylab = "E", axes = F)
box(bty = "L")
axis(side = 2)
for(i in 1:(length(x) - 1)) {
lines(bezf(x1 = x[i], x2 = x[i + 1], y1 = y[i], y2 = y[i + 1], delta = 0.1))
}
You can of course tweak the plot, add labels, and ablines as in your original. (Use my for loop with the lines command to draw only the smoothed lines.) I left the points on to show that we are passing through them, not just getting close.
I prefer plotting in ggplot2, if you do too you'll need to extract the data into a data.frame:
bezlist = list()
for (i in 1:(length(x) - 1)) {
bezlist[[i]] = bezf(x1 = x[i], x2 = x[i + 1], y1 = y[i], y2 = y[i + 1], delta = 0.1)
}
xx = unlist(lapply(bezlist, FUN = '[', 'y'))
yy = unlist(lapply(bezlist, FUN = '[', 'y'))
bezdat = data.frame(react = xx, E = yy)
library(ggplot2)
ggplot(bezdat, aes(x = react, y = E)) +
geom_line() +
labs(x = "Reaction coordinate")
You could use a spline fit. Define some points along the energy diagram, and then fit to them using a spline function. The more points that you provide, the better that your fit will be. You can check out the smooth.splines function in the stats package for one implementation of the spline fit.
I am trying to visualize a curve for pollination distribution. I am very new to R so please don't be upset by my stupidity.
llim <- 0
ulim <- 6.29
f <- function(x,y) {(.156812/((2*pi)*(.000005^2)*(gamma(2/.156812)))*exp(-((sqrt(x^2+y^2))/.000005)^.156812))}
integrate(function(y) {
sapply(y, function(y) {
integrate(function(x) f(x,y), llim, ulim)$value
})
}, llim, ulim)
fv <- Vectorize(f)
curve(fv, from=0, to=1000)
And I get:
Error in y^2 : 'y' is missing
I'm not quite sure what you're asking to plot. But I know you want to visualise your scalar function of two arguments.
Here are some approaches. First we define your function.
llim <- 0
ulim <- 6.29
f <- function(x,y) {
(.156812/((2*pi)*(.000005^2)*(gamma(2/.156812)))*exp(-((sqrt(x^2+y^2))/.000005)^.156812))
}
From your title I thought of the following. The function defined below intf integrates your function over the square [0,ul] x [0,ul] and return the value. We then vectorise and plot the integral over the square as a function the length of the side of the square.
intf <- function(ul) {
integrate(function(y) {
sapply(y, function(y) {
integrate(function(x) f(x,y), 0, ul)$value
})
}, 0, ul)$value
}
fv <- Vectorize(intf)
curve(fv, from=0, to=1000)
If f is a distribution, I guess you can make your (somewhat) nice probability interpretation of this curve. (I.e. ~20 % probability of pollination(?) in the 200 by 200 meter square.)
However, you can also do a contour plot (of the log-transformed values) which illustrate the function we are integrating above:
logf <- function(x, y) log(f(x, y))
x <- y <- seq(llim, ulim, length.out = 100)
contour(x, y, outer(x, y, logf), lwd = 2, drawlabels = FALSE)
You can also plot some profiles of the surface:
plot(1, xlim = c(llim, ulim), ylim = c(0, 0.005), xlab = "x", ylab = "f")
y <- seq(llim, ulim, length.out = 6)
for (i in seq_along(y)) {
tmp <- function(x) f(x, y = y[i])
curve(tmp, llim, ulim, add = TRUE, col = i)
}
legend("topright", lty = 1, col = seq_along(y),
legend = as.expression(paste("y = ",y)))
They need to be modified a bit to make them publication worthy, but you get the idea. Lastly, you can do some 3d plots as others have suggested.
EDIT
As per your comments, you can also do something like this:
# Define the function times radius (this time with general a and b)
# The default of a and b is as before
g <- function(z, a = 5e-6, b = .156812) {
z * (b/(2*pi*a^2*gamma(2/b)))*exp(-(z/a)^b)
}
# A function that integrates g from 0 to Z and rotates
# As g is not dependent on the angle we just multiply by 2pi
intg <- function(Z, ...) {
2*pi*integrate(g, 0, Z, ...)$value
}
# Vectorize the Z argument of intg
gv <- Vectorize(intg, "Z")
# Plot
Z <- seq(0, 1000, length.out = 100)
plot(Z, gv(Z), type = "l", lwd = 2)
lines(Z, gv(Z, a = 5e-5), col = "blue", lwd = 2)
lines(Z, gv(Z, b = .150), col = "red", lwd = 2)
lines(Z, gv(Z, a = 1e-4, b = .2), col = "orange", lwd = 2)
You can then plot the curves for the a and b you want. If either is not specified, the default is used.
Disclaimer: my calculus is rusty and I just did off this top of my head. You should verify that I've done the rotation of the function around the axis properly.
The lattice package has several functions that can help you draw 3 dimensional plots, including wireframe() and persp(). If you prefer not to use a 3d-plot, you can create a contour plot using contour().
Note: I don't know if this is intentional, but your data produces a very large spike in one corner of the plot. This produces a plot that is for all intents flat, with a barely noticable spike in one corner. This is particularly problematic with the contour plot below.
library(lattice)
x <- seq(0, 1000, length.out = 50)
y <- seq(0, 1000, length.out = 50)
First the wire frame plot:
df <- expand.grid(x=x, y=y)
df$z <- with(df, f(x, y))
wireframe(z ~ x * y, data = df)
Next the perspective plot:
dm <- outer(x, y, FUN=f)
persp(x, y, dm)
The contour plot:
contour(x, y, dm)
I'm trying to reproduce this plot, i.e., a surface with dots and lines on top.
For reference, this figure is from page 160 (figure 4.8) Wood - GAM an introduction with R.
To make this example reproducible I used the data
library(data.table)
library(lattice)
library(latticeExtra)
surf <- expand.grid(x = seq(-pi, pi, length = 50),y = seq(-pi, pi, length = 50))
surf$z <-
with(surf, {
d <- 3 * sqrt(x^2 + y^2)
exp(-0.02 * d^2) * sin(d)
})
surf <- as.data.table(surf)
pts <- surf[x==2.37222302413923]
Adjusting the wireframe function with the panel-functions I used
wireframe(z ~ x * y, surf, aspect = c(1, .5),
screen = list(z=-160, x=-70),
scales = list(arrows = FALSE),
shade=TRUE,
shade.colors = function(irr, ref, height, w = 0.4){grey(w * irr + (1 - w)*(1 - (1 - ref)^0.4))},
light.source= c(0,10,10),
pts = pts,
panel.3d.wireframe =
function(x, y, z,
xlim, ylim, zlim,
xlim.scaled, ylim.scaled, zlim.scaled,
pts,
...) {
panel.3dwire(x = x, y = y, z = z,
xlim = xlim,
ylim = ylim,
zlim = zlim,
xlim.scaled = xlim.scaled,
ylim.scaled = ylim.scaled,
zlim.scaled = zlim.scaled,
...)
xx <-
xlim.scaled[1] + diff(xlim.scaled) *
(pts$x - xlim[1]) / diff(xlim)
yy <-
ylim.scaled[1] + diff(ylim.scaled) *
(pts$y - ylim[1]) / diff(ylim)
zz <-
zlim.scaled[1] + diff(zlim.scaled) *
(pts$z - zlim[1]) / diff(zlim)
panel.3dscatter(x = xx,
y = yy,
z = zz,
xlim = xlim,
ylim = ylim,
zlim = zlim,
xlim.scaled = xlim.scaled,
ylim.scaled = ylim.scaled,
zlim.scaled = zlim.scaled,
col="black",
type="l",
pch=19,
cex=1,
...)
})
To get
I agree that this is not as pretty but It is at least a combination of a line and a surface. Anyway I don't get how I can add multiple lines as a change of the x within the panel-argument will yield a wired result by getting lines which are not parallel to each other (R connects all the points here). Further how do I add points on top?
I'm trying to plot the two circumferences with dimensions xy together with the 3d plot and colour the intersection of the two circles, how can I do that?
# objective function
x <- seq(-1,1,.1)
y <- seq(-1,1,.1)
z <- x^2 + y^2
library(scatterplot3d)
library(plotrix)
scatterplot3d(x,y,z,pch=19,color="royalblue4")
draw.circle (1,1,1)
draw.circle (1,-1,1)
I'm not really into mathematic stuff, but I'll post as answer because it might be of use and, also, is too big for a comment. Excuse any ignorance of mine, though, if I post nonsense.
#your data
library(scatterplot3d)
x <- seq(-1,1,.1)
y <- seq(-1,1,.1)
z <- x^2 + y^2
ang = 60 #angle of the 3D plot. experiment with different values
#your 3D plot, with extended xx', yy' limits
sp3d <- scatterplot3d(x, y, z, pch=19, color="royalblue4",
xlim = c(-1, 3), ylim = c(-3, 3), angle = ang)
#to use parametric equations of circles
f <- seq(-2*pi, 2*pi, 0.1)
#circle1
sp3d$points(x = 1 + 1*cos(f), y = 1 + 1*sin(f), z = rep(0, length(f)), type = "l")
#circle2
sp3d$points(x = 1 + 1*cos(f), y = -1 + 1*sin(f), z = rep(0, length(f)), type = "l")
The plot is:
I have a plot with two logarithmic axes. I'd like to add a circle to a certain position of the plot. I tried to use plotrix, but this does not give options for "log-radius".
# data to plot
x = 10^(-1 * c(5:0))
y = x ^-1.5
#install.packages("plotrix", dependencies=T)
# use require() within functions
library("plotrix")
plot (x, y, log="xy", type="o")
draw.circle(x=1e-2, y=1e2, radius=1e1, col=2)
How can I add a circle to my log-log plot?
As krlmlr suggests, the easiest solution is to slightly modify plotrix::draw.circle(). The log-log coordinate system distorts coordinates of a circle given in the linear scale; to counteract that, you just need to exponentiate the calculated coordinates, as I've done in the lines marked with ## <- in the code below:
library("plotrix")
draw.circle.loglog <-
function (x, y, radius, nv = 100, border = NULL, col = NA, lty = 1,
lwd = 1)
{
xylim <- par("usr")
plotdim <- par("pin")
ymult <- (xylim[4] - xylim[3])/(xylim[2] - xylim[1]) * plotdim[1]/plotdim[2]
angle.inc <- 2 * pi/nv
angles <- seq(0, 2 * pi - angle.inc, by = angle.inc)
if (length(col) < length(radius))
col <- rep(col, length.out = length(radius))
for (circle in 1:length(radius)) {
xv <- exp(cos(angles) * log(radius[circle])) * x[circle] ## <-
yv <- exp(sin(angles) * ymult * log(radius[circle])) * y[circle] ## <-
polygon(xv, yv, border = border, col = col[circle], lty = lty,
lwd = lwd)
}
invisible(list(x = xv, y = yv))
}
# Try it out
x = 10^(-1 * c(5:0))
y = x ^-1.5
plot (x, y, log="xy", type="o")
draw.circle.loglog(x = c(1e-2, 1e-3, 1e-4), y = c(1e2, 1e6, 1e2),
radius = c(2,4,8), col = 1:3)
A work around would be to apply log10 explicitly.
plot (log10(x), log10(y), type="o")
draw.circle(x=log10(1e-2), y=log10(1e2), radius=log10(1e1), col=2)
Edit (using symbols):
plot (x, y, log="xy", type="o",xlim=c(1e-5,1), ylim=c(1,1e8))
par(new=T)
symbols(x=1e-2, y=1e2, circles=1e1, xlim=c(1e-5,1), ylim=c(1,1e8),
xaxt='n', yaxt='n', ann=F, log="xy")
The function draw.circle from the plotrix package looks like that on my system:
> draw.circle
function (x, y, radius, nv = 100, border = NULL, col = NA, lty = 1,
lwd = 1)
{
xylim <- par("usr")
plotdim <- par("pin")
ymult <- (xylim[4] - xylim[3])/(xylim[2] - xylim[1]) * plotdim[1]/plotdim[2]
angle.inc <- 2 * pi/nv
angles <- seq(0, 2 * pi - angle.inc, by = angle.inc)
if (length(col) < length(radius))
col <- rep(col, length.out = length(radius))
for (circle in 1:length(radius)) {
xv <- cos(angles) * radius[circle] + x
yv <- sin(angles) * radius[circle] * ymult + y
polygon(xv, yv, border = border, col = col[circle], lty = lty,
lwd = lwd)
}
invisible(list(x = xv, y = yv))
}
<environment: namespace:plotrix>
What happens here is essentially that the circle is approximated by a polygon of 100 vertices (parameter nv). You can do either of the following:
Create your own version of draw.circle that does the necessary coordinate transformation to "undo" the log transform of the axes.
The function invisibly returns the list of coordinates that are used for plotting.
(If you pass a vector as radius, then only the coordinates of the last circle are returned.) You might be able to apply a transform to those coordinates and call polygon on the result. Pass appropriate values for border, col, lty and/or lwd to hide the polygon drawn by the functions itself.
The first version sounds easier to me. Simply replace the + x by a * x, same for y, inside the for loop, and you're done. Equivalently, for the second version, you subtract x and then multiply by x each coordinate, same for y. EDIT: These transformations are slightly wrong, see Josh's answer for the correct ones.