Подтвердить что ты не робот

Параллельный график координат в Matplotlib

Двумерные и трехмерные данные можно просматривать относительно просто, используя традиционные типы графиков. Даже с четырехмерными данными мы часто можем найти способ отображения данных. Размеры выше четырех, тем не менее, становятся все труднее отображать. К счастью, графики параллельных координат предоставляют механизм для просмотра результатов с более высокими размерами.

Example Parallel Coordinates Plot from Wikipedia

Некоторые графические пакеты предоставляют параллельные координатные графики, такие как Matlab, R, VTK type 1 и VTK type 2, но я не вижу, как их создать с помощью Matplotlib.

  1. Есть ли встроенный параллельный график координат в Matplotlib? Я, конечно, не вижу никого в галерее.
  2. Если встроенного типа нет, возможно ли построить график параллельных координат, используя стандартные функции Matplotlib?

Редактировать:

Основываясь на ответе, который дал Женя ниже, я разработал следующее обобщение, которое поддерживает произвольное число осей. Следуя стилю графика примера, который я разместил в исходном вопросе выше, каждая ось получает свой собственный масштаб. Я выполнил это путем нормализации данных в каждой точке оси и присвоения осям диапазона от 0 до 1. Затем я возвращаюсь и применяю метки к каждой отметке, которые дают правильное значение на этом пересечении.

Функция работает, принимая итерируемые наборы данных. Каждый набор данных считается набором точек, где каждая точка лежит на другой оси. Пример в __main__ собирает случайные числа для каждой оси в двух наборах по 30 строк. Линии являются случайными в пределах диапазонов, которые вызывают кластеризацию линий; поведение, которое я хотел проверить.

Это решение не так хорошо, как встроенное решение, поскольку у вас странное поведение мыши, и я подделываю диапазоны данных через метки, но пока Matplotlib не добавит встроенное решение, это приемлемо.

#!/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 __name__ == '__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()

Изменить 2:

Вот пример того, что получается из приведенного выше кода при построении данных Fisher Iris. Это не так хорошо, как эталонное изображение из Википедии, но оно приемлемо, если у вас есть только Matplotlib и вам нужны многомерные графики.

Example result of parallel coordinates plot from this answer

4b9b3361

Ответ 1

Я уверен, что есть лучший способ сделать это, но здесь быстрый и грязный (действительно грязный):

#!/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()

Это по существу основано на (гораздо приятнее) Джо Кингоне, Python/Matplotlib - Есть ли способ сделать разрывную ось?. Вы также можете взглянуть на другой ответ на тот же вопрос.

В этом примере я даже не пытаюсь масштабировать вертикальные масштабы, поскольку это зависит от того, чего именно вы пытаетесь достичь.

EDIT: Вот результат enter image description here

Ответ 2

pandas имеет оболочку параллельных координат:

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

Исходный код, как они это сделали: plotting.py#L494

Ответ 3

При использовании панд (например, предложенных тэтой) нет способа независимо масштабировать оси.

Причина, по которой вы не можете найти разные вертикальные оси, заключается в том, что их нет. Наши параллельные координаты "подделывают" две другие оси, просто рисуя вертикальную линию и некоторые метки.

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

Ответ 4

Лучший пример, который я видел до сих пор, - это

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

См. функцию normalised_coordinates. Не супер быстрый, но работает от того, что я пробовал.

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])

Ответ 5

Все еще далеко от совершенства, но это работает и относительно коротко:

import numpy as np

import matplotlib.pyplot as plt

def plot_parallel(data,labels):

    data=np.array(data)
    x=list(range(len(data[0])))
    fig, axis = plt.subplots(1, len(data[0])-1, sharey=False)


    for d in data:
        for i, a in enumerate(axis):
            temp=d[i:i+2].copy()
            temp[1]=(temp[1]-np.min(data[:,i+1]))*(np.max(data[:,i])-np.min(data[:,i]))/(np.max(data[:,i+1])-np.min(data[:,i+1]))+np.min(data[:,i])
            a.plot(x[i:i+2], temp)


    for i, a in enumerate(axis):
        a.set_xlim([x[i], x[i+1]])
        a.set_xticks([x[i], x[i+1]])
        a.set_xticklabels([labels[i], labels[i+1]], minor=False, rotation=45)
        a.set_ylim([np.min(data[:,i]),np.max(data[:,i])])


    plt.subplots_adjust(wspace=0)

    plt.show()