How to plot 3D scatter diagram using ggplot? - r

I tried to use the plotly package, but it is not working in my case at all. The ggplot package is working for 2D plots but it is giving an error when adding one more axis. How to solve this issue?
ggplot(data,aes(x=D1,y=D2,z=D3,color=Sample)) +
geom_point()
How to add one more axis and get the 3D plot in this?

Since you tagged your question with plotly and said that you've tried to use it with plotly, I think it would be helpful to give you a working code solution in plotly:
Creating some data to plot with:
set.seed(417)
library(plotly)
temp <- rnorm(100, mean=30, sd=5)
pressure <- rnorm(100)
dtime <- 1:100
Graphing your 3d scatterplot using plotly's scatter3d type:
plot_ly(x=temp, y=pressure, z=dtime, type="scatter3d", mode="markers", color=temp)
Renders the following:
ggplot as others have note, by itself does not support 3d graphics rendering.

A possible solutions is gg3D.
gg3D is a package created to extend ggplot2 to produce 3D plots. It does exactly what you are asking for: it adds a third axis to a ggplot. I find it quite good and easy to use and that is what I use for my limited needs.
An example taken from the vignette to produce a basic plot
devtools::install_github("AckerDWM/gg3D")
library("gg3D")
## An empty plot with 3 axes
qplot(x=0, y=0, z=0, geom="blank") +
theme_void() +
axes_3D()
## Axes can be populated with points using the function stat_3D.
data(iris)
ggplot(iris, aes(x=Petal.Width, y=Sepal.Width, z=Petal.Length, color=Species)) +
theme_void() +
axes_3D() +
stat_3D()
There are other options not involving ggplot. For example the excellent plot3D package with its extension plot3Drgl to plot in openGL.

In your question, you refer to the plotly package and to the ggplot2 package. Both plotly and ggplot2 are great packages: plotly is good at creating dynamic plots that users can interact with, while ggplot2 is good at creating static plots for extreme customization and scientific publication. It is also possible to send ggplot2 output to plotly. Unfortunately, at the time of writing (April 2021), ggplot2 does not natively support 3d plots. However, there are other packages that can be used to produce 3d plots and some ways to get pretty close to ggplot2 quality. Below I review several options. These suggestions are by no means exhaustive.
plotly
See onlyphantom's answer in this thread.
gg3D
See Marco Stamazza's answer in this thread. See also my effort below.
scatterplot3d
See Seth's answer in a related thread.
lattice
See Backlin's answer in a related thread.
rgl
See this overview in the wiki guide.
rayshader
See this overview of this package's wonderful capabilities.
trans3d
See data-imaginist use trans3d to get a cube into ggplot2.
ggrgl
See this cool and useful coolbutuseless introduction.
Now let me review some of my efforts with the Lorenz attractor trajectories. While customization remains limited, I've had best results for PDF output with gg3D. I also include a ggrgl example.
gg3D
# Packages
library(deSolve)
library(ggplot2)
library(gg3D) # remotes::install_github("AckerDWM/gg3D")
# Directory
setwd("~/R/workspace/")
# Parameters
parms <- c(a=10, b=8/3, c=28)
# Initial state
state <- c(x=0.01, y=0.0, z=0.0)
# Time span
times <- seq(0, 50, by=1/200)
# Lorenz system
lorenz <- function(times, state, parms) {
with(as.list(c(state, parms)), {
dxdt <- a*(y-x)
dydt <- x*(c-z)-y
dzdt <- x*y-b*z
return(list(c(dxdt, dydt, dzdt)))
})
}
# Make dataframe
df <- as.data.frame(ode(func=lorenz, y=state, parms=parms, times=times))
# Make plot
make_plot <- function(theta=0, phi=0){
ggplot(df, aes(x=x, y=y, z=z, colour=time)) +
axes_3D(theta=theta, phi=phi) +
stat_3D(theta=theta, phi=phi, geom="path") +
labs_3D(theta=theta, phi=phi,
labs=c("x", "y", "z"),
angle=c(0,0,0),
hjust=c(0,2,2),
vjust=c(2,2,-2)) +
ggtitle("Lorenz butterfly") +
theme_void() +
theme(legend.position = "none")
}
make_plot()
make_plot(theta=180,phi=0)
# Save plot as PDF
ggsave(last_plot(), filename="lorenz-gg3d.pdf")
Pros: Outputs high-quality PDF:
Cons: Still limited customization. But for my specific needs, currently the best option.
ggrgl
# Packages
library(deSolve)
library(ggplot2)
library(rgl)
#remotes::install_github("dmurdoch/rgl")
library(ggrgl)
# remotes::install_github('coolbutuseless/ggrgl', ref='main')
library(devout)
library(devoutrgl)
# remotes::install_github('coolbutuseless/devoutrgl', ref='main')
library(webshot2)
# remotes::install_github("rstudio/webshot2")
library(ggthemes)
# Directory
setwd("~/R/workspace/")
# Parameters
parms <- c(a=10, b=8/3, c=26.48)
# Initial state
state <- c(x=0.01, y=0.0, z=0.0)
# Time span
times <- seq(0, 100, by=1/500)
# Lorenz system
lorenz <- function(times, state, parms) {
with(as.list(c(state, parms)), {
dxdt <- a*(y-x)
dydt <- x*(c-z)-y
dzdt <- x*y-b*z
return(list(c(dxdt, dydt, dzdt)))
})
}
# Make dataframe
df <- as.data.frame(ode(func=lorenz, y=state, parms=parms, times=times))
# Make plot
ggplot(df, aes(x=x, y=y, z=z)) +
geom_path_3d() +
ggtitle("Lorenz butterfly") -> p
# Render Plot in window
rgldev(fov=30, view_angle=-10, zoom=0.7)
p + theme_ggrgl(16)
# Save plot as PNG
rgldev(fov=30, view_angle=-10, zoom=0.7,
file = "~/R/Work/plots/lorenz-attractor/ggrgl/lorenz-ggrgl.png",
close_window = TRUE, dpi = 300)
p + theme_ggrgl(16)
dev.off()
Pros: The plot can be rotated in a way similar to plotly. It is possible to 'theme' a basic plot:
Cons: The figure is missing a third axis with labels. Cannot output high-quality plots. While I've been able to view and save a low-quality black trajectory in PNG, I could view a colored trajectory like the above, but could not save it, except with a low-quality screenshot:
Related threads: plot-3d-data-in-r, ploting-3d-graphics-with-r.

