How to plot two 3D graphs on the same plot in R - r

I'm using the plot3d function in the library rgl. Suppose my data looks something like this.
x <- c(1,2,3,4,5)
y <- c(4,2,1,4,2)
z <- c(2,2,4,5,1)
x2 <- c(1,5,2,3,4)
y2 <- c(2,3,4,1,2)
z2 <- c(3,4,2,3,1)
plot3d(x, y, z)
plot3d(x2, y2, z2)
Using the commands above would give me 2 separate plots. How can I plot both datasets on the same graph? Also I would like to use different symbols for the points in the two different data sets.

I just wrote a function cube and an accompanying one that might be sufficient for your needs:
require(rgl); library('magrittr')
cube <- function(x=0,y=0,z=0, bordered=TRUE,
filled = TRUE, lwd=2, scale=1,
fillcol = gray(.95),
bordercol ='black', ...) {
mytetra <- cube3d()
# Reduce size to unit
mytetra$vb[4,] <- mytetra$vb[4,]/scale*2
for (i in 1:length(x)) {
# Add cube border
if (bordered) {
btetra <- mytetra
btetra$material$lwd <- lwd
btetra$material$front <- 'line'
btetra$material$back <- 'line'
btetra %>% translate3d(x[i], y[i], z[i]) %>% shade3d
}
# Add cube fill
if (filled) {
ftetra <- mytetra
ftetra$vb[4,] <- ftetra$vb[4,]*1.01
ftetra$material$col <- fillcol
ftetra %>% translate3d(x[i], y[i], z[i]) %>% shade3d
}
}
}
tetra <- function(x=0,y=0,z=0, bordered=TRUE,
filled = TRUE, lwd=2, scale=1,
fillcol = gray(.95),
bordercol ='black', ...) {
mytetra <- tetrahedron3d()
# Reduce size to unit
mytetra$vb[4,] <- mytetra$vb[4,]/scale*2
for (i in 1:length(x)) {
# Add cube border
if (bordered) {
btetra <- mytetra
btetra$material$lwd <- lwd
btetra$material$front <- 'line'
btetra$material$back <- 'line'
btetra %>% translate3d(x[i], y[i], z[i]) %>% shade3d
}
# Add cube fill
if (filled) {
ftetra <- mytetra
ftetra$vb[4,] <- ftetra$vb[4,]*1.01
ftetra$material$col <- fillcol
ftetra %>% translate3d(x[i], y[i], z[i]) %>% shade3d
}
}
}
plot3d(x,y,z)
tetra(x,y,z, scale=1/2)
cube(x2,y2,z2, scale=1/2)

Related

How to increase coef label size in plot.glmnet - cex does not work

Using: the package "glmnet"
Problem: I use the plot function to plot a lasso image, I feel the labels are too small. So, I want to change the cex, but, it's not working. I looked up the documents of "glmnet", the plot function seems like normal plot. Any idea?
My code:
plot(f1, xvar='lambda', label=TRUE, cex=1.5)
Other: I tried like cex.lab=3. It worked but for the x-y axis labels.
What you are actually using is the generic plot which dispatches a method depending on the class of the object. In this case,
class(fit1)
# [1] "elnet" "glmnet"
glmnet:::plot.glmnet will be used, which internally uses glmnet:::plotCoef. And here lies the problem; in glmnet:::plotCoef the respective cex parameter in glmnet:::plotCoef is hard coded to 0.5.―We need a hack:
plot.glmnet <- function(x, xvar=c("norm", "lambda", "dev"),
label=FALSE, ...) {
xvar <- match.arg(xvar)
plotCoef2(x$beta, lambda=x$lambda, df=x$df, dev=x$dev.ratio, ## changed
label=label, xvar=xvar, ...)
}
plotCoef2 <- function(beta, norm, lambda, df, dev, label=FALSE,
xvar=c("norm", "lambda", "dev"), xlab=iname, ylab="Coefficients",
lab.cex=0.5, xadj=0, ...) {
which <- glmnet:::nonzeroCoef(beta)
nwhich <- length(which)
switch(nwhich + 1, `0`={
warning("No plot produced since all coefficients zero")
return()
}, `1`=warning("1 or less nonzero coefficients; glmnet plot is not meaningful"))
beta <- as.matrix(beta[which, , drop=FALSE])
xvar <- match.arg(xvar)
switch(xvar, norm={
index=if (missing(norm)) apply(abs(beta), 2, sum) else norm
iname="L1 Norm"
approx.f=1
}, lambda={
index=log(lambda)
iname="Log Lambda"
approx.f=0
}, dev={
index=dev
iname="Fraction Deviance Explained"
approx.f=1
})
dotlist <- list(...)
type <- dotlist$type
if (is.null(type))
matplot(index, t(beta), lty=1, xlab=xlab, ylab=ylab, type="l", ...)
else matplot(index, t(beta), lty=1, xlab=xlab, ylab=ylab, ...)
atdf <- pretty(index)
prettydf <- approx(x=index, y=df, xout=atdf, rule=2, method="constant", f=approx.f)$y
axis(3, at=atdf, labels=prettydf, tcl=NA)
if (label) {
nnz <- length(which)
xpos <- max(index)
pos <- 4
if (xvar == "lambda") {
xpos <- min(index)
pos <- 2
}
xpos <- rep(xpos + xadj, nnz) ## changed
ypos <- beta[, ncol(beta)]
text(xpos, ypos, paste(which), cex=lab.cex, pos=pos) ## changed
}
}
If we load the two hacked functions, we now can adapt the coefficient labels. New are the parameters lab.cex for size and xadj to adjust the x position of the numbers.
plot(fit1, xvar='lambda', label=TRUE, lab.cex=.8, xadj=.085)
Data:
set.seed(122873)
x <- matrix(rnorm(100 * 10), 100, 10)
y <- rnorm(100)
fit1 <- glmnet(x, y)

