This is the first part of a series of blog posts on multidimensional data visualization strategies. The main objectives of this first part are:
- Show you how to expand plotting capabilities by modifying matplotlib source code.
- Generate a tailored 6-D Pareto front plot with completely customized legends.
- Provide a glimpse of a recently developed Pareto front video repository in R.
1. Expanding matplotlib capabilities
Keeping in mind that matplotlib is an opensource project developed in the contributors’ free time, there is no guarantee that features that contributors make will be added straightaway. In my case, I needed the marker rotation capabilities in a 3 D scatter plot. Luckily, someone already had figured out how to do so and started a pull request in the matplotlib github repository but this change has not yet been implemented. Since I couldn’t wait for the changes to happen, here’s the straightforward solution that I found:
Here’s the link to the pull request that I am referring to.
First, I located where Matplotlib lives in my computer, the path in my case is:
Then, I located the files that the contributor changed. The files’ paths are circled in red in the following snippets of the pull request:
I located those files in my local matplotlib folder, which in my case are:
In the previous snippets, the lines of code that were added to the original script are highlighted in green and those that were removed are highlighted in red. Hence, to access the clean version I clicked on the view button and selected the entire script and copied and pasted it in my local matplotlib code. For this exercise I ended changing only a couple of scripts: the axes.py and the collections.py.
NOTE: If you ever need to undertake this type of solution, make sure you paste the lines of code in the right places, do this part carefully. Also, it’s always a good idea to make backups of the original files in case something goes irreversibly wrong. Or you can always uninstall and install, no big deal.
2. Generate a tailored 6D Pareto front plot with customized legends.
Matplotlib allows visualization of 5 objectives quite easily, but scaling to 6 or more objectives can be a bit tricky. So, lets walk through our 6 D plots in Matplotlib. We will learn how to do one of the following plots:
Pie Day Plot:
St. Patrick’s Day Plot:
2.1. Required libraries:
The following are the only libraries that you’ll need. I import seaborn sometimes because it looks fancy but it’s totally unnecessary in this case, which is why it is commented out.
import matplotlib as mpl from mpl_toolkits.mplot3d import Axes3D import numpy as np import matplotlib.pyplot as plt #import seaborn
2.2. Importing data:
The data file that I used consists of 6 space-separated columns, if your data has another delimiter you can just add it like so: data= np.loadtxt(‘sample_data.txt, delimiter=’,’). I am also multiplying the first five columns by -1 because I want to remove the negatives, this is specific to my data, you may not require to do so.
data= np.loadtxt('sample_data.txt') #Organizing the data by objectives obj1 = data[:,0]*-1 obj2 = data[:,1]*-1 obj3 = data[:,2]*-1 obj4 = data[:,3]*-1 obj5 = data[:,4]*-1 obj6 = data[:,5]
2.3. Object-based plotting:
To allow more customization, we need to move to a more object-based way to make the plots. That is, storing elements of the plots in variables.
<span class="n"> fig = plt.figure() # create a figure object ax = fig.add_subplot(111, projection='3d') # create an axes object in the figure
2.4. Setting marker options:
Any mathtext symbol can be used as a marker. In order to use rotation to represent an additional objective it’s preferable if the marker has a single axis of symmetry so that the rotation is distinguishable. Here are some marker options:
pie=r'$\pi$' #pie themed option arrow = u'$\u2193$' # Arrows clover=r'$\clubsuit$' #Saint Patrick's theme heart=r'$\heartsuit$' # Valentine's theme marker=pie #this is were you provide the marker options
More marker options can be found in : http://matplotlib.org/users/mathtext.html
2.4. Scatter 6D plot:
The first three objectives are plotted in a 3-D scatter plot, in the x,y, and z axis respectively. The fourth objective is represented by color, the fifth by size and the sixth by rotation. Note that the rotation is scaled in degrees. This is the step were I had to modify matplotlib source code to enable the ‘angles’ option shown below. Also, it may be required to scale the size objective to have the desired marker size in your plot. You can also plot the ideal point by adding a second scatter plot specifying the ideal values for each objective. Finally, we assign the size objective “objs” and rotation objective “objr”, this will be useful later on when setting up the legend for these two objectives.
rot_angle=180 #rotation angle multiplier scale=2000 #size objective multiplier #Plotting 6 objectives: im= ax.scatter(obj1, obj2, obj3, c=obj4, s= obj5*scale, marker=marker, angles=obj6*rot_angle, alpha=1, cmap=plt.cm.summer_r) ax.scatter(1,1,0, marker=pie, c='seagreen', s=scale, alpha=1) objs=obj5 #size objective objr=obj6 #rotation objective
2.5. Main axis labels and limits:
This is extremely straightforward, you can set the x,y, and z labels and specify their limits as follows:
#Main axis labels: ax.set_xlabel('Objective 1') ax.set_ylabel('Objective 2') ax.set_zlabel('Objective 3') #Axis limits: plt.xlim([0,1]) plt.ylim([0,1]) ax.set_zlim3d(0, 1)
2.6. Color bar options:
The colorbar limits and labels can also be specified, as shown in the code below. There are many colormap options in matplotlib, some of the most popular ones are: jet, hsv and spectral. As an example, if you want to change the colormap in the code shown in part 2.4, do cmap= plt.cm.hsv. To reverse the colormap attach an ‘_r ‘ like so: cmap= plt.cm.hsv_r. There is also a color brewer package for the more artistic plotter.
# Set the color limits.. not necessary here, but good to know how. im.set_clim(0.0, 1.0) #Colorbar label: cbar = plt.colorbar(im) cbar.ax.set_ylabel('Objective 4')
2.6. Size and rotation legends:
This is were it gets interesting. The first couple of lines get the labels for legend and chose which ones to display. This allows for much flexibility when creating the legends. As you can see in the code below, you can show markers that correspond to the maximum and the minimum objective values to orient the reader. You can assign the spacing between lines in the legend, the title, weather you want to frame your legend or not, the location in the figure, etc. Line 22 of the following code shows how to add more than one legend. There are many options for an entirely customized legend in the legend documentation which you can explore for more options.
<pre>handles, labels = ax.get_legend_handles_labels() display = (0,1,2) #Code for size and rotation legends begins here for Objectives 5 and 6: min_size=np.amin(objs) max_size=np.amax(objs) #Custom size legend: size_max = plt.Line2D((0,1),(0,0), color='k', marker=marker, markersize=max_size,linestyle='') size_min = plt.Line2D((0,1),(0,0), color='k', marker=marker, markersize=min_size,linestyle='') legend1= ax.legend([handle for i,handle in enumerate(handles) if i in display]+[size_max,size_min], [label for i,label in enumerate(labels) if i in display]+["%.2f"%(np.amax(objs)), "%.2f"%(np.amin(objs))], labelspacing=1.5, title='Objective 6', loc=1, frameon=True, numpoints=1, markerscale=1) markersize=15 #Custom rotation legend rotation_max = plt.Line2D((0,1),(0,0),color='k',marker=r'$\Uparrow$', markersize=15, linestyle='') rotation_min = plt.Line2D((0,1),(0,0),color='k', marker=r'$\Downarrow$', markersize=15, linestyle='') ax.legend([handle for i,handle in enumerate(handles) if i in display]+[rotation_max,rotation_min], [label for i,label in enumerate(labels) if i in display]+["%.2f"%(np.amax(objr)), "%.2f"%(np.amin(objr))], labelspacing=1.5, title='Objective 5',loc=2, frameon=True, numpoints=1, markerscale=1) plt.gca().add_artist(legend1) plt.show()
You can find the full code for the previous example in the following github repository:
3. Generate 6D Pareto front and runtime videos in R.
And last but not least, let me direct everyone to Calvin’s repository: https://github.com/calvinwhealton/ParetoFrontMovie. Where you can find the paretoMovieFront6D.R script which enables the exploration of the evolution of a 6D Pareto front. It is an extremely flexible tool and it has around 50 customization options to adapt your video or your plot to your visual needs, all you need is your runtime output, so check it out. I made the tiniest contribution to this repository so I feel totally entitled to talk about it. Here is a snippet of the video: