graph a function restricted in R - r

I'd want to plot a function: f(x,y)=x^2-2*y, with a constraint: x+y=1
in my graph functions overlap , well not seen the restricted function f(x,y). Would appreciate better if x+y-1=0 were transparent.
Mi code in R:
x <- seq(-5, 5, length= 10)
y <- x
fun1<-function(x,y){x^2-2*y}
m <- outer(x, y, fun1)
m[is.na(m)] <- 1
persp(x, y, m, theta = 30, phi = 30,
expand = 0.5, col = "royalblue", ltheta = 120,
shade = 0.75, ticktype = "detailed")
par(new=TRUE)
fun1<-function(x,y){x+y-1}
m <- outer(x, y, fun2)
m[is.na(m)] <- 1
persp(x, y, m, theta = 30, phi = 30,
expand = 0.5, col = "red", ltheta = 120,
shade = 0.75, ticktype = "detailed")

Some overplotting might help. First plot as suggested in comments above. Then de-select the segments where the constraint is violated by assigning NA, i.e. no plotting and overplot with a heavier color. ( I found that unless I froze the z-limits that they "shifted" at the last step. You may need to suppress the z-axis labels, since they are still overlaying each other.)
png(); x <- seq(-5, 5, length= 10)
y <- x
fun1<-function(x,y){x^2-2*y}
m1 <- outer(x, y, fun1)
m1[is.na(m)] <- 1
persp(x, y, m1, theta = 30, phi = 30,
expand = 0.5, col = "#4169E155", ltheta = 120,
shade = 0.75, ticktype = "detailed",zlim=c(-15,35))
par(new=TRUE)
fun2<-function(x,y){x+y-1}
m2 <- outer(x, y, fun2)
m2[is.na(m)] <- 1
persp(x, y, m2, theta = 30, phi = 30,
expand = 0.5, col = adjustcolor("red", alpha.f=0.5), ltheta = 120,
shade = 0.75, ticktype = "detailed",zlim=c(-15,35))
par(new=TRUE)
fun3<-function(x,y){x^2-2*y}
m3 <- outer(x, y, fun3)
m3[ m3 < m2 ] <- NA # <--- logical indexing; this is the key step
persp(x, y, m3, theta = 30, phi = 30,
expand = 0.5, col = "#4169E1", ltheta = 120, # solid-blue
shade = 0.75, ticktype = "detailed",zlim=c(-15,35));dev.off()

Related

Trying to produce a 3D surface plot in r using the persp() function, get an error message

I am trying to plot a function which is dependent on two variables, x and y in a 3D surface plot in r. I get an error message which says that increasing 'x' and 'y' values expected. I am not sure how to arrange the two variables in increasing order which can help produce the plot.
my code:
x <- c(-1, 2, 15, 0, 1, 4, 7, 4, 5, 2)
y <- c(1.5, 3, 7, 2, 1.5, 15, 12, 8, 20, 21)
f <- sqrt(x^2+y^2)
df <- data.frame(x, y, f)
func <- function(x, y){
df$f + (x-x + y-y)}
z <- outer(x, y, func)
persp(x, y, z,
main="Perspective Plot of a function",
zlab = "Height",
theta = 30, phi = 15,
col = "springgreen", shade = 0.5,
ltheta = 120,
ticktype = "detailed")
If you are trying to plot a function of two variables as a surface plot, then you really don't need your two input vectors. You need a regularly spaced grid of input values in both the x and y direction that will cover the range of both inputs:
f <- function(x, y) sqrt(x^2 + y^2)
x <- y <- -1:20
persp(x, y, z = outer(x, y, f),
main="Perspective Plot of a function",
zlab = "Height",
theta = -45, phi = 15,
col = "springgreen", shade = 0.5,
ltheta = 120,
ticktype = "detailed")

Generalizing a 2D plot to 3D in R

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

scatterplot3d: regression plane with residuals