Plotly (R) Legend Won't Appear?

I'm trying to create a plot showing the CDFs of two different categories of data, with a legend to show which color corresponds to which (Plotly version 4.9.2.1). For some reason, it's a royal pain in the rear to get the legend to show. Below is a toy example with three of my attempts--only the last one works, but it's obnoxiously contrived and makes the resulting data appear misleadingly dense in the plot. Can anyone explain how to do this right?
library(plotly)
library(magrittr)
color.dat <- runif(30)
x.mat <- matrix(0, nrow=500, ncol=30)
for (i in 1:30){
x.mat[,i] <- rnorm(500, 0, color.dat[i])
}
### Attempt 1, no legend appears at all ###
p <- plot_ly(showlegend=TRUE)
for (i in 1:30){
tmp.cdf <- ecdf(x.mat[,i])
p <- p %>%
add_lines(x=sort(x.mat[,i]), y=tmp.cdf(sort(x.mat[,i])),
name=ifelse(color.dat[i] > 0.5, 'A', 'B'),
showlegend=FALSE,
line=list(color=ifelse(color.dat[i] > 0.5, 'blue', 'orange')))
}
p <- p %>%
add_lines(x=c(0,1), y=c(0,0), name='A',
line=list(color='blue'),
showlegend=TRUE, visible=FALSE) %>%
add_lines(x=c(0,1), y=c(0,0), name='B',
line=list(color='orange'),
showlegend=TRUE, visible=FALSE)
### Attempt 2, legend entry appears only for class B (doesn't appear without invisible traces added at end) ###
p <- plot_ly(showlegend=TRUE)
a.bool <- TRUE
b.bool <- TRUE
for (i in 1:30){
tmp.cdf <- ecdf(x.mat[,i])
if (color.dat[i] > 0.5 && a.bool){
class.bool <- TRUE
a.bool <- FALSE
} else {
class.bool <- FALSE
}
if (color.dat[i] < 0.5 && b.bool){
class.bool <- TRUE
b.bool <- FALSE
} else {
class.bool <- FALSE
}
p <- p %>%
add_lines(x=sort(x.mat[,i]), y=tmp.cdf(sort(x.mat[,i])),
name=ifelse(color.dat[i] > 0.5, 'A', 'B'),
showlegend=class.bool,
line=list(color=ifelse(color.dat[i] > 0.5, 'blue', 'orange')))
}
p <- p %>%
add_lines(x=c(0,1), y=c(0,0), name='A',
line=list(color='blue'),
showlegend=TRUE, visible=FALSE) %>%
add_lines(x=c(0,1), y=c(0,0), name='B',
line=list(color='orange'),
showlegend=TRUE, visible=FALSE)
### Attempt 3, both legend entries appear, but plot is misleading and obscures a lot of detail ###
p <- plot_ly(showlegend=TRUE)
flat.mat.a <- c()
flat.mat.b <- c()
flat.cdf.a <- c()
flat.cdf.b <- c()
for (i in 1:30){
tmp.cdf <- ecdf(x.mat[,i])
if (color.dat[i] > 0.5){
flat.mat.a <- c(flat.mat.a, sort(x.mat[,i]))
flat.cdf.a <- c(flat.cdf.a, tmp.cdf(sort(x.mat[,i])))
} else {
flat.mat.b <- c(flat.mat.b, sort(x.mat[,i]))
flat.cdf.b <- c(flat.cdf.b, tmp.cdf(sort(x.mat[,i])))
}
}
p <- p %>%
add_lines(x=flat.mat.a, y=flat.cdf.a,
showlegend=TRUE, name='A',
line=list(color='blue')) %>%
add_lines(x=flat.mat.b, y=flat.cdf.b,
showlegend=TRUE, name='B',
line=list(color='orange'))
My preferred approach to plotting stuff with ploty is to put the data in dataframe.
After the data preparation steps it's just takes two lines of code to get the plot and the legend.
library(plotly)
library(tidyr)
library(dplyr)
set.seed(42)
color.dat <- runif(30)
x.mat <- matrix(0, nrow=500, ncol=30)
for (i in 1:30){
x.mat[,i] <- rnorm(500, 0, color.dat[i])
}
# Put the data in a dataframe
dfx <- data.frame(x.mat) %>%
tidyr::pivot_longer(everything()) %>%
arrange(name, value) %>%
mutate(id = as.integer(gsub("^X", "", name)),
color = color.dat[id],
color = ifelse(color > 0.5, 'blue', 'orange')) %>%
group_by(name) %>%
mutate(cdf = ecdf(value)(value)) %>%
ungroup()
p <- dfx %>%
group_by(name) %>%
plot_ly(showlegend=TRUE) %>%
add_lines(x = ~value, y =~cdf, color = ~color, colors = c(blue = "blue", orange = "orange"))
p