Related

Extracting the exact coordinates of a mouse click in an interactive plot

In short: I'm looking for a way to get the exact coordinates of a series of mouse positions (on-clicks) in an interactive x/y scatter plot rendered by ggplot2 and ggplotly.
I'm aware that plotly (and several other interactive plotting packages for R) can be combined with Shiny, where a box- or lazzo select can return a list of all data points within the selected subspace. This list will be HUGE in most of the datasets I'm analysing, however, and I need to be able to do the analysis reproducibly in an R markdown format (writing a few, mostly less than 5-6, point coordinates is much more readable). Furthermore, I have to know the exact positions of the clicks to be able to extract points within the same polygon of points in a different dataset, so a list of points within the selection in one dataset is not useful.
The grid.locator() function from the grid package does almost what I'm looking for (the one wrapped in fx gglocator), however I hope there is a way to do the same within an interactive plot rendered by plotly (or maybe something else that I don't know of?) as the data sets are often HUGE (see the plot below) and thus being able to zoom in and out interactively is very much appreciated during several iterations of analysis.
Normally I have to rescale the axes several times to simulate zooming in and out which is exhausting when doing it MANY times. As you can see in the plot above, there is a LOT of information in the plots to explore (the plot is about 300MB in memory).
Below is a small reprex of how I'm currently doing it using grid.locator on a static plot:
library(ggplot2)
library(grid)
p <- ggplot(mtcars, aes(wt, mpg)) +
geom_point()
locator <- function(p) {
# Build ggplot object
ggobj <- ggplot_build(p)
# Extract coordinates
xr <- ggobj$layout$panel_ranges[[1]]$x.range
yr <- ggobj$layout$panel_ranges[[1]]$y.range
# Variable for selected points
selection <- data.frame(x = as.numeric(), y = as.numeric())
colnames(selection) <- c(ggobj$plot$mapping$x, ggobj$plot$mapping$y)
# Detect and move to plot area viewport
suppressWarnings(print(ggobj$plot))
panels <- unlist(current.vpTree()) %>%
grep("panel", ., fixed = TRUE, value = TRUE)
p_n <- length(panels)
seekViewport(panels, recording=TRUE)
pushViewport(viewport(width=1, height=1))
# Select point, plot, store and repeat
for (i in 1:10){
tmp <- grid.locator('native')
if (is.null(tmp)) break
grid.points(tmp$x,tmp$y, pch = 16, gp=gpar(cex=0.5, col="darkred"))
selection[i, ] <- as.numeric(tmp)
}
grid.polygon(x= unit(selection[,1], "native"), y= unit(selection[,2], "native"), gp=gpar(fill=NA))
#return a data frame with the coordinates of the selection
return(selection)
}
locator(p)
and from here use the point.in.polygon function to subset the data based on the selection.
A possible solution could be to add, say 100x100, invisible points to the plot and then use the plotly_click feature of event_data() in a Shiny app, but this is not at all ideal.
Thanks in advance for your ideas or solutions, I hope my question was clear enough.
-- Kasper
I used ggplot2. Besides the materials at https://shiny.rstudio.com/articles/plot-interaction.html, I'd like to mention the following:
Firstly, when you create the plot, don't use "print( )" within "renderPlot( )", or the coordinates would be wrong. For instance, if you have the following in UI:
plotOutput("myplot", click = "myclick")
The following in the Server would work:
output$myplot <- renderPlot({
p = ggplot(data = mtcars, aes(x=mpg, y=hp)) + geom_point()
p
})
But the clicking coordinates would be wrong if you do:
output$myplot <- renderPlot({
p = ggplot(data = mtcars, aes(x=mpg, y=hp)) + geom_point()
print(p)
})
Then, you could store the coordinates by adding to the Server:
mydata = reactiveValues(x_values = c(), y_values = c())
observeEvent(input$myclick, {
mydata$x_values = c(mydata$x_values, input$myclick$x)
mydata$y_values = c(mydata$y_values, input$myclick$y)
})
In addition to X-Y coordinates, when you use facet with ggplot2, you refer to the clicked facet panel by
input$myclick$panelvar1

Plot ggplot polygons with holes with geom_polygon

There are questions out there about the fact that ggplot2 can't plot polygon shapes that have holes.
That is because, if the order of points is not OK, the end graph looks bad, usually with clipping/trimming lines inside the donut shape.
I have read a lot about how order matters, but I am not able to step forward.
I have a SpatialPolygonsDataFrame with 26 features (comes from raster::rasterToPolygons(dissolve=T)) and I want to plot it with ggplot.
Here's what happens -
r3.pol <- rasterToPolygons(r3, dissolve=T)
r3.df <- fortify(r3.pol)
names(r3.df) <- c('x','y','order','hole','piece','ID','group')
p <- ggplot(r3.df)
p <- p + geom_polygon(mapping=aes(x=x,y=y,group=ID), fill='red')
p <- p + coord_equal()
I see this output:
While it should be like so, with plot(r3.pol):
How can I make this work?
I tried for hours but I am not able to reorder r3.df.
Also, can the information in r3.df$hole be helpful? It is returned by the function fortify for points that are holes (I think).
Side question: how can I give you my r3.pol SpatialPolygonsDataFrame, so that you can try yourself? I remember seeing long, reproducible "dumps" of objects here, but I don't know how to do it.
I saved the polygons data frame here. Was not able to save it using dput, sorry. You can fetch it using load.
I suggest to install the package "ggpolypath" and use geom_polypath instead of geom_polygon. Works for me.
My temporary solution is: ##$% polygons, and use the raster package.
Namely:
r <- raster(x=extent(r3.pol), crs=crs(r3.pol)) # empty raster from r3.pol
res(r) <- 250 # set a decent resolution (depends on your extent)
r <- setValues(r, 1) # fill r with ones
r <- mask(r, r3.pol) # clip r with the shape polygons
And now plot it as you would do with any raster with ggplot. The rasterVis package might come helpful here, but I'm not using it, so:
rdf <- data.frame(rasterToPoints(r))
p <- ggplot(rdf) + geom_raster(mapping=aes(x=x, y=y), fill='red')
p <- p + coord_equal()
And here it goes.
Alternatively, you can create the raster with rasterize, so the raster will hold the polygons values (in my case, just an integer):
r <- raster(x=extent(r3.pol), crs=crs(r3.pol))
res(r) <- 250
r <- rasterize(r3.pol, r)
rdf <- data.frame(rasterToPoints(r))
p <- ggplot(rdf) + geom_raster(mapping=aes(x=x, y=y, fill=factor(layer)))
p <- p + coord_equal()
If someone comes up with a decent solution for geom_polygon, probably involving re-ordering of the polygons data frame, I'll be glad to consider it.

