Parallel Coordinates plot in Matplotlib
pandas has a parallel coordinates wrapper:
import pandasimport matplotlib.pyplot as pltfrom pandas.tools.plotting import parallel_coordinatesdata = pandas.read_csv(r'C:\Python27\Lib\site-packages\pandas\tests\data\iris.csv', sep=',')parallel_coordinates(data, 'Name')plt.show()
Source code, how they made it: plotting.py#L494
I'm sure there is a better way of doing it, but here's a quick-and-dirty one (a really dirty one):
#!/usr/bin/pythonimport numpy as npimport matplotlib.pyplot as pltimport matplotlib.ticker as ticker#vectors to plot: 4D for this exampley1=[1,2.3,8.0,2.5]y2=[1.5,1.7,2.2,2.9]x=[1,2,3,8] # spinesfig,(ax,ax2,ax3) = plt.subplots(1, 3, sharey=False)# plot the same on all the subplotsax.plot(x,y1,'r-', x,y2,'b-')ax2.plot(x,y1,'r-', x,y2,'b-')ax3.plot(x,y1,'r-', x,y2,'b-')# now zoom in each of the subplots ax.set_xlim([ x[0],x[1]])ax2.set_xlim([ x[1],x[2]])ax3.set_xlim([ x[2],x[3]])# set the x axis ticks for axx,xx in zip([ax,ax2,ax3],x[:-1]): axx.xaxis.set_major_locator(ticker.FixedLocator([xx]))ax3.xaxis.set_major_locator(ticker.FixedLocator([x[-2],x[-1]])) # the last one# EDIT: add the labels to the rightmost spinefor tick in ax3.yaxis.get_major_ticks(): tick.label2On=True# stack the subplots togetherplt.subplots_adjust(wspace=0)plt.show()
This is essentially based on a (much nicer) one by Joe Kingon, Python/Matplotlib - Is there a way to make a discontinuous axis?. You might also want to have a look at the other answer to the same question.
In this example I don't even attempt at scaling the vertical scales, since it depends on what exactly you are trying to achieve.
EDIT: Here is the result
When answering a related question, I worked out a version using only one subplot (so it can be easily fit together with other plots) and optionally using cubic bezier curves to connect the points. The plot adjusts itself to the desired number of axes.
import matplotlib.pyplot as pltfrom matplotlib.path import Pathimport matplotlib.patches as patchesimport numpy as npfig, host = plt.subplots()# create some dummy dataynames = ['P1', 'P2', 'P3', 'P4', 'P5']N1, N2, N3 = 10, 5, 8N = N1 + N2 + N3category = np.concatenate([np.full(N1, 1), np.full(N2, 2), np.full(N3, 3)])y1 = np.random.uniform(0, 10, N) + 7 * categoryy2 = np.sin(np.random.uniform(0, np.pi, N)) ** categoryy3 = np.random.binomial(300, 1 - category / 10, N)y4 = np.random.binomial(200, (category / 6) ** 1/3, N)y5 = np.random.uniform(0, 800, N)# organize the datays = np.dstack([y1, y2, y3, y4, y5])[0]ymins = ys.min(axis=0)ymaxs = ys.max(axis=0)dys = ymaxs - yminsymins -= dys * 0.05 # add 5% padding below and aboveymaxs += dys * 0.05dys = ymaxs - ymins# transform all data to be compatible with the main axiszs = np.zeros_like(ys)zs[:, 0] = ys[:, 0]zs[:, 1:] = (ys[:, 1:] - ymins[1:]) / dys[1:] * dys[0] + ymins[0]axes = [host] + [host.twinx() for i in range(ys.shape[1] - 1)]for i, ax in enumerate(axes): ax.set_ylim(ymins[i], ymaxs[i]) ax.spines['top'].set_visible(False) ax.spines['bottom'].set_visible(False) if ax != host: ax.spines['left'].set_visible(False) ax.yaxis.set_ticks_position('right') ax.spines["right"].set_position(("axes", i / (ys.shape[1] - 1)))host.set_xlim(0, ys.shape[1] - 1)host.set_xticks(range(ys.shape[1]))host.set_xticklabels(ynames, fontsize=14)host.tick_params(axis='x', which='major', pad=7)host.spines['right'].set_visible(False)host.xaxis.tick_top()host.set_title('Parallel Coordinates Plot', fontsize=18)colors = plt.cm.tab10.colorsfor j in range(N): # to just draw straight lines between the axes: # host.plot(range(ys.shape[1]), zs[j,:], c=colors[(category[j] - 1) % len(colors) ]) # create bezier curves # for each axis, there will a control vertex at the point itself, one at 1/3rd towards the previous and one # at one third towards the next axis; the first and last axis have one less control vertex # x-coordinate of the control vertices: at each integer (for the axes) and two inbetween # y-coordinate: repeat every point three times, except the first and last only twice verts = list(zip([x for x in np.linspace(0, len(ys) - 1, len(ys) * 3 - 2, endpoint=True)], np.repeat(zs[j, :], 3)[1:-1])) # for x,y in verts: host.plot(x, y, 'go') # to show the control points of the beziers codes = [Path.MOVETO] + [Path.CURVE4 for _ in range(len(verts) - 1)] path = Path(verts, codes) patch = patches.PathPatch(path, facecolor='none', lw=1, edgecolor=colors[category[j] - 1]) host.add_patch(patch)plt.tight_layout()plt.show()
Here's similar code for the iris data set. The second axis is reversed to avoid some crossing lines.
import matplotlib.pyplot as pltfrom matplotlib.path import Pathimport matplotlib.patches as patchesimport numpy as npfrom sklearn import datasetsiris = datasets.load_iris()ynames = iris.feature_namesys = iris.dataymins = ys.min(axis=0)ymaxs = ys.max(axis=0)dys = ymaxs - yminsymins -= dys * 0.05 # add 5% padding below and aboveymaxs += dys * 0.05ymaxs[1], ymins[1] = ymins[1], ymaxs[1] # reverse axis 1 to have less crossingsdys = ymaxs - ymins# transform all data to be compatible with the main axiszs = np.zeros_like(ys)zs[:, 0] = ys[:, 0]zs[:, 1:] = (ys[:, 1:] - ymins[1:]) / dys[1:] * dys[0] + ymins[0]fig, host = plt.subplots(figsize=(10,4))axes = [host] + [host.twinx() for i in range(ys.shape[1] - 1)]for i, ax in enumerate(axes): ax.set_ylim(ymins[i], ymaxs[i]) ax.spines['top'].set_visible(False) ax.spines['bottom'].set_visible(False) if ax != host: ax.spines['left'].set_visible(False) ax.yaxis.set_ticks_position('right') ax.spines["right"].set_position(("axes", i / (ys.shape[1] - 1)))host.set_xlim(0, ys.shape[1] - 1)host.set_xticks(range(ys.shape[1]))host.set_xticklabels(ynames, fontsize=14)host.tick_params(axis='x', which='major', pad=7)host.spines['right'].set_visible(False)host.xaxis.tick_top()host.set_title('Parallel Coordinates Plot — Iris', fontsize=18, pad=12)colors = plt.cm.Set2.colorslegend_handles = [None for _ in iris.target_names]for j in range(ys.shape[0]): # create bezier curves verts = list(zip([x for x in np.linspace(0, len(ys) - 1, len(ys) * 3 - 2, endpoint=True)], np.repeat(zs[j, :], 3)[1:-1])) codes = [Path.MOVETO] + [Path.CURVE4 for _ in range(len(verts) - 1)] path = Path(verts, codes) patch = patches.PathPatch(path, facecolor='none', lw=2, alpha=0.7, edgecolor=colors[iris.target[j]]) legend_handles[iris.target[j]] = patch host.add_patch(patch)host.legend(legend_handles, iris.target_names, loc='lower center', bbox_to_anchor=(0.5, -0.18), ncol=len(iris.target_names), fancybox=True, shadow=True)plt.tight_layout()plt.show()