How to Visualize Multivariate Trajectory Confidence Intervals using Plotly R | by Marcus Richards Ph.D. | Towards Data Science

Any probabilistic regression approach in statistics or machine learning will result in a line chart together with associated confidence intervals, usually defined as two standard deviations around the mean. Although Plotly has many powerful charting tools for rendering charts in 2 or 3 dimensions, and can draw error bars or confidence intervals for 2D line charts, it does not have an option for rendering confidence intervals in 3D line or surface plots.
Many interesting probabilistic regression tasks are multivariate by nature; for example forecasting a data value z, over time t across a positional coordinate, x. The outcome of this forecast will be a 3D surface representing the expected value z at each point x at time t, together with an estimation of the associated error at every value point. This type of surface plot would be common in surface topology exploration where a grid of sensors captures data at every grid point within the target space. This grid would be well visualized in Plotly as a 3D surface plot of data value across all x, y coordinates, however Plotly does not render the confidence boundary across this surface.
Often the domain of the problem is limited to a trajectory through the target space, rather than a complete exploration of the entire domain. For example, imagine a rocket trajectory plotted with measurements of engine temperature at each latitude and longitude of flight. Temperature data is captured at each point against position, but the positional data is a subset of the entire space of meridians, being only measured along the trajectory of the rocket. This when modelled and visualised graphically shows as a line, as opposed to a surface, of temperature traversing through a trajectory in a 3D space of temperature, altitude and longitude. Plotly allows us to draw this trajectory, but it does not allow us easily to draw the upper and lower confidence intervals on this line. We could refactor the calculation to show temperature against altitude, as a 2D chart, but then we lose the spatial resolution and for example will not be able to distinguish the outbound rocket direction from the inbound, earthward, trajectory, crossing the same altitudes but at different times, and this will create problems with our regression analysis.
The chart shown is a rendering of simulated data representing three trajectories of sample data across the x, y plane, with z showing the data value at each point, together with a ribbon showing the upper and lower confidence limits. Looking directly down the z axis it can be seen that the confidence bands lie in the z plane, since errors are shown against data value, with the observed x and y observation points assumed to be known without error. This is the normal approach taken in regression analysis, where it is generally assumed that we can precisely obtain the measurement coordinates.
The R code used to create these plots uses the Plotly mesh3D chart type to build a surface of upward and downward pointing interlaced triangles that link together to form a continuous surface in the plane of the data values.
The method described is completely general and could be used to create any form of surface, for example to construct upper and lower bound surfaces on a 3D Plotly surface. The only limitation to the use of this is the complexity of the overlapping of surfaces which can become confusing to the eye when rendered in a flat image, rather that as a Plotly dynamic visualization.
#R code for 3D confidence error intervalsinstall.packages("plotly")library(plotly) # Generate Simulated Data Pointsx <- seq(-5,5, .1)y <- seq(0, 10, .1)y <- cbind(y, sin(y), cos(y)) # Generate Simulated Data Valuesz1 = sin(x+y[,1])z2 = sin((x+y[,1])/2.)z3 = sin((x+y[,1])/3.) z <- cbind(z1, z2, z3)# Generate Simulated Standard Deviationssd <- sqrt(abs(z) * .05) n = length(x)i = seq(0,n-1)# Create Plots for each of three simulated trajectoriesp <- plot_ly(type = 'scatter3d')for (index in 1:3){  p <- add_trace(p, x = x, y = y[,index], z = z[,index], mode = 'lines', line = list(width = 8, color=index))  p <- add_trace(p, type = 'mesh3d',     # Setup triangle vertices     x = c(x, x),     y = c(y[,index], y[,index]),     z = c(z[,index] - 2 * sd[,index], z[,index] + 2 * sd[,index]),     # Create triangles     i = c(i[1:n - 1], i[1:n - 1]),     j = c(n + i[1:n - 1], n + i[2:n]) ,     k = c(n + i[2:n], i[2:n]),     color = index  )} #END