plot raster with discrete colors using rasterVis

I have a few rasters I would like to plot using gplot in the rasterVis package. I just discovered gplot (which is fantastic and so much faster than doing data.frame(rasterToPoints(r))). However, I can't get a discrete image to show. Normally if r is a raster, I'd do:
rdf=data.frame(rasterToPoints(r))
rdf$cuts=cut(rdf$value,breaks=seq(0,max(rdf$value),length.out=5))
ggplot(rdf)+geom_raster(aes(x,y,fill=cuts))
But is there a way to avoid the call to rasterToPoints? It is very slow with large rasters. I did find I could do:
cuts=cut_interval(r#data#values,n=5)
but if you set the fill to cuts it plots the integer representation of the factors.
Here is some reproducible data:
x=seq(-107,-106,.1)
y=seq(33,34,.1)
coords=expand.grid(x,y)
rdf=data.frame(coords,depth=runif(nrow(coords),0,2)))
names(rdf)=c('x','y','value')
r=rasterFromXYZ(rdf)
Thanks
gplot is a very simple wrapper around ggplot so don't expect too
much from it. Instead, you can use part of its code to build your own
solution. The main point here is to use sampleRegular to reduce the
number of points to be displayed.
library(raster)
library(ggplot2)
x <- sampleRegular(r, size=5000, asRaster = TRUE)
dat <- as.data.frame(r, xy=TRUE)
dat$cuts <- cut(dat$value,
breaks=seq(0, max(dat$value), length.out=5))
ggplot(aes(x = x, y = y), data = dat) +
geom_raster(aes(x, y, fill=cuts))
However, if you are open to plot without ggplot2 you may find useful
this other
answer.

Multiple lattice plots with gridExtra

There is very convenient way of plotting multiple graphs and that's with gridExtra - grid.arrange:
grid.arrange(plot1,plot2,plot3,plot4,plot5,plot6,plot7,plot8,plot9, ncol=3)
The above command draws 3x3 graphs in one window.
Now, I'm using my own lattice setup to draw unique lines etc. via
trellis.par.set(my.setup)
However using the grid.arrange command for plotting multiple plots won't pass on the setup as the output plots are in default colours.
So the question is how to pass on the my.setup onto grid.arrange or alternatively how to plot easily multiple graphs in one go for lattice.
EDIT: Reproducible example:
Data <- data.frame(Col1=rnorm(10,0,1),Col2=rexp(10,2),Col3=rnorm(10,2,2),Col4=runif(10,0,2),
Time=seq(1,10,1))
trellis.par.set(col.whitebg())
newSet <- col.whitebg()
newSet$superpose.symbol$col <- c("blue3","orange2","gray1","tomato3")
newSet$superpose.symbol$pch <- 1
newSet$superpose.symbol$cex <- 1
newSet$superpose.line$col <- c("blue3","orange2","gray1","tomato3")
trellis.par.set(newSet)
Plot1 <- xyplot(Col1+Col2~Time, Data, type="spline")
Plot2 <- xyplot(Col2+Col3~Time, Data, type="spline")
Plot3 <- xyplot(Col1+Col3~Time, Data, type="spline")
Plot4 <- xyplot(Col3+Col4~Time, Data, type="spline")
grid.arrange(Plot1,Plot2,Plot3,Plot4, ncol=2)
I guess it's got something to do with the plot.trellis method not finding the global theme settings when it's wrapped in gridExtra::drawDetails.lattice. I don't understand these lattice options, but as far as I recall you can specify them explicitly at the plot level too,
pl = list(Plot1, Plot2, Plot3, Plot4)
# do.call(grid.arrange, c(pl, nrow=1))
do.call(grid.arrange, c(lapply(pl, update, par.settings=newSet), list(nrow=1)))

