Black Lives Matter. Please consider donating to Black Girls Code today.
Learn how to use COVID-19 data in open source Dash apps. Register for the Sept 23rd webinar with IQT!

3D scatter + 3D regression line

Hi, I’ve never needed to plot in 3D before but have an application for it now and am having trouble plotting a regression line with two predictors in 3D using plotly.

Is there no way to input a function directly to plotly when doing 3D scatter? I have the regression equation (z = .36 + .19x + .69y) and a nice scatterplot that looks great, but I’m not sure the best way to add the line. Do I need to create three vectors and connect them with add_trace( … mode = ‘line’) ? If so, how would I go about making the vectors? It’s been awhile but I think lines in 3D need to be defined as the intersection of planes…?

I think you want a 3D plane implemented as a surface. Here is an example:

# fit model
m <- lm(mpg ~ wt + disp, data = mtcars)

# predict over sensible grid of values
wts <- unique(mtcars$wt)
disps <- unique(mtcars$disp)
grid <- with(mtcars, expand.grid(wts, disps))
d <- setNames(data.frame(grid), c("wt", "disp"))
vals <- predict(m, newdata = d)

# form matrix and give to plotly
m <- matrix(vals, nrow = length(unique(d$wt)), ncol = length(unique(d$disp)))

library(plotly)
plot_ly() %>% add_surface(x = ~disps, y = ~wts, z = ~m)
1 Like

Thanks, carson — I was planning on doing a line in 3D space (intersection of 2 planes) but a surface plot will actually work fine for this application. One thing is that I can’t seem to get the color scaling to work properly. Here’s some simple code for a plane that I’m trying to tweak from default colors to be a gray to black gradient:

oneplane <- expand.grid(x = 1:6, y = 1:6)
oneplane$z <- 1:6
oneplane.m <- as.matrix(spread(oneplane, key = x, value = z)[, -1])
plot_ly() %>% add_trace(x = 1:6, 
                        y = 1:6, 
                        z = oneplane.m, 
                        type = "surface", 
                        opacity = .5,
                        cauto = FALSE,
                        cmax = 1,
                        cmin = 0,
                        colorscale = list(c(0,'#d1d1d1'),c(1,'#000000')))

This produces, bizarrely, a solid color red plane. Am I going wrong somewhere? I’d also like to know how to just choose a single solid color for the surface, which might look better on the final plot.

thanks!

Use the colors argument

plot_ly() %>% add_surface(x = ~disps, y = ~wts, z = ~m, colors = c("#d1d1d1", "#000000"))
1 Like

Ah, awesome! thanks. One last question: I seem to be getting some kind of bleed-over from other arguments in a 3D scatterplot that I want to add this graph to. When my 3D scatter is by itself it looks great (code & plot below), and the surface plot by itself works great with your code (both below again). But doing them together completely screws up the colors… what’s going on here?

3D scatter:

plot_ly() %>%
  add_trace(x = danc.x, 
            y = danc.y,
            z = danc.z, 
            type = "scatter3d", 
            mode = "markers",
            color = coh$dance,
            colors = c("gray70", '#6d98f3'),
            opacity = 1) %>%
  add_trace(x = danc.x[which(coh$dance == 1)], 
            y = danc.y[which(coh$dance == 1)],
            z = danc.z[which(coh$dance == 1)], 
            type = "scatter3d",
            mode = "markers",
            marker = list(color = "black", symbol = "circle-open")) %>%
  add_trace(x = l1.x,
            y = l1.y,
            z = l1.z,
            type = "scatter3d",
            mode = "lines",
            line = list(color = "black", width = 5, dash = 'dash')) %>%
  layout(title = '"...used for dancing"',
         scene = list(xaxis = list(title = 'India', range = c(1,6), ticktype = "array", tickvals = ticks),
                      yaxis = list(title = 'World', range = c(1,6), ticktype = "array", tickvals = ticks),
                      zaxis = list(title = 'USA', range = c(1,6), ticktype = "array", tickvals = ticks),
                      camera = list(eye = list(x = 2, y = -2, z = 1.25), zoom = 5),
                      showlegend = FALSE))

Simple surface plot:

plot_ly() %>% add_surface(x = 1:6, 
                     y = 1:6, 
                     z = dancplane.m, 
                     type = "surface", 
                     opacity = 1,
                     colors = c('#d1d1d1','#000000'))

Together, screwy colors…

danc <- plot_ly() %>%
  add_surface(x = 1:6, 
                  y = 1:6, 
                  z = dancplane.m, 
                  type = "surface", 
                  opacity = 1,
                  colors = c('#d1d1d1','#000000')) %>%
  add_trace(x = danc.x, 
            y = danc.y,
            z = danc.z, 
            type = "scatter3d", 
            mode = "markers",
            color = coh$dance,
            colors = c("gray70", '#6d98f3'),
            opacity = 1) %>%
  add_trace(x = danc.x[which(coh$dance == 1)], 
            y = danc.y[which(coh$dance == 1)],
            z = danc.z[which(coh$dance == 1)], 
            type = "scatter3d",
            mode = "markers",
            marker = list(color = "black", symbol = "circle-open")) %>%
  add_trace(x = l1.x,
            y = l1.y,
            z = l1.z,
            type = "scatter3d",
            mode = "lines",
            line = list(color = "black", width = 5, dash = 'dash')) %>%
  layout(title = '"...used for dancing"',
         scene = list(xaxis = list(title = 'India', range = c(1,6), ticktype = "array", tickvals = ticks),
                      yaxis = list(title = 'World', range = c(1,6), ticktype = "array", tickvals = ticks),
                      zaxis = list(title = 'USA', range = c(1,6), ticktype = "array", tickvals = ticks),
                      camera = list(eye = list(x = 2, y = -2, z = 1.25), zoom = 5),
                      showlegend = FALSE))

Using color/colors assumes one colorscale per plot (just like ggplot2)

You could use the “lower-level” colorscale approach if you really want two different colorscales, but I would recommend using symbol/symbols for the markers instead.

Thanks, will play around with the symbol attribute and see where I get. I don’t think I actually need two different colorscales, just need three different colors for the two groups of markers and the plane. Apologies as I am new to plotly (Stata expert / R newbie…) :slight_smile: