web-dev-qa-db-fra.com

Tracé des coordonnées parallèles dans Matplotlib

Les données bidimensionnelles et tridimensionnelles peuvent être visualisées de manière relativement simple à l'aide de types de tracé traditionnels. Même avec des données en quatre dimensions, nous pouvons souvent trouver un moyen d'afficher les données. Les dimensions supérieures à quatre, cependant, deviennent de plus en plus difficiles à afficher. Heureusement, les tracés de coordonnées parallèles fournissent un mécanisme permettant d'afficher les résultats avec des dimensions supérieures.

Example Parallel Coordinates Plot from Wikipedia

Plusieurs packages de tracé fournissent des tracés de coordonnées parallèles, tels que Matlab , R , VTK type 1 et VTK type 2 , mais je ne vois pas comment en créer un en utilisant Matplotlib. 

  1. Existe-t-il un tracé de coordonnées parallèles intégré dans Matplotlib? Je ne vois certainement pas un dans la galerie
  2. S'il n'y a pas de type intégré, est-il possible de construire un tracé de coordonnées parallèles à l'aide des fonctionnalités standard de Matplotlib?

Modifier:

Sur la base de la réponse fournie par Zhenya ci-dessous, j'ai développé la généralisation suivante qui prend en charge un nombre arbitraire d'axes. Suivant le style de tracé de l'exemple que j'ai posté dans la question initiale ci-dessus, chaque axe reçoit sa propre échelle. Pour ce faire, j'ai normalisé les données au niveau de chaque point d'axe et fait en sorte que les axes aient une plage allant de 0 à 1. Je reviens ensuite en arrière et applique des étiquettes à chaque repère qui donnent la valeur correcte à cette interception.

La fonction fonctionne en acceptant un itérable de jeux de données. Chaque ensemble de données est considéré comme un ensemble de points où chaque point est situé sur un axe différent. L'exemple de __main__ saisit des nombres aléatoires pour chaque axe en deux séries de 30 lignes. Les lignes sont aléatoires dans les plages qui provoquent le regroupement de lignes; un comportement que je voulais vérifier.

Cette solution n’est pas aussi efficace qu’une solution intégrée car vous avez un comportement étrange avec la souris et que je simule les plages de données au moyen d’étiquettes, mais jusqu’à ce que Matplotlib ajoute une solution intégrée, elle est acceptable.

#!/usr/bin/python
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

def parallel_coordinates(data_sets, style=None):

    dims = len(data_sets[0])
    x    = range(dims)
    fig, axes = plt.subplots(1, dims-1, sharey=False)

    if style is None:
        style = ['r-']*len(data_sets)

    # Calculate the limits on the data
    min_max_range = list()
    for m in Zip(*data_sets):
        mn = min(m)
        mx = max(m)
        if mn == mx:
            mn -= 0.5
            mx = mn + 1.
        r  = float(mx - mn)
        min_max_range.append((mn, mx, r))

    # Normalize the data sets
    norm_data_sets = list()
    for ds in data_sets:
        nds = [(value - min_max_range[dimension][0]) / 
                min_max_range[dimension][2] 
                for dimension,value in enumerate(ds)]
        norm_data_sets.append(nds)
    data_sets = norm_data_sets

    # Plot the datasets on all the subplots
    for i, ax in enumerate(axes):
        for dsi, d in enumerate(data_sets):
            ax.plot(x, d, style[dsi])
        ax.set_xlim([x[i], x[i+1]])

    # Set the x axis ticks 
    for dimension, (axx,xx) in enumerate(Zip(axes, x[:-1])):
        axx.xaxis.set_major_locator(ticker.FixedLocator([xx]))
        ticks = len(axx.get_yticklabels())
        labels = list()
        step = min_max_range[dimension][2] / (ticks - 1)
        mn   = min_max_range[dimension][0]
        for i in xrange(ticks):
            v = mn + i*step
            labels.append('%4.2f' % v)
        axx.set_yticklabels(labels)


    # Move the final axis' ticks to the right-hand side
    axx = plt.twinx(axes[-1])
    dimension += 1
    axx.xaxis.set_major_locator(ticker.FixedLocator([x[-2], x[-1]]))
    ticks = len(axx.get_yticklabels())
    step = min_max_range[dimension][2] / (ticks - 1)
    mn   = min_max_range[dimension][0]
    labels = ['%4.2f' % (mn + i*step) for i in xrange(ticks)]
    axx.set_yticklabels(labels)

    # Stack the subplots 
    plt.subplots_adjust(wspace=0)

    return plt


