Pylab: сопоставьте метки с цветами

Я только начинаю со стека scipy. Я использую набор данных радужной оболочки в версии CSV. Я могу загрузить его просто отлично, используя:

iris=numpy.recfromcsv("iris.csv")

и постройте его:

pylab.scatter(iris.field(0), iris.field(1))
pylab.show()

Теперь я хотел бы также построить классы, которые хранятся в iris.field(4):

chararray(['setosa', ...], dtype='|S10')

Каков элегантный способ сопоставить эти строки с цветами для построения графика? scatter(iris.field(0), iris.field(1), c=iris.field(4)) не работает (из документов ожидаются значения с плавающей запятой или цветовая карта). Я не нашел элегантного способа автоматического создания карты цветов.

cols = {"versicolor": "blue", "virginica": "green", "setosa": "red"}
scatter(iris.field(0), iris.field(1), c=map(lambda x:cols[x], iris.field(4)))

делает примерно то, что я хочу, но мне не очень нравится ручная спецификация цвета.

Правка: немного более элегантная версия последней строки:

scatter(iris.field(0), iris.field(1), c=map(cols.get, iris.field(4)))

person Has QUIT--Anony-Mousse    schedule 16.03.2012    source источник


Ответы (2)


Как бы то ни было, в этом случае вы обычно делаете что-то вроде этого:

import numpy as np
import matplotlib.pyplot as plt

iris = np.recfromcsv('iris.csv')
names = set(iris['class'])

x,y = iris['sepal_length'],  iris['sepal_width']

for name in names:
    cond = iris['class'] == name
    plt.plot(x[cond], y[cond], linestyle='none', marker='o', label=name)

plt.legend(numpoints=1)
plt.show()

введите здесь описание изображения

В том, что предложил @Yann, нет ничего плохого, но scatter лучше подходит для непрерывных данных.

Легче полагаться на цветовой цикл осей и просто вызывать график несколько раз (вы также получаете отдельных художников вместо коллекции, что хорошо для таких дискретных данных).

По умолчанию цветовой цикл для осей: синий, зеленый, красный, голубой, пурпурный, желтый, черный.

После 7 вызовов plot он будет возвращаться к этим цветам, поэтому, если у вас есть больше элементов, вам нужно задайте его вручную (или просто укажите цвет в каждом вызове plot, используя интерполированную цветовую полосу, аналогичную той, что предложил @Yann выше).

person Joe Kington    schedule 17.03.2012
comment
Спасибо. Я видел возможность создания нескольких графиков, но я еще не знал об элегантном трюке с условием, который вы использовали здесь (+1). Я не согласен с scatter. Насколько я понимаю, это как раз и предназначено для таких графиков, где точки независимы и не связаны (что вы обходите, устанавливая linestyle="none") - person Has QUIT--Anony-Mousse; 17.03.2012
comment
Точка plot против scatter является неудачным и распространенным заблуждением. Используйте plot для построения точек и используйте scatter только для построения объектов, когда вам нужно постоянно изменять размер и/или цвет маркеров на основе 3-й или 4-й переменной. scatter возвращает коллекцию, которой гораздо сложнее управлять. plot на самом деле предназначен для построения несвязанных точек, по умолчанию это просто линия. Если вам нужен более краткий вызов, plt.plot(x, y, 'o') сделает то же самое, что и plt.plot(x, y, linestyle='none', marker='o'). - person Joe Kington; 17.03.2012
comment
Спасибо. Я использую np.unique(iris.field(4)) (поскольку в моем CSV нет строки метки столбца). Но помимо этого я теперь по существу использую ваш код. Мне очень нравится трюк с условием. - person Has QUIT--Anony-Mousse; 19.03.2012
comment
Как я могу воспроизвести это с помощью sklearn.datasets.load_iris()? Я не уверен, что возвращает numpy.recfromcsv(). - person imrek; 05.09.2015