Using scatterplot3d in R, I'm trying to draw red lines from the observations to the regression plane:
wh <- iris$Species != "setosa"
x <- iris$Sepal.Width[wh]
y <- iris$Sepal.Length[wh]
z <- iris$Petal.Width[wh]
df <- data.frame(x, y, z)
LM <- lm(y ~ x + z, df)
library(scatterplot3d)
G <- scatterplot3d(x, z, y, highlight.3d = FALSE, type = "p")
G$plane3d(LM, draw_polygon = TRUE, draw_lines = FALSE)
To obtain the 3D equivalent of the following picture:
In 2D, I could just use segments:
pred <- predict(model)
segments(x, y, x, pred, col = 2)
But in 3D I got confused with the coordinates.
I decided to include my own implementation as well, in case anyone else wants to use it.
The Regression Plane
require("scatterplot3d")
# Data, linear regression with two explanatory variables
wh <- iris$Species != "setosa"
x <- iris$Sepal.Width[wh]
y <- iris$Sepal.Length[wh]
z <- iris$Petal.Width[wh]
df <- data.frame(x, y, z)
LM <- lm(y ~ x + z, df)
# scatterplot
s3d <- scatterplot3d(x, z, y, pch = 19, type = "p", color = "darkgrey",
main = "Regression Plane", grid = TRUE, box = FALSE,
mar = c(2.5, 2.5, 2, 1.5), angle = 55)
# regression plane
s3d$plane3d(LM, draw_polygon = TRUE, draw_lines = TRUE,
polygon_args = list(col = rgb(.1, .2, .7, .5)))
# overlay positive residuals
wh <- resid(LM) > 0
s3d$points3d(x[wh], z[wh], y[wh], pch = 19)
The Residuals
# scatterplot
s3d <- scatterplot3d(x, z, y, pch = 19, type = "p", color = "darkgrey",
main = "Regression Plane", grid = TRUE, box = FALSE,
mar = c(2.5, 2.5, 2, 1.5), angle = 55)
# compute locations of segments
orig <- s3d$xyz.convert(x, z, y)
plane <- s3d$xyz.convert(x, z, fitted(LM))
i.negpos <- 1 + (resid(LM) > 0) # which residuals are above the plane?
# draw residual distances to regression plane
segments(orig$x, orig$y, plane$x, plane$y, col = "red", lty = c(2, 1)[i.negpos],
lwd = 1.5)
# draw the regression plane
s3d$plane3d(LM, draw_polygon = TRUE, draw_lines = TRUE,
polygon_args = list(col = rgb(0.8, 0.8, 0.8, 0.8)))
# redraw positive residuals and segments above the plane
wh <- resid(LM) > 0
segments(orig$x[wh], orig$y[wh], plane$x[wh], plane$y[wh], col = "red", lty = 1, lwd = 1.5)
s3d$points3d(x[wh], z[wh], y[wh], pch = 19)
The End Result:
While I really appreciate the convenience of the scatterplot3d function, in the end I ended up copying the entire function from github, since several arguments that are in base plot are either forced by or not properly passed to scatterplot3d (e.g. axis rotation with las, character expansion with cex, cex.main, etc.). I am not sure whether such a long and messy chunk of code would be appropriate here, so I included the MWE above.
Anyway, this is what I ended up including in my book:
(Yes, that is actually just the iris data set, don't tell anyone.)
Using the advertising dataset from An Introduction to Statistical Learning, you can do
advertising_fit1 <- lm(sales~TV+radio, data = advertising)
sp <- scatterplot3d::scatterplot3d(advertising$TV,
advertising$radio,
advertising$sales,
angle = 45)
sp$plane3d(advertising_fit1, lty.box = "solid")#,
# polygon_args = list(col = rgb(.1, .2, .7, .5)) # Fill color
orig <- sp$xyz.convert(advertising$TV,
advertising$radio,
advertising$sales)
plane <- sp$xyz.convert(advertising$TV,
advertising$radio, fitted(advertising_fit1))
i.negpos <- 1 + (resid(advertising_fit1) > 0)
segments(orig$x, orig$y, plane$x, plane$y,
col = c("blue", "red")[i.negpos],
lty = 1) # (2:1)[i.negpos]
sp <- FactoClass::addgrids3d(advertising$TV,
advertising$radio,
advertising$sales,
angle = 45,
grid = c("xy", "xz", "yz"))
And another interactive version using rgl package
rgl::plot3d(advertising$TV,
advertising$radio,
advertising$sales, type = "p",
xlab = "TV",
ylab = "radio",
zlab = "Sales", site = 5, lwd = 15)
rgl::planes3d(advertising_fit1$coefficients["TV"],
advertising_fit1$coefficients["radio"], -1,
advertising_fit1$coefficients["(Intercept)"], alpha = 0.3, front = "line")
rgl::segments3d(rep(advertising$TV, each = 2),
rep(advertising$radio, each = 2),
matrix(t(cbind(advertising$sales, predict(advertising_fit1))), nc = 1),
col = c("blue", "red")[i.negpos],
lty = 1) # (2:1)[i.negpos]
rgl::rgl.postscript("./pics/plot-advertising-rgl.pdf","pdf") # does not really work...

How to have only every other border in a persp

I would like to do the following
set.seed(1)
x <- seq(-10, 10, length= 600)
y <- x
f <- function(x, y) { r <- sqrt(x^2+y^2); 10 * sin(r)/r }
z <- outer(x, y, f)
persp(x, y, z, theta = 30, phi = 30, expand = 0.5, col = "lightblue")
But the grid is too thin so the border dominates (You can confirm it is indeed the border and not the lighting by using border = NA. It turns to blue):
One way to address this is of course to use not so fine of a grid (for example if you change length = 600 to length= 50 it looks very pleasant, and is actually the example in ?persp). But I want the same shape and smooth exactly as this fine grid. I just don't want to draw all of the borders, maybe only 1/5th of them for example (or half which I assume I can customize).
An issue with plotting the smooth shape and then plotting a grid over top of it is that you can see through the shape to the grid on the other side. To address this, you can start by plotting the course grid on top of a white object, meaning you can't see the back side of the grid, saving the result to a file.
x <- seq(-10, 10, length=50)
y <- x
z <- outer(x, y, f)
png("top.png")
print(persp(x, y, z, theta = 30, phi = 30, expand = 0.5, border="black", col="white"))
dev.off()
Then, you can plot smoothed image followed by the grid with all white colors fully transparent.
x <- seq(-10, 10, length= 600)
y <- x
f <- function(x, y) { r <- sqrt(x^2+y^2); 10 * sin(r)/r }
z <- outer(x, y, f)
png("bottom.png")
print(persp(x, y, z, theta = 30, phi = 30, expand = 0.5, border="lightblue", col="lightblue"))
dev.off()
par(oma=c(0, 0, 0, 0), mar=c(0, 0, 0, 0))
library(png)
top.img <- readPNG("top.png")
top.img[,,4][top.img[,,1] + top.img[,,2] + top.img[,,3] > 2] <- 0
plot.new()
rasterImage(bottom.img, 0, 0, 1, 1)
rasterImage(top.img, 0, 0, 1, 1)
I have got two solutions, but I think both of them are not exactly what you are searching for. I make a line overlay, but it doesn't get overlapped by the surface.
set.seed(1)
x <- seq(-10, 10, length= 600)
y <- x
f <- function(x, y) { r <- sqrt(x^2+y^2); 10 * sin(r)/r }
z <- outer(x, y, f)
persp(x, y, z, theta = 30, phi = 30, expand = 0.5, col = "lightblue", border=NA, shade=0.75, ticktype = "detailed")
par(new=T)
set.seed(1)
x <- seq(-10, 10, length=20)
y <- x
f <- function(x, y) { r <- sqrt(x^2+y^2); 10 * sin(r)/r }
z <- outer(x, y, f)
persp(x, y, z, theta = 30, phi = 30, expand = 0.5, col = NA, border="green")
set.seed(1)
x <- seq(-10, 10, length= 600)
y <- x
f <- function(x, y) { r <- sqrt(x^2+y^2); 10 * sin(r)/r }
z <- outer(x, y, f)
res <- persp(x, y, z, theta = 30, phi = 30, expand = 0.5, col = "lightblue", border=NA, shade=0.75, ticktype = "detailed")
library(grDevices)
xlines <- seq(1, length(x), length.out=20)
for(line in xlines){
lines (trans3d(x=x[line], y = y, z = z[line, ], pmat = res), col = 3, lwd=2)
}
ylines <- seq(1, length(y), length.out=20)
for(line in ylines){
lines (trans3d(x=x, y = y[line], z = z[,line], pmat = res), col = 3, lwd=2)
}
Here are two approaches, neither of which are not ideal. You can use NAs to force transparent "lines" onto the surface (Approach 1) or use NAs to get rid of all but the "grid lines" (Approach 2)
Approach 1:
z2 <- z
lin.seq<- seq(10, 600, 10)
z2[lin.seq,] <- NA
z2[,lin.seq] <- NA
persp(x, y, z2, theta = 30, phi = 30, expand = 0.5,
border=NA, col="lightblue", box=TRUE)
You can then overlay plot above on a solid black surface:
# using original example data
persp(x, y, z, theta = 30, phi = 30, expand = 0.5, col = "black", border=NA)
par(new=TRUE)
z2 <- z
lin.seq<- seq(10, 600, 10)
z2[lin.seq,] <- NA
z2[,lin.seq] <- NA
persp(x, y, z2, theta = 30, phi = 30, expand = 0.5,
border=NA, col="lightblue", box=FALSE)
Approach 2:
# using original example data
persp(x, y, z, theta = 30, phi = 30, expand = 0.5, col = "lightblue", border=NA)
z3 <- matrix(ncol=600, nrow=600) # NA matrix
lin.seq <- seq(25, 600, 25) # spacing of "grid lines"
lin.seq <- c(lin.seq, lin.seq-1, lin.seq-2) # to make lines a bit thicker
# replace some NAs on "grid lines" with values from z.
z3[lin.seq,] <- z[lin.seq,]
z3[,lin.seq] <- z[,lin.seq]
par(new=TRUE)
persp(x, y, z3, theta = 30, phi = 30, expand = 0.5,
border=NA, col="black", box=FALSE)

How to include a graphical output to function

I have written a function to calculate the BMI and have the code to create a corresponding graphical output. My goal is to include the graphical output into the function so that I get the plot by just using the function.
My current code:
BMI <- function(meter, kg){
BMI <- kg/(meter^2)
return(BMI)
}
BMI(1.8,70)
x <- seq(1.5, 1.9, by = 0.001)
y <- seq(30, 200, by = 0.5)
z <- outer(x, y, FUN = function(x, y) {BMI(x, y)})
contour(x, y, z, nlevels = 10, method = "edge", main = "BMI")
abline(h = 70, v=1.8, col="darkgrey")
points(1.8,70, col="red", cex=2, pch=16, bg="red")
By just modifying the meter & kg in the function I would like to get a diagram with the correct line and point positioning. I started with the code below - however it does not work, yet.
graphicalBMI <- function(meter, kg){
BMI <- kg/(meter^2)
x <- seq(1.5, 1.9, by = 0.001)
y <- seq(30, 200, by = 0.5)
z <- outer(x, y, FUN = function(x, y) {graphicalBMI(x, y)})
contour(x, y, z, nlevels = 10, method = "edge", main = "BMI")
abline(h = kg, v= meter, col="darkgrey")
points(meter, kg, col="red", cex=2, pch=16, bg="red")
return(graphicalBMI)
}
The problem of your second function is that it produces an infinite-recursion.
If you change it like this you'll get what you want:
graphicalBMI <- function(meter, kg, showPlot=TRUE){
BMI <- kg/(meter^2)
if(showPlot){
x <- seq(1.5, 1.9, by = 0.001)
y <- seq(30, 200, by = 0.5)
# here we call graphicalBMI by setting showPlot=F to avoid infinite recursion
z <- outer(x, y, FUN = function(x, y) {graphicalBMI(x, y, FALSE)})
contour(x, y, z, nlevels = 10, method = "edge", main = "BMI")
abline(h = kg, v= meter, col="darkgrey")
points(meter, kg, col="red", cex=2, pch=16, bg="red")
}
return(BMI)
}
# usage example:
graphicalBMI(1.8,70) # plot produced
graphicalBMI(1.8,70,FALSE) # no plot produced

Resources