if __== '__main__':
    import random
    base  = [0,   0,  5,   5,  0]
    scale = [1.5, 2., 1.0, 2., 2.]
    data = [[base[x] + random.uniform(0., 1.)*scale[x]
            for x in xrange(5)] for y in xrange(30)]
    colors = ['r'] * 30

    base  = [3,   6,  0,   1,  3]
    scale = [1.5, 2., 2.5, 2., 2.]
    data.extend([[base[x] + random.uniform(0., 1.)*scale[x]
                 for x in xrange(5)] for y in xrange(30)])
    colors.extend(['b'] * 30)

    parallel_coordinates(data, style=colors).show()

Edit 2:

Voici un exemple de ce qui ressort du code ci-dessus lors du traçage Données de l'iris de Fisher . Ce n’est pas tout à fait aussi agréable que l’image de référence de Wikipedia, mais elle est praticable si vous n’avez que Matplotlib et que vous avez besoin de tracés multidimensionnels.

Example result of parallel coordinates plot from this answer

40
Nathan

Je suis sûr qu'il existe une meilleure façon de le faire, mais voici une solution rapide et très sale (vraiment sale): 

#!/usr/bin/python
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

#vectors to plot: 4D for this example
y1=[1,2.3,8.0,2.5]
y2=[1.5,1.7,2.2,2.9]

x=[1,2,3,8] # spines

fig,(ax,ax2,ax3) = plt.subplots(1, 3, sharey=False)

# plot the same on all the subplots
ax.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 spine
for tick in ax3.yaxis.get_major_ticks():
  tick.label2On=True

# stack the subplots together
plt.subplots_adjust(wspace=0)

plt.show()

Ceci est essentiellement basé sur un (beaucoup plus agréable) de Joe Kingon, Python/Matplotlib - Existe-t-il un moyen de créer un axe discontinu? . Vous voudrez peut-être aussi regarder l’autre réponse à la même question. 

Dans cet exemple, je n'essaye même pas de mettre à l'échelle les échelles verticales, car cela dépend de l'objectif recherché. 

EDIT: Voici le résultatenter image description here

14
ev-br

pandas a un wrapper de coordonnées parallèles:

import pandas
import matplotlib.pyplot as plt
from pandas.tools.plotting import parallel_coordinates

data = pandas.read_csv(r'C:\Python27\Lib\site-packages\pandas\tests\data\iris.csv', sep=',')
parallel_coordinates(data, 'Name')
plt.show()

screenshot

Le code source, comment ils l'ont fait: plotting.py # L494

41
theta

Lors de l'utilisation de pandas (comme suggéré par thêta), il n'y a aucun moyen de redimensionner les axes indépendamment.

La raison pour laquelle vous ne pouvez pas trouver les différents axes verticaux est qu’il n’y en a pas. Nos coordonnées parallèles «simulent» les deux autres axes en dessinant simplement une ligne verticale et des étiquettes.

https://github.com/pydata/pandas/issues/7083#issuecomment-74253671

10
Timmm

Le meilleur exemple que j'ai vu jusqu'à présent est celui-ci

https://python.g-node.org/python-summerschool-2013/_media/wiki/datavis/olympics_vis.py

Voir la fonction normalised_coordinates. Pas super rapide, mais fonctionne d'après ce que j'ai essayé.

normalised_coordinates(['VAL_1', 'VAL_2', 'VAL_3'], np.array([[1230.23, 1500000, 12453.03], [930.23, 140000, 12453.03], [130.23, 120000, 1243.03]]), [1, 2, 1])
0
Eamonn