Является ли способ элегантным или нет, несколько субъективно. Я лично нахожу ваши подходы лучше, чем способ «matplotlib». Из модуля matplotlib color:

Отображение цветов обычно включает два этапа: массив данных сначала отображается в диапазоне 0-1 с использованием экземпляра Normalize или подкласса; затем это число в диапазоне 0-1 сопоставляется с цветом с использованием экземпляра подкласса Colormap.

Что я понимаю из этого в отношении вашей проблемы, так это то, что вам нужен подкласс Normalize, который принимает строки и отображает их в 0-1.

Вот пример, который наследуется от Normalize для создания подкласса TextNorm, который используется для преобразования строки в значение от 0 до 1. Эта нормализация используется для получения соответствующего цвета.

import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
import numpy as np
from numpy import ma

class TextNorm(Normalize):
    '''Map a list of text values to the float range 0-1'''

    def __init__(self, textvals, clip=False):
        self.clip = clip
        # if you want, clean text here, for duplicate, sorting, etc
        ltextvals = set(textvals)
        self.N = len(ltextvals)
        self.textmap = dict(
            [(text, float(i)/(self.N-1)) for i, text in enumerate(ltextvals)])
        self.vmin = 0
        self.vmax = 1

    def __call__(self, x, clip=None):
        #Normally this would have a lot more to do with masking
        ret = ma.asarray([self.textmap.get(xkey, -1) for xkey in x])
        return ret

    def inverse(self, value):
        return ValueError("TextNorm is not invertible")

iris = np.recfromcsv("iris.csv")
norm = TextNorm(iris.field(4))

plt.scatter(iris.field(0), iris.field(1), c=norm(iris.field(4)), cmap='RdYlGn')
plt.savefig('textvals.png')
plt.show()

Это производит:

введите здесь описание изображения

Я выбрал цветовую карту «RdYlGn», чтобы можно было легко различать три типа точек. Я не включил функцию clip как часть __call__, хотя это возможно с некоторыми изменениями.

Традиционно вы можете проверить нормализацию метода scatter, используя ключевое слово norm, но scatter проверяет ключевое слово c, чтобы увидеть, хранит ли оно строки, и если да, то предполагается, что вы передаете цвета в качестве их строковых значений, например «Красный», «Синий» и т. д. Таким образом, вызов plt.scatter(iris.field(0), iris.field(1), c=iris.field(4), cmap='RdYlGn', norm=norm) невозможен. Вместо этого я просто использую TextNorm и «работаю» с iris.field(4), чтобы вернуть массив значений в диапазоне от 0 до 1.

Обратите внимание, что для строки, не входящей в список textvals, возвращается значение -1. Вот тут и пригодилась бы маскировка.

person Yann    schedule 16.03.2012
comment
Поскольку я только что сделал то же самое в R (пытаясь сделать обзор инструментов), мне было интересно, есть ли эквивалент unclass в scipy. - person Has QUIT--Anony-Mousse; 16.03.2012
comment
@ Anony-Mousse Я не уверен, что вы спрашиваете в своем комментарии. Как бы вы использовали unclass и на чем бы вы его использовали. - person Yann; 16.03.2012
comment
Ну, unclass по существу перечисляет различные метки. Таким образом, каждая строка является одной из 1..3 для набора данных радужной оболочки (индексы R начинаются с 1). И затем вы можете использовать это для индексации списка различных цветов. Типа cls = list(numpy.unique(iris.field(4))); cols=["red", "green", "blue"] и scatter(..., c=map(lamda x:cols[cls.index(x)], iris.field(4))) - person Has QUIT--Anony-Mousse; 17.03.2012
comment
Как видно из другого ответа, аналогичного эффекта можно добиться, отфильтровав набор данных по каждой метке и отобразив их отдельно с помощью plot вместо scatter. - person Has QUIT--Anony-Mousse; 20.03.2012