Home Data Tutorials Visualizing 3D plots in Matplotlib 2.0

Visualizing 3D plots in Matplotlib 2.0

0
11565
Boxes and whisker plot in Tableau
7 min read

[box type=”note” align=”” class=”” width=””]This article is an excerpt from a book by Allen Chi Shing Yu, Claire Yik Lok Chung, and Aldrin Kay Yuen Yim titled Matplotlib 2.x By Example.[/box]

By transitioning to the three-dimensional space, you may enjoy greater creative freedom when creating visualizations. The extra dimension can also accommodate more information in a single plot. However, some may argue that 3D is nothing more than a visual gimmick when projected to a 2D surface (such as paper) as it would obfuscate the interpretation of data points.

Learn Programming & Development with a Packt Subscription

In Matplotlib version 2, despite significant developments in the 3D API, annoying bugs or glitches still exist. We will discuss some workarounds toward the end of this article. More powerful Python 3D visualization packages do exist (such as MayaVi2, Plotly, and VisPy), but it’s good to use Matplotlib’s 3D plotting functions if you want to use the same package for both 2D and 3D plots, or you would like to maintain the aesthetics of its 2D plots.

For the most part, 3D plots in Matplotlib have similar structures to 2D plots. As such, we will not go through every 3D plot type in this section. We will put our focus on 3D scatter plots and bar charts.

3D scatter plot

Let’s try to create a 3D scatter plot. Before doing that, we need some data points in three dimensions (x, y, z):

import pandas as pd
 
source = "https://raw.githubusercontent.com/PointCloudLibrary/data/master/tutorials/ ism_train_cat.pcd"
cat_df = pd.read_csv(source, skiprows=11, delimiter=" ", names=["x","y","z"], encoding='latin_1')
cat_df.head()

To declare a 3D plot, we first need to import the Axes3D object from the mplot3d extension in mpl_toolkits, which is responsible for rendering 3D plots in a 2D plane. After that, we need to specify projection='3d' when we create subplots:

from mpl_toolkits.mplot3d import Axes3D 
import matplotlib.pyplot as plt
 
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d') 
ax.scatter(cat_df.x, cat_df.y, cat_df.z)
 
plt.show()

Behold, the mighty sCATter plot in 3D. Cats are currently taking over the internet. According to the New York Times, cats are “the essential building block of the Internet” (https://www.nytimes.com/2014/07/23/upshot/what-the-internet-can-see-from-your-cat-pictures.html). Undoubtedly, they deserve a place in this chapter as well.

Contrary to the 2D version of scatter(), we need to provide X, Y, and Z coordinates when we are creating a 3D scatter plot. Yet the parameters that are supported in 2D scatter() can be applied to 3D scatter() as well:

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
 
# Change the size, shape and color of markers ax.scatter(cat_df.x, cat_df.y, cat_df.z, s=4, c="g", marker="o")
 
plt.show()

To change the viewing angle and elevation of the 3D plot, we can make use of view_init(). The azim parameter specifies the azimuth angle in the X-Y plane, while elev specifies the elevation angle. When the azimuth angle is 0, the X-Y plane would appear to the north from you. Meanwhile, an azimuth angle of 180 would show you the south side of the X-Y plane:

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(cat_df.x, cat_df.y, cat_df.z,s=4, c="g", marker="o")
 
# elev stores the elevation angle in the z plane azim stores the
# azimuth angle in the x,y plane ax.view_init(azim=180, elev=10)
 
plt.show()

3D bar chart

We introduced candlestick plots for showing Open-High-Low-Close (OHLC) financial data. In addition, a 3D bar chart can be employed to show OHLC across time. The next figure shows a typical example of plotting a 5-day OHLC bar chart:

import matplotlib.pyplot as plt 
import numpy as np
from mpl_toolkits.mplot3d import Axes3D

# Get 1 and every fifth row for the 5-day AAPL OHLC data ohlc_5d = stock_df[stock_df["Company"]=="AAPL"].iloc[1::5, :]
 
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
 
# Create one color-coded bar chart for Open, High, Low and Close prices. for color, col, z in zip(['r', 'g', 'b', 'y'], ["Open", "High", "Low",
"Close"], [30, 20, 10, 0]):
xs = np.arange(ohlc_5d.shape[0]) ys = ohlc_5d[col]
# Assign color to the bars colors = [color] * len(xs)
ax.bar(xs, ys, zs=z, zdir='y', color=colors, alpha=0.8, width=5) plt.show()

