Plotly Fundamentals -
3D Plots
In this chapter of our Plotly tutorial we will look at a family of charts that might be considered a little bit fringe and mostly used in scientific applications when displaying three dimensional data. Nevertheless, there are some pretty cool applications such as drawing the surface of landscapes, lower dimensional projections and others which make it worth it to explore them. You probably know how it goes by now so lets start importing some libraries. Here we will also use the numpy library which contains many useful functions for scientific computing applications.
import pandas as pd
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
For the first example we will once again use the iris data set provided by the Plotly Express package.
iris = px.data.iris()
iris.head()
sepal_length | sepal_width | petal_length | petal_width | species | species_id | |
---|---|---|---|---|---|---|
0 | 5.1 | 3.5 | 1.4 | 0.2 | setosa | 1 |
1 | 4.9 | 3.0 | 1.4 | 0.2 | setosa | 1 |
2 | 4.7 | 3.2 | 1.3 | 0.2 | setosa | 1 |
3 | 4.6 | 3.1 | 1.5 | 0.2 | setosa | 1 |
4 | 5.0 | 3.6 | 1.4 | 0.2 | setosa | 1 |
Conveniently, everything we have been using including colouring of certain attributes and other features work out of the box for three dimensional scatters too. The only need to exchange the standard scatter function with scatter_3d and complement our x and y values with a z value and voila, a nice 3d scatter of our flower data which once again reconfirms what we discovered already in two dimensions, i.e. that flowers of the Setosa species can be easily separated from the others.
fig = px.scatter_3d(iris, x='sepal_length', y='sepal_width', z='petal_width',
color='species', height = 800)
fig.show()
For those of you that would like to cheat in a fourth dimension you can also add a series of continuous values to the color attribute to achieve gradient colouring of the data points. To still have the categorial values displayed you can add differently shaped markers by providing the values to the symbol attribute. Finally, adding something more to our bag of tricks, we can use the update_layout function to position the legend in the top left corner of the chart which makes the chart much more clean.
fig = px.scatter_3d(iris, x='sepal_length', y='sepal_width', z='petal_width',
color='petal_length',symbol = 'species', height = 800)
fig.update_layout(legend=dict(
yanchor="top",
y=0.99,
xanchor="left",
x=0.01
))
fig.show()
Going away from the popular scatter graph we will now explore a different way to present three dimensional data with surface plots. To do this we have to leave the express functions aside and create a figure object with graph objects.
For starters, we will create a function and a x/y grid against which the function is evaluated. Thereafter, we can pass the obtained values to the Surface function. The numpy function meshgrid conveniently helps to set up the grid of function values.
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
X, Y = np.meshgrid(x, y)
z = f(X, Y)
fig = go.Figure(data=[go.Surface(z=z)])
fig.update_layout(height = 800)
fig.show()
In the following example we have added some additional styling by making the chart a bit opaque and at the same time add contour lines using the contours_z attribute in combination with the update_traces function. This allows much easier inspection of the function when rotating the chart around.
fig = go.Figure(data=[go.Surface(z=z, opacity =0.5)])
fig.update_traces(contours_z=dict(show=True, project_z=True))
fig.update_layout(height = 800)
fig.show()
Finally, we can also use line plots in three dimensions with the line_3d function in plotly express. We are using the gapminder dataset as it is more meaningful than our trusted iris dataset. It contains some socio-economic data indexed by a year column. We will start looking at French gdp and population size across time.
france = px.data.gapminder()
france = france[france['country'] == 'France']
france.head()
country | continent | year | lifeExp | pop | gdpPercap | iso_alpha | iso_num | |
---|---|---|---|---|---|---|---|---|
528 | France | Europe | 1952 | 67.41 | 42459667 | 7029.809327 | FRA | 250 |
529 | France | Europe | 1957 | 68.93 | 44310863 | 8662.834898 | FRA | 250 |
530 | France | Europe | 1962 | 70.51 | 47124000 | 10560.485530 | FRA | 250 |
531 | France | Europe | 1967 | 71.55 | 49569000 | 12999.917660 | FRA | 250 |
532 | France | Europe | 1972 | 72.38 | 51732000 | 16107.191710 | FRA | 250 |
fig = px.line_3d(france, x="gdpPercap", y="pop", z="year", height = 800)
fig.show()
Once again, you can dump a larger dataframe into your function call and color code the series based on a classification. In this case we are using the countries as class inputs.
europe = px.data.gapminder()
europe = europe[europe['continent'] == 'Europe']
europe.head()
fig = px.line_3d(europe, x="gdpPercap", y="pop", z="year", color = 'country', height = 800)
fig.show()
This concludes our exploration of three dimensional data display. No worries though, there is still plenty unchartered territory we need to visit. Therefore, read on and learn how to easily create pretty, responsive map plots in the upcoming chapter or our tutorial series.