Dendrogram plot remove tree labels at end of the branches

Using the example located here https://www.datacamp.com/community/tutorials/hierarchical-clustering-R and the data located https://archive.ics.uci.edu/ml/datasets/seeds# i am trying to remove the labels at the bottom of the dendrogram when using the color_branches
when plot(hclust_avg, labels=FALSE) it works but not later when using color_branches. is there a way to remove them?
`set.seed(786)
seeds_df <- read.csv("seeds_dataset.txt",sep = '\t',header = FALSE)
feature_name <- c('area','perimeter','compactness','length.of.kernel','width.of.kernal','asymmetry.coefficient','length.of.kernel.groove','type.of.seed')
colnames(seeds_df) <- feature_name
seeds_df<- seeds_df[complete.cases(seeds_df), ]
seeds_label <- seeds_df$type.of.seed
seeds_df$type.of.seed <- NULL
seeds_df_sc <- as.data.frame(scale(seeds_df))
dist_mat <- dist(seeds_df_sc, method = 'euclidean')
hclust_avg <- hclust(dist_mat, method = 'average')
cut_avg <- cutree(hclust_avg, k = 3)
suppressPackageStartupMessages(library(dendextend))
avg_dend_obj <- as.dendrogram(hclust_avg)
avg_col_dend <- color_branches(avg_dend_obj, h = 3)
plot(avg_col_dend)`
Figured this out by colouring the the labels white to the background
avg_dend_obj <- as.dendrogram(hclust_avg)
labels_colors(avg_dend_obj) <- "white"
plot(avg_dend_obj)

Drawing Land data from 'Natural Earth' shape file in spherical coordinates by RGL