The method for setting ticks and labels is similar to other Matplotlib plotting functions:

fig = plt.figure(figsize=(9,7))
ax = fig.add_subplot(111, projection='3d')
 
# Create one color-coded bar chart for Open, High, Low and Close prices. for color, col, z in zip(['r', 'g', 'b', 'y'], ["Open", "High", "Low",
"Close"], [30, 20, 10, 0]):
xs = np.arange(ohlc_5d.shape[0]) ys = ohlc_5d[col]
# Assign color to the bars colors = [color] * len(xs)
ax.bar(xs, ys, zs=z, zdir='y', color=colors, alpha=0.8)
 
# Manually assign the ticks and tick labels ax.set_xticks(np.arange(ohlc_5d.shape[0])) ax.set_xticklabels(ohlc_5d["Date"], rotation=20,
verticalalignment='baseline', horizontalalignment='right', fontsize='8')
ax.set_yticks([30, 20, 10, 0]) ax.set_yticklabels(["Open", "High", "Low", "Close"])
 
# Set the z-axis label ax.set_zlabel('Price (US $)')
 
# Rotate the viewport ax.view_init(azim=-42, elev=31) 
plt.tight_layout()
plt.show()

Caveats to consider while visualizing 3D plots in Matplotlib

Due to the lack of a true 3D graphical rendering backend (such as OpenGL) and proper algorithm for detecting 3D objects’ intersections, the 3D plotting capabilities of Matplotlib are not great but just adequate for typical applications. In the official Matplotlib FAQ (https://matplotlib.org/mpl_toolkits/mplot3d/faq.html), the author noted that 3D plots may not look right at certain angles. Besides, we also reported that mplot3d would failed to clip bar charts if zlim is set (https://github.com/matplotlib/matplotlib/ issues/8902; see also https://github.com/matplotlib/matplotlib/issues/209). Without improvements in the 3D rendering backend, these issues are hard to fix.

To better illustrate the latter issue, let’s try to add ax.set_zlim3d(bottom=110, top=150) right above plt.tight_layout() in the previous 3D bar chart:

Clearly, something is going wrong, as the bars overshoot the lower boundary of the axes. We will try to address the latter issue through the following workaround:

# FuncFormatter to add 110 to the tick labels def major_formatter(x, pos):
return "{}".format(x+110)
 
fig = plt.figure(figsize=(9,7))
ax = fig.add_subplot(111, projection='3d')
 
# Create one color-coded bar chart for Open, High, Low and Close prices. for color, col, z in zip(['r', 'g', 'b', 'y'], ["Open", "High", "Low",
"Close"], [30, 20, 10, 0]):
xs = np.arange(ohlc_5d.shape[0]) ys = ohlc_5d[col]
 
# Assign color to the bars colors = [color] * len(xs)
# Truncate the y-values by 110
ax.bar(xs, ys-110, zs=z, zdir='y', color=colors, alpha=0.8)
 
# Manually assign the ticks and tick labels ax.set_xticks(np.arange(ohlc_5d.shape[0])) ax.set_xticklabels(ohlc_5d["Date"], rotation=20,
verticalalignment='baseline', horizontalalignment='right', fontsize='8')
 
# Set the z-axis label ax.set_yticks([30, 20, 10, 0])
ax.set_yticklabels(["Open", "High", "Low", "Close"]) ax.zaxis.set_major_formatter(FuncFormatter(major_formatter)) ax.set_zlabel('Price (US $)')
 
# Rotate the viewport ax.view_init(azim=-42, elev=31)
 
plt.tight_layout() 
plt.show()

Basically, we truncated the y values by 110, and then we used a tick formatter (major_formatter) to shift the tick value back to the original. For 3D scatter plots, we can simply remove the data points that exceed the boundary of set_zlim3d() in order to generate a proper figure. However, these workarounds may not work for every 3D plot type.

Conclusion

We didn’t go into too much detail of the 3D plotting capability of Matplotlib, as it is yet to be polished. For simple 3D plots, Matplotlib already suffices. The learning curve can be reduced if we use the same package for both 2D and 3D plots. You are advised to take a look at MayaVi2, Plotly, and VisPy if you require more powerful 3D plotting functions.

If you enjoyed this excerpt, be sure to check out the book it is from.

NO COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here