How to plot a violin scatter boxplot (in R)?

I just came by the following plot:
And wondered how can it be done in R? (or other softwares)
Update 10.03.11: Thank you everyone who participated in answering this question - you gave wonderful solutions! I've compiled all the solution presented here (as well as some others I've came by online) in a post on my blog.
Make.Funny.Plot does more or less what I think it should do. To be adapted according to your own needs, and might be optimized a bit, but this should be a nice start.
Make.Funny.Plot <- function(x){
unique.vals <- length(unique(x))
N <- length(x)
N.val <- min(N/20,unique.vals)
if(unique.vals>N.val){
x <- ave(x,cut(x,N.val),FUN=min)
x <- signif(x,4)
}
# construct the outline of the plot
outline <- as.vector(table(x))
outline <- outline/max(outline)
# determine some correction to make the V shape,
# based on the range
y.corr <- diff(range(x))*0.05
# Get the unique values
yval <- sort(unique(x))
plot(c(-1,1),c(min(yval),max(yval)),
type="n",xaxt="n",xlab="")
for(i in 1:length(yval)){
n <- sum(x==yval[i])
x.plot <- seq(-outline[i],outline[i],length=n)
y.plot <- yval[i]+abs(x.plot)*y.corr
points(x.plot,y.plot,pch=19,cex=0.5)
}
}
N <- 500
x <- rpois(N,4)+abs(rnorm(N))
Make.Funny.Plot(x)
EDIT : corrected so it always works.
I recently came upon the beeswarm package, that bears some similarity.
The bee swarm plot is a
one-dimensional scatter plot like
"stripchart", but with closely-packed,
non-overlapping points.
Here's an example:
library(beeswarm)
beeswarm(time_survival ~ event_survival, data = breast,
method = 'smile',
pch = 16, pwcol = as.numeric(ER),
xlab = '', ylab = 'Follow-up time (months)',
labels = c('Censored', 'Metastasis'))
legend('topright', legend = levels(breast$ER),
title = 'ER', pch = 16, col = 1:2)
(source: eklund at www.cbs.dtu.dk)
I have come up with the code similar to Joris, still I think this is more than a stem plot; here I mean that they y value in each series is a absolute value of a distance to the in-bin mean, and x value is more about whether the value is lower or higher than mean.
Example code (sometimes throws warnings but works):
px<-function(x,N=40,...){
x<-sort(x);
#Cutting in bins
cut(x,N)->p;
#Calculate the means over bins
sapply(levels(p),function(i) mean(x[p==i]))->meansl;
means<-meansl[p];
#Calculate the mins over bins
sapply(levels(p),function(i) min(x[p==i]))->minl;
mins<-minl[p];
#Each dot is one value.
#X is an order of a value inside bin, moved so that the values lower than bin mean go below 0
X<-rep(0,length(x));
for(e in levels(p)) X[p==e]<-(1:sum(p==e))-1-sum((x-means)[p==e]<0);
#Y is a bin minum + absolute value of a difference between value and its bin mean
plot(X,mins+abs(x-means),pch=19,cex=0.5,...);
}
Try the vioplot package:
library(vioplot)
vioplot(rnorm(100))
(with awful default color ;-)
There is also wvioplot() in the wvioplot package, for weighted violin plot, and beanplot, which combines violin and rug plots. They are also available through the lattice package, see ?panel.violin.
Since this hasn't been mentioned yet, there is also ggbeeswarm as a relatively new R package based on ggplot2.
Which adds another geom to ggplot to be used instead of geom_jitter or the like.
In particular geom_quasirandom (see second example below) produces really good results and I have in fact adapted it as default plot.
Noteworthy is also the package vipor (VIolin POints in R) which produces plots using the standard R graphics and is in fact also used by ggbeeswarm behind the scenes.
set.seed(12345)
install.packages('ggbeeswarm')
library(ggplot2)
library(ggbeeswarm)
ggplot(iris,aes(Species, Sepal.Length)) + geom_beeswarm()
ggplot(iris,aes(Species, Sepal.Length)) + geom_quasirandom()
#compare to jitter
ggplot(iris,aes(Species, Sepal.Length)) + geom_jitter()

Resources