I downloaded land polygon shape file from Natural Earth [1] and I am able to plot/draw coastlines on a spherical coordinates system. Questions is how I can fill the land regions! I've been searching on this for couple of days and the resources that I came up are below. Also I added a function how to plot/draw the coastlines. Any idea?
require(rgl)
require(maptools)
shapefile_lines <- function() {
save <- par3d(skipRedraw=TRUE)
on.exit(par3d(save))
r <- 6378100 # earth radius in meters
shp <- readShapeSpatial(fn = "ne_10m_coastline/ne_10m_coastline.shp")
# Combine lines in a single matrix
mat <- do.call(rbind, sapply(1:length(shp#lines), function(i) rbind(shp#lines[[i]]#Lines[[1]]#coords,c(NA,NA))))
# Convert spherical to cartesian
xyz <- rgl.sph2car(mat[,2], mat[,1], radius=r + 200)
bg3d("black")
# Draw sphere
rgl.sphere(ng=100, radius=r, col="gray50",specular = "black", add=T)
plot3d(xyz, col = "white", add = T, type = "l")
}
rgl.sphere <- function (x=0, y=NULL, z=NULL, ng=50, radius = 1, color="white", add=F, set_normals=T, ...) {
if(length(ng)==1) ng <- c(ng, ng)
nlon <- ng[1]; nlat <- ng[2]
lat <- matrix(seq(90, -90, len = nlat)*pi/180, nlon, nlat, byrow = TRUE)
long <- matrix(seq(-180, 180, len = nlon)*pi/180, nlon, nlat)
vertex <- rgl:::rgl.vertex(x, y, z)
nvertex <- rgl:::rgl.nvertex(vertex)
radius <- rbind(vertex, rgl:::rgl.attr(radius, nvertex))[4,]
color <- rbind(vertex, rgl:::rgl.attr(color, nvertex))[4,]
for(i in 1:nvertex) {
add2 <- if(!add) i>1 else T
x <- vertex[1,i] + radius[i]*cos(lat)*cos(long)
y <- vertex[2,i] + radius[i]*cos(lat)*sin(long)
z <- vertex[3,i] + radius[i]*sin(lat)
if(set_normals)
persp3d(x, y, z, add=add2, color=color[i], normal_x=x, normal_y=y, normal_z=z, ...)
else
persp3d(x, y, z, add=add2, color=color[i], ...)
}
}
rgl.sph2car <- function(lat=0, lon=0, radius=1, deg=T, precise=T) {
if(deg) {
if(precise){
lat <- lat/180
lon <- lon/180
x <- radius*cospi(lat)*cospi(lon)
y <- radius*cospi(lat)*sinpi(lon)
z <- radius*sinpi(lat)
}else{
lat <- lat*pi/180
lon <- lon*pi/180
x <- radius*cos(lat)*cos(lon)
y <- radius*cos(lat)*sin(lon)
z <- radius*sin(lat)
}
}
return(matrix(c(x,y,z),nrow=length(x), ncol=3, dimnames=list(NULL, c("x","y","z"))))
}
Other Possible Related Resources:
1- 3d surface plot with xyz coordinates
2- https://gis.stackexchange.com/questions/90635/what-programs-would-allow-for-the-mapping-of-a-geoid-in-3d
3- Mesh generation from points with x, y and z coordinates
4- Z - Values for polygon (shapefile) in R

Can GGPLOT make 2D summaries of data?

I wish to plot mean (or other function) of reaction time as a function of the location of the target in the x y plane.
As test data:
library(ggplot2)
xs <- runif(100,-1,1)
ys <- runif(100,-1,1)
rts <- rnorm(100)
testDF <- data.frame("x"=xs,"y"=ys,"rt"=rts)
I know I can do this:
p <- ggplot(data = testDF,aes(x=x,y=y))+geom_bin2d(bins=10)
What I would like to be able to do, is the same thing but plot a function of the data in each bin rather than counts. Can I do this?
Or do I need to generate the conditional means first in R (e.g. drt <- tapply(testDF$rt,list(cut(testDF$x,10),cut(testDF$y,10)),mean)) and then plot that?
Thank you.
Update With the release of ggplot2 0.9.0, much of this functionality is covered by the new additions of stat_summary2d and stat_summary_bin.
here is a gist for this answer: https://gist.github.com/1341218
here is a slight modification of stat_bin2d so as to accept arbitrary function:
StatAggr2d <- proto(Stat, {
objname <- "aggr2d"
default_aes <- function(.) aes(fill = ..value..)
required_aes <- c("x", "y", "z")
default_geom <- function(.) GeomRect
calculate <- function(., data, scales, binwidth = NULL, bins = 30, breaks = NULL, origin = NULL, drop = TRUE, fun = mean, ...) {
range <- list(
x = scales$x$output_set(),
y = scales$y$output_set()
)
# Determine binwidth, if omitted
if (is.null(binwidth)) {
binwidth <- c(NA, NA)
if (is.integer(data$x)) {
binwidth[1] <- 1
} else {
binwidth[1] <- diff(range$x) / bins
}
if (is.integer(data$y)) {
binwidth[2] <- 1
} else {
binwidth[2] <- diff(range$y) / bins
}
}
stopifnot(is.numeric(binwidth))
stopifnot(length(binwidth) == 2)
# Determine breaks, if omitted
if (is.null(breaks)) {
if (is.null(origin)) {
breaks <- list(
fullseq(range$x, binwidth[1]),
fullseq(range$y, binwidth[2])
)
} else {
breaks <- list(
seq(origin[1], max(range$x) + binwidth[1], binwidth[1]),
seq(origin[2], max(range$y) + binwidth[2], binwidth[2])
)
}
}
stopifnot(is.list(breaks))
stopifnot(length(breaks) == 2)
stopifnot(all(sapply(breaks, is.numeric)))
names(breaks) <- c("x", "y")
xbin <- cut(data$x, sort(breaks$x), include.lowest=TRUE)
ybin <- cut(data$y, sort(breaks$y), include.lowest=TRUE)
if (is.null(data$weight)) data$weight <- 1
ans <- ddply(data.frame(data, xbin, ybin), .(xbin, ybin), function(d) data.frame(value = fun(d$z)))
within(ans,{
xint <- as.numeric(xbin)
xmin <- breaks$x[xint]
xmax <- breaks$x[xint + 1]
yint <- as.numeric(ybin)
ymin <- breaks$y[yint]
ymax <- breaks$y[yint + 1]
})
}
})
stat_aggr2d <- StatAggr2d$build_accessor()
and usage:
ggplot(data = testDF,aes(x=x,y=y, z=rts))+stat_aggr2d(bins=3)
ggplot(data = testDF,aes(x=x,y=y, z=rts))+
stat_aggr2d(bins=3, fun = function(x) sum(x^2))
As well, here is a slight modification of stat_binhex:
StatAggrhex <- proto(Stat, {
objname <- "aggrhex"
default_aes <- function(.) aes(fill = ..value..)
required_aes <- c("x", "y", "z")
default_geom <- function(.) GeomHex
calculate <- function(., data, scales, binwidth = NULL, bins = 30, na.rm = FALSE, fun = mean, ...) {
try_require("hexbin")
data <- remove_missing(data, na.rm, c("x", "y"), name="stat_hexbin")
if (is.null(binwidth)) {
binwidth <- c(
diff(scales$x$input_set()) / bins,
diff(scales$y$input_set() ) / bins
)
}
try_require("hexbin")
x <- data$x
y <- data$y
# Convert binwidths into bounds + nbins
xbnds <- c(
round_any(min(x), binwidth[1], floor) - 1e-6,
round_any(max(x), binwidth[1], ceiling) + 1e-6
)
xbins <- diff(xbnds) / binwidth[1]
ybnds <- c(
round_any(min(y), binwidth[1], floor) - 1e-6,
round_any(max(y), binwidth[2], ceiling) + 1e-6
)
ybins <- diff(ybnds) / binwidth[2]
# Call hexbin
hb <- hexbin(
x, xbnds = xbnds, xbins = xbins,
y, ybnds = ybnds, shape = ybins / xbins,
IDs = TRUE
)
value <- tapply(data$z, hb#cID, fun)
# Convert to data frame
data.frame(hcell2xy(hb), value)
}
})
stat_aggrhex <- StatAggrhex$build_accessor()
and usage:
ggplot(data = testDF,aes(x=x,y=y, z=rts))+stat_aggrhex(bins=3)
ggplot(data = testDF,aes(x=x,y=y, z=rts))+
stat_aggrhex(bins=3, fun = function(x) sum(x^2))
This turned out to be harder than I expected.
You can almost trick ggplot into doing this, by providing a weights aesthetic, but that only gives you the sum of the weights in the bin, not the mean (and you have to specify drop=FALSE to retain negative bin values). You can also retrieve either counts or density within a bin, but neither of those really solves the problem.
Here's what I ended up with:
## breaks vector (slightly coarser than the 10x10 spec above;
## even 64 bins is a lot for binning only 100 points)
bvec <- seq(-1,1,by=0.25)
## helper function
tmpf <- function(x,y,z,FUN=mean,breaks) {
midfun <- function(x) (head(x,-1)+tail(x,-1))/2
mids <- list(x=midfun(breaks$x),y=midfun(breaks$y))
tt <- tapply(z,list(cut(x,breaks$x),cut(y,breaks$y)),FUN)
mt <- melt(tt)
## factor order gets scrambled (argh), reset it
mt$X1 <- factor(mt$X1,levels=rownames(tt))
mt$X2 <- factor(mt$X2,levels=colnames(tt))
transform(X,
x=mids$x[mt$X1],
y=mids$y[mt$X2])
}
ggplot(data=with(testDF,tmpf(x,y,rt,breaks=list(x=bvec,y=bvec))),
aes(x=x,y=y,fill=value))+
geom_tile()+
scale_x_continuous(expand=c(0,0))+ ## expand to fill plot region
scale_y_continuous(expand=c(0,0))
This assumes equal bin widths, etc., could be extended ... it really is too bad that (as far as I can tell) stat_bin2d doesn't accept a user-specified function.

Resources